import React, { useState, useCallback } from 'react'; import { SelectableValue } from '@grafana/data'; import { css, cx } from 'emotion'; import { useTheme } from '../../themes'; import { BasicAuthSettings } from './BasicAuthSettings'; import { HttpProxySettings } from './HttpProxySettings'; import { TLSAuthSettings } from './TLSAuthSettings'; import { DataSourceSettings } from '@grafana/data'; import { HttpSettingsProps } from './types'; import { CustomHeadersSettings } from './CustomHeadersSettings'; import { Select } from '../Select/Select'; import { Input } from '../Input/Input'; import { FormField } from '../FormField/FormField'; import { FormLabel } from '../FormLabel/FormLabel'; import { Switch } from '../Switch/Switch'; import { TagsInput } from '../TagsInput/TagsInput'; const ACCESS_OPTIONS: Array<SelectableValue<string>> = [ { label: 'Server (default)', value: 'proxy', }, { label: 'Browser', value: 'direct', }, ]; const DEFAULT_ACCESS_OPTION = { label: 'Server (default)', value: 'proxy', }; const HttpAccessHelp = () => ( <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> ); export const DataSourceHttpSettings: React.FC<HttpSettingsProps> = props => { const { defaultUrl, dataSourceConfig, onChange, showAccessOptions } = props; let urlTooltip; const [isAccessHelpVisible, setIsAccessHelpVisible] = useState(false); const theme = useTheme(); const onSettingsChange = useCallback( (change: Partial<DataSourceSettings<any, any>>) => { onChange({ ...dataSourceConfig, ...change, }); }, [dataSourceConfig] ); switch (dataSourceConfig.access) { case 'direct': urlTooltip = ( <> Your access method is <em>Browser</em>, this means the URL needs to be accessible from the browser. </> ); break; case 'proxy': urlTooltip = ( <> Your access method is <em>Server</em>, this means the URL needs to be accessible from the grafana backend/server. </> ); break; default: urlTooltip = 'Specify a complete HTTP URL (for example http://your_server:8080)'; } const accessSelect = ( <Select width={20} options={ACCESS_OPTIONS} value={ACCESS_OPTIONS.filter(o => o.value === dataSourceConfig.access)[0] || DEFAULT_ACCESS_OPTION} onChange={selectedValue => onSettingsChange({ access: selectedValue.value })} /> ); const isValidUrl = /^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/.test( dataSourceConfig.url ); const notValidStyle = css` box-shadow: inset 0 0px 5px ${theme.colors.red}; `; const inputStyle = cx({ [`width-20`]: true, [notValidStyle]: !isValidUrl }); const urlInput = ( <Input className={inputStyle} placeholder={defaultUrl} value={dataSourceConfig.url} onChange={event => onSettingsChange({ url: event.currentTarget.value })} /> ); return ( <div className="gf-form-group"> <> <h3 className="page-heading">HTTP</h3> <div className="gf-form-group"> <div className="gf-form"> <FormField label="URL" labelWidth={11} tooltip={urlTooltip} inputEl={urlInput} /> </div> {showAccessOptions && ( <> <div className="gf-form-inline"> <div className="gf-form"> <FormField label="Access" labelWidth={11} inputWidth={20} inputEl={accessSelect} /> </div> <div className="gf-form"> <label className="gf-form-label query-keyword pointer" onClick={() => setIsAccessHelpVisible(isVisible => !isVisible)} > Help <i className={`fa fa-caret-${isAccessHelpVisible ? 'down' : 'right'}`} /> </label> </div> </div> {isAccessHelpVisible && <HttpAccessHelp />} </> )} {dataSourceConfig.access === 'proxy' && ( <div className="gf-form"> <FormLabel width={11} tooltip="Grafana Proxy deletes forwarded cookies by default. Specify cookies by name that should be forwarded to the data source." > Whitelisted Cookies </FormLabel> <TagsInput tags={dataSourceConfig.jsonData.keepCookies} onChange={cookies => onSettingsChange({ jsonData: { ...dataSourceConfig.jsonData, keepCookies: cookies } }) } width={20} /> </div> )} </div> </> <> <h3 className="page-heading">Auth</h3> <div className="gf-form-group"> <div className="gf-form-inline"> <Switch label="Basic auth" labelClass="width-13" checked={dataSourceConfig.basicAuth} onChange={event => { onSettingsChange({ basicAuth: event!.currentTarget.checked }); }} /> <Switch label="With Credentials" labelClass="width-13" checked={dataSourceConfig.withCredentials} onChange={event => { onSettingsChange({ withCredentials: event!.currentTarget.checked }); }} tooltip="Whether credentials such as cookies or auth headers should be sent with cross-site requests." /> </div> {dataSourceConfig.access === 'proxy' && ( <HttpProxySettings dataSourceConfig={dataSourceConfig} onChange={jsonData => onSettingsChange({ jsonData })} /> )} </div> {dataSourceConfig.basicAuth && ( <> <h6>Basic Auth Details</h6> <div className="gf-form-group"> <BasicAuthSettings {...props} /> </div> </> )} {(dataSourceConfig.jsonData.tlsAuth || dataSourceConfig.jsonData.tlsAuthWithCACert) && ( <TLSAuthSettings dataSourceConfig={dataSourceConfig} onChange={onChange} /> )} <CustomHeadersSettings dataSourceConfig={dataSourceConfig} onChange={onChange} /> </> </div> ); };