mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Merge remote-tracking branch 'origin/data-source-settings-to-react' into develop
This commit is contained in:
commit
e0feb72637
1
.gitignore
vendored
1
.gitignore
vendored
@ -76,3 +76,4 @@ debug.test
|
|||||||
/devenv/bulk_alerting_dashboards/*.json
|
/devenv/bulk_alerting_dashboards/*.json
|
||||||
|
|
||||||
/scripts/build/release_publisher/release_publisher
|
/scripts/build/release_publisher/release_publisher
|
||||||
|
*.patch
|
||||||
|
@ -4,6 +4,7 @@ import _ from 'lodash';
|
|||||||
|
|
||||||
export interface AngularComponent {
|
export interface AngularComponent {
|
||||||
destroy();
|
destroy();
|
||||||
|
digest();
|
||||||
}
|
}
|
||||||
|
|
||||||
export class AngularLoader {
|
export class AngularLoader {
|
||||||
@ -24,6 +25,9 @@ export class AngularLoader {
|
|||||||
scope.$destroy();
|
scope.$destroy();
|
||||||
compiledElem.remove();
|
compiledElem.remove();
|
||||||
},
|
},
|
||||||
|
digest: () => {
|
||||||
|
scope.$digest();
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import coreModule from 'app/core/core_module';
|
import coreModule from 'app/core/core_module';
|
||||||
import appEvents from 'app/core/app_events';
|
import appEvents from 'app/core/app_events';
|
||||||
import { store } from 'app/store/configureStore';
|
import { store } from 'app/store/store';
|
||||||
import locationUtil from 'app/core/utils/location_util';
|
import locationUtil from 'app/core/utils/location_util';
|
||||||
import { updateLocation } from 'app/core/actions';
|
import { updateLocation } from 'app/core/actions';
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { store } from '../../store/configureStore';
|
import { store } from '../../store/store';
|
||||||
|
|
||||||
export function connectWithStore(WrappedComponent, ...args) {
|
export function connectWithStore(WrappedComponent, ...args) {
|
||||||
const ConnectedWrappedComponent = connect(...args)(WrappedComponent);
|
const ConnectedWrappedComponent = connect(...args)(WrappedComponent);
|
||||||
|
@ -4,7 +4,7 @@ import classNames from 'classnames';
|
|||||||
import { QueriesTab } from './QueriesTab';
|
import { QueriesTab } from './QueriesTab';
|
||||||
import { VizTypePicker } from './VizTypePicker';
|
import { VizTypePicker } from './VizTypePicker';
|
||||||
|
|
||||||
import { store } from 'app/store/configureStore';
|
import { store } from 'app/store/store';
|
||||||
import { updateLocation } from 'app/core/actions';
|
import { updateLocation } from 'app/core/actions';
|
||||||
|
|
||||||
import { PanelModel } from '../panel_model';
|
import { PanelModel } from '../panel_model';
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { updateLocation } from 'app/core/actions';
|
import { updateLocation } from 'app/core/actions';
|
||||||
import { store } from 'app/store/configureStore';
|
import { store } from 'app/store/store';
|
||||||
|
|
||||||
import { removePanel, duplicatePanel, copyPanel, editPanelJson, sharePanel } from 'app/features/dashboard/utils/panel';
|
import { removePanel, duplicatePanel, copyPanel, editPanelJson, sharePanel } from 'app/features/dashboard/utils/panel';
|
||||||
import { PanelModel } from 'app/features/dashboard/panel_model';
|
import { PanelModel } from 'app/features/dashboard/panel_model';
|
||||||
|
@ -1,125 +0,0 @@
|
|||||||
import React, { PureComponent } from 'react';
|
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import { DataSource, Plugin } from 'app/types';
|
|
||||||
|
|
||||||
export interface Props {
|
|
||||||
dataSource: DataSource;
|
|
||||||
dataSourceMeta: Plugin;
|
|
||||||
}
|
|
||||||
interface State {
|
|
||||||
name: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
enum DataSourceStates {
|
|
||||||
Alpha = 'alpha',
|
|
||||||
Beta = 'beta',
|
|
||||||
}
|
|
||||||
|
|
||||||
export class DataSourceSettings extends PureComponent<Props, State> {
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
name: props.dataSource.name,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
onNameChange = event => {
|
|
||||||
this.setState({
|
|
||||||
name: event.target.value,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
onSubmit = event => {
|
|
||||||
event.preventDefault();
|
|
||||||
console.log(event);
|
|
||||||
};
|
|
||||||
|
|
||||||
onDelete = event => {
|
|
||||||
console.log(event);
|
|
||||||
};
|
|
||||||
|
|
||||||
isReadyOnly() {
|
|
||||||
return this.props.dataSource.readOnly === true;
|
|
||||||
}
|
|
||||||
|
|
||||||
shouldRenderInfoBox() {
|
|
||||||
const { state } = this.props.dataSourceMeta;
|
|
||||||
|
|
||||||
return state === DataSourceStates.Alpha || state === DataSourceStates.Beta;
|
|
||||||
}
|
|
||||||
|
|
||||||
getInfoText() {
|
|
||||||
const { dataSourceMeta } = this.props;
|
|
||||||
|
|
||||||
switch (dataSourceMeta.state) {
|
|
||||||
case DataSourceStates.Alpha:
|
|
||||||
return (
|
|
||||||
'This plugin is marked as being in alpha state, which means it is in early development phase and updates' +
|
|
||||||
' will include breaking changes.'
|
|
||||||
);
|
|
||||||
|
|
||||||
case DataSourceStates.Beta:
|
|
||||||
return (
|
|
||||||
'This plugin is marked as being in a beta development state. This means it is in currently in active' +
|
|
||||||
' development and could be missing important features.'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { name } = this.state;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<h3 className="page-sub-heading">Settings</h3>
|
|
||||||
<form onSubmit={this.onSubmit}>
|
|
||||||
<div className="gf-form-group">
|
|
||||||
<div className="gf-form-inline">
|
|
||||||
<div className="gf-form max-width-30">
|
|
||||||
<span className="gf-form-label width-10">Name</span>
|
|
||||||
<input
|
|
||||||
className="gf-form-input max-width-23"
|
|
||||||
type="text"
|
|
||||||
value={name}
|
|
||||||
placeholder="name"
|
|
||||||
onChange={this.onNameChange}
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{this.shouldRenderInfoBox() && <div className="grafana-info-box">{this.getInfoText()}</div>}
|
|
||||||
{this.isReadyOnly() && (
|
|
||||||
<div className="grafana-info-box span8">
|
|
||||||
This datasource was added by config and cannot be modified using the UI. Please contact your server admin
|
|
||||||
to update this datasource.
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<div className="gf-form-button-row">
|
|
||||||
<button type="submit" className="btn btn-success" disabled={this.isReadyOnly()} onClick={this.onSubmit}>
|
|
||||||
Save & Test
|
|
||||||
</button>
|
|
||||||
<button type="submit" className="btn btn-danger" disabled={this.isReadyOnly()} onClick={this.onDelete}>
|
|
||||||
Delete
|
|
||||||
</button>
|
|
||||||
<a className="btn btn-inverse" href="datasources">
|
|
||||||
Back
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function mapStateToProps(state) {
|
|
||||||
return {
|
|
||||||
dataSource: state.dataSources.dataSource,
|
|
||||||
dataSourceMeta: state.dataSources.dataSourceMeta,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export default connect(mapStateToProps)(DataSourceSettings);
|
|
@ -29,6 +29,9 @@ export const getMockDataSource = (): DataSource => {
|
|||||||
return {
|
return {
|
||||||
access: '',
|
access: '',
|
||||||
basicAuth: false,
|
basicAuth: false,
|
||||||
|
basicAuthUser: '',
|
||||||
|
basicAuthPassword: '',
|
||||||
|
withCredentials: false,
|
||||||
database: '',
|
database: '',
|
||||||
id: 13,
|
id: 13,
|
||||||
isDefault: false,
|
isDefault: false,
|
||||||
|
@ -0,0 +1,20 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { shallow } from 'enzyme';
|
||||||
|
import BasicSettings, { Props } from './BasicSettings';
|
||||||
|
|
||||||
|
const setup = () => {
|
||||||
|
const props: Props = {
|
||||||
|
dataSourceName: 'Graphite',
|
||||||
|
onChange: jest.fn(),
|
||||||
|
};
|
||||||
|
|
||||||
|
return shallow(<BasicSettings {...props} />);
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('Render', () => {
|
||||||
|
it('should render component', () => {
|
||||||
|
const wrapper = setup();
|
||||||
|
|
||||||
|
expect(wrapper).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
34
public/app/features/datasources/settings/BasicSettings.tsx
Normal file
34
public/app/features/datasources/settings/BasicSettings.tsx
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import React, { SFC } from 'react';
|
||||||
|
import { Label } from 'app/core/components/Label/Label';
|
||||||
|
|
||||||
|
export interface Props {
|
||||||
|
dataSourceName: string;
|
||||||
|
onChange: (name: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const BasicSettings: SFC<Props> = ({ dataSourceName, onChange }) => {
|
||||||
|
return (
|
||||||
|
<div className="gf-form-group">
|
||||||
|
<div className="gf-form max-width-30">
|
||||||
|
<Label
|
||||||
|
tooltip={
|
||||||
|
'The name is used when you select the data source in panels. The Default data source is' +
|
||||||
|
'preselected in new panels.'
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Name
|
||||||
|
</Label>
|
||||||
|
<input
|
||||||
|
className="gf-form-input max-width-23"
|
||||||
|
type="text"
|
||||||
|
value={dataSourceName}
|
||||||
|
placeholder="Name"
|
||||||
|
onChange={event => onChange(event.target.value)}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default BasicSettings;
|
31
public/app/features/datasources/settings/ButtonRow.test.tsx
Normal file
31
public/app/features/datasources/settings/ButtonRow.test.tsx
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { shallow } from 'enzyme';
|
||||||
|
import ButtonRow, { Props } from './ButtonRow';
|
||||||
|
|
||||||
|
const setup = (propOverrides?: object) => {
|
||||||
|
const props: Props = {
|
||||||
|
isReadOnly: true,
|
||||||
|
onSubmit: jest.fn(),
|
||||||
|
onDelete: jest.fn(),
|
||||||
|
};
|
||||||
|
|
||||||
|
Object.assign(props, propOverrides);
|
||||||
|
|
||||||
|
return shallow(<ButtonRow {...props} />);
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('Render', () => {
|
||||||
|
it('should render component', () => {
|
||||||
|
const wrapper = setup();
|
||||||
|
|
||||||
|
expect(wrapper).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render with buttons enabled', () => {
|
||||||
|
const wrapper = setup({
|
||||||
|
isReadOnly: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(wrapper).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
25
public/app/features/datasources/settings/ButtonRow.tsx
Normal file
25
public/app/features/datasources/settings/ButtonRow.tsx
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import React, { SFC } from 'react';
|
||||||
|
|
||||||
|
export interface Props {
|
||||||
|
isReadOnly: boolean;
|
||||||
|
onDelete: () => void;
|
||||||
|
onSubmit: (event) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ButtonRow: SFC<Props> = ({ isReadOnly, onDelete, onSubmit }) => {
|
||||||
|
return (
|
||||||
|
<div className="gf-form-button-row">
|
||||||
|
<button type="submit" className="btn btn-success" disabled={isReadOnly} onClick={event => onSubmit(event)}>
|
||||||
|
Save & Test
|
||||||
|
</button>
|
||||||
|
<button type="submit" className="btn btn-danger" disabled={isReadOnly} onClick={onDelete}>
|
||||||
|
Delete
|
||||||
|
</button>
|
||||||
|
<a className="btn btn-inverse" href="/datasources">
|
||||||
|
Back
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ButtonRow;
|
@ -0,0 +1,63 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { shallow } from 'enzyme';
|
||||||
|
import { DataSourceSettings, Props } from './DataSourceSettings';
|
||||||
|
import { DataSource, NavModel } from '../../../types';
|
||||||
|
import { getMockDataSource } from '../__mocks__/dataSourcesMocks';
|
||||||
|
import { getMockPlugin } from '../../plugins/__mocks__/pluginMocks';
|
||||||
|
|
||||||
|
const setup = (propOverrides?: object) => {
|
||||||
|
const props: Props = {
|
||||||
|
navModel: {} as NavModel,
|
||||||
|
dataSource: getMockDataSource(),
|
||||||
|
dataSourceMeta: getMockPlugin(),
|
||||||
|
pageId: 1,
|
||||||
|
deleteDataSource: jest.fn(),
|
||||||
|
loadDataSource: jest.fn(),
|
||||||
|
setDataSourceName: jest.fn(),
|
||||||
|
updateDataSource: jest.fn(),
|
||||||
|
};
|
||||||
|
|
||||||
|
Object.assign(props, propOverrides);
|
||||||
|
|
||||||
|
return shallow(<DataSourceSettings {...props} />);
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('Render', () => {
|
||||||
|
it('should render component', () => {
|
||||||
|
const wrapper = setup();
|
||||||
|
|
||||||
|
expect(wrapper).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render loader', () => {
|
||||||
|
const wrapper = setup({
|
||||||
|
dataSource: {} as DataSource,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(wrapper).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render beta info text', () => {
|
||||||
|
const wrapper = setup({
|
||||||
|
dataSourceMeta: { ...getMockPlugin(), state: 'beta' },
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(wrapper).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render alpha info text', () => {
|
||||||
|
const wrapper = setup({
|
||||||
|
dataSourceMeta: { ...getMockPlugin(), state: 'alpha' },
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(wrapper).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render is ready only message', () => {
|
||||||
|
const wrapper = setup({
|
||||||
|
dataSource: { ...getMockDataSource(), readOnly: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(wrapper).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
245
public/app/features/datasources/settings/DataSourceSettings.tsx
Normal file
245
public/app/features/datasources/settings/DataSourceSettings.tsx
Normal file
@ -0,0 +1,245 @@
|
|||||||
|
import React, { PureComponent } from 'react';
|
||||||
|
import { hot } from 'react-hot-loader';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
|
import PageHeader from 'app/core/components/PageHeader/PageHeader';
|
||||||
|
import PageLoader from 'app/core/components/PageLoader/PageLoader';
|
||||||
|
import PluginSettings from './PluginSettings';
|
||||||
|
import BasicSettings from './BasicSettings';
|
||||||
|
import ButtonRow from './ButtonRow';
|
||||||
|
|
||||||
|
import appEvents from 'app/core/app_events';
|
||||||
|
import { getBackendSrv } from 'app/core/services/backend_srv';
|
||||||
|
import { getDatasourceSrv } from 'app/features/plugins/datasource_srv';
|
||||||
|
|
||||||
|
import { getDataSource, getDataSourceMeta } from '../state/selectors';
|
||||||
|
import { deleteDataSource, loadDataSource, setDataSourceName, updateDataSource } from '../state/actions';
|
||||||
|
import { getNavModel } from 'app/core/selectors/navModel';
|
||||||
|
import { getRouteParamsId } from 'app/core/selectors/location';
|
||||||
|
|
||||||
|
import { DataSource, NavModel, Plugin } from 'app/types/';
|
||||||
|
import { getDataSourceLoadingNav } from '../state/navModel';
|
||||||
|
|
||||||
|
export interface Props {
|
||||||
|
navModel: NavModel;
|
||||||
|
dataSource: DataSource;
|
||||||
|
dataSourceMeta: Plugin;
|
||||||
|
pageId: number;
|
||||||
|
deleteDataSource: typeof deleteDataSource;
|
||||||
|
loadDataSource: typeof loadDataSource;
|
||||||
|
setDataSourceName: typeof setDataSourceName;
|
||||||
|
updateDataSource: typeof updateDataSource;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface State {
|
||||||
|
dataSource: DataSource;
|
||||||
|
isTesting?: boolean;
|
||||||
|
testingMessage?: string;
|
||||||
|
testingStatus?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum DataSourceStates {
|
||||||
|
Alpha = 'alpha',
|
||||||
|
Beta = 'beta',
|
||||||
|
}
|
||||||
|
|
||||||
|
export class DataSourceSettings extends PureComponent<Props, State> {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
dataSource: {} as DataSource,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async componentDidMount() {
|
||||||
|
const { loadDataSource, pageId } = this.props;
|
||||||
|
|
||||||
|
await loadDataSource(pageId);
|
||||||
|
}
|
||||||
|
|
||||||
|
onSubmit = async event => {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
await this.props.updateDataSource({ ...this.state.dataSource, name: this.props.dataSource.name });
|
||||||
|
|
||||||
|
this.testDataSource();
|
||||||
|
};
|
||||||
|
|
||||||
|
onDelete = () => {
|
||||||
|
appEvents.emit('confirm-modal', {
|
||||||
|
title: 'Delete',
|
||||||
|
text: 'Are you sure you want to delete this data source?',
|
||||||
|
yesText: 'Delete',
|
||||||
|
icon: 'fa-trash',
|
||||||
|
onConfirm: () => {
|
||||||
|
this.confirmDelete();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
confirmDelete = () => {
|
||||||
|
this.props.deleteDataSource();
|
||||||
|
};
|
||||||
|
|
||||||
|
onModelChange = dataSource => {
|
||||||
|
this.setState({
|
||||||
|
dataSource: dataSource,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
isReadOnly() {
|
||||||
|
return this.props.dataSource.readOnly === true;
|
||||||
|
}
|
||||||
|
|
||||||
|
shouldRenderInfoBox() {
|
||||||
|
const { state } = this.props.dataSourceMeta;
|
||||||
|
|
||||||
|
return state === DataSourceStates.Alpha || state === DataSourceStates.Beta;
|
||||||
|
}
|
||||||
|
|
||||||
|
getInfoText() {
|
||||||
|
const { dataSourceMeta } = this.props;
|
||||||
|
|
||||||
|
switch (dataSourceMeta.state) {
|
||||||
|
case DataSourceStates.Alpha:
|
||||||
|
return (
|
||||||
|
'This plugin is marked as being in alpha state, which means it is in early development phase and updates' +
|
||||||
|
' will include breaking changes.'
|
||||||
|
);
|
||||||
|
|
||||||
|
case DataSourceStates.Beta:
|
||||||
|
return (
|
||||||
|
'This plugin is marked as being in a beta development state. This means it is in currently in active' +
|
||||||
|
' development and could be missing important features.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
renderIsReadOnlyMessage() {
|
||||||
|
return (
|
||||||
|
<div className="grafana-info-box span8">
|
||||||
|
This datasource was added by config and cannot be modified using the UI. Please contact your server admin to
|
||||||
|
update this datasource.
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async testDataSource() {
|
||||||
|
const dsApi = await getDatasourceSrv().get(this.state.dataSource.name);
|
||||||
|
|
||||||
|
if (!dsApi.testDatasource) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({ isTesting: true, testingMessage: 'Testing...', testingStatus: 'info' });
|
||||||
|
|
||||||
|
getBackendSrv().withNoBackendCache(async () => {
|
||||||
|
try {
|
||||||
|
const result = await dsApi.testDatasource();
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
isTesting: false,
|
||||||
|
testingStatus: result.status,
|
||||||
|
testingMessage: result.message,
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
let message = '';
|
||||||
|
|
||||||
|
if (err.statusText) {
|
||||||
|
message = 'HTTP Error ' + err.statusText;
|
||||||
|
} else {
|
||||||
|
message = err.message;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
isTesting: false,
|
||||||
|
testingStatus: 'error',
|
||||||
|
testingMessage: message,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { dataSource, dataSourceMeta, navModel } = this.props;
|
||||||
|
const { testingMessage, testingStatus } = this.state;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<PageHeader model={navModel} />
|
||||||
|
{Object.keys(dataSource).length === 0 ? (
|
||||||
|
<PageLoader pageName="Data source settings" />
|
||||||
|
) : (
|
||||||
|
<div className="page-container page-body">
|
||||||
|
<div>
|
||||||
|
<form onSubmit={this.onSubmit}>
|
||||||
|
<BasicSettings
|
||||||
|
dataSourceName={this.props.dataSource.name}
|
||||||
|
onChange={name => this.props.setDataSourceName(name)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{this.shouldRenderInfoBox() && <div className="grafana-info-box">{this.getInfoText()}</div>}
|
||||||
|
|
||||||
|
{this.isReadOnly() && this.renderIsReadOnlyMessage()}
|
||||||
|
{dataSourceMeta.module && (
|
||||||
|
<PluginSettings
|
||||||
|
dataSource={dataSource}
|
||||||
|
dataSourceMeta={dataSourceMeta}
|
||||||
|
onModelChange={this.onModelChange}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="gf-form-group section">
|
||||||
|
{testingMessage && (
|
||||||
|
<div className={`alert-${testingStatus} alert`}>
|
||||||
|
<div className="alert-icon">
|
||||||
|
{testingStatus === 'error' ? (
|
||||||
|
<i className="fa fa-exclamation-triangle" />
|
||||||
|
) : (
|
||||||
|
<i className="fa fa-check" />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="alert-body">
|
||||||
|
<div className="alert-title">{testingMessage}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ButtonRow
|
||||||
|
onSubmit={event => this.onSubmit(event)}
|
||||||
|
isReadOnly={this.isReadOnly()}
|
||||||
|
onDelete={this.onDelete}
|
||||||
|
/>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapStateToProps(state) {
|
||||||
|
const pageId = getRouteParamsId(state.location);
|
||||||
|
const dataSource = getDataSource(state.dataSources, pageId);
|
||||||
|
|
||||||
|
return {
|
||||||
|
navModel: getNavModel(state.navIndex, `datasource-settings-${pageId}`, getDataSourceLoadingNav('settings')),
|
||||||
|
dataSource: getDataSource(state.dataSources, pageId),
|
||||||
|
dataSourceMeta: getDataSourceMeta(state.dataSources, dataSource.type),
|
||||||
|
pageId: pageId,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const mapDispatchToProps = {
|
||||||
|
deleteDataSource,
|
||||||
|
loadDataSource,
|
||||||
|
setDataSourceName,
|
||||||
|
updateDataSource,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default hot(module)(connect(mapStateToProps, mapDispatchToProps)(DataSourceSettings));
|
63
public/app/features/datasources/settings/PluginSettings.tsx
Normal file
63
public/app/features/datasources/settings/PluginSettings.tsx
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
import React, { PureComponent } from 'react';
|
||||||
|
import _ from 'lodash';
|
||||||
|
import { DataSource, Plugin } from 'app/types/';
|
||||||
|
import { getAngularLoader, AngularComponent } from 'app/core/services/AngularLoader';
|
||||||
|
|
||||||
|
export interface Props {
|
||||||
|
dataSource: DataSource;
|
||||||
|
dataSourceMeta: Plugin;
|
||||||
|
onModelChange: (dataSource: DataSource) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class PluginSettings extends PureComponent<Props> {
|
||||||
|
element: any;
|
||||||
|
component: AngularComponent;
|
||||||
|
scopeProps: {
|
||||||
|
ctrl: { datasourceMeta: Plugin; current: DataSource };
|
||||||
|
onModelChanged: (dataSource: DataSource) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.scopeProps = {
|
||||||
|
ctrl: { datasourceMeta: props.dataSourceMeta, current: _.cloneDeep(props.dataSource) },
|
||||||
|
onModelChanged: this.onModelChanged,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
if (!this.element) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const loader = getAngularLoader();
|
||||||
|
const template = '<plugin-component type="datasource-config-ctrl" />';
|
||||||
|
|
||||||
|
this.component = loader.load(this.element, this.scopeProps, template);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUpdate(prevProps) {
|
||||||
|
if (this.props.dataSource !== prevProps.dataSource) {
|
||||||
|
this.scopeProps.ctrl.current = _.cloneDeep(this.props.dataSource);
|
||||||
|
|
||||||
|
this.component.digest();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
if (this.component) {
|
||||||
|
this.component.destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onModelChanged = (dataSource: DataSource) => {
|
||||||
|
this.props.onModelChange(dataSource);
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return <div ref={element => (this.element = element)} />;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PluginSettings;
|
@ -0,0 +1,25 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`Render should render component 1`] = `
|
||||||
|
<div
|
||||||
|
className="gf-form-group"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="gf-form max-width-30"
|
||||||
|
>
|
||||||
|
<Component
|
||||||
|
tooltip="The name is used when you select the data source in panels. The Default data source ispreselected in new panels."
|
||||||
|
>
|
||||||
|
Name
|
||||||
|
</Component>
|
||||||
|
<input
|
||||||
|
className="gf-form-input max-width-23"
|
||||||
|
onChange={[Function]}
|
||||||
|
placeholder="Name"
|
||||||
|
required={true}
|
||||||
|
type="text"
|
||||||
|
value="Graphite"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
@ -0,0 +1,59 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`Render should render component 1`] = `
|
||||||
|
<div
|
||||||
|
className="gf-form-button-row"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
className="btn btn-success"
|
||||||
|
disabled={true}
|
||||||
|
onClick={[Function]}
|
||||||
|
type="submit"
|
||||||
|
>
|
||||||
|
Save & Test
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className="btn btn-danger"
|
||||||
|
disabled={true}
|
||||||
|
onClick={[MockFunction]}
|
||||||
|
type="submit"
|
||||||
|
>
|
||||||
|
Delete
|
||||||
|
</button>
|
||||||
|
<a
|
||||||
|
className="btn btn-inverse"
|
||||||
|
href="/datasources"
|
||||||
|
>
|
||||||
|
Back
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Render should render with buttons enabled 1`] = `
|
||||||
|
<div
|
||||||
|
className="gf-form-button-row"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
className="btn btn-success"
|
||||||
|
disabled={false}
|
||||||
|
onClick={[Function]}
|
||||||
|
type="submit"
|
||||||
|
>
|
||||||
|
Save & Test
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className="btn btn-danger"
|
||||||
|
disabled={false}
|
||||||
|
onClick={[MockFunction]}
|
||||||
|
type="submit"
|
||||||
|
>
|
||||||
|
Delete
|
||||||
|
</button>
|
||||||
|
<a
|
||||||
|
className="btn btn-inverse"
|
||||||
|
href="/datasources"
|
||||||
|
>
|
||||||
|
Back
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
`;
|
@ -0,0 +1,379 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`Render should render alpha info text 1`] = `
|
||||||
|
<div>
|
||||||
|
<PageHeader
|
||||||
|
model={Object {}}
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
className="page-container page-body"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<form
|
||||||
|
onSubmit={[Function]}
|
||||||
|
>
|
||||||
|
<BasicSettings
|
||||||
|
dataSourceName="gdev-cloudwatch"
|
||||||
|
onChange={[Function]}
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
className="grafana-info-box"
|
||||||
|
>
|
||||||
|
This plugin is marked as being in alpha state, which means it is in early development phase and updates will include breaking changes.
|
||||||
|
</div>
|
||||||
|
<PluginSettings
|
||||||
|
dataSource={
|
||||||
|
Object {
|
||||||
|
"access": "",
|
||||||
|
"basicAuth": false,
|
||||||
|
"basicAuthPassword": "",
|
||||||
|
"basicAuthUser": "",
|
||||||
|
"database": "",
|
||||||
|
"id": 13,
|
||||||
|
"isDefault": false,
|
||||||
|
"jsonData": Object {
|
||||||
|
"authType": "credentials",
|
||||||
|
"defaultRegion": "eu-west-2",
|
||||||
|
},
|
||||||
|
"name": "gdev-cloudwatch",
|
||||||
|
"orgId": 1,
|
||||||
|
"password": "",
|
||||||
|
"readOnly": false,
|
||||||
|
"type": "cloudwatch",
|
||||||
|
"typeLogoUrl": "public/app/plugins/datasource/cloudwatch/img/amazon-web-services.png",
|
||||||
|
"url": "",
|
||||||
|
"user": "",
|
||||||
|
"withCredentials": false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dataSourceMeta={
|
||||||
|
Object {
|
||||||
|
"defaultNavUrl": "some/url",
|
||||||
|
"enabled": false,
|
||||||
|
"hasUpdate": false,
|
||||||
|
"id": "1",
|
||||||
|
"info": Object {
|
||||||
|
"author": Object {
|
||||||
|
"name": "Grafana Labs",
|
||||||
|
"url": "url/to/GrafanaLabs",
|
||||||
|
},
|
||||||
|
"description": "pretty decent plugin",
|
||||||
|
"links": Array [
|
||||||
|
"one link",
|
||||||
|
],
|
||||||
|
"logos": Object {
|
||||||
|
"large": "large/logo",
|
||||||
|
"small": "small/logo",
|
||||||
|
},
|
||||||
|
"screenshots": "screenshot/1",
|
||||||
|
"updated": "2018-09-26",
|
||||||
|
"version": "1",
|
||||||
|
},
|
||||||
|
"latestVersion": "1",
|
||||||
|
"module": Object {},
|
||||||
|
"name": "pretty cool plugin 1",
|
||||||
|
"pinned": false,
|
||||||
|
"state": "alpha",
|
||||||
|
"type": "",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onModelChange={[Function]}
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
className="gf-form-group section"
|
||||||
|
/>
|
||||||
|
<ButtonRow
|
||||||
|
isReadOnly={false}
|
||||||
|
onDelete={[Function]}
|
||||||
|
onSubmit={[Function]}
|
||||||
|
/>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Render should render beta info text 1`] = `
|
||||||
|
<div>
|
||||||
|
<PageHeader
|
||||||
|
model={Object {}}
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
className="page-container page-body"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<form
|
||||||
|
onSubmit={[Function]}
|
||||||
|
>
|
||||||
|
<BasicSettings
|
||||||
|
dataSourceName="gdev-cloudwatch"
|
||||||
|
onChange={[Function]}
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
className="grafana-info-box"
|
||||||
|
>
|
||||||
|
This plugin is marked as being in a beta development state. This means it is in currently in active development and could be missing important features.
|
||||||
|
</div>
|
||||||
|
<PluginSettings
|
||||||
|
dataSource={
|
||||||
|
Object {
|
||||||
|
"access": "",
|
||||||
|
"basicAuth": false,
|
||||||
|
"basicAuthPassword": "",
|
||||||
|
"basicAuthUser": "",
|
||||||
|
"database": "",
|
||||||
|
"id": 13,
|
||||||
|
"isDefault": false,
|
||||||
|
"jsonData": Object {
|
||||||
|
"authType": "credentials",
|
||||||
|
"defaultRegion": "eu-west-2",
|
||||||
|
},
|
||||||
|
"name": "gdev-cloudwatch",
|
||||||
|
"orgId": 1,
|
||||||
|
"password": "",
|
||||||
|
"readOnly": false,
|
||||||
|
"type": "cloudwatch",
|
||||||
|
"typeLogoUrl": "public/app/plugins/datasource/cloudwatch/img/amazon-web-services.png",
|
||||||
|
"url": "",
|
||||||
|
"user": "",
|
||||||
|
"withCredentials": false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dataSourceMeta={
|
||||||
|
Object {
|
||||||
|
"defaultNavUrl": "some/url",
|
||||||
|
"enabled": false,
|
||||||
|
"hasUpdate": false,
|
||||||
|
"id": "1",
|
||||||
|
"info": Object {
|
||||||
|
"author": Object {
|
||||||
|
"name": "Grafana Labs",
|
||||||
|
"url": "url/to/GrafanaLabs",
|
||||||
|
},
|
||||||
|
"description": "pretty decent plugin",
|
||||||
|
"links": Array [
|
||||||
|
"one link",
|
||||||
|
],
|
||||||
|
"logos": Object {
|
||||||
|
"large": "large/logo",
|
||||||
|
"small": "small/logo",
|
||||||
|
},
|
||||||
|
"screenshots": "screenshot/1",
|
||||||
|
"updated": "2018-09-26",
|
||||||
|
"version": "1",
|
||||||
|
},
|
||||||
|
"latestVersion": "1",
|
||||||
|
"module": Object {},
|
||||||
|
"name": "pretty cool plugin 1",
|
||||||
|
"pinned": false,
|
||||||
|
"state": "beta",
|
||||||
|
"type": "",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onModelChange={[Function]}
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
className="gf-form-group section"
|
||||||
|
/>
|
||||||
|
<ButtonRow
|
||||||
|
isReadOnly={false}
|
||||||
|
onDelete={[Function]}
|
||||||
|
onSubmit={[Function]}
|
||||||
|
/>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Render should render component 1`] = `
|
||||||
|
<div>
|
||||||
|
<PageHeader
|
||||||
|
model={Object {}}
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
className="page-container page-body"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<form
|
||||||
|
onSubmit={[Function]}
|
||||||
|
>
|
||||||
|
<BasicSettings
|
||||||
|
dataSourceName="gdev-cloudwatch"
|
||||||
|
onChange={[Function]}
|
||||||
|
/>
|
||||||
|
<PluginSettings
|
||||||
|
dataSource={
|
||||||
|
Object {
|
||||||
|
"access": "",
|
||||||
|
"basicAuth": false,
|
||||||
|
"basicAuthPassword": "",
|
||||||
|
"basicAuthUser": "",
|
||||||
|
"database": "",
|
||||||
|
"id": 13,
|
||||||
|
"isDefault": false,
|
||||||
|
"jsonData": Object {
|
||||||
|
"authType": "credentials",
|
||||||
|
"defaultRegion": "eu-west-2",
|
||||||
|
},
|
||||||
|
"name": "gdev-cloudwatch",
|
||||||
|
"orgId": 1,
|
||||||
|
"password": "",
|
||||||
|
"readOnly": false,
|
||||||
|
"type": "cloudwatch",
|
||||||
|
"typeLogoUrl": "public/app/plugins/datasource/cloudwatch/img/amazon-web-services.png",
|
||||||
|
"url": "",
|
||||||
|
"user": "",
|
||||||
|
"withCredentials": false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dataSourceMeta={
|
||||||
|
Object {
|
||||||
|
"defaultNavUrl": "some/url",
|
||||||
|
"enabled": false,
|
||||||
|
"hasUpdate": false,
|
||||||
|
"id": "1",
|
||||||
|
"info": Object {
|
||||||
|
"author": Object {
|
||||||
|
"name": "Grafana Labs",
|
||||||
|
"url": "url/to/GrafanaLabs",
|
||||||
|
},
|
||||||
|
"description": "pretty decent plugin",
|
||||||
|
"links": Array [
|
||||||
|
"one link",
|
||||||
|
],
|
||||||
|
"logos": Object {
|
||||||
|
"large": "large/logo",
|
||||||
|
"small": "small/logo",
|
||||||
|
},
|
||||||
|
"screenshots": "screenshot/1",
|
||||||
|
"updated": "2018-09-26",
|
||||||
|
"version": "1",
|
||||||
|
},
|
||||||
|
"latestVersion": "1",
|
||||||
|
"module": Object {},
|
||||||
|
"name": "pretty cool plugin 1",
|
||||||
|
"pinned": false,
|
||||||
|
"state": "",
|
||||||
|
"type": "",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onModelChange={[Function]}
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
className="gf-form-group section"
|
||||||
|
/>
|
||||||
|
<ButtonRow
|
||||||
|
isReadOnly={false}
|
||||||
|
onDelete={[Function]}
|
||||||
|
onSubmit={[Function]}
|
||||||
|
/>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Render should render is ready only message 1`] = `
|
||||||
|
<div>
|
||||||
|
<PageHeader
|
||||||
|
model={Object {}}
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
className="page-container page-body"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<form
|
||||||
|
onSubmit={[Function]}
|
||||||
|
>
|
||||||
|
<BasicSettings
|
||||||
|
dataSourceName="gdev-cloudwatch"
|
||||||
|
onChange={[Function]}
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
className="grafana-info-box span8"
|
||||||
|
>
|
||||||
|
This datasource was added by config and cannot be modified using the UI. Please contact your server admin to update this datasource.
|
||||||
|
</div>
|
||||||
|
<PluginSettings
|
||||||
|
dataSource={
|
||||||
|
Object {
|
||||||
|
"access": "",
|
||||||
|
"basicAuth": false,
|
||||||
|
"basicAuthPassword": "",
|
||||||
|
"basicAuthUser": "",
|
||||||
|
"database": "",
|
||||||
|
"id": 13,
|
||||||
|
"isDefault": false,
|
||||||
|
"jsonData": Object {
|
||||||
|
"authType": "credentials",
|
||||||
|
"defaultRegion": "eu-west-2",
|
||||||
|
},
|
||||||
|
"name": "gdev-cloudwatch",
|
||||||
|
"orgId": 1,
|
||||||
|
"password": "",
|
||||||
|
"readOnly": true,
|
||||||
|
"type": "cloudwatch",
|
||||||
|
"typeLogoUrl": "public/app/plugins/datasource/cloudwatch/img/amazon-web-services.png",
|
||||||
|
"url": "",
|
||||||
|
"user": "",
|
||||||
|
"withCredentials": false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dataSourceMeta={
|
||||||
|
Object {
|
||||||
|
"defaultNavUrl": "some/url",
|
||||||
|
"enabled": false,
|
||||||
|
"hasUpdate": false,
|
||||||
|
"id": "1",
|
||||||
|
"info": Object {
|
||||||
|
"author": Object {
|
||||||
|
"name": "Grafana Labs",
|
||||||
|
"url": "url/to/GrafanaLabs",
|
||||||
|
},
|
||||||
|
"description": "pretty decent plugin",
|
||||||
|
"links": Array [
|
||||||
|
"one link",
|
||||||
|
],
|
||||||
|
"logos": Object {
|
||||||
|
"large": "large/logo",
|
||||||
|
"small": "small/logo",
|
||||||
|
},
|
||||||
|
"screenshots": "screenshot/1",
|
||||||
|
"updated": "2018-09-26",
|
||||||
|
"version": "1",
|
||||||
|
},
|
||||||
|
"latestVersion": "1",
|
||||||
|
"module": Object {},
|
||||||
|
"name": "pretty cool plugin 1",
|
||||||
|
"pinned": false,
|
||||||
|
"state": "",
|
||||||
|
"type": "",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onModelChange={[Function]}
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
className="gf-form-group section"
|
||||||
|
/>
|
||||||
|
<ButtonRow
|
||||||
|
isReadOnly={true}
|
||||||
|
onDelete={[Function]}
|
||||||
|
onSubmit={[Function]}
|
||||||
|
/>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Render should render loader 1`] = `
|
||||||
|
<div>
|
||||||
|
<PageHeader
|
||||||
|
model={Object {}}
|
||||||
|
/>
|
||||||
|
<PageLoader
|
||||||
|
pageName="Data source settings"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
`;
|
@ -1,10 +1,12 @@
|
|||||||
import { ThunkAction } from 'redux-thunk';
|
import { ThunkAction } from 'redux-thunk';
|
||||||
import { DataSource, Plugin, StoreState } from 'app/types';
|
import config from '../../../core/config';
|
||||||
import { getBackendSrv } from '../../../core/services/backend_srv';
|
import { getBackendSrv } from 'app/core/services/backend_srv';
|
||||||
import { LayoutMode } from '../../../core/components/LayoutSelector/LayoutSelector';
|
import { getDatasourceSrv } from 'app/features/plugins/datasource_srv';
|
||||||
import { updateLocation, updateNavIndex, UpdateNavIndexAction } from '../../../core/actions';
|
import { LayoutMode } from 'app/core/components/LayoutSelector/LayoutSelector';
|
||||||
import { UpdateLocationAction } from '../../../core/actions/location';
|
import { updateLocation, updateNavIndex, UpdateNavIndexAction } from 'app/core/actions';
|
||||||
|
import { UpdateLocationAction } from 'app/core/actions/location';
|
||||||
import { buildNavModel } from './navModel';
|
import { buildNavModel } from './navModel';
|
||||||
|
import { DataSource, Plugin, StoreState } from 'app/types';
|
||||||
|
|
||||||
export enum ActionTypes {
|
export enum ActionTypes {
|
||||||
LoadDataSources = 'LOAD_DATA_SOURCES',
|
LoadDataSources = 'LOAD_DATA_SOURCES',
|
||||||
@ -14,43 +16,49 @@ export enum ActionTypes {
|
|||||||
SetDataSourcesSearchQuery = 'SET_DATA_SOURCES_SEARCH_QUERY',
|
SetDataSourcesSearchQuery = 'SET_DATA_SOURCES_SEARCH_QUERY',
|
||||||
SetDataSourcesLayoutMode = 'SET_DATA_SOURCES_LAYOUT_MODE',
|
SetDataSourcesLayoutMode = 'SET_DATA_SOURCES_LAYOUT_MODE',
|
||||||
SetDataSourceTypeSearchQuery = 'SET_DATA_SOURCE_TYPE_SEARCH_QUERY',
|
SetDataSourceTypeSearchQuery = 'SET_DATA_SOURCE_TYPE_SEARCH_QUERY',
|
||||||
|
SetDataSourceName = 'SET_DATA_SOURCE_NAME',
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LoadDataSourcesAction {
|
interface LoadDataSourcesAction {
|
||||||
type: ActionTypes.LoadDataSources;
|
type: ActionTypes.LoadDataSources;
|
||||||
payload: DataSource[];
|
payload: DataSource[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SetDataSourcesSearchQueryAction {
|
interface SetDataSourcesSearchQueryAction {
|
||||||
type: ActionTypes.SetDataSourcesSearchQuery;
|
type: ActionTypes.SetDataSourcesSearchQuery;
|
||||||
payload: string;
|
payload: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SetDataSourcesLayoutModeAction {
|
interface SetDataSourcesLayoutModeAction {
|
||||||
type: ActionTypes.SetDataSourcesLayoutMode;
|
type: ActionTypes.SetDataSourcesLayoutMode;
|
||||||
payload: LayoutMode;
|
payload: LayoutMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LoadDataSourceTypesAction {
|
interface LoadDataSourceTypesAction {
|
||||||
type: ActionTypes.LoadDataSourceTypes;
|
type: ActionTypes.LoadDataSourceTypes;
|
||||||
payload: Plugin[];
|
payload: Plugin[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SetDataSourceTypeSearchQueryAction {
|
interface SetDataSourceTypeSearchQueryAction {
|
||||||
type: ActionTypes.SetDataSourceTypeSearchQuery;
|
type: ActionTypes.SetDataSourceTypeSearchQuery;
|
||||||
payload: string;
|
payload: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LoadDataSourceAction {
|
interface LoadDataSourceAction {
|
||||||
type: ActionTypes.LoadDataSource;
|
type: ActionTypes.LoadDataSource;
|
||||||
payload: DataSource;
|
payload: DataSource;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LoadDataSourceMetaAction {
|
interface LoadDataSourceMetaAction {
|
||||||
type: ActionTypes.LoadDataSourceMeta;
|
type: ActionTypes.LoadDataSourceMeta;
|
||||||
payload: Plugin;
|
payload: Plugin;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface SetDataSourceNameAction {
|
||||||
|
type: ActionTypes.SetDataSourceName;
|
||||||
|
payload: string;
|
||||||
|
}
|
||||||
|
|
||||||
const dataSourcesLoaded = (dataSources: DataSource[]): LoadDataSourcesAction => ({
|
const dataSourcesLoaded = (dataSources: DataSource[]): LoadDataSourcesAction => ({
|
||||||
type: ActionTypes.LoadDataSources,
|
type: ActionTypes.LoadDataSources,
|
||||||
payload: dataSources,
|
payload: dataSources,
|
||||||
@ -86,6 +94,11 @@ export const setDataSourceTypeSearchQuery = (query: string): SetDataSourceTypeSe
|
|||||||
payload: query,
|
payload: query,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const setDataSourceName = (name: string) => ({
|
||||||
|
type: ActionTypes.SetDataSourceName,
|
||||||
|
payload: name,
|
||||||
|
});
|
||||||
|
|
||||||
export type Action =
|
export type Action =
|
||||||
| LoadDataSourcesAction
|
| LoadDataSourcesAction
|
||||||
| SetDataSourcesSearchQueryAction
|
| SetDataSourcesSearchQueryAction
|
||||||
@ -95,7 +108,8 @@ export type Action =
|
|||||||
| SetDataSourceTypeSearchQueryAction
|
| SetDataSourceTypeSearchQueryAction
|
||||||
| LoadDataSourceAction
|
| LoadDataSourceAction
|
||||||
| UpdateNavIndexAction
|
| UpdateNavIndexAction
|
||||||
| LoadDataSourceMetaAction;
|
| LoadDataSourceMetaAction
|
||||||
|
| SetDataSourceNameAction;
|
||||||
|
|
||||||
type ThunkResult<R> = ThunkAction<R, StoreState, undefined, Action>;
|
type ThunkResult<R> = ThunkAction<R, StoreState, undefined, Action>;
|
||||||
|
|
||||||
@ -145,6 +159,23 @@ export function loadDataSourceTypes(): ThunkResult<void> {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function updateDataSource(dataSource: DataSource): ThunkResult<void> {
|
||||||
|
return async dispatch => {
|
||||||
|
await getBackendSrv().put(`/api/datasources/${dataSource.id}`, dataSource);
|
||||||
|
await updateFrontendSettings();
|
||||||
|
return dispatch(loadDataSource(dataSource.id));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function deleteDataSource(): ThunkResult<void> {
|
||||||
|
return async (dispatch, getStore) => {
|
||||||
|
const dataSource = getStore().dataSources.dataSource;
|
||||||
|
|
||||||
|
await getBackendSrv().delete(`/api/datasources/${dataSource.id}`);
|
||||||
|
dispatch(updateLocation({ path: '/datasources' }));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export function nameExits(dataSources, name) {
|
export function nameExits(dataSources, name) {
|
||||||
return (
|
return (
|
||||||
dataSources.filter(dataSource => {
|
dataSources.filter(dataSource => {
|
||||||
@ -173,6 +204,16 @@ export function findNewName(dataSources, name) {
|
|||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function updateFrontendSettings() {
|
||||||
|
return getBackendSrv()
|
||||||
|
.get('/api/frontend/settings')
|
||||||
|
.then(settings => {
|
||||||
|
config.datasources = settings.datasources;
|
||||||
|
config.defaultDatasource = settings.defaultDatasource;
|
||||||
|
getDatasourceSrv().init();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function nameHasSuffix(name) {
|
function nameHasSuffix(name) {
|
||||||
return name.endsWith('-', name.length - 1);
|
return name.endsWith('-', name.length - 1);
|
||||||
}
|
}
|
||||||
|
@ -48,6 +48,9 @@ export function getDataSourceLoadingNav(pageName: string): NavModel {
|
|||||||
{
|
{
|
||||||
access: '',
|
access: '',
|
||||||
basicAuth: false,
|
basicAuth: false,
|
||||||
|
basicAuthUser: '',
|
||||||
|
basicAuthPassword: '',
|
||||||
|
withCredentials: false,
|
||||||
database: '',
|
database: '',
|
||||||
id: 1,
|
id: 1,
|
||||||
isDefault: false,
|
isDefault: false,
|
||||||
|
@ -10,8 +10,8 @@ const initialState: DataSourcesState = {
|
|||||||
dataSourcesCount: 0,
|
dataSourcesCount: 0,
|
||||||
dataSourceTypes: [] as Plugin[],
|
dataSourceTypes: [] as Plugin[],
|
||||||
dataSourceTypeSearchQuery: '',
|
dataSourceTypeSearchQuery: '',
|
||||||
dataSourceMeta: {} as Plugin,
|
|
||||||
hasFetched: false,
|
hasFetched: false,
|
||||||
|
dataSourceMeta: {} as Plugin,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const dataSourcesReducer = (state = initialState, action: Action): DataSourcesState => {
|
export const dataSourcesReducer = (state = initialState, action: Action): DataSourcesState => {
|
||||||
@ -36,6 +36,9 @@ export const dataSourcesReducer = (state = initialState, action: Action): DataSo
|
|||||||
|
|
||||||
case ActionTypes.LoadDataSourceMeta:
|
case ActionTypes.LoadDataSourceMeta:
|
||||||
return { ...state, dataSourceMeta: action.payload };
|
return { ...state, dataSourceMeta: action.payload };
|
||||||
|
|
||||||
|
case ActionTypes.SetDataSourceName:
|
||||||
|
return { ...state, dataSource: { ...state.dataSource, name: action.payload } };
|
||||||
}
|
}
|
||||||
|
|
||||||
return state;
|
return state;
|
||||||
|
@ -20,7 +20,15 @@ export const getDataSource = (state, dataSourceId): DataSource | null => {
|
|||||||
if (state.dataSource.id === parseInt(dataSourceId, 10)) {
|
if (state.dataSource.id === parseInt(dataSourceId, 10)) {
|
||||||
return state.dataSource;
|
return state.dataSource;
|
||||||
}
|
}
|
||||||
return null;
|
return {} as DataSource;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getDataSourceMeta = (state, type): Plugin => {
|
||||||
|
if (state.dataSourceMeta.id === type) {
|
||||||
|
return state.dataSourceMeta;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {} as Plugin;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getDataSourcesSearchQuery = state => state.searchQuery;
|
export const getDataSourcesSearchQuery = state => state.searchQuery;
|
||||||
|
@ -26,6 +26,7 @@ export const getMockPlugins = (amount: number): Plugin[] => {
|
|||||||
pinned: false,
|
pinned: false,
|
||||||
state: '',
|
state: '',
|
||||||
type: '',
|
type: '',
|
||||||
|
module: {},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -55,5 +56,6 @@ export const getMockPlugin = () => {
|
|||||||
pinned: false,
|
pinned: false,
|
||||||
state: '',
|
state: '',
|
||||||
type: '',
|
type: '',
|
||||||
|
module: {},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -33,6 +33,7 @@ exports[`Render should render component 1`] = `
|
|||||||
"version": "1",
|
"version": "1",
|
||||||
},
|
},
|
||||||
"latestVersion": "1.0",
|
"latestVersion": "1.0",
|
||||||
|
"module": Object {},
|
||||||
"name": "pretty cool plugin-0",
|
"name": "pretty cool plugin-0",
|
||||||
"pinned": false,
|
"pinned": false,
|
||||||
"state": "",
|
"state": "",
|
||||||
@ -66,6 +67,7 @@ exports[`Render should render component 1`] = `
|
|||||||
"version": "1",
|
"version": "1",
|
||||||
},
|
},
|
||||||
"latestVersion": "1.1",
|
"latestVersion": "1.1",
|
||||||
|
"module": Object {},
|
||||||
"name": "pretty cool plugin-1",
|
"name": "pretty cool plugin-1",
|
||||||
"pinned": false,
|
"pinned": false,
|
||||||
"state": "",
|
"state": "",
|
||||||
@ -99,6 +101,7 @@ exports[`Render should render component 1`] = `
|
|||||||
"version": "1",
|
"version": "1",
|
||||||
},
|
},
|
||||||
"latestVersion": "1.2",
|
"latestVersion": "1.2",
|
||||||
|
"module": Object {},
|
||||||
"name": "pretty cool plugin-2",
|
"name": "pretty cool plugin-2",
|
||||||
"pinned": false,
|
"pinned": false,
|
||||||
"state": "",
|
"state": "",
|
||||||
@ -132,6 +135,7 @@ exports[`Render should render component 1`] = `
|
|||||||
"version": "1",
|
"version": "1",
|
||||||
},
|
},
|
||||||
"latestVersion": "1.3",
|
"latestVersion": "1.3",
|
||||||
|
"module": Object {},
|
||||||
"name": "pretty cool plugin-3",
|
"name": "pretty cool plugin-3",
|
||||||
"pinned": false,
|
"pinned": false,
|
||||||
"state": "",
|
"state": "",
|
||||||
@ -165,6 +169,7 @@ exports[`Render should render component 1`] = `
|
|||||||
"version": "1",
|
"version": "1",
|
||||||
},
|
},
|
||||||
"latestVersion": "1.4",
|
"latestVersion": "1.4",
|
||||||
|
"module": Object {},
|
||||||
"name": "pretty cool plugin-4",
|
"name": "pretty cool plugin-4",
|
||||||
"pinned": false,
|
"pinned": false,
|
||||||
"state": "",
|
"state": "",
|
||||||
@ -198,6 +203,7 @@ exports[`Render should render component 1`] = `
|
|||||||
"version": "1",
|
"version": "1",
|
||||||
},
|
},
|
||||||
"latestVersion": "1.5",
|
"latestVersion": "1.5",
|
||||||
|
"module": Object {},
|
||||||
"name": "pretty cool plugin-5",
|
"name": "pretty cool plugin-5",
|
||||||
"pinned": false,
|
"pinned": false,
|
||||||
"state": "",
|
"state": "",
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import './plugin_edit_ctrl';
|
|
||||||
import './plugin_page_ctrl';
|
import './plugin_page_ctrl';
|
||||||
import './import_list/import_list';
|
import './import_list/import_list';
|
||||||
import './ds_edit_ctrl';
|
import './ds_edit_ctrl';
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { coreModule } from 'app/core/core';
|
import { coreModule } from 'app/core/core';
|
||||||
import { store } from 'app/store/configureStore';
|
import { store } from 'app/store/store';
|
||||||
import { getNavModel } from 'app/core/selectors/navModel';
|
import { getNavModel } from 'app/core/selectors/navModel';
|
||||||
import { buildNavModel } from './state/navModel';
|
import { buildNavModel } from './state/navModel';
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import config from 'app/core/config';
|
import config from 'app/core/config';
|
||||||
import { coreModule, appEvents } from 'app/core/core';
|
import { coreModule, appEvents } from 'app/core/core';
|
||||||
import { store } from 'app/store/configureStore';
|
import { store } from 'app/store/store';
|
||||||
import { getNavModel } from 'app/core/selectors/navModel';
|
import { getNavModel } from 'app/core/selectors/navModel';
|
||||||
import { buildNavModel } from './state/navModel';
|
import { buildNavModel } from './state/navModel';
|
||||||
|
|
||||||
|
@ -1,72 +0,0 @@
|
|||||||
<page-header model="ctrl.navModel"></page-header>
|
|
||||||
|
|
||||||
<div class="page-container page-body">
|
|
||||||
<h3 class="page-sub-heading">Settings</h3>
|
|
||||||
|
|
||||||
<form name="ctrl.editForm" ng-if="ctrl.current">
|
|
||||||
<div class="gf-form-group">
|
|
||||||
<div class="gf-form-inline">
|
|
||||||
<div class="gf-form max-width-30">
|
|
||||||
<span class="gf-form-label width-10">Name</span>
|
|
||||||
<input class="gf-form-input max-width-23" type="text" ng-model="ctrl.current.name" placeholder="name" required>
|
|
||||||
<info-popover offset="0px -135px" mode="right-absolute">
|
|
||||||
The name is used when you select the data source in panels.
|
|
||||||
The <em>Default</em> data source is preselected in new
|
|
||||||
panels.
|
|
||||||
</info-popover>
|
|
||||||
</div>
|
|
||||||
<gf-form-switch class="gf-form" label="Default" checked="ctrl.current.isDefault" switch-class="max-width-6"></gf-form-switch>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="grafana-info-box" ng-if="ctrl.datasourceMeta.state === 'alpha'">
|
|
||||||
This plugin is marked as being in alpha state, which means it is in early development phase and
|
|
||||||
updates will include breaking changes.
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="grafana-info-box" ng-if="ctrl.datasourceMeta.state === 'beta'">
|
|
||||||
This plugin is marked as being in a beta development state. This means it is in currently in active development and could be
|
|
||||||
missing important features.
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<rebuild-on-change property="ctrl.datasourceMeta.id">
|
|
||||||
<plugin-component type="datasource-config-ctrl">
|
|
||||||
</plugin-component>
|
|
||||||
</rebuild-on-change>
|
|
||||||
|
|
||||||
<div ng-if="ctrl.hasDashboards">
|
|
||||||
<h3 class="section-heading">Bundled Plugin Dashboards</h3>
|
|
||||||
<div class="section">
|
|
||||||
<dashboard-import-list plugin="ctrl.datasourceMeta" datasource="ctrl.current"></dashboard-import-list>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div ng-if="ctrl.testing" class="gf-form-group section">
|
|
||||||
<h5 ng-show="!ctrl.testing.done">Testing.... <i class="fa fa-spiner fa-spin"></i></h5>
|
|
||||||
<div class="alert-{{ctrl.testing.status}} alert" ng-show="ctrl.testing.done">
|
|
||||||
<div class="alert-icon">
|
|
||||||
<i class="fa fa-exclamation-triangle" ng-show="ctrl.testing.status === 'error'"></i>
|
|
||||||
<i class="fa fa-check" ng-show="ctrl.testing.status !== 'error'"></i>
|
|
||||||
</div>
|
|
||||||
<div class="alert-body">
|
|
||||||
<div class="alert-title">{{ctrl.testing.message}}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="grafana-info-box span8" ng-if="ctrl.current.readOnly">
|
|
||||||
This datasource was added by config and cannot be modified using the UI. Please contact your server admin to update this datasource.
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="gf-form-button-row">
|
|
||||||
<button type="submit" class="btn btn-success" ng-disabled="ctrl.current.readOnly" ng-click="ctrl.saveChanges()">Save & Test</button>
|
|
||||||
<button type="submit" class="btn btn-danger" ng-disabled="ctrl.current.readOnly" ng-show="!ctrl.isNew" ng-click="ctrl.delete()">Delete</button>
|
|
||||||
<a class="btn btn-inverse" href="datasources">Back</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
|
|
||||||
</form>
|
|
||||||
</div>
|
|
@ -149,6 +149,14 @@ function pluginDirectiveLoader($compile, datasourceSrv, $rootScope, $q, $http, $
|
|||||||
return { notFound: true };
|
return { notFound: true };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
scope.$watch(
|
||||||
|
'ctrl.current',
|
||||||
|
() => {
|
||||||
|
scope.onModelChanged(scope.ctrl.current);
|
||||||
|
},
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
baseUrl: dsMeta.baseUrl,
|
baseUrl: dsMeta.baseUrl,
|
||||||
name: 'ds-config-' + dsMeta.id,
|
name: 'ds-config-' + dsMeta.id,
|
||||||
|
@ -1,179 +0,0 @@
|
|||||||
import angular from 'angular';
|
|
||||||
import _ from 'lodash';
|
|
||||||
import Remarkable from 'remarkable';
|
|
||||||
|
|
||||||
export class PluginEditCtrl {
|
|
||||||
model: any;
|
|
||||||
pluginIcon: string;
|
|
||||||
pluginId: any;
|
|
||||||
includes: any;
|
|
||||||
readmeHtml: any;
|
|
||||||
includedDatasources: any;
|
|
||||||
tab: string;
|
|
||||||
navModel: any;
|
|
||||||
hasDashboards: any;
|
|
||||||
preUpdateHook: () => any;
|
|
||||||
postUpdateHook: () => any;
|
|
||||||
|
|
||||||
/** @ngInject */
|
|
||||||
constructor(private $scope, private $rootScope, private backendSrv, private $sce, private $routeParams, navModelSrv) {
|
|
||||||
this.pluginId = $routeParams.pluginId;
|
|
||||||
this.preUpdateHook = () => Promise.resolve();
|
|
||||||
this.postUpdateHook = () => Promise.resolve();
|
|
||||||
|
|
||||||
this.init();
|
|
||||||
}
|
|
||||||
|
|
||||||
setNavModel(model) {
|
|
||||||
let defaultTab = 'readme';
|
|
||||||
|
|
||||||
this.navModel = {
|
|
||||||
main: {
|
|
||||||
img: model.info.logos.large,
|
|
||||||
subTitle: model.info.author.name,
|
|
||||||
url: '',
|
|
||||||
text: model.name,
|
|
||||||
breadcrumbs: [{ title: 'Plugins', url: 'plugins' }],
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
icon: 'fa fa-fw fa-file-text-o',
|
|
||||||
id: 'readme',
|
|
||||||
text: 'Readme',
|
|
||||||
url: `plugins/${this.model.id}/edit?tab=readme`,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
if (model.type === 'app') {
|
|
||||||
this.navModel.main.children.push({
|
|
||||||
icon: 'gicon gicon-cog',
|
|
||||||
id: 'config',
|
|
||||||
text: 'Config',
|
|
||||||
url: `plugins/${this.model.id}/edit?tab=config`,
|
|
||||||
});
|
|
||||||
|
|
||||||
const hasDashboards = _.find(model.includes, { type: 'dashboard' });
|
|
||||||
|
|
||||||
if (hasDashboards) {
|
|
||||||
this.navModel.main.children.push({
|
|
||||||
icon: 'gicon gicon-dashboard',
|
|
||||||
id: 'dashboards',
|
|
||||||
text: 'Dashboards',
|
|
||||||
url: `plugins/${this.model.id}/edit?tab=dashboards`,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
defaultTab = 'config';
|
|
||||||
}
|
|
||||||
|
|
||||||
this.tab = this.$routeParams.tab || defaultTab;
|
|
||||||
|
|
||||||
for (const tab of this.navModel.main.children) {
|
|
||||||
if (tab.id === this.tab) {
|
|
||||||
tab.active = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
init() {
|
|
||||||
return this.backendSrv.get(`/api/plugins/${this.pluginId}/settings`).then(result => {
|
|
||||||
this.model = result;
|
|
||||||
this.pluginIcon = this.getPluginIcon(this.model.type);
|
|
||||||
|
|
||||||
this.model.dependencies.plugins.forEach(plug => {
|
|
||||||
plug.icon = this.getPluginIcon(plug.type);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.includes = _.map(result.includes, plug => {
|
|
||||||
plug.icon = this.getPluginIcon(plug.type);
|
|
||||||
return plug;
|
|
||||||
});
|
|
||||||
|
|
||||||
this.setNavModel(this.model);
|
|
||||||
return this.initReadme();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
initReadme() {
|
|
||||||
return this.backendSrv.get(`/api/plugins/${this.pluginId}/markdown/readme`).then(res => {
|
|
||||||
const md = new Remarkable({
|
|
||||||
linkify: true,
|
|
||||||
});
|
|
||||||
this.readmeHtml = this.$sce.trustAsHtml(md.render(res));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
getPluginIcon(type) {
|
|
||||||
switch (type) {
|
|
||||||
case 'datasource':
|
|
||||||
return 'icon-gf icon-gf-datasources';
|
|
||||||
case 'panel':
|
|
||||||
return 'icon-gf icon-gf-panel';
|
|
||||||
case 'app':
|
|
||||||
return 'icon-gf icon-gf-apps';
|
|
||||||
case 'page':
|
|
||||||
return 'icon-gf icon-gf-endpoint-tiny';
|
|
||||||
case 'dashboard':
|
|
||||||
return 'icon-gf icon-gf-dashboard';
|
|
||||||
default:
|
|
||||||
return 'icon-gf icon-gf-apps';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
update() {
|
|
||||||
this.preUpdateHook()
|
|
||||||
.then(() => {
|
|
||||||
const updateCmd = _.extend(
|
|
||||||
{
|
|
||||||
enabled: this.model.enabled,
|
|
||||||
pinned: this.model.pinned,
|
|
||||||
jsonData: this.model.jsonData,
|
|
||||||
secureJsonData: this.model.secureJsonData,
|
|
||||||
},
|
|
||||||
{}
|
|
||||||
);
|
|
||||||
return this.backendSrv.post(`/api/plugins/${this.pluginId}/settings`, updateCmd);
|
|
||||||
})
|
|
||||||
.then(this.postUpdateHook)
|
|
||||||
.then(res => {
|
|
||||||
window.location.href = window.location.href;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
importDashboards() {
|
|
||||||
return Promise.resolve();
|
|
||||||
}
|
|
||||||
|
|
||||||
setPreUpdateHook(callback: () => any) {
|
|
||||||
this.preUpdateHook = callback;
|
|
||||||
}
|
|
||||||
|
|
||||||
setPostUpdateHook(callback: () => any) {
|
|
||||||
this.postUpdateHook = callback;
|
|
||||||
}
|
|
||||||
|
|
||||||
updateAvailable() {
|
|
||||||
const modalScope = this.$scope.$new(true);
|
|
||||||
modalScope.plugin = this.model;
|
|
||||||
|
|
||||||
this.$rootScope.appEvent('show-modal', {
|
|
||||||
src: 'public/app/features/plugins/partials/update_instructions.html',
|
|
||||||
scope: modalScope,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
enable() {
|
|
||||||
this.model.enabled = true;
|
|
||||||
this.model.pinned = true;
|
|
||||||
this.update();
|
|
||||||
}
|
|
||||||
|
|
||||||
disable() {
|
|
||||||
this.model.enabled = false;
|
|
||||||
this.model.pinned = false;
|
|
||||||
this.update();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
angular.module('grafana.controllers').controller('PluginEditCtrl', PluginEditCtrl);
|
|
@ -362,14 +362,9 @@ export default class CloudWatchDatasource {
|
|||||||
const metricName = 'EstimatedCharges';
|
const metricName = 'EstimatedCharges';
|
||||||
const dimensions = {};
|
const dimensions = {};
|
||||||
|
|
||||||
return this.getDimensionValues(region, namespace, metricName, 'ServiceName', dimensions).then(
|
return this.getDimensionValues(region, namespace, metricName, 'ServiceName', dimensions).then(() => {
|
||||||
() => {
|
return { status: 'success', message: 'Data source is working' };
|
||||||
return { status: 'success', message: 'Data source is working' };
|
});
|
||||||
},
|
|
||||||
err => {
|
|
||||||
return { status: 'error', message: err.message };
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
awsRequest(url, data) {
|
awsRequest(url, data) {
|
||||||
|
@ -3,7 +3,7 @@ import ReactDOM from 'react-dom';
|
|||||||
import { Provider } from 'react-redux';
|
import { Provider } from 'react-redux';
|
||||||
|
|
||||||
import coreModule from 'app/core/core_module';
|
import coreModule from 'app/core/core_module';
|
||||||
import { store } from 'app/store/configureStore';
|
import { store } from 'app/store/store';
|
||||||
import { BackendSrv } from 'app/core/services/backend_srv';
|
import { BackendSrv } from 'app/core/services/backend_srv';
|
||||||
import { DatasourceSrv } from 'app/features/plugins/datasource_srv';
|
import { DatasourceSrv } from 'app/features/plugins/datasource_srv';
|
||||||
import { ContextSrv } from 'app/core/services/context_srv';
|
import { ContextSrv } from 'app/core/services/context_srv';
|
||||||
|
@ -14,6 +14,7 @@ import DataSourcesListPage from 'app/features/datasources/DataSourcesListPage';
|
|||||||
import NewDataSourcePage from '../features/datasources/NewDataSourcePage';
|
import NewDataSourcePage from '../features/datasources/NewDataSourcePage';
|
||||||
import UsersListPage from 'app/features/users/UsersListPage';
|
import UsersListPage from 'app/features/users/UsersListPage';
|
||||||
import DataSourceDashboards from 'app/features/datasources/DataSourceDashboards';
|
import DataSourceDashboards from 'app/features/datasources/DataSourceDashboards';
|
||||||
|
import DataSourceSettings from '../features/datasources/settings/DataSourceSettings';
|
||||||
import OrgDetailsPage from '../features/org/OrgDetailsPage';
|
import OrgDetailsPage from '../features/org/OrgDetailsPage';
|
||||||
|
|
||||||
/** @ngInject */
|
/** @ngInject */
|
||||||
@ -74,10 +75,11 @@ export function setupAngularRoutes($routeProvider, $locationProvider) {
|
|||||||
component: () => DataSourcesListPage,
|
component: () => DataSourcesListPage,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.when('/datasources/edit/:id', {
|
.when('/datasources/edit/:id/', {
|
||||||
templateUrl: 'public/app/features/plugins/partials/ds_edit.html',
|
template: '<react-container />',
|
||||||
controller: 'DataSourceEditCtrl',
|
resolve: {
|
||||||
controllerAs: 'ctrl',
|
component: () => DataSourceSettings,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
.when('/datasources/edit/:id/dashboards', {
|
.when('/datasources/edit/:id/dashboards', {
|
||||||
template: '<react-container />',
|
template: '<react-container />',
|
||||||
|
@ -11,6 +11,7 @@ import pluginReducers from 'app/features/plugins/state/reducers';
|
|||||||
import dataSourcesReducers from 'app/features/datasources/state/reducers';
|
import dataSourcesReducers from 'app/features/datasources/state/reducers';
|
||||||
import usersReducers from 'app/features/users/state/reducers';
|
import usersReducers from 'app/features/users/state/reducers';
|
||||||
import organizationReducers from 'app/features/org/state/reducers';
|
import organizationReducers from 'app/features/org/state/reducers';
|
||||||
|
import { setStore } from './store';
|
||||||
|
|
||||||
const rootReducers = {
|
const rootReducers = {
|
||||||
...sharedReducers,
|
...sharedReducers,
|
||||||
@ -25,8 +26,6 @@ const rootReducers = {
|
|||||||
...organizationReducers,
|
...organizationReducers,
|
||||||
};
|
};
|
||||||
|
|
||||||
export let store;
|
|
||||||
|
|
||||||
export function addRootReducer(reducers) {
|
export function addRootReducer(reducers) {
|
||||||
Object.assign(rootReducers, ...reducers);
|
Object.assign(rootReducers, ...reducers);
|
||||||
}
|
}
|
||||||
@ -38,8 +37,8 @@ export function configureStore() {
|
|||||||
|
|
||||||
if (process.env.NODE_ENV !== 'production') {
|
if (process.env.NODE_ENV !== 'production') {
|
||||||
// DEV builds we had the logger middleware
|
// DEV builds we had the logger middleware
|
||||||
store = createStore(rootReducer, {}, composeEnhancers(applyMiddleware(thunk, createLogger())));
|
setStore(createStore(rootReducer, {}, composeEnhancers(applyMiddleware(thunk, createLogger()))));
|
||||||
} else {
|
} else {
|
||||||
store = createStore(rootReducer, {}, composeEnhancers(applyMiddleware(thunk)));
|
setStore(createStore(rootReducer, {}, composeEnhancers(applyMiddleware(thunk))));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
5
public/app/store/store.ts
Normal file
5
public/app/store/store.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
export let store;
|
||||||
|
|
||||||
|
export function setStore(newStore) {
|
||||||
|
store = newStore;
|
||||||
|
}
|
@ -13,9 +13,12 @@ export interface DataSource {
|
|||||||
user: string;
|
user: string;
|
||||||
database: string;
|
database: string;
|
||||||
basicAuth: boolean;
|
basicAuth: boolean;
|
||||||
|
basicAuthPassword: string;
|
||||||
|
basicAuthUser: string;
|
||||||
isDefault: boolean;
|
isDefault: boolean;
|
||||||
jsonData: { authType: string; defaultRegion: string };
|
jsonData: { authType: string; defaultRegion: string };
|
||||||
readOnly: boolean;
|
readOnly: boolean;
|
||||||
|
withCredentials: boolean;
|
||||||
meta?: PluginMeta;
|
meta?: PluginMeta;
|
||||||
pluginExports?: PluginExports;
|
pluginExports?: PluginExports;
|
||||||
init?: () => void;
|
init?: () => void;
|
||||||
|
@ -73,6 +73,7 @@ export interface Plugin {
|
|||||||
pinned: boolean;
|
pinned: boolean;
|
||||||
state: string;
|
state: string;
|
||||||
type: string;
|
type: string;
|
||||||
|
module: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PluginDashboard {
|
export interface PluginDashboard {
|
||||||
|
@ -88,4 +88,5 @@ export interface DataQueryOptions {
|
|||||||
|
|
||||||
export interface DataSourceApi {
|
export interface DataSourceApi {
|
||||||
query(options: DataQueryOptions): Promise<DataQueryResponse>;
|
query(options: DataQueryOptions): Promise<DataQueryResponse>;
|
||||||
|
testDatasource(): Promise<any>;
|
||||||
}
|
}
|
||||||
|
@ -4,22 +4,19 @@ const merge = require('webpack-merge');
|
|||||||
const common = require('./webpack.common.js');
|
const common = require('./webpack.common.js');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const webpack = require('webpack');
|
const webpack = require('webpack');
|
||||||
const HtmlWebpackPlugin = require("html-webpack-plugin");
|
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||||
const HtmlWebpackHarddiskPlugin = require('html-webpack-harddisk-plugin');
|
const HtmlWebpackHarddiskPlugin = require('html-webpack-harddisk-plugin');
|
||||||
const CleanWebpackPlugin = require('clean-webpack-plugin');
|
const CleanWebpackPlugin = require('clean-webpack-plugin');
|
||||||
|
|
||||||
module.exports = merge(common, {
|
module.exports = merge(common, {
|
||||||
entry: {
|
entry: {
|
||||||
app: [
|
app: ['webpack-dev-server/client?http://localhost:3333', './public/app/dev.ts'],
|
||||||
'webpack-dev-server/client?http://localhost:3333',
|
|
||||||
'./public/app/dev.ts',
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
|
|
||||||
output: {
|
output: {
|
||||||
path: path.resolve(__dirname, '../../public/build'),
|
path: path.resolve(__dirname, '../../public/build'),
|
||||||
filename: '[name].[hash].js',
|
filename: '[name].[hash].js',
|
||||||
publicPath: "/public/build/",
|
publicPath: '/public/build/',
|
||||||
pathinfo: false,
|
pathinfo: false,
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -34,8 +31,8 @@ module.exports = merge(common, {
|
|||||||
hot: true,
|
hot: true,
|
||||||
port: 3333,
|
port: 3333,
|
||||||
proxy: {
|
proxy: {
|
||||||
'!/public/build': 'http://localhost:3000'
|
'!/public/build': 'http://localhost:3000',
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
optimization: {
|
optimization: {
|
||||||
@ -49,38 +46,37 @@ module.exports = merge(common, {
|
|||||||
{
|
{
|
||||||
test: /\.tsx?$/,
|
test: /\.tsx?$/,
|
||||||
exclude: /node_modules/,
|
exclude: /node_modules/,
|
||||||
use: [{
|
use: [
|
||||||
loader: 'babel-loader',
|
{
|
||||||
options: {
|
loader: 'babel-loader',
|
||||||
cacheDirectory: true,
|
options: {
|
||||||
babelrc: false,
|
cacheDirectory: true,
|
||||||
plugins: [
|
babelrc: false,
|
||||||
'syntax-dynamic-import',
|
plugins: ['syntax-dynamic-import', 'react-hot-loader/babel'],
|
||||||
'react-hot-loader/babel'
|
},
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
loader: 'ts-loader',
|
|
||||||
options: {
|
|
||||||
transpileOnly: true,
|
|
||||||
experimentalWatchApi: true
|
|
||||||
},
|
},
|
||||||
}],
|
{
|
||||||
|
loader: 'ts-loader',
|
||||||
|
options: {
|
||||||
|
transpileOnly: true,
|
||||||
|
experimentalWatchApi: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
test: /\.scss$/,
|
test: /\.scss$/,
|
||||||
use: [
|
use: [
|
||||||
"style-loader", // creates style nodes from JS strings
|
'style-loader', // creates style nodes from JS strings
|
||||||
"css-loader", // translates CSS into CommonJS
|
'css-loader', // translates CSS into CommonJS
|
||||||
"sass-loader" // compiles Sass to CSS
|
'sass-loader', // compiles Sass to CSS
|
||||||
]
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
test: /\.(png|jpg|gif|ttf|eot|svg|woff(2)?)(\?[a-z0-9=&.]+)?$/,
|
test: /\.(png|jpg|gif|ttf|eot|svg|woff(2)?)(\?[a-z0-9=&.]+)?$/,
|
||||||
loader: 'file-loader'
|
loader: 'file-loader',
|
||||||
},
|
},
|
||||||
]
|
],
|
||||||
},
|
},
|
||||||
|
|
||||||
plugins: [
|
plugins: [
|
||||||
@ -89,16 +85,16 @@ module.exports = merge(common, {
|
|||||||
filename: path.resolve(__dirname, '../../public/views/index.html'),
|
filename: path.resolve(__dirname, '../../public/views/index.html'),
|
||||||
template: path.resolve(__dirname, '../../public/views/index-template.html'),
|
template: path.resolve(__dirname, '../../public/views/index-template.html'),
|
||||||
inject: 'body',
|
inject: 'body',
|
||||||
alwaysWriteToDisk: true
|
alwaysWriteToDisk: true,
|
||||||
}),
|
}),
|
||||||
new HtmlWebpackHarddiskPlugin(),
|
new HtmlWebpackHarddiskPlugin(),
|
||||||
new webpack.NamedModulesPlugin(),
|
new webpack.NamedModulesPlugin(),
|
||||||
new webpack.HotModuleReplacementPlugin(),
|
new webpack.HotModuleReplacementPlugin(),
|
||||||
new webpack.DefinePlugin({
|
new webpack.DefinePlugin({
|
||||||
'GRAFANA_THEME': JSON.stringify(process.env.GRAFANA_THEME || 'dark'),
|
GRAFANA_THEME: JSON.stringify(process.env.GRAFANA_THEME || 'dark'),
|
||||||
'process.env': {
|
'process.env': {
|
||||||
'NODE_ENV': JSON.stringify('development')
|
NODE_ENV: JSON.stringify('development'),
|
||||||
}
|
},
|
||||||
}),
|
}),
|
||||||
]
|
],
|
||||||
});
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user