import React, { PureComponent } from 'react'; import { connect, ConnectedProps } from 'react-redux'; import { DataSourceSettings, urlUtil } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; import { Alert, Button } from '@grafana/ui'; import { cleanUpAction } from 'app/core/actions/cleanUp'; import appEvents from 'app/core/app_events'; import Page from 'app/core/components/Page/Page'; import { contextSrv } from 'app/core/core'; import { GrafanaRouteComponentProps } from 'app/core/navigation/types'; import { getNavModel } from 'app/core/selectors/navModel'; import { PluginStateInfo } from 'app/features/plugins/components/PluginStateInfo'; import { StoreState, AccessControlAction } from 'app/types/'; import { ShowConfirmModalEvent } from '../../../types/events'; import { deleteDataSource, initDataSourceSettings, loadDataSource, testDataSource, updateDataSource, } from '../state/actions'; import { getDataSourceLoadingNav, buildNavModel, getDataSourceNav } from '../state/navModel'; import { dataSourceLoaded, setDataSourceName, setIsDefault } from '../state/reducers'; import { getDataSource, getDataSourceMeta } from '../state/selectors'; import BasicSettings from './BasicSettings'; import ButtonRow from './ButtonRow'; import { CloudInfoBox } from './CloudInfoBox'; import { PluginSettings } from './PluginSettings'; export interface OwnProps extends GrafanaRouteComponentProps<{ uid: string }> {} function mapStateToProps(state: StoreState, props: OwnProps) { const dataSourceId = props.match.params.uid; const params = new URLSearchParams(props.location.search); const dataSource = getDataSource(state.dataSources, dataSourceId); const { plugin, loadError, loading, testingStatus } = state.dataSourceSettings; const page = params.get('page'); const nav = plugin ? getDataSourceNav(buildNavModel(dataSource, plugin), page || 'settings') : getDataSourceLoadingNav('settings'); const navModel = getNavModel( state.navIndex, page ? `datasource-page-${page}` : `datasource-settings-${dataSourceId}`, nav ); return { dataSource: getDataSource(state.dataSources, dataSourceId), dataSourceMeta: getDataSourceMeta(state.dataSources, dataSource.type), dataSourceId: dataSourceId, page, plugin, loadError, loading, testingStatus, navModel, }; } const mapDispatchToProps = { deleteDataSource, loadDataSource, setDataSourceName, updateDataSource, setIsDefault, dataSourceLoaded, initDataSourceSettings, testDataSource, cleanUpAction, }; const connector = connect(mapStateToProps, mapDispatchToProps); export type Props = OwnProps & ConnectedProps; export class DataSourceSettingsPage extends PureComponent { componentDidMount() { const { initDataSourceSettings, dataSourceId } = this.props; initDataSourceSettings(dataSourceId); } componentWillUnmount() { this.props.cleanUpAction({ stateSelector: (state) => state.dataSourceSettings, }); } onSubmit = async (evt: React.FormEvent) => { evt.preventDefault(); await this.props.updateDataSource({ ...this.props.dataSource }); this.testDataSource(); }; onTest = async (evt: React.FormEvent) => { evt.preventDefault(); this.testDataSource(); }; onDelete = () => { appEvents.publish( new ShowConfirmModalEvent({ title: 'Delete', text: `Are you sure you want to delete the "${this.props.dataSource.name}" data source?`, yesText: 'Delete', icon: 'trash-alt', onConfirm: () => { this.confirmDelete(); }, }) ); }; confirmDelete = () => { this.props.deleteDataSource(); }; onModelChange = (dataSource: DataSourceSettings) => { this.props.dataSourceLoaded(dataSource); }; isReadOnly() { return this.props.dataSource.readOnly === true; } renderIsReadOnlyMessage() { return ( This data source was added by config and cannot be modified using the UI. Please contact your server admin to update this data source. ); } renderMissingEditRightsMessage() { return ( You are not allowed to modify this data source. Please contact your server admin to update this data source. ); } testDataSource() { const { dataSource, testDataSource } = this.props; testDataSource(dataSource.name); } get hasDataSource() { return this.props.dataSource.id > 0; } onNavigateToExplore() { const { dataSource } = this.props; const exploreState = JSON.stringify({ datasource: dataSource.name, context: 'explore' }); const url = urlUtil.renderUrl('/explore', { left: exploreState }); return url; } renderLoadError() { const { loadError, dataSource } = this.props; const canDeleteDataSource = !this.isReadOnly() && contextSrv.hasPermissionInMetadata(AccessControlAction.DataSourcesDelete, dataSource); const node = { text: loadError!, subTitle: 'Data Source Error', icon: 'exclamation-triangle', }; const nav = { node: node, main: node, }; return ( {this.isReadOnly() && this.renderIsReadOnlyMessage()}
{canDeleteDataSource && ( )}
); } renderConfigPageBody(page: string) { const { plugin } = this.props; if (!plugin || !plugin.configPages) { return null; // still loading } for (const p of plugin.configPages) { if (p.id === page) { // Investigate is any plugins using this? We should change this interface return ; } } return
Page not found: {page}
; } renderAlertDetails() { const { testingStatus } = this.props; return ( <> {testingStatus?.details?.message} {testingStatus?.details?.verboseMessage ? (
{testingStatus?.details?.verboseMessage}
) : null} ); } renderSettings() { const { dataSourceMeta, setDataSourceName, setIsDefault, dataSource, plugin, testingStatus } = this.props; const canWriteDataSource = contextSrv.hasPermissionInMetadata(AccessControlAction.DataSourcesWrite, dataSource); const canDeleteDataSource = contextSrv.hasPermissionInMetadata(AccessControlAction.DataSourcesDelete, dataSource); return (
{!canWriteDataSource && this.renderMissingEditRightsMessage()} {this.isReadOnly() && this.renderIsReadOnlyMessage()} {dataSourceMeta.state && (
)} setIsDefault(state)} onNameChange={(name) => setDataSourceName(name)} /> {plugin && ( )} {testingStatus?.message && (
{testingStatus.details && this.renderAlertDetails()}
)} this.onSubmit(event)} canSave={!this.isReadOnly() && canWriteDataSource} canDelete={!this.isReadOnly() && canDeleteDataSource} onDelete={this.onDelete} onTest={(event) => this.onTest(event)} exploreUrl={this.onNavigateToExplore()} /> ); } render() { const { navModel, page, loadError, loading } = this.props; if (loadError) { return this.renderLoadError(); } return ( {this.hasDataSource ?
{page ? this.renderConfigPageBody(page) : this.renderSettings()}
: null}
); } } export default connector(DataSourceSettingsPage);