mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Merge branch 'data-source-instance-to-react' into datasource-dashboards-to-react
This commit is contained in:
@@ -18,6 +18,10 @@ export interface Props {
|
||||
}
|
||||
|
||||
class AddPermissions extends Component<Props, NewDashboardAclItem> {
|
||||
static defaultProps = {
|
||||
showPermissionLevels: true,
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = this.getCleanState();
|
||||
|
||||
@@ -22,10 +22,6 @@ export interface Props {
|
||||
const getSelectedOption = (optionsWithDesc, value) => optionsWithDesc.find(option => option.value === value);
|
||||
|
||||
class DescriptionPicker extends Component<Props, any> {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { optionsWithDesc, onSelected, disabled, className, value } = this.props;
|
||||
const selectedOption = getSelectedOption(optionsWithDesc, value);
|
||||
|
||||
125
public/app/features/datasources/DataSourceSettings.tsx
Normal file
125
public/app/features/datasources/DataSourceSettings.tsx
Normal file
@@ -0,0 +1,125 @@
|
||||
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);
|
||||
@@ -4,7 +4,6 @@ import { hot } from 'react-hot-loader';
|
||||
import PageHeader from 'app/core/components/PageHeader/PageHeader';
|
||||
import { NavModel, Plugin } from 'app/types';
|
||||
import { addDataSource, loadDataSourceTypes, setDataSourceTypeSearchQuery } from './state/actions';
|
||||
import { updateLocation } from '../../core/actions';
|
||||
import { getNavModel } from 'app/core/selectors/navModel';
|
||||
import { getDataSourceTypes } from './state/selectors';
|
||||
|
||||
@@ -13,7 +12,6 @@ export interface Props {
|
||||
dataSourceTypes: Plugin[];
|
||||
addDataSource: typeof addDataSource;
|
||||
loadDataSourceTypes: typeof loadDataSourceTypes;
|
||||
updateLocation: typeof updateLocation;
|
||||
dataSourceTypeSearchQuery: string;
|
||||
setDataSourceTypeSearchQuery: typeof setDataSourceTypeSearchQuery;
|
||||
}
|
||||
@@ -81,7 +79,6 @@ function mapStateToProps(state) {
|
||||
const mapDispatchToProps = {
|
||||
addDataSource,
|
||||
loadDataSourceTypes,
|
||||
updateLocation,
|
||||
setDataSourceTypeSearchQuery,
|
||||
};
|
||||
|
||||
|
||||
@@ -2,12 +2,15 @@ import { ThunkAction } from 'redux-thunk';
|
||||
import { DataSource, Plugin, StoreState } from 'app/types';
|
||||
import { getBackendSrv } from '../../../core/services/backend_srv';
|
||||
import { LayoutMode } from '../../../core/components/LayoutSelector/LayoutSelector';
|
||||
import { updateLocation } from '../../../core/actions';
|
||||
import { updateLocation, updateNavIndex, UpdateNavIndexAction } from '../../../core/actions';
|
||||
import { UpdateLocationAction } from '../../../core/actions/location';
|
||||
import { buildNavModel } from './navModel';
|
||||
|
||||
export enum ActionTypes {
|
||||
LoadDataSources = 'LOAD_DATA_SOURCES',
|
||||
LoadDataSourceTypes = 'LOAD_DATA_SOURCE_TYPES',
|
||||
LoadDataSource = 'LOAD_DATA_SOURCE',
|
||||
LoadDataSourceMeta = 'LOAD_DATA_SOURCE_META',
|
||||
SetDataSourcesSearchQuery = 'SET_DATA_SOURCES_SEARCH_QUERY',
|
||||
SetDataSourcesLayoutMode = 'SET_DATA_SOURCES_LAYOUT_MODE',
|
||||
SetDataSourceTypeSearchQuery = 'SET_DATA_SOURCE_TYPE_SEARCH_QUERY',
|
||||
@@ -38,11 +41,31 @@ export interface SetDataSourceTypeSearchQueryAction {
|
||||
payload: string;
|
||||
}
|
||||
|
||||
export interface LoadDataSourceAction {
|
||||
type: ActionTypes.LoadDataSource;
|
||||
payload: DataSource;
|
||||
}
|
||||
|
||||
export interface LoadDataSourceMetaAction {
|
||||
type: ActionTypes.LoadDataSourceMeta;
|
||||
payload: Plugin;
|
||||
}
|
||||
|
||||
const dataSourcesLoaded = (dataSources: DataSource[]): LoadDataSourcesAction => ({
|
||||
type: ActionTypes.LoadDataSources,
|
||||
payload: dataSources,
|
||||
});
|
||||
|
||||
const dataSourceLoaded = (dataSource: DataSource): LoadDataSourceAction => ({
|
||||
type: ActionTypes.LoadDataSource,
|
||||
payload: dataSource,
|
||||
});
|
||||
|
||||
const dataSourceMetaLoaded = (dataSourceMeta: Plugin): LoadDataSourceMetaAction => ({
|
||||
type: ActionTypes.LoadDataSourceMeta,
|
||||
payload: dataSourceMeta,
|
||||
});
|
||||
|
||||
const dataSourceTypesLoaded = (dataSourceTypes: Plugin[]): LoadDataSourceTypesAction => ({
|
||||
type: ActionTypes.LoadDataSourceTypes,
|
||||
payload: dataSourceTypes,
|
||||
@@ -69,7 +92,10 @@ export type Action =
|
||||
| SetDataSourcesLayoutModeAction
|
||||
| UpdateLocationAction
|
||||
| LoadDataSourceTypesAction
|
||||
| SetDataSourceTypeSearchQueryAction;
|
||||
| SetDataSourceTypeSearchQueryAction
|
||||
| LoadDataSourceAction
|
||||
| UpdateNavIndexAction
|
||||
| LoadDataSourceMetaAction;
|
||||
|
||||
type ThunkResult<R> = ThunkAction<R, StoreState, undefined, Action>;
|
||||
|
||||
@@ -80,6 +106,16 @@ export function loadDataSources(): ThunkResult<void> {
|
||||
};
|
||||
}
|
||||
|
||||
export function loadDataSource(id: number): ThunkResult<void> {
|
||||
return async dispatch => {
|
||||
const dataSource = await getBackendSrv().get(`/api/datasources/${id}`);
|
||||
const pluginInfo = await getBackendSrv().get(`/api/plugins/${dataSource.type}/settings`);
|
||||
dispatch(dataSourceLoaded(dataSource));
|
||||
dispatch(dataSourceMetaLoaded(pluginInfo));
|
||||
dispatch(updateNavIndex(buildNavModel(dataSource, pluginInfo)));
|
||||
};
|
||||
}
|
||||
|
||||
export function addDataSource(plugin: Plugin): ThunkResult<void> {
|
||||
return async (dispatch, getStore) => {
|
||||
await dispatch(loadDataSources());
|
||||
|
||||
109
public/app/features/datasources/state/navModel.ts
Normal file
109
public/app/features/datasources/state/navModel.ts
Normal file
@@ -0,0 +1,109 @@
|
||||
import { DataSource, NavModel, NavModelItem, PluginMeta } from 'app/types';
|
||||
import config from 'app/core/config';
|
||||
|
||||
export function buildNavModel(dataSource: DataSource, pluginMeta: PluginMeta): NavModelItem {
|
||||
const navModel = {
|
||||
img: pluginMeta.info.logos.large,
|
||||
id: 'datasource-' + dataSource.id,
|
||||
subTitle: `Type: ${pluginMeta.name}`,
|
||||
url: '',
|
||||
text: dataSource.name,
|
||||
breadcrumbs: [{ title: 'Data Sources', url: 'datasources' }],
|
||||
children: [
|
||||
{
|
||||
active: false,
|
||||
icon: 'fa fa-fw fa-sliders',
|
||||
id: `datasource-settings-${dataSource.id}`,
|
||||
text: 'Settings',
|
||||
url: `datasources/edit/${dataSource.id}`,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
if (pluginMeta.includes && hasDashboards(pluginMeta.includes)) {
|
||||
navModel.children.push({
|
||||
active: false,
|
||||
icon: 'fa fa-fw fa-th-large',
|
||||
id: `datasource-dashboards-${dataSource.id}`,
|
||||
text: 'Dashboards',
|
||||
url: `datasources/edit/${dataSource.id}/dashboards`,
|
||||
});
|
||||
}
|
||||
|
||||
if (config.buildInfo.isEnterprise) {
|
||||
navModel.children.push({
|
||||
active: false,
|
||||
icon: 'fa fa-fw fa-lock',
|
||||
id: `datasource-permissions-${dataSource.id}`,
|
||||
text: 'Permissions',
|
||||
url: `datasources/edit/${dataSource.id}/permissions`,
|
||||
});
|
||||
}
|
||||
|
||||
return navModel;
|
||||
}
|
||||
|
||||
export function getDataSourceLoadingNav(pageName: string): NavModel {
|
||||
const main = buildNavModel(
|
||||
{
|
||||
access: '',
|
||||
basicAuth: false,
|
||||
database: '',
|
||||
id: 1,
|
||||
isDefault: false,
|
||||
jsonData: { authType: 'credentials', defaultRegion: 'eu-west-2' },
|
||||
name: 'Loading',
|
||||
orgId: 1,
|
||||
password: '',
|
||||
readOnly: false,
|
||||
type: 'Loading',
|
||||
typeLogoUrl: 'public/img/icn-datasource.svg',
|
||||
url: '',
|
||||
user: '',
|
||||
},
|
||||
{
|
||||
id: '1',
|
||||
name: '',
|
||||
info: {
|
||||
author: {
|
||||
name: '',
|
||||
url: '',
|
||||
},
|
||||
description: '',
|
||||
links: [''],
|
||||
logos: {
|
||||
large: '',
|
||||
small: '',
|
||||
},
|
||||
screenshots: '',
|
||||
updated: '',
|
||||
version: '',
|
||||
},
|
||||
includes: [{ type: '', name: '', path: '' }],
|
||||
}
|
||||
);
|
||||
|
||||
let node: NavModelItem;
|
||||
|
||||
// find active page
|
||||
for (const child of main.children) {
|
||||
if (child.id.indexOf(pageName) > 0) {
|
||||
child.active = true;
|
||||
node = child;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
main: main,
|
||||
node: node,
|
||||
};
|
||||
}
|
||||
|
||||
function hasDashboards(includes) {
|
||||
return (
|
||||
includes.filter(include => {
|
||||
return include.type === 'dashboard';
|
||||
}).length > 0
|
||||
);
|
||||
}
|
||||
@@ -4,11 +4,13 @@ import { LayoutModes } from '../../../core/components/LayoutSelector/LayoutSelec
|
||||
|
||||
const initialState: DataSourcesState = {
|
||||
dataSources: [] as DataSource[],
|
||||
dataSource: {} as DataSource,
|
||||
layoutMode: LayoutModes.Grid,
|
||||
searchQuery: '',
|
||||
dataSourcesCount: 0,
|
||||
dataSourceTypes: [] as Plugin[],
|
||||
dataSourceTypeSearchQuery: '',
|
||||
dataSourceMeta: {} as Plugin,
|
||||
hasFetched: false,
|
||||
};
|
||||
|
||||
@@ -17,6 +19,9 @@ export const dataSourcesReducer = (state = initialState, action: Action): DataSo
|
||||
case ActionTypes.LoadDataSources:
|
||||
return { ...state, hasFetched: true, dataSources: action.payload, dataSourcesCount: action.payload.length };
|
||||
|
||||
case ActionTypes.LoadDataSource:
|
||||
return { ...state, dataSource: action.payload };
|
||||
|
||||
case ActionTypes.SetDataSourcesSearchQuery:
|
||||
return { ...state, searchQuery: action.payload };
|
||||
|
||||
@@ -28,6 +33,9 @@ export const dataSourcesReducer = (state = initialState, action: Action): DataSo
|
||||
|
||||
case ActionTypes.SetDataSourceTypeSearchQuery:
|
||||
return { ...state, dataSourceTypeSearchQuery: action.payload };
|
||||
|
||||
case ActionTypes.LoadDataSourceMeta:
|
||||
return { ...state, dataSourceMeta: action.payload };
|
||||
}
|
||||
|
||||
return state;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { DataSource } from '../../../types';
|
||||
|
||||
export const getDataSources = state => {
|
||||
const regex = new RegExp(state.searchQuery, 'i');
|
||||
|
||||
@@ -14,6 +16,13 @@ export const getDataSourceTypes = state => {
|
||||
});
|
||||
};
|
||||
|
||||
export const getDataSource = (state, dataSourceId): DataSource | null => {
|
||||
if (state.dataSource.id === parseInt(dataSourceId, 10)) {
|
||||
return state.dataSource;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
export const getDataSourcesSearchQuery = state => state.searchQuery;
|
||||
export const getDataSourcesLayoutMode = state => state.layoutMode;
|
||||
export const getDataSourcesCount = state => state.dataSourcesCount;
|
||||
|
||||
@@ -571,10 +571,10 @@ class PromQueryField extends React.PureComponent<PromQueryFieldProps, PromQueryF
|
||||
<button className="btn navbar-button navbar-button--tight">Log labels</button>
|
||||
</Cascader>
|
||||
) : (
|
||||
<Cascader options={metricsOptions} onChange={this.onChangeMetrics}>
|
||||
<button className="btn navbar-button navbar-button--tight">Metrics</button>
|
||||
</Cascader>
|
||||
)}
|
||||
<Cascader options={metricsOptions} onChange={this.onChangeMetrics}>
|
||||
<button className="btn navbar-button navbar-button--tight">Metrics</button>
|
||||
</Cascader>
|
||||
)}
|
||||
</div>
|
||||
<div className="prom-query-field-wrapper">
|
||||
<div className="slate-query-field-wrapper">
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import _ from 'lodash';
|
||||
import { DataSource, PluginMeta, NavModel } from 'app/types';
|
||||
import config from 'app/core/config';
|
||||
|
||||
export function buildNavModel(ds: DataSource, plugin: PluginMeta, currentPage: string): NavModel {
|
||||
let title = 'New';
|
||||
@@ -38,6 +39,16 @@ export function buildNavModel(ds: DataSource, plugin: PluginMeta, currentPage: s
|
||||
});
|
||||
}
|
||||
|
||||
if (config.buildInfo.isEnterprise) {
|
||||
main.children.push({
|
||||
active: currentPage === 'datasource-permissions',
|
||||
icon: 'fa fa-fw fa-lock',
|
||||
id: 'datasource-permissions',
|
||||
text: 'Permissions',
|
||||
url: `datasources/edit/${ds.id}/permissions`,
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
main: main,
|
||||
node: _.find(main.children, { active: true }),
|
||||
|
||||
@@ -11,7 +11,7 @@ import pluginReducers from 'app/features/plugins/state/reducers';
|
||||
import dataSourcesReducers from 'app/features/datasources/state/reducers';
|
||||
import usersReducers from 'app/features/users/state/reducers';
|
||||
|
||||
const rootReducer = combineReducers({
|
||||
const rootReducers = {
|
||||
...sharedReducers,
|
||||
...alertingReducers,
|
||||
...teamsReducers,
|
||||
@@ -21,13 +21,19 @@ const rootReducer = combineReducers({
|
||||
...pluginReducers,
|
||||
...dataSourcesReducers,
|
||||
...usersReducers,
|
||||
});
|
||||
};
|
||||
|
||||
export let store;
|
||||
|
||||
export function addRootReducer(reducers) {
|
||||
Object.assign(rootReducers, ...reducers);
|
||||
}
|
||||
|
||||
export function configureStore() {
|
||||
const composeEnhancers = (window as any).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
|
||||
|
||||
const rootReducer = combineReducers(rootReducers);
|
||||
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
// DEV builds we had the logger middleware
|
||||
store = createStore(rootReducer, {}, composeEnhancers(applyMiddleware(thunk, createLogger())));
|
||||
|
||||
@@ -61,6 +61,11 @@ export enum PermissionLevel {
|
||||
Admin = 4,
|
||||
}
|
||||
|
||||
export enum DataSourcePermissionLevel {
|
||||
Query = 1,
|
||||
Admin = 2,
|
||||
}
|
||||
|
||||
export enum AclTarget {
|
||||
Team = 'Team',
|
||||
User = 'User',
|
||||
@@ -73,6 +78,10 @@ export interface AclTargetInfo {
|
||||
text: string;
|
||||
}
|
||||
|
||||
export const dataSourceAclLevels = [
|
||||
{ value: DataSourcePermissionLevel.Query, label: 'Query', description: 'Can query data source.' },
|
||||
];
|
||||
|
||||
export const dashboardAclTargets: AclTargetInfo[] = [
|
||||
{ value: AclTarget.Team, text: 'Team' },
|
||||
{ value: AclTarget.User, text: 'User' },
|
||||
|
||||
@@ -12,10 +12,10 @@ export interface DataSource {
|
||||
password: string;
|
||||
user: string;
|
||||
database: string;
|
||||
basicAuth: false;
|
||||
isDefault: false;
|
||||
basicAuth: boolean;
|
||||
isDefault: boolean;
|
||||
jsonData: { authType: string; defaultRegion: string };
|
||||
readOnly: false;
|
||||
readOnly: boolean;
|
||||
}
|
||||
|
||||
export interface DataSourcesState {
|
||||
@@ -25,5 +25,7 @@ export interface DataSourcesState {
|
||||
layoutMode: LayoutMode;
|
||||
dataSourcesCount: number;
|
||||
dataSourceTypes: Plugin[];
|
||||
dataSource: DataSource;
|
||||
dataSourceMeta: Plugin;
|
||||
hasFetched: boolean;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user