Commit f37a60dc by Torkel Ödegaard

Merge branch 'data-sources-list-to-react'

parents 8e9c0a44 8fd1d8a0
import React from 'react';
import { shallow } from 'enzyme';
import DataSourcesList from './DataSourcesList';
import { getMockDataSources } from './__mocks__/dataSourcesMocks';
import { LayoutModes } from '../../core/components/LayoutSelector/LayoutSelector';
const setup = () => {
const props = {
dataSources: getMockDataSources(3),
layoutMode: LayoutModes.Grid,
};
return shallow(<DataSourcesList {...props} />);
};
describe('Render', () => {
it('should render component', () => {
const wrapper = setup();
expect(wrapper).toMatchSnapshot();
});
});
import React from 'react';
import { shallow } from 'enzyme';
import { DataSourcesActionBar, Props } from './DataSourcesActionBar';
import { LayoutModes } from '../../core/components/LayoutSelector/LayoutSelector';
const setup = (propOverrides?: object) => {
const props: Props = {
layoutMode: LayoutModes.Grid,
searchQuery: '',
setDataSourcesLayoutMode: jest.fn(),
setDataSourcesSearchQuery: jest.fn(),
};
return shallow(<DataSourcesActionBar {...props} />);
};
describe('Render', () => {
it('should render component', () => {
const wrapper = setup();
expect(wrapper).toMatchSnapshot();
});
});
import React, { PureComponent } from 'react';
import { connect } from 'react-redux';
import LayoutSelector, { LayoutMode } from '../../core/components/LayoutSelector/LayoutSelector';
import { setDataSourcesLayoutMode, setDataSourcesSearchQuery } from './state/actions';
import { getDataSourcesLayoutMode, getDataSourcesSearchQuery } from './state/selectors';
export interface Props {
searchQuery: string;
layoutMode: LayoutMode;
setDataSourcesLayoutMode: typeof setDataSourcesLayoutMode;
setDataSourcesSearchQuery: typeof setDataSourcesSearchQuery;
}
export class DataSourcesActionBar extends PureComponent<Props> {
onSearchQueryChange = event => {
this.props.setDataSourcesSearchQuery(event.target.value);
};
render() {
const { searchQuery, layoutMode, setDataSourcesLayoutMode } = this.props;
return (
<div className="page-action-bar">
<div className="gf-form gf-form--grow">
<label className="gf-form--has-input-icon">
<input
type="text"
className="gf-form-input width-20"
value={searchQuery}
onChange={this.onSearchQueryChange}
placeholder="Filter by name or type"
/>
<i className="gf-form-input-icon fa fa-search" />
</label>
<LayoutSelector
mode={layoutMode}
onLayoutModeChanged={(mode: LayoutMode) => setDataSourcesLayoutMode(mode)}
/>
</div>
<div className="page-action-bar__spacer" />
<a className="page-header__cta btn btn-success" href="datasources/new">
<i className="fa fa-plus" />
Add data source
</a>
</div>
);
}
}
function mapStateToProps(state) {
return {
searchQuery: getDataSourcesSearchQuery(state.dataSources),
layoutMode: getDataSourcesLayoutMode(state.dataSources),
};
}
const mapDispatchToProps = {
setDataSourcesLayoutMode,
setDataSourcesSearchQuery,
};
export default connect(mapStateToProps, mapDispatchToProps)(DataSourcesActionBar);
import React, { PureComponent } from 'react';
import classNames from 'classnames/bind';
import DataSourcesListItem from './DataSourcesListItem';
import { DataSource } from 'app/types';
import { LayoutMode, LayoutModes } from '../../core/components/LayoutSelector/LayoutSelector';
export interface Props {
dataSources: DataSource[];
layoutMode: LayoutMode;
}
export class DataSourcesList extends PureComponent<Props> {
render() {
const { dataSources, layoutMode } = this.props;
const listStyle = classNames({
'card-section': true,
'card-list-layout-grid': layoutMode === LayoutModes.Grid,
'card-list-layout-list': layoutMode === LayoutModes.List,
});
return (
<section className={listStyle}>
<ol className="card-list">
{dataSources.map((dataSource, index) => {
return <DataSourcesListItem dataSource={dataSource} key={`${dataSource.id}-${index}`} />;
})}
</ol>
</section>
);
}
}
export default DataSourcesList;
import React from 'react';
import { shallow } from 'enzyme';
import DataSourcesListItem from './DataSourcesListItem';
import { getMockDataSource } from './__mocks__/dataSourcesMocks';
const setup = () => {
const props = {
dataSource: getMockDataSource(),
};
return shallow(<DataSourcesListItem {...props} />);
};
describe('Render', () => {
it('should render component', () => {
const wrapper = setup();
expect(wrapper).toMatchSnapshot();
});
});
import React, { PureComponent } from 'react';
import { DataSource } from 'app/types';
export interface Props {
dataSource: DataSource;
}
export class DataSourcesListItem extends PureComponent<Props> {
render() {
const { dataSource } = this.props;
return (
<li className="card-item-wrapper">
<a className="card-item" href={`datasources/edit/${dataSource.id}`}>
<div className="card-item-header">
<div className="card-item-type">{dataSource.type}</div>
</div>
<div className="card-item-body">
<figure className="card-item-figure">
<img src={dataSource.typeLogoUrl} />
</figure>
<div className="card-item-details">
<div className="card-item-name">
{dataSource.name}
{dataSource.isDefault && <span className="btn btn-secondary btn-mini">default</span>}
</div>
<div className="card-item-sub-name">{dataSource.url}</div>
</div>
</div>
</a>
</li>
);
}
}
export default DataSourcesListItem;
import React from 'react';
import { shallow } from 'enzyme';
import { DataSourcesListPage, Props } from './DataSourcesListPage';
import { DataSource, NavModel } from 'app/types';
import { LayoutModes } from '../../core/components/LayoutSelector/LayoutSelector';
import { getMockDataSources } from './__mocks__/dataSourcesMocks';
const setup = (propOverrides?: object) => {
const props: Props = {
dataSources: [] as DataSource[],
layoutMode: LayoutModes.Grid,
loadDataSources: jest.fn(),
navModel: {} as NavModel,
dataSourcesCount: 0,
};
Object.assign(props, propOverrides);
return shallow(<DataSourcesListPage {...props} />);
};
describe('Render', () => {
it('should render component', () => {
const wrapper = setup();
expect(wrapper).toMatchSnapshot();
});
it('should render action bar and datasources', () => {
const wrapper = setup({
dataSources: getMockDataSources(5),
dataSourcesCount: 5,
});
expect(wrapper).toMatchSnapshot();
});
});
import React, { PureComponent } from 'react';
import { connect } from 'react-redux';
import { hot } from 'react-hot-loader';
import PageHeader from '../../core/components/PageHeader/PageHeader';
import DataSourcesActionBar from './DataSourcesActionBar';
import DataSourcesList from './DataSourcesList';
import { loadDataSources } from './state/actions';
import { getDataSources, getDataSourcesCount, getDataSourcesLayoutMode } from './state/selectors';
import { getNavModel } from '../../core/selectors/navModel';
import { DataSource, NavModel } from 'app/types';
import { LayoutMode } from '../../core/components/LayoutSelector/LayoutSelector';
import EmptyListCTA from '../../core/components/EmptyListCTA/EmptyListCTA';
export interface Props {
navModel: NavModel;
dataSources: DataSource[];
dataSourcesCount: number;
layoutMode: LayoutMode;
loadDataSources: typeof loadDataSources;
}
const emptyListModel = {
title: 'There are no data sources defined yet',
buttonIcon: 'gicon gicon-add-datasources',
buttonLink: 'datasources/new',
buttonTitle: 'Add data source',
proTip: 'You can also define data sources through configuration files.',
proTipLink: 'http://docs.grafana.org/administration/provisioning/#datasources?utm_source=grafana_ds_list',
proTipLinkTitle: 'Learn more',
proTipTarget: '_blank',
};
export class DataSourcesListPage extends PureComponent<Props> {
componentDidMount() {
this.fetchDataSources();
}
async fetchDataSources() {
return await this.props.loadDataSources();
}
render() {
const { dataSources, dataSourcesCount, navModel, layoutMode } = this.props;
return (
<div>
<PageHeader model={navModel} />
<div className="page-container page-body">
{dataSourcesCount === 0 ? (
<EmptyListCTA model={emptyListModel} />
) : (
[
<DataSourcesActionBar key="action-bar" />,
<DataSourcesList dataSources={dataSources} layoutMode={layoutMode} key="list" />,
]
)}
</div>
</div>
);
}
}
function mapStateToProps(state) {
return {
navModel: getNavModel(state.navIndex, 'datasources'),
dataSources: getDataSources(state.dataSources),
layoutMode: getDataSourcesLayoutMode(state.dataSources),
dataSourcesCount: getDataSourcesCount(state.dataSources),
};
}
const mapDispatchToProps = {
loadDataSources,
};
export default hot(module)(connect(mapStateToProps, mapDispatchToProps)(DataSourcesListPage));
import { DataSource } from 'app/types';
export const getMockDataSources = (amount: number): DataSource[] => {
const dataSources = [];
for (let i = 0; i <= amount; i++) {
dataSources.push({
access: '',
basicAuth: false,
database: `database-${i}`,
id: i,
isDefault: false,
jsonData: { authType: 'credentials', defaultRegion: 'eu-west-2' },
name: `dataSource-${i}`,
orgId: 1,
password: '',
readOnly: false,
type: 'cloudwatch',
typeLogoUrl: 'public/app/plugins/datasource/cloudwatch/img/amazon-web-services.png',
url: '',
user: '',
});
}
return dataSources;
};
export const getMockDataSource = (): DataSource => {
return {
access: '',
basicAuth: false,
database: '',
id: 13,
isDefault: false,
jsonData: { 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: '',
};
};
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Render should render component 1`] = `
<section
className="card-section card-list-layout-grid"
>
<ol
className="card-list"
>
<DataSourcesListItem
dataSource={
Object {
"access": "",
"basicAuth": false,
"database": "database-0",
"id": 0,
"isDefault": false,
"jsonData": Object {
"authType": "credentials",
"defaultRegion": "eu-west-2",
},
"name": "dataSource-0",
"orgId": 1,
"password": "",
"readOnly": false,
"type": "cloudwatch",
"typeLogoUrl": "public/app/plugins/datasource/cloudwatch/img/amazon-web-services.png",
"url": "",
"user": "",
}
}
key="0-0"
/>
<DataSourcesListItem
dataSource={
Object {
"access": "",
"basicAuth": false,
"database": "database-1",
"id": 1,
"isDefault": false,
"jsonData": Object {
"authType": "credentials",
"defaultRegion": "eu-west-2",
},
"name": "dataSource-1",
"orgId": 1,
"password": "",
"readOnly": false,
"type": "cloudwatch",
"typeLogoUrl": "public/app/plugins/datasource/cloudwatch/img/amazon-web-services.png",
"url": "",
"user": "",
}
}
key="1-1"
/>
<DataSourcesListItem
dataSource={
Object {
"access": "",
"basicAuth": false,
"database": "database-2",
"id": 2,
"isDefault": false,
"jsonData": Object {
"authType": "credentials",
"defaultRegion": "eu-west-2",
},
"name": "dataSource-2",
"orgId": 1,
"password": "",
"readOnly": false,
"type": "cloudwatch",
"typeLogoUrl": "public/app/plugins/datasource/cloudwatch/img/amazon-web-services.png",
"url": "",
"user": "",
}
}
key="2-2"
/>
<DataSourcesListItem
dataSource={
Object {
"access": "",
"basicAuth": false,
"database": "database-3",
"id": 3,
"isDefault": false,
"jsonData": Object {
"authType": "credentials",
"defaultRegion": "eu-west-2",
},
"name": "dataSource-3",
"orgId": 1,
"password": "",
"readOnly": false,
"type": "cloudwatch",
"typeLogoUrl": "public/app/plugins/datasource/cloudwatch/img/amazon-web-services.png",
"url": "",
"user": "",
}
}
key="3-3"
/>
</ol>
</section>
`;
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Render should render component 1`] = `
<div
className="page-action-bar"
>
<div
className="gf-form gf-form--grow"
>
<label
className="gf-form--has-input-icon"
>
<input
className="gf-form-input width-20"
onChange={[Function]}
placeholder="Filter by name or type"
type="text"
value=""
/>
<i
className="gf-form-input-icon fa fa-search"
/>
</label>
<LayoutSelector
mode="grid"
onLayoutModeChanged={[Function]}
/>
</div>
<div
className="page-action-bar__spacer"
/>
<a
className="page-header__cta btn btn-success"
href="datasources/new"
>
<i
className="fa fa-plus"
/>
Add data source
</a>
</div>
`;
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Render should render component 1`] = `
<li
className="card-item-wrapper"
>
<a
className="card-item"
href="datasources/edit/13"
>
<div
className="card-item-header"
>
<div
className="card-item-type"
>
cloudwatch
</div>
</div>
<div
className="card-item-body"
>
<figure
className="card-item-figure"
>
<img
src="public/app/plugins/datasource/cloudwatch/img/amazon-web-services.png"
/>
</figure>
<div
className="card-item-details"
>
<div
className="card-item-name"
>
gdev-cloudwatch
</div>
<div
className="card-item-sub-name"
/>
</div>
</div>
</a>
</li>
`;
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Render should render action bar and datasources 1`] = `
<div>
<PageHeader
model={Object {}}
/>
<div
className="page-container page-body"
>
<Connect(DataSourcesActionBar)
key="action-bar"
/>
<DataSourcesList
dataSources={
Array [
Object {
"access": "",
"basicAuth": false,
"database": "database-0",
"id": 0,
"isDefault": false,
"jsonData": Object {
"authType": "credentials",
"defaultRegion": "eu-west-2",
},
"name": "dataSource-0",
"orgId": 1,
"password": "",
"readOnly": false,
"type": "cloudwatch",
"typeLogoUrl": "public/app/plugins/datasource/cloudwatch/img/amazon-web-services.png",
"url": "",
"user": "",
},
Object {
"access": "",
"basicAuth": false,
"database": "database-1",
"id": 1,
"isDefault": false,
"jsonData": Object {
"authType": "credentials",
"defaultRegion": "eu-west-2",
},
"name": "dataSource-1",
"orgId": 1,
"password": "",
"readOnly": false,
"type": "cloudwatch",
"typeLogoUrl": "public/app/plugins/datasource/cloudwatch/img/amazon-web-services.png",
"url": "",
"user": "",
},
Object {
"access": "",
"basicAuth": false,
"database": "database-2",
"id": 2,
"isDefault": false,
"jsonData": Object {
"authType": "credentials",
"defaultRegion": "eu-west-2",
},
"name": "dataSource-2",
"orgId": 1,
"password": "",
"readOnly": false,
"type": "cloudwatch",
"typeLogoUrl": "public/app/plugins/datasource/cloudwatch/img/amazon-web-services.png",
"url": "",
"user": "",
},
Object {
"access": "",
"basicAuth": false,
"database": "database-3",
"id": 3,
"isDefault": false,
"jsonData": Object {
"authType": "credentials",
"defaultRegion": "eu-west-2",
},
"name": "dataSource-3",
"orgId": 1,
"password": "",
"readOnly": false,
"type": "cloudwatch",
"typeLogoUrl": "public/app/plugins/datasource/cloudwatch/img/amazon-web-services.png",
"url": "",
"user": "",
},
Object {
"access": "",
"basicAuth": false,
"database": "database-4",
"id": 4,
"isDefault": false,
"jsonData": Object {
"authType": "credentials",
"defaultRegion": "eu-west-2",
},
"name": "dataSource-4",
"orgId": 1,
"password": "",
"readOnly": false,
"type": "cloudwatch",
"typeLogoUrl": "public/app/plugins/datasource/cloudwatch/img/amazon-web-services.png",
"url": "",
"user": "",
},
Object {
"access": "",
"basicAuth": false,
"database": "database-5",
"id": 5,
"isDefault": false,
"jsonData": Object {
"authType": "credentials",
"defaultRegion": "eu-west-2",
},
"name": "dataSource-5",
"orgId": 1,
"password": "",
"readOnly": false,
"type": "cloudwatch",
"typeLogoUrl": "public/app/plugins/datasource/cloudwatch/img/amazon-web-services.png",
"url": "",
"user": "",
},
]
}
key="list"
layoutMode="grid"
/>
</div>
</div>
`;
exports[`Render should render component 1`] = `
<div>
<PageHeader
model={Object {}}
/>
<div
className="page-container page-body"
>
<EmptyListCTA
model={
Object {
"buttonIcon": "gicon gicon-add-datasources",
"buttonLink": "datasources/new",
"buttonTitle": "Add data source",
"proTip": "You can also define data sources through configuration files.",
"proTipLink": "http://docs.grafana.org/administration/provisioning/#datasources?utm_source=grafana_ds_list",
"proTipLinkTitle": "Learn more",
"proTipTarget": "_blank",
"title": "There are no data sources defined yet",
}
}
/>
</div>
</div>
`;
import { ThunkAction } from 'redux-thunk';
import { DataSource, StoreState } from 'app/types';
import { getBackendSrv } from '../../../core/services/backend_srv';
import { LayoutMode } from '../../../core/components/LayoutSelector/LayoutSelector';
export enum ActionTypes {
LoadDataSources = 'LOAD_DATA_SOURCES',
SetDataSourcesSearchQuery = 'SET_DATA_SOURCES_SEARCH_QUERY',
SetDataSourcesLayoutMode = 'SET_DATA_SOURCES_LAYOUT_MODE',
}
export interface LoadDataSourcesAction {
type: ActionTypes.LoadDataSources;
payload: DataSource[];
}
export interface SetDataSourcesSearchQueryAction {
type: ActionTypes.SetDataSourcesSearchQuery;
payload: string;
}
export interface SetDataSourcesLayoutModeAction {
type: ActionTypes.SetDataSourcesLayoutMode;
payload: LayoutMode;
}
const dataSourcesLoaded = (dataSources: DataSource[]): LoadDataSourcesAction => ({
type: ActionTypes.LoadDataSources,
payload: dataSources,
});
export const setDataSourcesSearchQuery = (searchQuery: string): SetDataSourcesSearchQueryAction => ({
type: ActionTypes.SetDataSourcesSearchQuery,
payload: searchQuery,
});
export const setDataSourcesLayoutMode = (layoutMode: LayoutMode): SetDataSourcesLayoutModeAction => ({
type: ActionTypes.SetDataSourcesLayoutMode,
payload: layoutMode,
});
export type Action = LoadDataSourcesAction | SetDataSourcesSearchQueryAction | SetDataSourcesLayoutModeAction;
type ThunkResult<R> = ThunkAction<R, StoreState, undefined, Action>;
export function loadDataSources(): ThunkResult<void> {
return async dispatch => {
const response = await getBackendSrv().get('/api/datasources');
dispatch(dataSourcesLoaded(response));
};
}
import { DataSource, DataSourcesState } from 'app/types';
import { Action, ActionTypes } from './actions';
import { LayoutModes } from '../../../core/components/LayoutSelector/LayoutSelector';
const initialState: DataSourcesState = {
dataSources: [] as DataSource[],
layoutMode: LayoutModes.Grid,
searchQuery: '',
dataSourcesCount: 0,
};
export const dataSourcesReducer = (state = initialState, action: Action): DataSourcesState => {
switch (action.type) {
case ActionTypes.LoadDataSources:
return { ...state, dataSources: action.payload, dataSourcesCount: action.payload.length };
case ActionTypes.SetDataSourcesSearchQuery:
return { ...state, searchQuery: action.payload };
case ActionTypes.SetDataSourcesLayoutMode:
return { ...state, layoutMode: action.payload };
}
return state;
};
export default {
dataSources: dataSourcesReducer,
};
export const getDataSources = state => {
const regex = new RegExp(state.searchQuery, 'i');
return state.dataSources.filter(dataSource => {
return regex.test(dataSource.name) || regex.test(dataSource.database);
});
};
export const getDataSourcesSearchQuery = state => state.searchQuery;
export const getDataSourcesLayoutMode = state => state.layoutMode;
export const getDataSourcesCount = state => state.dataSourcesCount;
...@@ -3,6 +3,5 @@ import './plugin_page_ctrl'; ...@@ -3,6 +3,5 @@ import './plugin_page_ctrl';
import './import_list/import_list'; import './import_list/import_list';
import './ds_edit_ctrl'; import './ds_edit_ctrl';
import './ds_dashboards_ctrl'; import './ds_dashboards_ctrl';
import './ds_list_ctrl';
import './datasource_srv'; import './datasource_srv';
import './plugin_component'; import './plugin_component';
import coreModule from '../../core/core_module';
import _ from 'lodash';
export class DataSourcesCtrl {
datasources: any;
unfiltered: any;
navModel: any;
searchQuery: string;
/** @ngInject */
constructor(private $scope, private backendSrv, private datasourceSrv, private navModelSrv) {
this.navModel = this.navModelSrv.getNav('cfg', 'datasources', 0);
backendSrv.get('/api/datasources').then(result => {
this.datasources = result;
this.unfiltered = result;
});
}
onQueryUpdated() {
const regex = new RegExp(this.searchQuery, 'ig');
this.datasources = _.filter(this.unfiltered, item => {
regex.lastIndex = 0;
return regex.test(item.name) || regex.test(item.type);
});
}
removeDataSourceConfirmed(ds) {
this.backendSrv
.delete('/api/datasources/' + ds.id)
.then(
() => {
this.$scope.appEvent('alert-success', ['Datasource deleted', '']);
},
() => {
this.$scope.appEvent('alert-error', ['Unable to delete datasource', '']);
}
)
.then(() => {
this.backendSrv.get('/api/datasources').then(result => {
this.datasources = result;
});
this.backendSrv.get('/api/frontend/settings').then(settings => {
this.datasourceSrv.init(settings.datasources);
});
});
}
removeDataSource(ds) {
this.$scope.appEvent('confirm-modal', {
title: 'Delete',
text: 'Are you sure you want to delete datasource ' + ds.name + '?',
yesText: 'Delete',
icon: 'fa-trash',
onConfirm: () => {
this.removeDataSourceConfirmed(ds);
},
});
}
}
coreModule.controller('DataSourcesCtrl', DataSourcesCtrl);
<page-header model="ctrl.navModel"></page-header>
<div class="page-container page-body">
<div ng-if="ctrl.unfiltered.length">
<div class="page-action-bar">
<div class="gf-form gf-form--grow">
<label class="gf-form--has-input-icon">
<input type="text" class="gf-form-input width-20" ng-model="ctrl.searchQuery" ng-change="ctrl.onQueryUpdated()" placeholder="Filter by name or type" />
<i class="gf-form-input-icon fa fa-search"></i>
</label>
<layout-selector />
</div>
<div class="page-action-bar__spacer"></div>
<a class="page-header__cta btn btn-success" href="datasources/new">
<i class="fa fa-plus"></i>
Add data source
</a>
</div>
<section class="card-section" layout-mode>
<ol class="card-list">
<li class="card-item-wrapper" ng-repeat="ds in ctrl.datasources">
<a class="card-item" href="datasources/edit/{{ds.id}}/">
<div class="card-item-header">
<div class="card-item-type">
{{ds.type}}
</div>
</div>
<div class="card-item-body">
<figure class="card-item-figure">
<img ng-src="{{ds.typeLogoUrl}}">
</figure>
<div class="card-item-details">
<div class="card-item-name">
{{ds.name}}
<span ng-if="ds.isDefault">
<span class="btn btn-secondary btn-mini">default</span>
</span>
</div>
<div class="card-item-sub-name">
{{ds.url}}
</div>
</div>
</div>
</a>
</li>
</ol>
</section>
</div>
<div ng-if="ctrl.unfiltered.length === 0">
<empty-list-cta model="{
title: 'There are no data sources defined yet',
buttonIcon: 'gicon gicon-add-datasources',
buttonLink: 'datasources/new',
buttonTitle: 'Add data source',
proTip: 'You can also define data sources through configuration files.',
proTipLink: 'http://docs.grafana.org/administration/provisioning/#datasources?utm_source=grafana_ds_list',
proTipLinkTitle: 'Learn more',
proTipTarget: '_blank'
}" />
</div>
</div>
...@@ -9,6 +9,7 @@ import ApiKeys from 'app/features/api-keys/ApiKeysPage'; ...@@ -9,6 +9,7 @@ import ApiKeys from 'app/features/api-keys/ApiKeysPage';
import PluginListPage from 'app/features/plugins/PluginListPage'; import PluginListPage from 'app/features/plugins/PluginListPage';
import FolderSettingsPage from 'app/features/folders/FolderSettingsPage'; import FolderSettingsPage from 'app/features/folders/FolderSettingsPage';
import FolderPermissions from 'app/features/folders/FolderPermissions'; import FolderPermissions from 'app/features/folders/FolderPermissions';
import DataSourcesListPage from 'app/features/datasources/DataSourcesListPage';
/** @ngInject */ /** @ngInject */
export function setupAngularRoutes($routeProvider, $locationProvider) { export function setupAngularRoutes($routeProvider, $locationProvider) {
...@@ -63,9 +64,10 @@ export function setupAngularRoutes($routeProvider, $locationProvider) { ...@@ -63,9 +64,10 @@ export function setupAngularRoutes($routeProvider, $locationProvider) {
controllerAs: 'ctrl', controllerAs: 'ctrl',
}) })
.when('/datasources', { .when('/datasources', {
templateUrl: 'public/app/features/plugins/partials/ds_list.html', template: '<react-container />',
controller: 'DataSourcesCtrl', resolve: {
controllerAs: 'ctrl', component: () => DataSourcesListPage,
},
}) })
.when('/datasources/edit/:id', { .when('/datasources/edit/:id', {
templateUrl: 'public/app/features/plugins/partials/ds_edit.html', templateUrl: 'public/app/features/plugins/partials/ds_edit.html',
......
...@@ -8,6 +8,7 @@ import apiKeysReducers from 'app/features/api-keys/state/reducers'; ...@@ -8,6 +8,7 @@ import apiKeysReducers from 'app/features/api-keys/state/reducers';
import foldersReducers from 'app/features/folders/state/reducers'; import foldersReducers from 'app/features/folders/state/reducers';
import dashboardReducers from 'app/features/dashboard/state/reducers'; import dashboardReducers from 'app/features/dashboard/state/reducers';
import pluginReducers from 'app/features/plugins/state/reducers'; import pluginReducers from 'app/features/plugins/state/reducers';
import dataSourcesReducers from 'app/features/datasources/state/reducers';
const rootReducer = combineReducers({ const rootReducer = combineReducers({
...sharedReducers, ...sharedReducers,
...@@ -17,6 +18,7 @@ const rootReducer = combineReducers({ ...@@ -17,6 +18,7 @@ const rootReducer = combineReducers({
...foldersReducers, ...foldersReducers,
...dashboardReducers, ...dashboardReducers,
...pluginReducers, ...pluginReducers,
...dataSourcesReducers,
}); });
export let store; export let store;
......
import { LayoutMode } from '../core/components/LayoutSelector/LayoutSelector';
export interface DataSource { export interface DataSource {
id: number; id: number;
orgId: number; orgId: number;
name: string; name: string;
typeLogoUrl: string; typeLogoUrl: string;
type: string; type: string;
access: string;
url: string;
password: string;
user: string;
database: string;
basicAuth: false;
isDefault: false;
jsonData: { authType: string; defaultRegion: string };
readOnly: false;
}
export interface DataSourcesState {
dataSources: DataSource[];
searchQuery: string;
layoutMode: LayoutMode;
dataSourcesCount: number;
} }
...@@ -5,9 +5,9 @@ import { NavModel, NavModelItem, NavIndex } from './navModel'; ...@@ -5,9 +5,9 @@ import { NavModel, NavModelItem, NavIndex } from './navModel';
import { FolderDTO, FolderState, FolderInfo } from './folders'; import { FolderDTO, FolderState, FolderInfo } from './folders';
import { DashboardState } from './dashboard'; import { DashboardState } from './dashboard';
import { DashboardAcl, OrgRole, PermissionLevel } from './acl'; import { DashboardAcl, OrgRole, PermissionLevel } from './acl';
import { DataSource } from './datasources';
import { ApiKey, ApiKeysState, NewApiKey } from './apiKeys'; import { ApiKey, ApiKeysState, NewApiKey } from './apiKeys';
import { User } from './user'; import { User } from './user';
import { DataSource, DataSourcesState } from './datasources';
import { PluginMeta, Plugin, PluginsState } from './plugins'; import { PluginMeta, Plugin, PluginsState } from './plugins';
export { export {
...@@ -41,6 +41,7 @@ export { ...@@ -41,6 +41,7 @@ export {
User, User,
Plugin, Plugin,
PluginsState, PluginsState,
DataSourcesState,
}; };
export interface StoreState { export interface StoreState {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment