mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
created component for http settings
This commit is contained in:
7
public/app/core/components/InfoPopover/InfoPopover.tsx
Normal file
7
public/app/core/components/InfoPopover/InfoPopover.tsx
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import React, { SFC } from 'react';
|
||||||
|
interface Props {}
|
||||||
|
const InfoPopover: SFC<Props> = props => {
|
||||||
|
return <div />;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default InfoPopover;
|
||||||
223
public/app/features/datasources/DataSourceHttpSettings.tsx
Normal file
223
public/app/features/datasources/DataSourceHttpSettings.tsx
Normal file
@@ -0,0 +1,223 @@
|
|||||||
|
import React, { PureComponent } from 'react';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
access: any;
|
||||||
|
basicAuth: any;
|
||||||
|
showAccessOption: any;
|
||||||
|
tlsAuth: any;
|
||||||
|
tlsAuthWithCACert: any;
|
||||||
|
tlsCACert: any;
|
||||||
|
tlsClientCert: any;
|
||||||
|
tlsClientKey: any;
|
||||||
|
url: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface State {
|
||||||
|
basicAuthUser: string;
|
||||||
|
basicAuthPassword: string;
|
||||||
|
showAccessHelp: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class DataSourceHttpSettings extends PureComponent<Props, State> {
|
||||||
|
state = {
|
||||||
|
basicAuthUser: '',
|
||||||
|
basicAuthPassword: '',
|
||||||
|
showAccessHelp: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
onToggleAccessHelp = () => {};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
access,
|
||||||
|
basicAuth,
|
||||||
|
showAccessOption,
|
||||||
|
tlsAuth,
|
||||||
|
tlsAuthWithCACert,
|
||||||
|
tlsCACert,
|
||||||
|
tlsClientCert,
|
||||||
|
tlsClientKey,
|
||||||
|
url,
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
const { showAccessHelp, basicAuthUser, basicAuthPassword } = this.state;
|
||||||
|
|
||||||
|
// const accessOptions = [{key: 'proxy', value: 'Server (Default)'}, { key: 'direct', value: 'Browser'}];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="gf-form-group">
|
||||||
|
<h3 className="page-heading">HTTP</h3>
|
||||||
|
<div className="gf-form-group">
|
||||||
|
<div className="gf-form-inline">
|
||||||
|
<div className="gf-form max-width-30">
|
||||||
|
<span className="gf-form-label width-10">URL</span>
|
||||||
|
<input className="gf-form-input" type="text" value={url} placeholder="https://localhost:9090" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{showAccessOption && (
|
||||||
|
<div className="gf-form-inline">
|
||||||
|
<div className="gf-form max-width-30">
|
||||||
|
<span className="gf-form-label width-10">Access</span>
|
||||||
|
<div className="gf-form-select-wrapper max-width-24" />
|
||||||
|
</div>
|
||||||
|
<div className="gf-form">
|
||||||
|
<label className="gf-form-label query-keyword pointer" onClick={this.onToggleAccessHelp}>
|
||||||
|
Help
|
||||||
|
{showAccessHelp && <i className="fa fa-caret-down" />}
|
||||||
|
{!showAccessHelp && <i className="fa fa-caret-right"> </i>}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{showAccessHelp && (
|
||||||
|
<div className="grafana-info-box m-t-2">
|
||||||
|
<p>
|
||||||
|
Access mode controls how requests to the data source will be handled.
|
||||||
|
<strong>
|
||||||
|
<i>Server</i>
|
||||||
|
</strong>{' '}
|
||||||
|
should be the preferred way if nothing else stated.
|
||||||
|
</p>
|
||||||
|
<div className="alert-title">Server access mode (Default):</div>
|
||||||
|
<p>
|
||||||
|
All requests will be made from the browser to Grafana backend/server which in turn will forward the
|
||||||
|
requests to the data source and by that circumvent possible Cross-Origin Resource Sharing (CORS)
|
||||||
|
requirements. The URL needs to be accessible from the grafana backend/server if you select this access
|
||||||
|
mode.
|
||||||
|
</p>
|
||||||
|
<div className="alert-title">Browser access mode:</div>
|
||||||
|
<p>
|
||||||
|
All requests will be made from the browser directly to the data source and may be subject to
|
||||||
|
Cross-Origin Resource Sharing (CORS) requirements. The URL needs to be accessible from the browser if
|
||||||
|
you select this access mode.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{access === 'proxy' && (
|
||||||
|
<div className="gf-form-inline">
|
||||||
|
<div className="gf-form">
|
||||||
|
<span className="gf-form-label width-10">Whitelisted Cookies</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<h3 className="page-heading">Auth</h3>
|
||||||
|
<div className="gf-form-group">
|
||||||
|
<div className="gf-form-inline" />
|
||||||
|
<div className="gf-form-inline" />
|
||||||
|
<div className="gf-form-inline" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{basicAuth && (
|
||||||
|
<div className="gf-form-group">
|
||||||
|
<h6>Basic Auth Details</h6>
|
||||||
|
<div className="gf-form">
|
||||||
|
<span className="gf-form-label width-10">User</span>
|
||||||
|
<input className="gf-form-input max-width-21" type="text" value={basicAuthUser} placeholder="User" />
|
||||||
|
</div>
|
||||||
|
<div className="gf-form">
|
||||||
|
<span className="gf-form-label width-10">Password</span>
|
||||||
|
<input
|
||||||
|
className="gf-form-input max-width-21"
|
||||||
|
type="password"
|
||||||
|
value={basicAuthPassword}
|
||||||
|
placeholder="Password"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{(tlsAuth || tlsAuthWithCACert) &&
|
||||||
|
access === 'proxy' && (
|
||||||
|
<div className="gf-form-group">
|
||||||
|
<div className="gf-form">
|
||||||
|
<h6>TLS Auth Details</h6>
|
||||||
|
</div>
|
||||||
|
{tlsAuthWithCACert && (
|
||||||
|
<div>
|
||||||
|
<div className="gf-form-inline">
|
||||||
|
<div className="gf-form gf-form--v-stretch">
|
||||||
|
<label className="gf-form-label width-7">CA Cert</label>
|
||||||
|
</div>
|
||||||
|
{!tlsCACert && (
|
||||||
|
<div className="gf-form gf-form--grow">
|
||||||
|
<textarea
|
||||||
|
rows={7}
|
||||||
|
className="gf-form-input gf-form-textarea"
|
||||||
|
value={tlsCACert}
|
||||||
|
placeholder="Begins with -----BEGIN CERTIFICATE-----"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{tlsCACert && (
|
||||||
|
<div className="gf-form">
|
||||||
|
<input type="text" className="gf-form-input max-width-12" value="configured" />
|
||||||
|
<a className="btn btn-secondary gf-form-btn" href="#" onClick={() => {}}>
|
||||||
|
reset
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{tlsAuth && (
|
||||||
|
<div>
|
||||||
|
<div className="gf-form-inline">
|
||||||
|
<div className="gf-form gf-form--v-stretch">
|
||||||
|
<label className="gf-form-label width-7">Client Cert</label>
|
||||||
|
</div>
|
||||||
|
{!tlsClientCert && (
|
||||||
|
<div className="gf-form gf-form--grow">
|
||||||
|
<textarea
|
||||||
|
rows={7}
|
||||||
|
className="gf-form-input gf-form-textarea"
|
||||||
|
value={tlsClientCert}
|
||||||
|
placeholder="Begins with -----BEGIN CERTIFICATE-----"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{tlsClientCert && (
|
||||||
|
<div className="gf-form">
|
||||||
|
<input type="text" className="gf-form-input max-width-12" value="configured" />
|
||||||
|
<a className="btn btn-secondary gf-form-btn" href="#" onClick={() => {}}>
|
||||||
|
reset
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="gf-form-inline">
|
||||||
|
<div className="gf-form gf-form--v-stretch">
|
||||||
|
<label className="gf-form-label width-7">Client Key</label>
|
||||||
|
</div>
|
||||||
|
{tlsClientKey && (
|
||||||
|
<div className="gf-form gf-form--grow">
|
||||||
|
<textarea
|
||||||
|
rows={7}
|
||||||
|
className="gf-form-input gf-form-textarea"
|
||||||
|
value={tlsClientKey}
|
||||||
|
placeholder="Begins with -----BEGIN RSA PRIVATE KEY-----"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{tlsClientKey && (
|
||||||
|
<div className="gf-form">
|
||||||
|
<input type="text" className="gf-form-input max-width-12" value="configured" />
|
||||||
|
<a className="btn btn-secondary gf-form-btn" href="#" onClick={() => {}}>
|
||||||
|
reset
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import React, { PureComponent } from 'react';
|
import React, { PureComponent } from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { DataSource, Plugin } from 'app/types';
|
import { DataSource, Plugin } from 'app/types';
|
||||||
|
import DataSourceHttpSettings from './DataSourceHttpSettings';
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
dataSource: DataSource;
|
dataSource: DataSource;
|
||||||
@@ -8,6 +9,7 @@ export interface Props {
|
|||||||
}
|
}
|
||||||
interface State {
|
interface State {
|
||||||
name: string;
|
name: string;
|
||||||
|
showNamePopover: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
enum DataSourceStates {
|
enum DataSourceStates {
|
||||||
@@ -16,13 +18,10 @@ enum DataSourceStates {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class DataSourceSettings extends PureComponent<Props, State> {
|
export class DataSourceSettings extends PureComponent<Props, State> {
|
||||||
constructor(props) {
|
state = {
|
||||||
super(props);
|
name: this.props.dataSource.name,
|
||||||
|
showNamePopover: false,
|
||||||
this.state = {
|
};
|
||||||
name: props.dataSource.name,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
onNameChange = event => {
|
onNameChange = event => {
|
||||||
this.setState({
|
this.setState({
|
||||||
@@ -39,6 +38,12 @@ export class DataSourceSettings extends PureComponent<Props, State> {
|
|||||||
console.log(event);
|
console.log(event);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
onTogglePopover = () => {
|
||||||
|
this.setState(prevState => ({
|
||||||
|
showNamePopover: !prevState.showNamePopover,
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
isReadyOnly() {
|
isReadyOnly() {
|
||||||
return this.props.dataSource.readOnly === true;
|
return this.props.dataSource.readOnly === true;
|
||||||
}
|
}
|
||||||
@@ -70,11 +75,22 @@ export class DataSourceSettings extends PureComponent<Props, State> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { name } = this.state;
|
const { name, showNamePopover } = this.state;
|
||||||
|
|
||||||
|
const props = {
|
||||||
|
access: {},
|
||||||
|
basicAuth: {},
|
||||||
|
showAccessOption: {},
|
||||||
|
tlsAuth: {},
|
||||||
|
tlsAuthWithCACert: {},
|
||||||
|
tlsCACert: {},
|
||||||
|
tlsClientCert: {},
|
||||||
|
tlsClientKey: {},
|
||||||
|
url: {},
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<h3 className="page-sub-heading">Settings</h3>
|
|
||||||
<form onSubmit={this.onSubmit}>
|
<form onSubmit={this.onSubmit}>
|
||||||
<div className="gf-form-group">
|
<div className="gf-form-group">
|
||||||
<div className="gf-form-inline">
|
<div className="gf-form-inline">
|
||||||
@@ -84,10 +100,31 @@ export class DataSourceSettings extends PureComponent<Props, State> {
|
|||||||
className="gf-form-input max-width-23"
|
className="gf-form-input max-width-23"
|
||||||
type="text"
|
type="text"
|
||||||
value={name}
|
value={name}
|
||||||
placeholder="name"
|
placeholder="Name"
|
||||||
onChange={this.onNameChange}
|
onChange={this.onNameChange}
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
|
<div onClick={this.onTogglePopover}>
|
||||||
|
<i className="fa fa-info-circle" />
|
||||||
|
</div>
|
||||||
|
{showNamePopover && (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
position: 'absolute',
|
||||||
|
left: '450px',
|
||||||
|
top: '-20px',
|
||||||
|
padding: '10px',
|
||||||
|
backgroundColor: 'black',
|
||||||
|
zIndex: 2,
|
||||||
|
width: '300px',
|
||||||
|
border: '1px solid gray',
|
||||||
|
borderRadius: '3px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
The name is used when you select the data source in panels. The <em>Default</em> data source is
|
||||||
|
preselected in new panels.
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -110,6 +147,7 @@ export class DataSourceSettings extends PureComponent<Props, State> {
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
<DataSourceHttpSettings {...props} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
74
public/app/features/datasources/EditDataSourcePage.test.tsx
Normal file
74
public/app/features/datasources/EditDataSourcePage.test.tsx
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { shallow } from 'enzyme';
|
||||||
|
import { EditDataSourcePage, Props } from './EditDataSourcePage';
|
||||||
|
import { DataSource, NavModel } from '../../types';
|
||||||
|
|
||||||
|
const setup = (propOverrides?: object) => {
|
||||||
|
const props: Props = {
|
||||||
|
navModel: {} as NavModel,
|
||||||
|
dataSource: {} as DataSource,
|
||||||
|
dataSourceId: 1,
|
||||||
|
pageName: '',
|
||||||
|
loadDataSource: jest.fn(),
|
||||||
|
};
|
||||||
|
|
||||||
|
Object.assign(props, propOverrides);
|
||||||
|
|
||||||
|
const wrapper = shallow(<EditDataSourcePage {...props} />);
|
||||||
|
const instance = wrapper.instance() as EditDataSourcePage;
|
||||||
|
|
||||||
|
return {
|
||||||
|
wrapper,
|
||||||
|
instance,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('Render', () => {
|
||||||
|
it('should render component', () => {
|
||||||
|
const { wrapper } = setup();
|
||||||
|
|
||||||
|
expect(wrapper).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render permissions page', () => {
|
||||||
|
const { wrapper } = setup({
|
||||||
|
pageName: 'permissions',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(wrapper).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Functions', () => {
|
||||||
|
describe('is page valid', () => {
|
||||||
|
it('should be a valid page', () => {
|
||||||
|
const { instance } = setup();
|
||||||
|
|
||||||
|
expect(instance.isValidPage('permissions')).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not be a valid page', () => {
|
||||||
|
const { instance } = setup();
|
||||||
|
|
||||||
|
expect(instance.isValidPage('asdf')).toBeFalsy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('get current page', () => {
|
||||||
|
it('should return permissions', () => {
|
||||||
|
const { instance } = setup({
|
||||||
|
pageName: 'permissions',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(instance.getCurrentPage()).toEqual('permissions');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return settings if bogus route', () => {
|
||||||
|
const { instance } = setup({
|
||||||
|
pageName: 'asdf',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(instance.getCurrentPage()).toEqual('settings');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -3,6 +3,7 @@ import { hot } from 'react-hot-loader';
|
|||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import PageHeader from '../../core/components/PageHeader/PageHeader';
|
import PageHeader from '../../core/components/PageHeader/PageHeader';
|
||||||
import DataSourcePermissions from './DataSourcePermissions';
|
import DataSourcePermissions from './DataSourcePermissions';
|
||||||
|
import DataSourceSettings from './DataSourceSettings';
|
||||||
import { DataSource, NavModel } from 'app/types';
|
import { DataSource, NavModel } from 'app/types';
|
||||||
import { loadDataSource } from './state/actions';
|
import { loadDataSource } from './state/actions';
|
||||||
import { getNavModel } from '../../core/selectors/navModel';
|
import { getNavModel } from '../../core/selectors/navModel';
|
||||||
@@ -24,6 +25,8 @@ enum PageTypes {
|
|||||||
Dashboards = 'dashboards',
|
Dashboards = 'dashboards',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const fallBackPage = PageTypes.Settings;
|
||||||
|
|
||||||
export class EditDataSourcePage extends PureComponent<Props> {
|
export class EditDataSourcePage extends PureComponent<Props> {
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.fetchDataSource();
|
this.fetchDataSource();
|
||||||
@@ -39,12 +42,13 @@ export class EditDataSourcePage extends PureComponent<Props> {
|
|||||||
|
|
||||||
getCurrentPage() {
|
getCurrentPage() {
|
||||||
const currentPage = this.props.pageName;
|
const currentPage = this.props.pageName;
|
||||||
|
return this.isValidPage(currentPage) ? currentPage : fallBackPage;
|
||||||
return this.isValidPage(currentPage) ? currentPage : PageTypes.Permissions;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
renderPage() {
|
renderPage() {
|
||||||
switch (this.getCurrentPage()) {
|
switch (this.getCurrentPage()) {
|
||||||
|
case PageTypes.Settings:
|
||||||
|
return <DataSourceSettings />;
|
||||||
case PageTypes.Permissions:
|
case PageTypes.Permissions:
|
||||||
return <DataSourcePermissions />;
|
return <DataSourcePermissions />;
|
||||||
}
|
}
|
||||||
@@ -65,7 +69,7 @@ export class EditDataSourcePage extends PureComponent<Props> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function mapStateToProps(state) {
|
function mapStateToProps(state) {
|
||||||
const pageName = getRouteParamsPage(state.location) || PageTypes.Permissions;
|
const pageName = getRouteParamsPage(state.location) || fallBackPage;
|
||||||
const dataSourceId = getRouteParamsId(state.location);
|
const dataSourceId = getRouteParamsId(state.location);
|
||||||
const dataSourceLoadingNav = getDataSourceLoadingNav(pageName);
|
const dataSourceLoadingNav = getDataSourceLoadingNav(pageName);
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`Render should render component 1`] = `
|
||||||
|
<div>
|
||||||
|
<PageHeader
|
||||||
|
model={Object {}}
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
className="page-container page-body"
|
||||||
|
>
|
||||||
|
<Connect(DataSourceSettings) />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Render should render permissions page 1`] = `
|
||||||
|
<div>
|
||||||
|
<PageHeader
|
||||||
|
model={Object {}}
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
className="page-container page-body"
|
||||||
|
>
|
||||||
|
<Connect(DataSourcePermissions) />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
@@ -73,11 +73,6 @@ export function setupAngularRoutes($routeProvider, $locationProvider) {
|
|||||||
component: () => DataSourcesListPage,
|
component: () => DataSourcesListPage,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.when('/datasources/edit/:id', {
|
|
||||||
templateUrl: 'public/app/features/plugins/partials/ds_edit.html',
|
|
||||||
controller: 'DataSourceEditCtrl',
|
|
||||||
controllerAs: 'ctrl',
|
|
||||||
})
|
|
||||||
.when('/datasources/edit/:id/dashboards', {
|
.when('/datasources/edit/:id/dashboards', {
|
||||||
templateUrl: 'public/app/features/plugins/partials/ds_dashboards.html',
|
templateUrl: 'public/app/features/plugins/partials/ds_dashboards.html',
|
||||||
controller: 'DataSourceDashboardsCtrl',
|
controller: 'DataSourceDashboardsCtrl',
|
||||||
|
|||||||
Reference in New Issue
Block a user