mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
CloudMonitoring: Migrate config editor from angular to react (#33645)
* fix broken config ctrl * replace angular config with react config editor * remove not used code * add extra linebreak * add noopener to link * only test jwt props that we actually need
This commit is contained in:
parent
1c58fd380f
commit
1a59117343
@ -0,0 +1,110 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import { Select, FieldSet, InlineField, Alert } from '@grafana/ui';
|
||||
import { DataSourcePluginOptionsEditorProps, onUpdateDatasourceJsonDataOptionSelect } from '@grafana/data';
|
||||
import { AuthType, authTypes, CloudMonitoringOptions, CloudMonitoringSecureJsonData } from '../../types';
|
||||
import { JWTConfig } from './JWTConfig';
|
||||
|
||||
export type Props = DataSourcePluginOptionsEditorProps<CloudMonitoringOptions, CloudMonitoringSecureJsonData>;
|
||||
|
||||
export class ConfigEditor extends PureComponent<Props> {
|
||||
render() {
|
||||
const { options, onOptionsChange } = this.props;
|
||||
const { secureJsonFields, jsonData } = options;
|
||||
|
||||
if (!jsonData.hasOwnProperty('authenticationType')) {
|
||||
jsonData.authenticationType = AuthType.JWT;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="gf-form-group">
|
||||
<div className="grafana-info-box">
|
||||
<h4>Google Cloud Monitoring Authentication</h4>
|
||||
<p>
|
||||
There are two ways to authenticate the Google Cloud Monitoring plugin - either by uploading a Service
|
||||
Account key file or by automatically retrieving credentials from the Google metadata server. The latter
|
||||
option is only available when running Grafana on a GCE virtual machine.
|
||||
</p>
|
||||
|
||||
<h5>Uploading a Service Account Key File</h5>
|
||||
<p>
|
||||
There are two ways to authenticate the Google Cloud Monitoring plugin. You can upload a Service Account
|
||||
key file or automatically retrieve credentials from the Google metadata server. The latter option is only
|
||||
available when running Grafana on a GCE virtual machine.
|
||||
</p>
|
||||
<p>
|
||||
The <strong>Monitoring Viewer</strong> role provides all the permissions that Grafana needs. The following
|
||||
API needs to be enabled on GCP for the data source to work:{' '}
|
||||
<a
|
||||
className="external-link"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
href="https://console.cloud.google.com/apis/library/monitoring.googleapis.com"
|
||||
>
|
||||
Monitoring API
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<h5>GCE Default Service Account</h5>
|
||||
<p>
|
||||
If Grafana is running on a Google Compute Engine (GCE) virtual machine, it is possible for Grafana to
|
||||
automatically retrieve the default project id and authentication token from the metadata server. In order
|
||||
for this to work, you need to make sure that you have a service account that is setup as the default
|
||||
account for the virtual machine and that the service account has been given read access to the Google
|
||||
Cloud Monitoring Monitoring API.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Detailed instructions on how to create a Service Account can be found{' '}
|
||||
<a
|
||||
className="external-link"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
href="https://grafana.com/docs/grafana/latest/datasources/google-cloud-monitoring/"
|
||||
>
|
||||
in the documentation.
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<FieldSet>
|
||||
<InlineField label="Authentication type" labelWidth={20}>
|
||||
<Select
|
||||
width={40}
|
||||
value={authTypes.find((x) => x.value === jsonData.authenticationType) || authTypes[0]}
|
||||
options={authTypes}
|
||||
defaultValue={jsonData.authenticationType}
|
||||
onChange={onUpdateDatasourceJsonDataOptionSelect(this.props, 'authenticationType')}
|
||||
/>
|
||||
</InlineField>
|
||||
{jsonData.authenticationType === AuthType.JWT && (
|
||||
<JWTConfig
|
||||
isConfigured={secureJsonFields && !!secureJsonFields.jwt}
|
||||
onChange={({ private_key, client_email, project_id, token_uri }) => {
|
||||
onOptionsChange({
|
||||
...options,
|
||||
secureJsonData: {
|
||||
...options.secureJsonData,
|
||||
privateKey: private_key,
|
||||
},
|
||||
jsonData: {
|
||||
...options.jsonData,
|
||||
defaultProject: project_id,
|
||||
clientEmail: client_email,
|
||||
tokenUri: token_uri,
|
||||
},
|
||||
});
|
||||
}}
|
||||
></JWTConfig>
|
||||
)}
|
||||
</FieldSet>
|
||||
{jsonData.authenticationType === AuthType.GCE && (
|
||||
<Alert title="" severity="info">
|
||||
Verify GCE default service account by clicking Save & Test
|
||||
</Alert>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,82 @@
|
||||
import React, { FormEvent, useState } from 'react';
|
||||
import { startCase } from 'lodash';
|
||||
import { Button, FileUpload, InlineField, Input, useStyles, Alert } from '@grafana/ui';
|
||||
import { css, cx } from '@emotion/css';
|
||||
import { GrafanaTheme } from '@grafana/data';
|
||||
|
||||
const configKeys = ['project_id', 'private_key', 'client_email', 'token_uri'];
|
||||
|
||||
export interface JWT {
|
||||
token_uri: string;
|
||||
client_email: string;
|
||||
private_key: string;
|
||||
project_id: string;
|
||||
}
|
||||
|
||||
export interface Props {
|
||||
onChange: (jwt: JWT) => void;
|
||||
isConfigured: boolean;
|
||||
}
|
||||
|
||||
const validateJson = (json: JWT): json is JWT => {
|
||||
return !!json.token_uri && !!json.client_email && !!json.project_id && !!json.project_id;
|
||||
};
|
||||
|
||||
export function JWTConfig({ onChange, isConfigured }: Props) {
|
||||
const styles = useStyles(getStyles);
|
||||
const [enableUpload, setEnableUpload] = useState<boolean>(!isConfigured);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
return enableUpload ? (
|
||||
<>
|
||||
<FileUpload
|
||||
className={styles}
|
||||
accept="application/json"
|
||||
onFileUpload={(event: FormEvent<HTMLInputElement>) => {
|
||||
if (event?.currentTarget?.files?.length === 1) {
|
||||
setError(null);
|
||||
const reader = new FileReader();
|
||||
const readerOnLoad = () => {
|
||||
return (e: any) => {
|
||||
const json = JSON.parse(e.target.result);
|
||||
if (validateJson(json)) {
|
||||
onChange(json);
|
||||
setEnableUpload(false);
|
||||
} else {
|
||||
setError('Invalid JWT file');
|
||||
}
|
||||
};
|
||||
};
|
||||
reader.onload = readerOnLoad();
|
||||
reader.readAsText(event.currentTarget.files[0]);
|
||||
} else {
|
||||
setError('You can only upload one file');
|
||||
}
|
||||
}}
|
||||
>
|
||||
Upload service account key file
|
||||
</FileUpload>
|
||||
|
||||
{error && <p className={cx(styles, 'alert')}>{error}</p>}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
{configKeys.map((key, i) => (
|
||||
<InlineField label={startCase(key)} key={i} labelWidth={20} disabled>
|
||||
<Input width={40} placeholder="configured" />
|
||||
</InlineField>
|
||||
))}
|
||||
<Button variant="secondary" onClick={() => setEnableUpload(true)} className={styles}>
|
||||
Upload another JWT file
|
||||
</Button>
|
||||
|
||||
<Alert title="" className={styles} severity="info">
|
||||
Do not forget to save your changes after uploading a file
|
||||
</Alert>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export const getStyles = (theme: GrafanaTheme) => css`
|
||||
margin: ${theme.spacing.md} 0 0;
|
||||
`;
|
@ -1,101 +0,0 @@
|
||||
import DatasourceSrv from 'app/features/plugins/datasource_srv';
|
||||
import { AuthType, authTypes } from './types';
|
||||
|
||||
export interface JWT {
|
||||
private_key: string;
|
||||
token_uri: string;
|
||||
client_email: string;
|
||||
project_id: string;
|
||||
}
|
||||
|
||||
export class CloudMonitoringConfigCtrl {
|
||||
static templateUrl = 'public/app/plugins/datasource/cloud-monitoring/partials/config.html';
|
||||
|
||||
// Set through angular bindings
|
||||
declare current: any;
|
||||
declare meta: any;
|
||||
|
||||
datasourceSrv: DatasourceSrv;
|
||||
jsonText: string;
|
||||
validationErrors: string[] = [];
|
||||
inputDataValid: boolean;
|
||||
authenticationTypes: Array<{ key: AuthType; value: string }>;
|
||||
defaultAuthenticationType: string;
|
||||
name: string;
|
||||
|
||||
/** @ngInject */
|
||||
constructor(datasourceSrv: DatasourceSrv) {
|
||||
this.defaultAuthenticationType = AuthType.JWT;
|
||||
this.datasourceSrv = datasourceSrv;
|
||||
this.name = this.meta.name;
|
||||
this.current.jsonData = this.current.jsonData || {};
|
||||
this.current.jsonData.authenticationType = this.current.jsonData.authenticationType
|
||||
? this.current.jsonData.authenticationType
|
||||
: this.defaultAuthenticationType;
|
||||
this.current.secureJsonData = this.current.secureJsonData || {};
|
||||
this.current.secureJsonFields = this.current.secureJsonFields || {};
|
||||
this.authenticationTypes = authTypes;
|
||||
}
|
||||
|
||||
save(jwt: JWT) {
|
||||
this.current.secureJsonData.privateKey = jwt.private_key;
|
||||
this.current.jsonData.tokenUri = jwt.token_uri;
|
||||
this.current.jsonData.clientEmail = jwt.client_email;
|
||||
this.current.jsonData.defaultProject = jwt.project_id;
|
||||
}
|
||||
|
||||
validateJwt(jwt: JWT) {
|
||||
this.resetValidationMessages();
|
||||
if (!jwt.private_key || jwt.private_key.length === 0) {
|
||||
this.validationErrors.push('Private key field missing in JWT file.');
|
||||
}
|
||||
|
||||
if (!jwt.token_uri || jwt.token_uri.length === 0) {
|
||||
this.validationErrors.push('Token URI field missing in JWT file.');
|
||||
}
|
||||
|
||||
if (!jwt.client_email || jwt.client_email.length === 0) {
|
||||
this.validationErrors.push('Client Email field missing in JWT file.');
|
||||
}
|
||||
|
||||
if (!jwt.project_id || jwt.project_id.length === 0) {
|
||||
this.validationErrors.push('Project Id field missing in JWT file.');
|
||||
}
|
||||
|
||||
if (this.validationErrors.length === 0) {
|
||||
this.inputDataValid = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
onUpload(json: JWT) {
|
||||
this.jsonText = '';
|
||||
if (this.validateJwt(json)) {
|
||||
this.save(json);
|
||||
}
|
||||
}
|
||||
|
||||
onPasteJwt(e: any) {
|
||||
try {
|
||||
const json = JSON.parse(e.originalEvent.clipboardData.getData('text/plain') || this.jsonText);
|
||||
if (this.validateJwt(json)) {
|
||||
this.save(json);
|
||||
}
|
||||
} catch (error) {
|
||||
this.resetValidationMessages();
|
||||
this.validationErrors.push(`Invalid json: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
resetValidationMessages() {
|
||||
this.validationErrors = [];
|
||||
this.inputDataValid = false;
|
||||
this.jsonText = '';
|
||||
|
||||
this.current.jsonData = Object.assign({}, { authenticationType: this.current.jsonData.authenticationType });
|
||||
this.current.secureJsonData = {};
|
||||
this.current.secureJsonFields = {};
|
||||
}
|
||||
}
|
@ -1,13 +1,14 @@
|
||||
import { DataSourcePlugin } from '@grafana/data';
|
||||
import CloudMonitoringDatasource from './datasource';
|
||||
import { QueryEditor } from './components/QueryEditor';
|
||||
import { CloudMonitoringConfigCtrl } from './config_ctrl';
|
||||
import { ConfigEditor } from './components/ConfigEditor/ConfigEditor';
|
||||
|
||||
import { CloudMonitoringAnnotationsQueryCtrl } from './annotations_query_ctrl';
|
||||
import { CloudMonitoringVariableQueryEditor } from './components/VariableQueryEditor';
|
||||
import { CloudMonitoringQuery } from './types';
|
||||
|
||||
export const plugin = new DataSourcePlugin<CloudMonitoringDatasource, CloudMonitoringQuery>(CloudMonitoringDatasource)
|
||||
.setQueryEditor(QueryEditor)
|
||||
.setConfigCtrl(CloudMonitoringConfigCtrl)
|
||||
.setConfigEditor(ConfigEditor)
|
||||
.setAnnotationQueryCtrl(CloudMonitoringAnnotationsQueryCtrl)
|
||||
.setVariableQueryEditor(CloudMonitoringVariableQueryEditor);
|
||||
|
@ -1,141 +0,0 @@
|
||||
<div class="gf-form-group">
|
||||
<div class="grafana-info-box">
|
||||
<h4>Google Cloud Monitoring Authentication</h4>
|
||||
<p>
|
||||
There are two ways to authenticate the Google Cloud Monitoring plugin - either by uploading a Service Account key file or by
|
||||
automatically retrieving credentials from the Google metadata server. The latter option is only available when
|
||||
running Grafana on a GCE virtual machine.
|
||||
</p>
|
||||
|
||||
<h5>Uploading a Service Account Key File</h5>
|
||||
<p>
|
||||
There are two ways to authenticate the Google Cloud Monitoring plugin. You can upload a Service Account key file or automatically retrieve
|
||||
credentials from the Google metadata server. The latter option is only available when running Grafana on a GCE virtual machine.
|
||||
</p>
|
||||
<p>
|
||||
The <strong>Monitoring Viewer</strong> role provides all the permissions that Grafana needs. The following API
|
||||
needs to be enabled on GCP for the data source to work:
|
||||
<a
|
||||
class="external-link"
|
||||
target="_blank"
|
||||
href="https://console.cloud.google.com/apis/library/monitoring.googleapis.com"
|
||||
>Monitoring API</a
|
||||
>
|
||||
</p>
|
||||
|
||||
<h5>GCE Default Service Account</h5>
|
||||
<p>
|
||||
If Grafana is running on a Google Compute Engine (GCE) virtual machine, it is possible for Grafana to
|
||||
automatically retrieve the default project id and authentication token from the metadata server. In order for this
|
||||
to work, you need to make sure that you have a service account that is setup as the default account for the
|
||||
virtual machine and that the service account has been given read access to the Google Cloud Monitoring Monitoring API.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Detailed instructions on how to create a Service Account can be found
|
||||
<a class="external-link" target="_blank" href="https://grafana.com/docs/grafana/latest/datasources/google-cloud-monitoring/_index.md"
|
||||
>in the documentation.</a
|
||||
>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="gf-form-group">
|
||||
<div class="gf-form">
|
||||
<h3>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 class="gf-form-inline">
|
||||
<div class="gf-form max-width-30">
|
||||
<span class="gf-form-label width-10">Authentication Type</span>
|
||||
<div class="gf-form-select-wrapper max-width-24">
|
||||
<select
|
||||
class="gf-form-input"
|
||||
ng-change="ctrl.gceError = ''"
|
||||
ng-model="ctrl.current.jsonData.authenticationType"
|
||||
ng-options="f.key as f.value for f in ctrl.authenticationTypes"
|
||||
></select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
ng-if="ctrl.current.jsonData.authenticationType === ctrl.defaultAuthenticationType && !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">
|
||||
<icon name="'exclamation-triangle'"></icon>
|
||||
{{valError}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="gf-form-group"
|
||||
ng-if="ctrl.current.jsonData.authenticationType === ctrl.defaultAuthenticationType && (ctrl.inputDataValid || ctrl.current.jsonData.clientEmail)"
|
||||
>
|
||||
<h6>Uploaded Key Details</h6>
|
||||
|
||||
<div class="gf-form">
|
||||
<span class="gf-form-label width-10">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-10">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-10">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-10">Private Key</span>
|
||||
<input type="text" class="gf-form-input max-width-12" disabled="disabled" value="configured" />
|
||||
</div>
|
||||
|
||||
<div class="gf-form width-18" style="margin-top: 24px;">
|
||||
<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>
|
||||
|
||||
<p
|
||||
class="gf-form-label"
|
||||
ng-hide="ctrl.current.secureJsonFields.privateKey || ctrl.current.jsonData.authenticationType !== ctrl.defaultAuthenticationType"
|
||||
>
|
||||
<icon name="'save'"></icon> Do not forget to save your changes after uploading a file.
|
||||
</p>
|
||||
|
||||
<div class="gf-form" ng-if="ctrl.gceError">
|
||||
<pre class="gf-form-pre alert alert-error">{{ctrl.gceError}}</pre>
|
||||
</div>
|
||||
|
||||
<p class="gf-form-label" ng-show="ctrl.current.jsonData.authenticationType !== ctrl.defaultAuthenticationType">
|
||||
<icon name="'save'"></icon> Verify GCE default service account by clicking Save & Test
|
||||
</p>
|
@ -5,9 +5,9 @@ export enum AuthType {
|
||||
GCE = 'gce',
|
||||
}
|
||||
|
||||
export const authTypes = [
|
||||
{ value: 'Google JWT File', key: AuthType.JWT },
|
||||
{ value: 'GCE Default Service Account', key: AuthType.GCE },
|
||||
export const authTypes: Array<SelectableValue<string>> = [
|
||||
{ label: 'Google JWT File', value: AuthType.JWT },
|
||||
{ label: 'GCE Default Service Account', value: AuthType.GCE },
|
||||
];
|
||||
|
||||
export enum MetricFindQueryTypes {
|
||||
@ -111,6 +111,12 @@ export interface CloudMonitoringOptions extends DataSourceJsonData {
|
||||
defaultProject?: string;
|
||||
gceDefaultProject?: string;
|
||||
authenticationType?: string;
|
||||
clientEmail?: string;
|
||||
tokenUri?: string;
|
||||
}
|
||||
|
||||
export interface CloudMonitoringSecureJsonData {
|
||||
privateKey?: string;
|
||||
}
|
||||
|
||||
export interface AnnotationTarget {
|
||||
|
Loading…
Reference in New Issue
Block a user