From d2464812eb90cc2c01e4361d74ce5a4cfbd56a8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Fri, 28 Sep 2018 14:52:12 +0200 Subject: [PATCH 01/30] noop services poc --- pkg/cmd/grafana-server/server.go | 1 + .../datasources/datasource_service.go | 50 +++++++++++++++++++ 2 files changed, 51 insertions(+) create mode 100644 pkg/services/datasources/datasource_service.go diff --git a/pkg/cmd/grafana-server/server.go b/pkg/cmd/grafana-server/server.go index 8794d7d8338..b2f4a620208 100644 --- a/pkg/cmd/grafana-server/server.go +++ b/pkg/cmd/grafana-server/server.go @@ -32,6 +32,7 @@ import ( _ "github.com/grafana/grafana/pkg/plugins" _ "github.com/grafana/grafana/pkg/services/alerting" _ "github.com/grafana/grafana/pkg/services/cleanup" + _ "github.com/grafana/grafana/pkg/services/datasources" _ "github.com/grafana/grafana/pkg/services/notifications" _ "github.com/grafana/grafana/pkg/services/provisioning" _ "github.com/grafana/grafana/pkg/services/rendering" diff --git a/pkg/services/datasources/datasource_service.go b/pkg/services/datasources/datasource_service.go new file mode 100644 index 00000000000..2fba0bb5b87 --- /dev/null +++ b/pkg/services/datasources/datasource_service.go @@ -0,0 +1,50 @@ +package datasources + +import ( + "github.com/grafana/grafana/pkg/log" + "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/registry" + "github.com/grafana/grafana/pkg/setting" +) + +type DataSourceService interface { + GetById(id int64, user *models.SignedInUser) (*models.DataSource, error) +} + +type DataSourceServiceImpl struct { + log log.Logger + Cfg *setting.Cfg `inject:""` + Guardian DataSourceGuardian `inject:""` +} + +func init() { + registry.RegisterService(&DataSourceServiceImpl{}) + registry.RegisterService(&DataSourceGuardianNoop{}) +} + +func (srv *DataSourceServiceImpl) Init() error { + srv.log = log.New("datasources") + srv.log.Info("hello", "guardian", srv.Guardian.GetPermission(0, nil)) + return nil +} + +func (srv *DataSourceServiceImpl) GetById(id int64, user *models.SignedInUser) { + // check cache + // Get by id from db + // check permissions +} + +type DataSourceGuardian interface { + GetPermission(id int64, user *models.SignedInUser) bool +} + +type DataSourceGuardianNoop struct { +} + +func (dsg *DataSourceGuardianNoop) Init() error { + return nil +} + +func (dsg *DataSourceGuardianNoop) GetPermission(id int64, user *models.SignedInUser) bool { + return false +} From b3c78f1265d3fdd35768fd54adff85917e91531f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Mon, 1 Oct 2018 15:38:55 +0200 Subject: [PATCH 02/30] wip: data source permissions hooks --- pkg/api/api.go | 6 +-- pkg/api/datasources.go | 34 ++++++++++--- pkg/cmd/grafana-server/server.go | 1 - pkg/models/datasource.go | 26 +++++++++- .../datasources/datasource_service.go | 50 ------------------- pkg/services/sqlstore/datasource.go | 1 + 6 files changed, 55 insertions(+), 63 deletions(-) delete mode 100644 pkg/services/datasources/datasource_service.go diff --git a/pkg/api/api.go b/pkg/api/api.go index 39b332aeb9f..dcbc3a7c58f 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -234,13 +234,13 @@ func (hs *HTTPServer) registerRoutes() { datasourceRoute.Get("/", Wrap(GetDataSources)) datasourceRoute.Post("/", quota("data_source"), bind(m.AddDataSourceCommand{}), Wrap(AddDataSource)) datasourceRoute.Put("/:id", bind(m.UpdateDataSourceCommand{}), Wrap(UpdateDataSource)) - datasourceRoute.Delete("/:id", Wrap(DeleteDataSourceByID)) + datasourceRoute.Delete("/:id", Wrap(DeleteDataSourceById)) datasourceRoute.Delete("/name/:name", Wrap(DeleteDataSourceByName)) - datasourceRoute.Get("/:id", Wrap(GetDataSourceByID)) + datasourceRoute.Get("/:id", Wrap(GetDataSourceById)) datasourceRoute.Get("/name/:name", Wrap(GetDataSourceByName)) }, reqOrgAdmin) - apiRoute.Get("/datasources/id/:name", Wrap(GetDataSourceIDByName), reqSignedIn) + apiRoute.Get("/datasources/id/:name", Wrap(GetDataSourceIdByName), reqSignedIn) apiRoute.Get("/plugins", Wrap(GetPluginList)) apiRoute.Get("/plugins/:pluginId/settings", Wrap(GetPluginSettingByID)) diff --git a/pkg/api/datasources.go b/pkg/api/datasources.go index 23dbb221d71..b1b13d7abfd 100644 --- a/pkg/api/datasources.go +++ b/pkg/api/datasources.go @@ -20,8 +20,8 @@ func GetDataSources(c *m.ReqContext) Response { result := make(dtos.DataSourceList, 0) for _, ds := range query.Result { dsItem := dtos.DataSourceListItemDTO{ - Id: ds.Id, OrgId: ds.OrgId, + Id: ds.Id, Name: ds.Name, Url: ds.Url, Type: ds.Type, @@ -49,7 +49,27 @@ func GetDataSources(c *m.ReqContext) Response { return JSON(200, &result) } -func GetDataSourceByID(c *m.ReqContext) Response { +func hasRequiredDatasourcePermission(dsId int64, permission m.DataSourcePermissionType, user *m.SignedInUser) Response { + query := m.HasRequiredDataSourcePermissionQuery{ + Id: dsId, + User: user, + RequiredPermission: permission, + } + + if err := bus.Dispatch(&query); err != nil { + if err == bus.ErrHandlerNotFound { + return nil + } + if err == m.ErrDataSourceAccessDenied { + return Error(403, err.Error(), nil) + } + return Error(500, "Failed to check data source permissions", err) + } + + return nil +} + +func GetDataSourceById(c *m.ReqContext) Response { query := m.GetDataSourceByIdQuery{ Id: c.ParamsInt64(":id"), OrgId: c.OrgId, @@ -68,14 +88,14 @@ func GetDataSourceByID(c *m.ReqContext) Response { return JSON(200, &dtos) } -func DeleteDataSourceByID(c *m.ReqContext) Response { +func DeleteDataSourceById(c *m.ReqContext) Response { id := c.ParamsInt64(":id") if id <= 0 { return Error(400, "Missing valid datasource id", nil) } - ds, err := getRawDataSourceByID(id, c.OrgId) + ds, err := getRawDataSourceById(id, c.OrgId) if err != nil { return Error(400, "Failed to delete datasource", nil) } @@ -186,7 +206,7 @@ func fillWithSecureJSONData(cmd *m.UpdateDataSourceCommand) error { return nil } - ds, err := getRawDataSourceByID(cmd.Id, cmd.OrgId) + ds, err := getRawDataSourceById(cmd.Id, cmd.OrgId) if err != nil { return err } @@ -206,7 +226,7 @@ func fillWithSecureJSONData(cmd *m.UpdateDataSourceCommand) error { return nil } -func getRawDataSourceByID(id int64, orgID int64) (*m.DataSource, error) { +func getRawDataSourceById(id int64, orgID int64) (*m.DataSource, error) { query := m.GetDataSourceByIdQuery{ Id: id, OrgId: orgID, @@ -236,7 +256,7 @@ func GetDataSourceByName(c *m.ReqContext) Response { } // Get /api/datasources/id/:name -func GetDataSourceIDByName(c *m.ReqContext) Response { +func GetDataSourceIdByName(c *m.ReqContext) Response { query := m.GetDataSourceByNameQuery{Name: c.Params(":name"), OrgId: c.OrgId} if err := bus.Dispatch(&query); err != nil { diff --git a/pkg/cmd/grafana-server/server.go b/pkg/cmd/grafana-server/server.go index b2f4a620208..8794d7d8338 100644 --- a/pkg/cmd/grafana-server/server.go +++ b/pkg/cmd/grafana-server/server.go @@ -32,7 +32,6 @@ import ( _ "github.com/grafana/grafana/pkg/plugins" _ "github.com/grafana/grafana/pkg/services/alerting" _ "github.com/grafana/grafana/pkg/services/cleanup" - _ "github.com/grafana/grafana/pkg/services/datasources" _ "github.com/grafana/grafana/pkg/services/notifications" _ "github.com/grafana/grafana/pkg/services/provisioning" _ "github.com/grafana/grafana/pkg/services/rendering" diff --git a/pkg/models/datasource.go b/pkg/models/datasource.go index cbdd0136f4d..d602acb3ed2 100644 --- a/pkg/models/datasource.go +++ b/pkg/models/datasource.go @@ -29,6 +29,7 @@ var ( ErrDataSourceNameExists = errors.New("Data source with same name already exists") ErrDataSourceUpdatingOldVersion = errors.New("Trying to update old version of datasource") ErrDatasourceIsReadOnly = errors.New("Data source is readonly. Can only be updated from configuration.") + ErrDataSourceAccessDenied = errors.New("Data source access denied") ) type DsAccess string @@ -165,6 +166,7 @@ type DeleteDataSourceByNameCommand struct { type GetDataSourcesQuery struct { OrgId int64 + User *SignedInUser Result []*DataSource } @@ -185,6 +187,26 @@ type GetDataSourceByNameQuery struct { } // --------------------- -// EVENTS -type DataSourceCreatedEvent struct { +// Permissions +// --------------------- + +type DataSourcePermissionType int + +const ( + DsPermissionQuery DataSourcePermissionType = 1 << iota + DsPermissionAdmin +) + +func (p DataSourcePermissionType) String() string { + names := map[int]string{ + int(DsPermissionQuery): "Query", + int(DsPermissionAdmin): "Admin", + } + return names[int(p)] +} + +type HasRequiredDataSourcePermissionQuery struct { + Id int64 + User *SignedInUser + RequiredPermission DataSourcePermissionType } diff --git a/pkg/services/datasources/datasource_service.go b/pkg/services/datasources/datasource_service.go deleted file mode 100644 index 2fba0bb5b87..00000000000 --- a/pkg/services/datasources/datasource_service.go +++ /dev/null @@ -1,50 +0,0 @@ -package datasources - -import ( - "github.com/grafana/grafana/pkg/log" - "github.com/grafana/grafana/pkg/models" - "github.com/grafana/grafana/pkg/registry" - "github.com/grafana/grafana/pkg/setting" -) - -type DataSourceService interface { - GetById(id int64, user *models.SignedInUser) (*models.DataSource, error) -} - -type DataSourceServiceImpl struct { - log log.Logger - Cfg *setting.Cfg `inject:""` - Guardian DataSourceGuardian `inject:""` -} - -func init() { - registry.RegisterService(&DataSourceServiceImpl{}) - registry.RegisterService(&DataSourceGuardianNoop{}) -} - -func (srv *DataSourceServiceImpl) Init() error { - srv.log = log.New("datasources") - srv.log.Info("hello", "guardian", srv.Guardian.GetPermission(0, nil)) - return nil -} - -func (srv *DataSourceServiceImpl) GetById(id int64, user *models.SignedInUser) { - // check cache - // Get by id from db - // check permissions -} - -type DataSourceGuardian interface { - GetPermission(id int64, user *models.SignedInUser) bool -} - -type DataSourceGuardianNoop struct { -} - -func (dsg *DataSourceGuardianNoop) Init() error { - return nil -} - -func (dsg *DataSourceGuardianNoop) GetPermission(id int64, user *models.SignedInUser) bool { - return false -} diff --git a/pkg/services/sqlstore/datasource.go b/pkg/services/sqlstore/datasource.go index 00d520bcfc6..7f70e5c25fc 100644 --- a/pkg/services/sqlstore/datasource.go +++ b/pkg/services/sqlstore/datasource.go @@ -27,6 +27,7 @@ func GetDataSourceById(query *m.GetDataSourceByIdQuery) error { datasource := m.DataSource{OrgId: query.OrgId, Id: query.Id} has, err := x.Get(&datasource) + if err != nil { return err } From 162a95bff92ac3d637fb2ff87ec2458da1d25c05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Mon, 1 Oct 2018 19:31:03 +0200 Subject: [PATCH 03/30] wip: going in circles --- pkg/api/datasources.go | 19 ++++++++++++++++++- pkg/models/datasource.go | 19 +++++++++++++------ 2 files changed, 31 insertions(+), 7 deletions(-) diff --git a/pkg/api/datasources.go b/pkg/api/datasources.go index b1b13d7abfd..3378fb5bf2e 100644 --- a/pkg/api/datasources.go +++ b/pkg/api/datasources.go @@ -17,6 +17,16 @@ func GetDataSources(c *m.ReqContext) Response { return Error(500, "Failed to query datasources", err) } + permissions := map[int64]m.DsPermissionType{} + permissionsQuery := m.GetDataSourcePermissionsForUserQuery{User: c.SignedInUser} + if err := bus.Dispatch(&permissionsQuery); err != nil { + if err != bus.ErrHandlerNotFound { + return Error(500, "failed to read datasource permissions", err) + } + } else { + permissions = permissionsQuery.Result + } + result := make(dtos.DataSourceList, 0) for _, ds := range query.Result { dsItem := dtos.DataSourceListItemDTO{ @@ -35,6 +45,13 @@ func GetDataSources(c *m.ReqContext) Response { ReadOnly: ds.ReadOnly, } + if permission, ok := permissions[ds.Id]; ok { + c.Logger.Info("Found permission", "permission", permission) + if permission == m.DsPermissionNoAccess { + continue + } + } + if plugin, exists := plugins.DataSources[ds.Type]; exists { dsItem.TypeLogoUrl = plugin.Info.Logos.Small } else { @@ -49,7 +66,7 @@ func GetDataSources(c *m.ReqContext) Response { return JSON(200, &result) } -func hasRequiredDatasourcePermission(dsId int64, permission m.DataSourcePermissionType, user *m.SignedInUser) Response { +func hasRequiredDatasourcePermission(dsId int64, permission m.DsPermissionType, user *m.SignedInUser) Response { query := m.HasRequiredDataSourcePermissionQuery{ Id: dsId, User: user, diff --git a/pkg/models/datasource.go b/pkg/models/datasource.go index d602acb3ed2..9a32b326a4b 100644 --- a/pkg/models/datasource.go +++ b/pkg/models/datasource.go @@ -190,17 +190,19 @@ type GetDataSourceByNameQuery struct { // Permissions // --------------------- -type DataSourcePermissionType int +type DsPermissionType int const ( - DsPermissionQuery DataSourcePermissionType = 1 << iota + DsPermissionQuery DsPermissionType = 1 << iota DsPermissionAdmin + DsPermissionNoAccess ) -func (p DataSourcePermissionType) String() string { +func (p DsPermissionType) String() string { names := map[int]string{ - int(DsPermissionQuery): "Query", - int(DsPermissionAdmin): "Admin", + int(DsPermissionQuery): "Query", + int(DsPermissionAdmin): "Admin", + int(DsPermissionNoAccess): "No Access", } return names[int(p)] } @@ -208,5 +210,10 @@ func (p DataSourcePermissionType) String() string { type HasRequiredDataSourcePermissionQuery struct { Id int64 User *SignedInUser - RequiredPermission DataSourcePermissionType + RequiredPermission DsPermissionType +} + +type GetDataSourcePermissionsForUserQuery struct { + User *SignedInUser + Result map[int64]DsPermissionType } From 6c41cfb72c3394a1e6a8513b55afdaf4d91312d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Tue, 2 Oct 2018 16:14:29 +0200 Subject: [PATCH 04/30] wip: made sqlstore dialect accessable from outside --- pkg/services/sqlstore/sqlstore.go | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/pkg/services/sqlstore/sqlstore.go b/pkg/services/sqlstore/sqlstore.go index 5477bc7b2d1..619b6862f6c 100644 --- a/pkg/services/sqlstore/sqlstore.go +++ b/pkg/services/sqlstore/sqlstore.go @@ -53,6 +53,7 @@ type SqlStore struct { dbCfg DatabaseConfig engine *xorm.Engine log log.Logger + Dialect migrator.Dialect skipEnsureAdmin bool } @@ -125,10 +126,12 @@ func (ss *SqlStore) Init() error { } ss.engine = engine + ss.Dialect = migrator.NewDialect(ss.engine) // temporarily still set global var x = engine - dialect = migrator.NewDialect(x) + dialect = ss.Dialect + migrator := migrator.NewMigrator(x) migrations.AddMigrations(migrator) @@ -347,7 +350,11 @@ func InitTestDB(t *testing.T) *SqlStore { t.Fatalf("Failed to init test database: %v", err) } - dialect = migrator.NewDialect(engine) + sqlstore.Dialect = migrator.NewDialect(engine) + + // temp global var until we get rid of global vars + dialect = sqlstore.Dialect + if err := dialect.CleanDB(); err != nil { t.Fatalf("Failed to clean test db %v", err) } From 4ecd33c79ceab85ad2b91b3ab13ba6dac59bee74 Mon Sep 17 00:00:00 2001 From: Peter Holmberg Date: Mon, 8 Oct 2018 14:09:02 +0200 Subject: [PATCH 05/30] Fixed nav model --- .../datasources/EditDataSourcePage.tsx | 90 +++++++++++++++++ .../app/features/datasources/state/actions.ts | 27 +++++- .../features/datasources/state/navModel.ts | 97 +++++++++++++++++++ .../features/datasources/state/reducers.ts | 4 + .../features/datasources/state/selectors.ts | 9 ++ public/app/routes/routes.ts | 15 ++- public/app/types/datasources.ts | 1 + 7 files changed, 232 insertions(+), 11 deletions(-) create mode 100644 public/app/features/datasources/EditDataSourcePage.tsx create mode 100644 public/app/features/datasources/state/navModel.ts diff --git a/public/app/features/datasources/EditDataSourcePage.tsx b/public/app/features/datasources/EditDataSourcePage.tsx new file mode 100644 index 00000000000..7c19266d53a --- /dev/null +++ b/public/app/features/datasources/EditDataSourcePage.tsx @@ -0,0 +1,90 @@ +import React, { PureComponent } from 'react'; +import { hot } from 'react-hot-loader'; +import { connect } from 'react-redux'; +import PageHeader from '../../core/components/PageHeader/PageHeader'; +import { DataSource, NavModel } from 'app/types'; +import { loadDataSource } from './state/actions'; +import { getNavModel } from '../../core/selectors/navModel'; +import { getRouteParamsId, getRouteParamsPage } from '../../core/selectors/location'; +import { getDataSourceLoadingNav } from './state/navModel'; +import { getDataSource } from './state/selectors'; + +export interface Props { + navModel: NavModel; + dataSource: DataSource; + dataSourceId: number; + pageName: string; + loadDataSource: typeof loadDataSource; +} + +enum PageTypes { + Settings = 'settings', + Permissions = 'permissions', + Dashboards = 'dashboards', +} + +export class EditDataSourcePage extends PureComponent { + componentDidMount() { + this.fetchDataSource(); + } + + async fetchDataSource() { + await this.props.loadDataSource(this.props.dataSourceId); + } + + isValidPage(currentPage) { + return (Object as any).values(PageTypes).includes(currentPage); + } + + getCurrentPage() { + const currentPage = this.props.pageName; + + return this.isValidPage(currentPage) ? currentPage : PageTypes.Settings; + } + + renderPage() { + switch (this.getCurrentPage()) { + case PageTypes.Settings: + return
Settings
; + + case PageTypes.Permissions: + return
Permissions
; + + case PageTypes.Dashboards: + return
Dashboards
; + } + + return null; + } + + render() { + const { navModel } = this.props; + + return ( +
+ +
+ {this.renderPage()} +
+ ); + } +} + +function mapStateToProps(state) { + const pageName = getRouteParamsPage(state.location) || 'settings'; + const dataSourceId = getRouteParamsId(state.location); + const dataSourceLoadingNav = getDataSourceLoadingNav(pageName); + + return { + navModel: getNavModel(state.navIndex, `datasource-${pageName}-${dataSourceId}`, dataSourceLoadingNav), + dataSourceId: dataSourceId, + dataSource: getDataSource(state.dataSources, dataSourceId), + pageName: pageName, + }; +} + +const mapDispatchToProps = { + loadDataSource, +}; + +export default hot(module)(connect(mapStateToProps, mapDispatchToProps)(EditDataSourcePage)); diff --git a/public/app/features/datasources/state/actions.ts b/public/app/features/datasources/state/actions.ts index 33d6b79c5df..2b6b986774f 100644 --- a/public/app/features/datasources/state/actions.ts +++ b/public/app/features/datasources/state/actions.ts @@ -2,12 +2,14 @@ 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', SetDataSourcesSearchQuery = 'SET_DATA_SOURCES_SEARCH_QUERY', SetDataSourcesLayoutMode = 'SET_DATA_SOURCES_LAYOUT_MODE', SetDataSourceTypeSearchQuery = 'SET_DATA_SOURCE_TYPE_SEARCH_QUERY', @@ -38,11 +40,21 @@ export interface SetDataSourceTypeSearchQueryAction { payload: string; } +export interface LoadDataSourceAction { + type: ActionTypes.LoadDataSource; + payload: DataSource; +} + const dataSourcesLoaded = (dataSources: DataSource[]): LoadDataSourcesAction => ({ type: ActionTypes.LoadDataSources, payload: dataSources, }); +const dataSourceLoaded = (dataSource: DataSource): LoadDataSourceAction => ({ + type: ActionTypes.LoadDataSource, + payload: dataSource, +}); + const dataSourceTypesLoaded = (dataSourceTypes: Plugin[]): LoadDataSourceTypesAction => ({ type: ActionTypes.LoadDataSourceTypes, payload: dataSourceTypes, @@ -69,7 +81,9 @@ export type Action = | SetDataSourcesLayoutModeAction | UpdateLocationAction | LoadDataSourceTypesAction - | SetDataSourceTypeSearchQueryAction; + | SetDataSourceTypeSearchQueryAction + | LoadDataSourceAction + | UpdateNavIndexAction; type ThunkResult = ThunkAction; @@ -80,6 +94,15 @@ export function loadDataSources(): ThunkResult { }; } +export function loadDataSource(id: number): ThunkResult { + 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(updateNavIndex(buildNavModel(dataSource, pluginInfo))); + }; +} + export function addDataSource(plugin: Plugin): ThunkResult { return async (dispatch, getStore) => { await dispatch(loadDataSources()); diff --git a/public/app/features/datasources/state/navModel.ts b/public/app/features/datasources/state/navModel.ts new file mode 100644 index 00000000000..d80ab5d52a2 --- /dev/null +++ b/public/app/features/datasources/state/navModel.ts @@ -0,0 +1,97 @@ +import { DataSource, NavModel, NavModelItem, PluginMeta } from 'app/types'; + +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}/settings`, + }, + { + active: false, + icon: 'fa fa-fw fa-sliders', + id: `datasource-permissions-${dataSource.id}`, + text: 'Permissions', + url: `datasources/edit/${dataSource.id}/permissions`, + }, + ], + }; + + if (pluginMeta.includes && pluginMeta.includes.length > 0) { + navModel.children.push({ + active: false, + icon: 'gicon gicon-dashboard', + id: `datasource-dashboards-${dataSource.id}`, + text: 'Dashboards', + url: `datasources/edit/${dataSource.id}/dashboards`, + }); + } + + 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, + }; +} diff --git a/public/app/features/datasources/state/reducers.ts b/public/app/features/datasources/state/reducers.ts index acb228d3ed6..075483945a4 100644 --- a/public/app/features/datasources/state/reducers.ts +++ b/public/app/features/datasources/state/reducers.ts @@ -4,6 +4,7 @@ import { LayoutModes } from '../../../core/components/LayoutSelector/LayoutSelec const initialState: DataSourcesState = { dataSources: [] as DataSource[], + dataSource: {} as DataSource, layoutMode: LayoutModes.Grid, searchQuery: '', dataSourcesCount: 0, @@ -16,6 +17,9 @@ export const dataSourcesReducer = (state = initialState, action: Action): DataSo case ActionTypes.LoadDataSources: return { ...state, dataSources: action.payload, dataSourcesCount: action.payload.length }; + case ActionTypes.LoadDataSource: + return { ...state, dataSource: action.payload }; + case ActionTypes.SetDataSourcesSearchQuery: return { ...state, searchQuery: action.payload }; diff --git a/public/app/features/datasources/state/selectors.ts b/public/app/features/datasources/state/selectors.ts index 80e1400114f..eef176eb49a 100644 --- a/public/app/features/datasources/state/selectors.ts +++ b/public/app/features/datasources/state/selectors.ts @@ -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; diff --git a/public/app/routes/routes.ts b/public/app/routes/routes.ts index 9aecf53e7bb..54f4a6718e1 100644 --- a/public/app/routes/routes.ts +++ b/public/app/routes/routes.ts @@ -12,6 +12,7 @@ import FolderPermissions from 'app/features/folders/FolderPermissions'; import DataSourcesListPage from 'app/features/datasources/DataSourcesListPage'; import NewDataSourcePage from '../features/datasources/NewDataSourcePage'; import UsersListPage from 'app/features/users/UsersListPage'; +import EditDataSourcePage from 'app/features/datasources/EditDataSourcePage'; /** @ngInject */ export function setupAngularRoutes($routeProvider, $locationProvider) { @@ -71,15 +72,11 @@ export function setupAngularRoutes($routeProvider, $locationProvider) { component: () => DataSourcesListPage, }, }) - .when('/datasources/edit/:id', { - templateUrl: 'public/app/features/plugins/partials/ds_edit.html', - controller: 'DataSourceEditCtrl', - controllerAs: 'ctrl', - }) - .when('/datasources/edit/:id/dashboards', { - templateUrl: 'public/app/features/plugins/partials/ds_dashboards.html', - controller: 'DataSourceDashboardsCtrl', - controllerAs: 'ctrl', + .when('/datasources/edit/:id/:page?', { + template: '', + resolve: { + component: () => EditDataSourcePage, + }, }) .when('/datasources/new', { template: '', diff --git a/public/app/types/datasources.ts b/public/app/types/datasources.ts index 4d8d755f106..9d35794b89e 100644 --- a/public/app/types/datasources.ts +++ b/public/app/types/datasources.ts @@ -25,4 +25,5 @@ export interface DataSourcesState { layoutMode: LayoutMode; dataSourcesCount: number; dataSourceTypes: Plugin[]; + dataSource: DataSource; } From 61cac5fd617bf65245fa48a023c3dc63bb7a7b78 Mon Sep 17 00:00:00 2001 From: Peter Holmberg Date: Mon, 8 Oct 2018 16:01:17 +0200 Subject: [PATCH 06/30] reverted back and using angular for settings and dashboards --- .../datasources/DataSourceSettings.tsx | 125 ++++++++++++++++++ .../datasources/EditDataSourcePage.tsx | 13 +- .../app/features/datasources/state/actions.ts | 15 ++- .../features/datasources/state/navModel.ts | 30 +++-- .../features/datasources/state/reducers.ts | 4 + public/app/features/plugins/state/navModel.ts | 11 ++ public/app/routes/routes.ts | 10 ++ public/app/types/datasources.ts | 7 +- 8 files changed, 192 insertions(+), 23 deletions(-) create mode 100644 public/app/features/datasources/DataSourceSettings.tsx diff --git a/public/app/features/datasources/DataSourceSettings.tsx b/public/app/features/datasources/DataSourceSettings.tsx new file mode 100644 index 00000000000..f7d641d34b0 --- /dev/null +++ b/public/app/features/datasources/DataSourceSettings.tsx @@ -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 { + 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 ( +
+

Settings

+
+
+
+
+ Name + +
+
+
+ {this.shouldRenderInfoBox() &&
{this.getInfoText()}
} + {this.isReadyOnly() && ( +
+ This datasource was added by config and cannot be modified using the UI. Please contact your server admin + to update this datasource. +
+ )} +
+ + + + Back + +
+
+
+ ); + } +} + +function mapStateToProps(state) { + return { + dataSource: state.dataSources.dataSource, + dataSourceMeta: state.dataSources.dataSourceMeta, + }; +} + +export default connect(mapStateToProps)(DataSourceSettings); diff --git a/public/app/features/datasources/EditDataSourcePage.tsx b/public/app/features/datasources/EditDataSourcePage.tsx index 7c19266d53a..46966dcbb58 100644 --- a/public/app/features/datasources/EditDataSourcePage.tsx +++ b/public/app/features/datasources/EditDataSourcePage.tsx @@ -39,19 +39,13 @@ export class EditDataSourcePage extends PureComponent { getCurrentPage() { const currentPage = this.props.pageName; - return this.isValidPage(currentPage) ? currentPage : PageTypes.Settings; + return this.isValidPage(currentPage) ? currentPage : PageTypes.Permissions; } renderPage() { switch (this.getCurrentPage()) { - case PageTypes.Settings: - return
Settings
; - case PageTypes.Permissions: return
Permissions
; - - case PageTypes.Dashboards: - return
Dashboards
; } return null; @@ -63,15 +57,14 @@ export class EditDataSourcePage extends PureComponent { return (
-
- {this.renderPage()} +
{this.renderPage()}
); } } function mapStateToProps(state) { - const pageName = getRouteParamsPage(state.location) || 'settings'; + const pageName = getRouteParamsPage(state.location) || PageTypes.Permissions; const dataSourceId = getRouteParamsId(state.location); const dataSourceLoadingNav = getDataSourceLoadingNav(pageName); diff --git a/public/app/features/datasources/state/actions.ts b/public/app/features/datasources/state/actions.ts index 2b6b986774f..bb8fce8424a 100644 --- a/public/app/features/datasources/state/actions.ts +++ b/public/app/features/datasources/state/actions.ts @@ -10,6 +10,7 @@ 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', @@ -45,6 +46,11 @@ export interface LoadDataSourceAction { payload: DataSource; } +export interface LoadDataSourceMetaAction { + type: ActionTypes.LoadDataSourceMeta; + payload: Plugin; +} + const dataSourcesLoaded = (dataSources: DataSource[]): LoadDataSourcesAction => ({ type: ActionTypes.LoadDataSources, payload: dataSources, @@ -55,6 +61,11 @@ const dataSourceLoaded = (dataSource: DataSource): LoadDataSourceAction => ({ payload: dataSource, }); +const dataSourceMetaLoaded = (dataSourceMeta: Plugin): LoadDataSourceMetaAction => ({ + type: ActionTypes.LoadDataSourceMeta, + payload: dataSourceMeta, +}); + const dataSourceTypesLoaded = (dataSourceTypes: Plugin[]): LoadDataSourceTypesAction => ({ type: ActionTypes.LoadDataSourceTypes, payload: dataSourceTypes, @@ -83,7 +94,8 @@ export type Action = | LoadDataSourceTypesAction | SetDataSourceTypeSearchQueryAction | LoadDataSourceAction - | UpdateNavIndexAction; + | UpdateNavIndexAction + | LoadDataSourceMetaAction; type ThunkResult = ThunkAction; @@ -99,6 +111,7 @@ export function loadDataSource(id: number): ThunkResult { 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))); }; } diff --git a/public/app/features/datasources/state/navModel.ts b/public/app/features/datasources/state/navModel.ts index d80ab5d52a2..47eadb82376 100644 --- a/public/app/features/datasources/state/navModel.ts +++ b/public/app/features/datasources/state/navModel.ts @@ -1,4 +1,5 @@ import { DataSource, NavModel, NavModelItem, PluginMeta } from 'app/types'; +import config from 'app/core/config'; export function buildNavModel(dataSource: DataSource, pluginMeta: PluginMeta): NavModelItem { const navModel = { @@ -16,26 +17,29 @@ export function buildNavModel(dataSource: DataSource, pluginMeta: PluginMeta): N text: 'Settings', url: `datasources/edit/${dataSource.id}/settings`, }, - { - active: false, - icon: 'fa fa-fw fa-sliders', - id: `datasource-permissions-${dataSource.id}`, - text: 'Permissions', - url: `datasources/edit/${dataSource.id}/permissions`, - }, ], }; - if (pluginMeta.includes && pluginMeta.includes.length > 0) { + if (pluginMeta.includes && hasDashboards(pluginMeta.includes)) { navModel.children.push({ active: false, - icon: 'gicon gicon-dashboard', + 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; } @@ -95,3 +99,11 @@ export function getDataSourceLoadingNav(pageName: string): NavModel { node: node, }; } + +function hasDashboards(includes) { + return ( + includes.filter(include => { + return include.type === 'dashboard'; + }).length > 0 + ); +} diff --git a/public/app/features/datasources/state/reducers.ts b/public/app/features/datasources/state/reducers.ts index 075483945a4..051966c81be 100644 --- a/public/app/features/datasources/state/reducers.ts +++ b/public/app/features/datasources/state/reducers.ts @@ -10,6 +10,7 @@ const initialState: DataSourcesState = { dataSourcesCount: 0, dataSourceTypes: [] as Plugin[], dataSourceTypeSearchQuery: '', + dataSourceMeta: {} as Plugin, }; export const dataSourcesReducer = (state = initialState, action: Action): DataSourcesState => { @@ -31,6 +32,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; diff --git a/public/app/features/plugins/state/navModel.ts b/public/app/features/plugins/state/navModel.ts index 852eb2806f9..f12967ebb7a 100644 --- a/public/app/features/plugins/state/navModel.ts +++ b/public/app/features/plugins/state/navModel.ts @@ -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 }), diff --git a/public/app/routes/routes.ts b/public/app/routes/routes.ts index 54f4a6718e1..43c513aea38 100644 --- a/public/app/routes/routes.ts +++ b/public/app/routes/routes.ts @@ -72,6 +72,16 @@ export function setupAngularRoutes($routeProvider, $locationProvider) { component: () => DataSourcesListPage, }, }) + .when('/datasources/edit/:id', { + templateUrl: 'public/app/features/plugins/partials/ds_edit.html', + controller: 'DataSourceEditCtrl', + controllerAs: 'ctrl', + }) + .when('/datasources/edit/:id/dashboards', { + templateUrl: 'public/app/features/plugins/partials/ds_dashboards.html', + controller: 'DataSourceDashboardsCtrl', + controllerAs: 'ctrl', + }) .when('/datasources/edit/:id/:page?', { template: '', resolve: { diff --git a/public/app/types/datasources.ts b/public/app/types/datasources.ts index 9d35794b89e..95c754faa6b 100644 --- a/public/app/types/datasources.ts +++ b/public/app/types/datasources.ts @@ -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 { @@ -26,4 +26,5 @@ export interface DataSourcesState { dataSourcesCount: number; dataSourceTypes: Plugin[]; dataSource: DataSource; + dataSourceMeta: Plugin; } From b283845e4e9854a1bbe552d7b8fd0a1bf07d557e Mon Sep 17 00:00:00 2001 From: Peter Holmberg Date: Mon, 8 Oct 2018 16:05:37 +0200 Subject: [PATCH 07/30] adding permissions component --- .../datasources/DataSourcePermissions.tsx | 20 +++++++++++++++++++ .../datasources/EditDataSourcePage.tsx | 3 ++- 2 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 public/app/features/datasources/DataSourcePermissions.tsx diff --git a/public/app/features/datasources/DataSourcePermissions.tsx b/public/app/features/datasources/DataSourcePermissions.tsx new file mode 100644 index 00000000000..7dfa71a2652 --- /dev/null +++ b/public/app/features/datasources/DataSourcePermissions.tsx @@ -0,0 +1,20 @@ +import React, { PureComponent } from 'react'; +import { connect } from 'react-redux'; + +export interface Props {} + +export class DataSourcePermissions extends PureComponent { + render() { + return ( +
+

Permissions

+
+ ); + } +} + +function mapStateToProps(state) { + return {}; +} + +export default connect(mapStateToProps)(DataSourcePermissions); diff --git a/public/app/features/datasources/EditDataSourcePage.tsx b/public/app/features/datasources/EditDataSourcePage.tsx index 46966dcbb58..1c7e67de0b9 100644 --- a/public/app/features/datasources/EditDataSourcePage.tsx +++ b/public/app/features/datasources/EditDataSourcePage.tsx @@ -2,6 +2,7 @@ import React, { PureComponent } from 'react'; import { hot } from 'react-hot-loader'; import { connect } from 'react-redux'; import PageHeader from '../../core/components/PageHeader/PageHeader'; +import DataSourcePermissions from './DataSourcePermissions'; import { DataSource, NavModel } from 'app/types'; import { loadDataSource } from './state/actions'; import { getNavModel } from '../../core/selectors/navModel'; @@ -45,7 +46,7 @@ export class EditDataSourcePage extends PureComponent { renderPage() { switch (this.getCurrentPage()) { case PageTypes.Permissions: - return
Permissions
; + return ; } return null; From ac6bee621c3d383ef9db417c29d6b1d405767261 Mon Sep 17 00:00:00 2001 From: Peter Holmberg Date: Tue, 9 Oct 2018 11:07:23 +0200 Subject: [PATCH 08/30] modified AddPermissions component --- .../PermissionList/AddPermission.tsx | 33 +++++++----- .../permissions/DashboardPermissions.tsx | 15 +++++- .../datasources/DataSourcePermissions.tsx | 50 ++++++++++++++++++- .../features/folders/FolderPermissions.tsx | 15 +++++- 4 files changed, 95 insertions(+), 18 deletions(-) diff --git a/public/app/core/components/PermissionList/AddPermission.tsx b/public/app/core/components/PermissionList/AddPermission.tsx index fc062ce63e4..a754bec1a18 100644 --- a/public/app/core/components/PermissionList/AddPermission.tsx +++ b/public/app/core/components/PermissionList/AddPermission.tsx @@ -4,20 +4,27 @@ import { TeamPicker, Team } from 'app/core/components/Picker/TeamPicker'; import DescriptionPicker, { OptionWithDescription } from 'app/core/components/Picker/DescriptionPicker'; import { User } from 'app/types'; import { - dashboardPermissionLevels, - dashboardAclTargets, AclTarget, PermissionLevel, NewDashboardAclItem, OrgRole, + DashboardPermissionInfo, + AclTargetInfo, } from 'app/types/acl'; export interface Props { onAddPermission: (item: NewDashboardAclItem) => void; onCancel: () => void; + showPermissionLevels?: boolean; + dashboardPermissionLevels?: DashboardPermissionInfo[]; + dashboardAclTargets: AclTargetInfo[]; } class AddPermissions extends Component { + static defaultProps = { + showPermissionLevels: true, + }; + constructor(props) { super(props); this.state = this.getCleanState(); @@ -78,7 +85,7 @@ class AddPermissions extends Component { } render() { - const { onCancel } = this.props; + const { onCancel, showPermissionLevels, dashboardPermissionLevels, dashboardAclTargets } = this.props; const newItem = this.state; const pickerClassName = 'width-20'; const isValid = this.isValid(); @@ -125,15 +132,17 @@ class AddPermissions extends Component {
) : null} -
- -
+ {showPermissionLevels && ( +
+ +
+ )}
- + { +interface State { + isAdding: boolean; +} + +export class DataSourcePermissions extends PureComponent { + state = { + isAdding: false, + }; + + onOpenAddPermissions = () => { + this.setState({ + isAdding: true, + }); + }; + + onAddPermission = () => {}; + + onCancelAddPermission = () => { + this.setState({ + isAdding: false, + }); + }; + render() { + const { isAdding } = this.state; + + const dashboardAclTargets: AclTargetInfo[] = [ + { value: AclTarget.Team, text: 'Team' }, + { value: AclTarget.User, text: 'User' }, + ]; + return (
-

Permissions

+
+

Permissions

+
+ +
+ + +
); } diff --git a/public/app/features/folders/FolderPermissions.tsx b/public/app/features/folders/FolderPermissions.tsx index 176e270038b..9237e0bbee7 100644 --- a/public/app/features/folders/FolderPermissions.tsx +++ b/public/app/features/folders/FolderPermissions.tsx @@ -6,7 +6,13 @@ import Tooltip from 'app/core/components/Tooltip/Tooltip'; import SlideDown from 'app/core/components/Animations/SlideDown'; import { getNavModel } from 'app/core/selectors/navModel'; import { NavModel, StoreState, FolderState } from 'app/types'; -import { DashboardAcl, PermissionLevel, NewDashboardAclItem } from 'app/types/acl'; +import { + dashboardAclTargets, + dashboardPermissionLevels, + DashboardAcl, + PermissionLevel, + NewDashboardAclItem, +} from 'app/types/acl'; import { getFolderByUid, getFolderPermissions, @@ -93,7 +99,12 @@ export class FolderPermissions extends PureComponent {
- + Date: Tue, 9 Oct 2018 14:50:46 +0200 Subject: [PATCH 09/30] reverted AddPermissions --- .../PermissionList/AddPermission.tsx | 29 ++++++++----------- .../permissions/DashboardPermissions.tsx | 15 ++-------- .../features/folders/FolderPermissions.tsx | 15 ++-------- 3 files changed, 16 insertions(+), 43 deletions(-) diff --git a/public/app/core/components/PermissionList/AddPermission.tsx b/public/app/core/components/PermissionList/AddPermission.tsx index a754bec1a18..d4e5dbba98d 100644 --- a/public/app/core/components/PermissionList/AddPermission.tsx +++ b/public/app/core/components/PermissionList/AddPermission.tsx @@ -4,20 +4,17 @@ import { TeamPicker, Team } from 'app/core/components/Picker/TeamPicker'; import DescriptionPicker, { OptionWithDescription } from 'app/core/components/Picker/DescriptionPicker'; import { User } from 'app/types'; import { + dashboardPermissionLevels, + dashboardAclTargets, AclTarget, PermissionLevel, NewDashboardAclItem, OrgRole, - DashboardPermissionInfo, - AclTargetInfo, } from 'app/types/acl'; export interface Props { onAddPermission: (item: NewDashboardAclItem) => void; onCancel: () => void; - showPermissionLevels?: boolean; - dashboardPermissionLevels?: DashboardPermissionInfo[]; - dashboardAclTargets: AclTargetInfo[]; } class AddPermissions extends Component { @@ -85,7 +82,7 @@ class AddPermissions extends Component { } render() { - const { onCancel, showPermissionLevels, dashboardPermissionLevels, dashboardAclTargets } = this.props; + const { onCancel } = this.props; const newItem = this.state; const pickerClassName = 'width-20'; const isValid = this.isValid(); @@ -132,17 +129,15 @@ class AddPermissions extends Component { ) : null} - {showPermissionLevels && ( -
- -
- )} +
+ +
- + { - + Date: Tue, 9 Oct 2018 15:23:28 +0200 Subject: [PATCH 10/30] remove datasource permission admin for now --- pkg/models/datasource.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/pkg/models/datasource.go b/pkg/models/datasource.go index de7158c0afb..b1648edbb45 100644 --- a/pkg/models/datasource.go +++ b/pkg/models/datasource.go @@ -196,14 +196,12 @@ type DsPermissionType int const ( DsPermissionQuery DsPermissionType = 1 << iota - DsPermissionAdmin DsPermissionNoAccess ) func (p DsPermissionType) String() string { names := map[int]string{ int(DsPermissionQuery): "Query", - int(DsPermissionAdmin): "Admin", int(DsPermissionNoAccess): "No Access", } return names[int(p)] From d71ae7bd4df509e831fe411d88a129c50b494399 Mon Sep 17 00:00:00 2001 From: Peter Holmberg Date: Tue, 9 Oct 2018 16:05:40 +0200 Subject: [PATCH 11/30] Redone with DataSourcePermissions --- .../datasources/AddDataSourcePermissions.tsx | 128 ++++++++++++++++++ .../datasources/DataSourcePermissions.tsx | 78 +++++++++-- .../app/features/datasources/state/actions.ts | 39 +++++- .../features/datasources/state/reducers.ts | 6 +- public/app/types/acl.ts | 5 + public/app/types/datasources.ts | 14 ++ public/app/types/index.ts | 3 +- 7 files changed, 255 insertions(+), 18 deletions(-) create mode 100644 public/app/features/datasources/AddDataSourcePermissions.tsx diff --git a/public/app/features/datasources/AddDataSourcePermissions.tsx b/public/app/features/datasources/AddDataSourcePermissions.tsx new file mode 100644 index 00000000000..42277f761aa --- /dev/null +++ b/public/app/features/datasources/AddDataSourcePermissions.tsx @@ -0,0 +1,128 @@ +import React, { PureComponent } from 'react'; +import { UserPicker } from 'app/core/components/Picker/UserPicker'; +import { Team, TeamPicker } from 'app/core/components/Picker/TeamPicker'; +import DescriptionPicker, { OptionWithDescription } from 'app/core/components/Picker/DescriptionPicker'; +import { AclTarget, DataSourcePermissionLevel } from 'app/types/acl'; +import { User } from 'app/types'; + +export interface Props { + onAddPermission: (state) => void; + onCancel: () => void; +} + +interface State { + userId: number; + teamId: number; + type: AclTarget; + permission: DataSourcePermissionLevel; +} + +export class AddDataSourcePermissions extends PureComponent { + cleanState = () => ({ + userId: 0, + teamId: 0, + type: AclTarget.Team, + permission: DataSourcePermissionLevel.Query, + }); + + state = this.cleanState(); + + isValid() { + switch (this.state.type) { + case AclTarget.Team: + return this.state.teamId > 0; + case AclTarget.User: + return this.state.userId > 0; + } + return true; + } + + onTeamSelected = (team: Team) => { + this.setState({ teamId: team ? team.id : 0 }); + }; + + onUserSelected = (user: User) => { + this.setState({ userId: user ? user.id : 0 }); + }; + + onPermissionChanged = (permission: OptionWithDescription) => { + this.setState({ permission: permission.value }); + }; + + onTypeChanged = event => { + const type = event.target.value as AclTarget; + + this.setState({ type: type, userId: 0, teamId: 0 }); + }; + + onSubmit = async event => { + event.preventDefault(); + + await this.props.onAddPermission(this.state); + this.setState(this.cleanState()); + }; + + render() { + const { onCancel } = this.props; + const { type, teamId, userId, permission } = this.state; + + const pickerClassName = 'width-20'; + const aclTargets = [{ value: AclTarget.Team, text: 'Team' }, { value: AclTarget.User, text: 'User' }]; + const permissionLevels = [ + { value: DataSourcePermissionLevel.Query, label: 'Query', description: 'Can query data source.' }, + ]; + + return ( +
+ +
+
Add Permission For
+
+
+
+ +
+
+ {type === AclTarget.User && ( +
+ +
+ )} + + {type === AclTarget.Team && ( +
+ +
+ )} +
+ +
+
+ +
+
+
+
+ ); + } +} + +export default AddDataSourcePermissions; diff --git a/public/app/features/datasources/DataSourcePermissions.tsx b/public/app/features/datasources/DataSourcePermissions.tsx index 5a878485b42..e01a3561647 100644 --- a/public/app/features/datasources/DataSourcePermissions.tsx +++ b/public/app/features/datasources/DataSourcePermissions.tsx @@ -1,10 +1,20 @@ import React, { PureComponent } from 'react'; import { connect } from 'react-redux'; import SlideDown from '../../core/components/Animations/SlideDown'; -import AddPermissions from '../../core/components/PermissionList/AddPermission'; -import { AclTarget, AclTargetInfo } from 'app/types/acl'; +import AddDataSourcePermissions from './AddDataSourcePermissions'; +import { AclTarget } from 'app/types/acl'; +import { addDataSourcePermission, loadDataSourcePermissions, removeDataSourcePermission } from './state/actions'; +import { DashboardAcl, DataSourcePermission } from 'app/types'; +import { getRouteParamsId } from '../../core/selectors/location'; +import PermissionList from '../../core/components/PermissionList/PermissionList'; -export interface Props {} +export interface Props { + dataSourcePermissions: DataSourcePermission[]; + pageId: number; + addDataSourcePermission: typeof addDataSourcePermission; + loadDataSourcePermissions: typeof loadDataSourcePermissions; + removeDataSourcePermission: typeof removeDataSourcePermission; +} interface State { isAdding: boolean; @@ -15,13 +25,42 @@ export class DataSourcePermissions extends PureComponent { isAdding: false, }; + componentDidMount() { + this.fetchDataSourcePermissions(); + } + + async fetchDataSourcePermissions() { + const { pageId, loadDataSourcePermissions } = this.props; + + return await loadDataSourcePermissions(pageId); + } + onOpenAddPermissions = () => { this.setState({ isAdding: true, }); }; - onAddPermission = () => {}; + onAddPermission = state => { + const { pageId, addDataSourcePermission } = this.props; + const data = { + permission: state.permission, + userId: 0, + teamId: 0, + }; + + if (state.type === AclTarget.Team) { + data.teamId = state.teamId; + } else if (state.team === AclTarget.User) { + data.userId = state.userId; + } + + addDataSourcePermission(pageId, data); + }; + + onRemovePermission = (item: DashboardAcl) => { + this.props.removeDataSourcePermission(1, 1); + }; onCancelAddPermission = () => { this.setState({ @@ -30,13 +69,9 @@ export class DataSourcePermissions extends PureComponent { }; render() { + const { dataSourcePermissions } = this.props; const { isAdding } = this.state; - const dashboardAclTargets: AclTargetInfo[] = [ - { value: AclTarget.Team, text: 'Team' }, - { value: AclTarget.User, text: 'User' }, - ]; - return (
@@ -47,20 +82,33 @@ export class DataSourcePermissions extends PureComponent {
- this.onAddPermission(state)} onCancel={this.onCancelAddPermission} /> + {}} + isFetching={false} + />
); } } function mapStateToProps(state) { - return {}; + return { + pageId: getRouteParamsId(state.location), + dataSourcePermissions: state.dataSources.dataSourcePermissions, + }; } -export default connect(mapStateToProps)(DataSourcePermissions); +const mapDispatchToProps = { + addDataSourcePermission, + loadDataSourcePermissions, + removeDataSourcePermission, +}; + +export default connect(mapStateToProps, mapDispatchToProps)(DataSourcePermissions); diff --git a/public/app/features/datasources/state/actions.ts b/public/app/features/datasources/state/actions.ts index bb8fce8424a..ebd7c860c32 100644 --- a/public/app/features/datasources/state/actions.ts +++ b/public/app/features/datasources/state/actions.ts @@ -5,12 +5,14 @@ import { LayoutMode } from '../../../core/components/LayoutSelector/LayoutSelect import { updateLocation, updateNavIndex, UpdateNavIndexAction } from '../../../core/actions'; import { UpdateLocationAction } from '../../../core/actions/location'; import { buildNavModel } from './navModel'; +import { DataSourcePermission } from '../../../types/datasources'; export enum ActionTypes { LoadDataSources = 'LOAD_DATA_SOURCES', LoadDataSourceTypes = 'LOAD_DATA_SOURCE_TYPES', LoadDataSource = 'LOAD_DATA_SOURCE', LoadDataSourceMeta = 'LOAD_DATA_SOURCE_META', + LoadDataSourcePermissions = 'LOAD_DATA_SOURCE_PERMISSIONS', SetDataSourcesSearchQuery = 'SET_DATA_SOURCES_SEARCH_QUERY', SetDataSourcesLayoutMode = 'SET_DATA_SOURCES_LAYOUT_MODE', SetDataSourceTypeSearchQuery = 'SET_DATA_SOURCE_TYPE_SEARCH_QUERY', @@ -51,6 +53,11 @@ export interface LoadDataSourceMetaAction { payload: Plugin; } +export interface LoadDataSourcePermissionsAction { + type: ActionTypes.LoadDataSourcePermissions; + payload: DataSourcePermission[]; +} + const dataSourcesLoaded = (dataSources: DataSource[]): LoadDataSourcesAction => ({ type: ActionTypes.LoadDataSources, payload: dataSources, @@ -71,6 +78,13 @@ const dataSourceTypesLoaded = (dataSourceTypes: Plugin[]): LoadDataSourceTypesAc payload: dataSourceTypes, }); +const dataSourcePermissionsLoaded = ( + dataSourcePermissions: DataSourcePermission[] +): LoadDataSourcePermissionsAction => ({ + type: ActionTypes.LoadDataSourcePermissions, + payload: dataSourcePermissions, +}); + export const setDataSourcesSearchQuery = (searchQuery: string): SetDataSourcesSearchQueryAction => ({ type: ActionTypes.SetDataSourcesSearchQuery, payload: searchQuery, @@ -95,7 +109,8 @@ export type Action = | SetDataSourceTypeSearchQueryAction | LoadDataSourceAction | UpdateNavIndexAction - | LoadDataSourceMetaAction; + | LoadDataSourceMetaAction + | LoadDataSourcePermissionsAction; type ThunkResult = ThunkAction; @@ -145,6 +160,28 @@ export function loadDataSourceTypes(): ThunkResult { }; } +export function loadDataSourcePermissions(id: number): ThunkResult { + return async dispatch => { + const response = await getBackendSrv().get(`/api/datasources/${id}/permissions`); + dispatch(dataSourcePermissionsLoaded(response.permissions)); + }; +} + +export function addDataSourcePermission(id: number, data: object): ThunkResult { + return async dispatch => { + await getBackendSrv().post(`/api/datasources/${id}/permissions`, data); + + dispatch(loadDataSourcePermissions(id)); + }; +} + +export function removeDataSourcePermission(id: number, permissionId: number): ThunkResult { + return async dispatch => { + await getBackendSrv().delete(`/api/datasources/${id}/permissions/${permissionId}`); + dispatch(loadDataSourcePermissions(id)); + }; +} + export function nameExits(dataSources, name) { return ( dataSources.filter(dataSource => { diff --git a/public/app/features/datasources/state/reducers.ts b/public/app/features/datasources/state/reducers.ts index 051966c81be..0793e03b4d8 100644 --- a/public/app/features/datasources/state/reducers.ts +++ b/public/app/features/datasources/state/reducers.ts @@ -1,4 +1,4 @@ -import { DataSource, DataSourcesState, Plugin } from 'app/types'; +import { DataSource, DataSourcePermission, DataSourcesState, Plugin } from 'app/types'; import { Action, ActionTypes } from './actions'; import { LayoutModes } from '../../../core/components/LayoutSelector/LayoutSelector'; @@ -11,6 +11,7 @@ const initialState: DataSourcesState = { dataSourceTypes: [] as Plugin[], dataSourceTypeSearchQuery: '', dataSourceMeta: {} as Plugin, + dataSourcePermissions: [] as DataSourcePermission[], }; export const dataSourcesReducer = (state = initialState, action: Action): DataSourcesState => { @@ -35,6 +36,9 @@ export const dataSourcesReducer = (state = initialState, action: Action): DataSo case ActionTypes.LoadDataSourceMeta: return { ...state, dataSourceMeta: action.payload }; + + case ActionTypes.LoadDataSourcePermissions: + return { ...state, dataSourcePermissions: action.payload }; } return state; diff --git a/public/app/types/acl.ts b/public/app/types/acl.ts index fa5ace388c4..3580b72769b 100644 --- a/public/app/types/acl.ts +++ b/public/app/types/acl.ts @@ -61,6 +61,11 @@ export enum PermissionLevel { Admin = 4, } +export enum DataSourcePermissionLevel { + Query = 1, + Admin = 2, +} + export enum AclTarget { Team = 'Team', User = 'User', diff --git a/public/app/types/datasources.ts b/public/app/types/datasources.ts index 95c754faa6b..7abd96ce27f 100644 --- a/public/app/types/datasources.ts +++ b/public/app/types/datasources.ts @@ -1,6 +1,19 @@ import { LayoutMode } from '../core/components/LayoutSelector/LayoutSelector'; import { Plugin } from './plugins'; +export interface DataSourcePermission { + id: number; + datasourceId: number; + userId: number; + userLogin: string; + userEmail: string; + userAvatarUrl: string; + permission: number; + permissionName: string; + created: string; + updated: string; +} + export interface DataSource { id: number; orgId: number; @@ -27,4 +40,5 @@ export interface DataSourcesState { dataSourceTypes: Plugin[]; dataSource: DataSource; dataSourceMeta: Plugin; + dataSourcePermissions: DataSourcePermission[]; } diff --git a/public/app/types/index.ts b/public/app/types/index.ts index 26f15d582ac..f7a52ec8b6d 100644 --- a/public/app/types/index.ts +++ b/public/app/types/index.ts @@ -7,7 +7,7 @@ import { DashboardState } from './dashboard'; import { DashboardAcl, OrgRole, PermissionLevel } from './acl'; import { ApiKey, ApiKeysState, NewApiKey } from './apiKeys'; import { Invitee, OrgUser, User, UsersState } from './user'; -import { DataSource, DataSourcesState } from './datasources'; +import { DataSource, DataSourcePermission, DataSourcesState } from './datasources'; import { PluginMeta, Plugin, PluginsState } from './plugins'; export { @@ -41,6 +41,7 @@ export { Plugin, PluginsState, DataSourcesState, + DataSourcePermission, Invitee, OrgUser, User, From 363592a97b6fe0586651fa09966b3b0e823524ae Mon Sep 17 00:00:00 2001 From: Peter Holmberg Date: Tue, 9 Oct 2018 16:53:59 +0200 Subject: [PATCH 12/30] pausing permissions list --- .../datasources/AddDataSourcePermissions.tsx | 7 +- .../datasources/DataSourcePermissions.tsx | 9 +- .../datasources/DataSourcePermissionsList.tsx | 84 +++++++++++++++++++ public/app/types/acl.ts | 4 + 4 files changed, 92 insertions(+), 12 deletions(-) create mode 100644 public/app/features/datasources/DataSourcePermissionsList.tsx diff --git a/public/app/features/datasources/AddDataSourcePermissions.tsx b/public/app/features/datasources/AddDataSourcePermissions.tsx index 42277f761aa..702175728db 100644 --- a/public/app/features/datasources/AddDataSourcePermissions.tsx +++ b/public/app/features/datasources/AddDataSourcePermissions.tsx @@ -2,7 +2,7 @@ import React, { PureComponent } from 'react'; import { UserPicker } from 'app/core/components/Picker/UserPicker'; import { Team, TeamPicker } from 'app/core/components/Picker/TeamPicker'; import DescriptionPicker, { OptionWithDescription } from 'app/core/components/Picker/DescriptionPicker'; -import { AclTarget, DataSourcePermissionLevel } from 'app/types/acl'; +import { dataSourceAclLevels, AclTarget, DataSourcePermissionLevel } from 'app/types/acl'; import { User } from 'app/types'; export interface Props { @@ -68,9 +68,6 @@ export class AddDataSourcePermissions extends PureComponent { const pickerClassName = 'width-20'; const aclTargets = [{ value: AclTarget.Team, text: 'Team' }, { value: AclTarget.User, text: 'User' }]; - const permissionLevels = [ - { value: DataSourcePermissionLevel.Query, label: 'Query', description: 'Can query data source.' }, - ]; return (
@@ -106,7 +103,7 @@ export class AddDataSourcePermissions extends PureComponent { )}
{ onCancel={this.onCancelAddPermission} /> - {}} - isFetching={false} - /> + this.onRemovePermission(item)} />
); } diff --git a/public/app/features/datasources/DataSourcePermissionsList.tsx b/public/app/features/datasources/DataSourcePermissionsList.tsx new file mode 100644 index 00000000000..e2064db05c6 --- /dev/null +++ b/public/app/features/datasources/DataSourcePermissionsList.tsx @@ -0,0 +1,84 @@ +import React, { PureComponent } from 'react'; +import { DataSourcePermission } from '../../types'; +import { dataSourceAclLevels, DataSourcePermissionLevel } from '../../types/acl'; +import DescriptionPicker from '../../core/components/Picker/DescriptionPicker'; + +interface Props { + items: DataSourcePermission[]; + onRemoveItem: (item) => void; +} + +export class DataSourcePermissionsList extends PureComponent { + render() { + const { items } = this.props; + const permissionLevels = dataSourceAclLevels; + permissionLevels.push({ value: DataSourcePermissionLevel.Admin, label: 'Admin', description: '' }); + + return ( + + + + + + + + + + {items.map((item, index) => { + return ( + + + + + + + + ); + })} + +
+ + + Admin + (Role) + + Can +
+ {}} + value={2} + disabled={true} + className={'gf-form-input--form-dropdown-right'} + /> +
+
+ +
+ + + {} + (Role) + + Can +
+ {}} + value={2} + disabled={true} + className={'gf-form-input--form-dropdown-right'} + /> +
+
+ +
+ ); + } +} + +export default DataSourcePermissionsList; diff --git a/public/app/types/acl.ts b/public/app/types/acl.ts index 3580b72769b..21f7bdac2d4 100644 --- a/public/app/types/acl.ts +++ b/public/app/types/acl.ts @@ -78,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' }, From 8ed5594dbc694107620d87f18c7a914480da227b Mon Sep 17 00:00:00 2001 From: Peter Holmberg Date: Wed, 10 Oct 2018 10:39:48 +0200 Subject: [PATCH 13/30] enable permissions for data source --- .../datasources/DataSourcePermissions.tsx | 66 +++++++++++++++---- .../app/features/datasources/state/actions.ts | 18 +++-- .../features/datasources/state/reducers.ts | 6 +- public/app/types/datasources.ts | 8 ++- public/app/types/index.ts | 3 +- 5 files changed, 76 insertions(+), 25 deletions(-) diff --git a/public/app/features/datasources/DataSourcePermissions.tsx b/public/app/features/datasources/DataSourcePermissions.tsx index dbee2d41634..ff02a9a6516 100644 --- a/public/app/features/datasources/DataSourcePermissions.tsx +++ b/public/app/features/datasources/DataSourcePermissions.tsx @@ -4,14 +4,20 @@ import SlideDown from '../../core/components/Animations/SlideDown'; import AddDataSourcePermissions from './AddDataSourcePermissions'; import DataSourcePermissionsList from './DataSourcePermissionsList'; import { AclTarget } from 'app/types/acl'; -import { addDataSourcePermission, loadDataSourcePermissions, removeDataSourcePermission } from './state/actions'; +import { + addDataSourcePermission, + enableDataSourcePermissions, + loadDataSourcePermissions, + removeDataSourcePermission, +} from './state/actions'; import { DashboardAcl, DataSourcePermission } from 'app/types'; import { getRouteParamsId } from '../../core/selectors/location'; export interface Props { - dataSourcePermissions: DataSourcePermission[]; + dataSourcePermission: { enabled: boolean; datasouceId: number; permissions: DataSourcePermission[] }; pageId: number; addDataSourcePermission: typeof addDataSourcePermission; + enableDataSourcePermissions: typeof enableDataSourcePermissions; loadDataSourcePermissions: typeof loadDataSourcePermissions; removeDataSourcePermission: typeof removeDataSourcePermission; } @@ -41,6 +47,11 @@ export class DataSourcePermissions extends PureComponent { }); }; + onEnablePermissions = () => { + const { pageId, enableDataSourcePermissions } = this.props; + enableDataSourcePermissions(pageId); + }; + onAddPermission = state => { const { pageId, addDataSourcePermission } = this.props; const data = { @@ -69,25 +80,51 @@ export class DataSourcePermissions extends PureComponent { }; render() { - const { dataSourcePermissions } = this.props; + const { dataSourcePermission } = this.props; const { isAdding } = this.state; + const isPermissionsEnabled = dataSourcePermission.enabled; return (

Permissions

- + {!isPermissionsEnabled && ( + + )} + {isPermissionsEnabled && ( + + )}
- - this.onAddPermission(state)} - onCancel={this.onCancelAddPermission} - /> - - this.onRemovePermission(item)} /> + {!isPermissionsEnabled ? ( +
+
{'Permissions not enabled for this data source.'}
+ +
+ ProTip:{' '} + {'Only admins will be able to query the data source after you enable permissions.'} +
+
+ ) : ( +
+ + this.onAddPermission(state)} + onCancel={this.onCancelAddPermission} + /> + + this.onRemovePermission(item)} + /> +
+ )}
); } @@ -96,12 +133,13 @@ export class DataSourcePermissions extends PureComponent { function mapStateToProps(state) { return { pageId: getRouteParamsId(state.location), - dataSourcePermissions: state.dataSources.dataSourcePermissions, + dataSourcePermission: state.dataSources.dataSourcePermission, }; } const mapDispatchToProps = { addDataSourcePermission, + enableDataSourcePermissions, loadDataSourcePermissions, removeDataSourcePermission, }; diff --git a/public/app/features/datasources/state/actions.ts b/public/app/features/datasources/state/actions.ts index ebd7c860c32..379b16b9e06 100644 --- a/public/app/features/datasources/state/actions.ts +++ b/public/app/features/datasources/state/actions.ts @@ -1,11 +1,10 @@ import { ThunkAction } from 'redux-thunk'; -import { DataSource, Plugin, StoreState } from 'app/types'; +import { DataSource, DataSourcePermissionDTO, Plugin, StoreState } from 'app/types'; import { getBackendSrv } from '../../../core/services/backend_srv'; import { LayoutMode } from '../../../core/components/LayoutSelector/LayoutSelector'; import { updateLocation, updateNavIndex, UpdateNavIndexAction } from '../../../core/actions'; import { UpdateLocationAction } from '../../../core/actions/location'; import { buildNavModel } from './navModel'; -import { DataSourcePermission } from '../../../types/datasources'; export enum ActionTypes { LoadDataSources = 'LOAD_DATA_SOURCES', @@ -55,7 +54,7 @@ export interface LoadDataSourceMetaAction { export interface LoadDataSourcePermissionsAction { type: ActionTypes.LoadDataSourcePermissions; - payload: DataSourcePermission[]; + payload: DataSourcePermissionDTO; } const dataSourcesLoaded = (dataSources: DataSource[]): LoadDataSourcesAction => ({ @@ -79,10 +78,10 @@ const dataSourceTypesLoaded = (dataSourceTypes: Plugin[]): LoadDataSourceTypesAc }); const dataSourcePermissionsLoaded = ( - dataSourcePermissions: DataSourcePermission[] + dataSourcePermission: DataSourcePermissionDTO ): LoadDataSourcePermissionsAction => ({ type: ActionTypes.LoadDataSourcePermissions, - payload: dataSourcePermissions, + payload: dataSourcePermission, }); export const setDataSourcesSearchQuery = (searchQuery: string): SetDataSourcesSearchQueryAction => ({ @@ -163,7 +162,14 @@ export function loadDataSourceTypes(): ThunkResult { export function loadDataSourcePermissions(id: number): ThunkResult { return async dispatch => { const response = await getBackendSrv().get(`/api/datasources/${id}/permissions`); - dispatch(dataSourcePermissionsLoaded(response.permissions)); + dispatch(dataSourcePermissionsLoaded(response)); + }; +} + +export function enableDataSourcePermissions(id: number): ThunkResult { + return async dispatch => { + await getBackendSrv().post(`/api/datasources/${id}/enable-permissions`, {}); + dispatch(loadDataSourcePermissions(id)); }; } diff --git a/public/app/features/datasources/state/reducers.ts b/public/app/features/datasources/state/reducers.ts index 0793e03b4d8..2fd8a21b9a7 100644 --- a/public/app/features/datasources/state/reducers.ts +++ b/public/app/features/datasources/state/reducers.ts @@ -1,4 +1,4 @@ -import { DataSource, DataSourcePermission, DataSourcesState, Plugin } from 'app/types'; +import { DataSource, DataSourcePermissionDTO, DataSourcesState, Plugin } from 'app/types'; import { Action, ActionTypes } from './actions'; import { LayoutModes } from '../../../core/components/LayoutSelector/LayoutSelector'; @@ -11,7 +11,7 @@ const initialState: DataSourcesState = { dataSourceTypes: [] as Plugin[], dataSourceTypeSearchQuery: '', dataSourceMeta: {} as Plugin, - dataSourcePermissions: [] as DataSourcePermission[], + dataSourcePermission: {} as DataSourcePermissionDTO, }; export const dataSourcesReducer = (state = initialState, action: Action): DataSourcesState => { @@ -38,7 +38,7 @@ export const dataSourcesReducer = (state = initialState, action: Action): DataSo return { ...state, dataSourceMeta: action.payload }; case ActionTypes.LoadDataSourcePermissions: - return { ...state, dataSourcePermissions: action.payload }; + return { ...state, dataSourcePermission: action.payload }; } return state; diff --git a/public/app/types/datasources.ts b/public/app/types/datasources.ts index 7abd96ce27f..7e62978e642 100644 --- a/public/app/types/datasources.ts +++ b/public/app/types/datasources.ts @@ -14,6 +14,12 @@ export interface DataSourcePermission { updated: string; } +export interface DataSourcePermissionDTO { + datasourceId: number; + enabled: boolean; + permissions: DataSourcePermission[]; +} + export interface DataSource { id: number; orgId: number; @@ -40,5 +46,5 @@ export interface DataSourcesState { dataSourceTypes: Plugin[]; dataSource: DataSource; dataSourceMeta: Plugin; - dataSourcePermissions: DataSourcePermission[]; + dataSourcePermission: DataSourcePermissionDTO; } diff --git a/public/app/types/index.ts b/public/app/types/index.ts index f7a52ec8b6d..eee34c291af 100644 --- a/public/app/types/index.ts +++ b/public/app/types/index.ts @@ -7,7 +7,7 @@ import { DashboardState } from './dashboard'; import { DashboardAcl, OrgRole, PermissionLevel } from './acl'; import { ApiKey, ApiKeysState, NewApiKey } from './apiKeys'; import { Invitee, OrgUser, User, UsersState } from './user'; -import { DataSource, DataSourcePermission, DataSourcesState } from './datasources'; +import { DataSource, DataSourcePermissionDTO, DataSourcePermission, DataSourcesState } from './datasources'; import { PluginMeta, Plugin, PluginsState } from './plugins'; export { @@ -41,6 +41,7 @@ export { Plugin, PluginsState, DataSourcesState, + DataSourcePermissionDTO, DataSourcePermission, Invitee, OrgUser, From f150f351129b0f6ec27f83ed198995e7e37a6b1a Mon Sep 17 00:00:00 2001 From: Peter Holmberg Date: Wed, 10 Oct 2018 11:22:08 +0200 Subject: [PATCH 14/30] fixing permission rows --- .../datasources/DataSourcePermissions.tsx | 8 ++-- .../datasources/DataSourcePermissionsList.tsx | 47 ++++++++++++++----- 2 files changed, 40 insertions(+), 15 deletions(-) diff --git a/public/app/features/datasources/DataSourcePermissions.tsx b/public/app/features/datasources/DataSourcePermissions.tsx index ff02a9a6516..010e766393e 100644 --- a/public/app/features/datasources/DataSourcePermissions.tsx +++ b/public/app/features/datasources/DataSourcePermissions.tsx @@ -10,7 +10,7 @@ import { loadDataSourcePermissions, removeDataSourcePermission, } from './state/actions'; -import { DashboardAcl, DataSourcePermission } from 'app/types'; +import { DataSourcePermission } from 'app/types'; import { getRouteParamsId } from '../../core/selectors/location'; export interface Props { @@ -62,15 +62,15 @@ export class DataSourcePermissions extends PureComponent { if (state.type === AclTarget.Team) { data.teamId = state.teamId; - } else if (state.team === AclTarget.User) { + } else if (state.type === AclTarget.User) { data.userId = state.userId; } addDataSourcePermission(pageId, data); }; - onRemovePermission = (item: DashboardAcl) => { - this.props.removeDataSourcePermission(1, 1); + onRemovePermission = item => { + this.props.removeDataSourcePermission(item.datasourceId, item.id); }; onCancelAddPermission = () => { diff --git a/public/app/features/datasources/DataSourcePermissionsList.tsx b/public/app/features/datasources/DataSourcePermissionsList.tsx index e2064db05c6..cd933dab13e 100644 --- a/public/app/features/datasources/DataSourcePermissionsList.tsx +++ b/public/app/features/datasources/DataSourcePermissionsList.tsx @@ -9,6 +9,36 @@ interface Props { } export class DataSourcePermissionsList extends PureComponent { + renderAvatar(item) { + if (item.teamId) { + return ; + } else if (item.userId) { + return ; + } + + return ; + } + + renderDescription(item) { + if (item.userId) { + return [ + {item.userLogin} , + + (User) + , + ]; + } + if (item.teamId) { + return [ + {item.team} , + + (Team) + , + ]; + } + return (Role); + } + render() { const { items } = this.props; const permissionLevels = dataSourceAclLevels; @@ -46,14 +76,9 @@ export class DataSourcePermissionsList extends PureComponent { {items.map((item, index) => { return ( - - - - - - {} - (Role) - + + {this.renderAvatar(item)} + {this.renderDescription(item)} Can @@ -61,15 +86,15 @@ export class DataSourcePermissionsList extends PureComponent { {}} - value={2} + value={1} disabled={true} className={'gf-form-input--form-dropdown-right'} />
- From 83480a1de68652bedbb02c72dca109616b60aef6 Mon Sep 17 00:00:00 2001 From: Peter Holmberg Date: Wed, 10 Oct 2018 13:28:48 +0200 Subject: [PATCH 15/30] fixing weird arrow in select --- .../datasources/AddDataSourcePermissions.tsx | 20 +++++++++---------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/public/app/features/datasources/AddDataSourcePermissions.tsx b/public/app/features/datasources/AddDataSourcePermissions.tsx index 702175728db..1eda0f114d2 100644 --- a/public/app/features/datasources/AddDataSourcePermissions.tsx +++ b/public/app/features/datasources/AddDataSourcePermissions.tsx @@ -78,17 +78,15 @@ export class AddDataSourcePermissions extends PureComponent {
Add Permission For
-
- -
+
{type === AclTarget.User && (
From 8583788119e08ba45cd7814d0f793125d024ffb5 Mon Sep 17 00:00:00 2001 From: Peter Holmberg Date: Wed, 10 Oct 2018 13:50:42 +0200 Subject: [PATCH 16/30] disable permissions --- .../datasources/DataSourcePermissions.tsx | 30 +++++++++++++------ .../app/features/datasources/state/actions.ts | 7 +++++ 2 files changed, 28 insertions(+), 9 deletions(-) diff --git a/public/app/features/datasources/DataSourcePermissions.tsx b/public/app/features/datasources/DataSourcePermissions.tsx index 010e766393e..506d6fb89b7 100644 --- a/public/app/features/datasources/DataSourcePermissions.tsx +++ b/public/app/features/datasources/DataSourcePermissions.tsx @@ -6,6 +6,7 @@ import DataSourcePermissionsList from './DataSourcePermissionsList'; import { AclTarget } from 'app/types/acl'; import { addDataSourcePermission, + disableDataSourcePermissions, enableDataSourcePermissions, loadDataSourcePermissions, removeDataSourcePermission, @@ -18,6 +19,7 @@ export interface Props { pageId: number; addDataSourcePermission: typeof addDataSourcePermission; enableDataSourcePermissions: typeof enableDataSourcePermissions; + disableDataSourcePermissions: typeof disableDataSourcePermissions; loadDataSourcePermissions: typeof loadDataSourcePermissions; removeDataSourcePermission: typeof removeDataSourcePermission; } @@ -52,6 +54,12 @@ export class DataSourcePermissions extends PureComponent { enableDataSourcePermissions(pageId); }; + onDisablePermissions = () => { + const { pageId, disableDataSourcePermissions } = this.props; + + disableDataSourcePermissions(pageId); + }; + onAddPermission = state => { const { pageId, addDataSourcePermission } = this.props; const data = { @@ -89,16 +97,19 @@ export class DataSourcePermissions extends PureComponent {

Permissions

- {!isPermissionsEnabled && ( - - )} - {isPermissionsEnabled && ( - - )} + , + , + ]}
{!isPermissionsEnabled ? (
@@ -140,6 +151,7 @@ function mapStateToProps(state) { const mapDispatchToProps = { addDataSourcePermission, enableDataSourcePermissions, + disableDataSourcePermissions, loadDataSourcePermissions, removeDataSourcePermission, }; diff --git a/public/app/features/datasources/state/actions.ts b/public/app/features/datasources/state/actions.ts index 379b16b9e06..664ed840928 100644 --- a/public/app/features/datasources/state/actions.ts +++ b/public/app/features/datasources/state/actions.ts @@ -173,6 +173,13 @@ export function enableDataSourcePermissions(id: number): ThunkResult { }; } +export function disableDataSourcePermissions(id: number): ThunkResult { + return async dispatch => { + await getBackendSrv().post(`/api/datasources/${id}/disable-permissions`, {}); + dispatch(loadDataSourcePermissions(id)); + }; +} + export function addDataSourcePermission(id: number, data: object): ThunkResult { return async dispatch => { await getBackendSrv().post(`/api/datasources/${id}/permissions`, data); From ccfcf02e3363bd8f45cbe6189abbbb707300fc64 Mon Sep 17 00:00:00 2001 From: Leonard Gram Date: Wed, 10 Oct 2018 14:21:57 +0200 Subject: [PATCH 17/30] Refactors ds permissions to a filter. --- pkg/api/datasources.go | 24 ++++++++++-------------- pkg/models/datasource.go | 6 ++++++ 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/pkg/api/datasources.go b/pkg/api/datasources.go index 3378fb5bf2e..e23f691ca2b 100644 --- a/pkg/api/datasources.go +++ b/pkg/api/datasources.go @@ -17,18 +17,21 @@ func GetDataSources(c *m.ReqContext) Response { return Error(500, "Failed to query datasources", err) } - permissions := map[int64]m.DsPermissionType{} - permissionsQuery := m.GetDataSourcePermissionsForUserQuery{User: c.SignedInUser} - if err := bus.Dispatch(&permissionsQuery); err != nil { + dsFilterQuery := m.DatasourcesPermissionFilterQuery{ + User: c.SignedInUser, + Datasources: query.Result, + } + + if err := bus.Dispatch(&dsFilterQuery); err != nil { if err != bus.ErrHandlerNotFound { - return Error(500, "failed to read datasource permissions", err) + return Error(500, "Could not get datasources", err) } - } else { - permissions = permissionsQuery.Result + + dsFilterQuery.Result = query.Result } result := make(dtos.DataSourceList, 0) - for _, ds := range query.Result { + for _, ds := range dsFilterQuery.Result { dsItem := dtos.DataSourceListItemDTO{ OrgId: ds.OrgId, Id: ds.Id, @@ -45,13 +48,6 @@ func GetDataSources(c *m.ReqContext) Response { ReadOnly: ds.ReadOnly, } - if permission, ok := permissions[ds.Id]; ok { - c.Logger.Info("Found permission", "permission", permission) - if permission == m.DsPermissionNoAccess { - continue - } - } - if plugin, exists := plugins.DataSources[ds.Type]; exists { dsItem.TypeLogoUrl = plugin.Info.Logos.Small } else { diff --git a/pkg/models/datasource.go b/pkg/models/datasource.go index b1648edbb45..488fb2fe1fa 100644 --- a/pkg/models/datasource.go +++ b/pkg/models/datasource.go @@ -217,3 +217,9 @@ type GetDataSourcePermissionsForUserQuery struct { User *SignedInUser Result map[int64]DsPermissionType } + +type DatasourcesPermissionFilterQuery struct { + User *SignedInUser + Datasources []*DataSource + Result []*DataSource +} From 721dd532e47ddd086308c1478fcbd17673f6087f Mon Sep 17 00:00:00 2001 From: Leonard Gram Date: Wed, 10 Oct 2018 15:54:37 +0200 Subject: [PATCH 18/30] User without permission to a datasource won't see it. --- pkg/api/frontendsettings.go | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/pkg/api/frontendsettings.go b/pkg/api/frontendsettings.go index a58be38781e..43fa0c858fc 100644 --- a/pkg/api/frontendsettings.go +++ b/pkg/api/frontendsettings.go @@ -22,7 +22,20 @@ func getFrontendSettingsMap(c *m.ReqContext) (map[string]interface{}, error) { return nil, err } - orgDataSources = query.Result + dsFilterQuery := m.DatasourcesPermissionFilterQuery{ + User: c.SignedInUser, + Datasources: query.Result, + } + + if err := bus.Dispatch(&dsFilterQuery); err != nil { + if err != bus.ErrHandlerNotFound { + return nil, err + } + + orgDataSources = query.Result + } else { + orgDataSources = dsFilterQuery.Result + } } datasources := make(map[string]interface{}) From 037e9ad0bd3f07cb226766d9056a9680ec70f2c0 Mon Sep 17 00:00:00 2001 From: Peter Holmberg Date: Wed, 10 Oct 2018 17:04:48 +0200 Subject: [PATCH 19/30] tests --- .../AddDataSourcePermissions.test.tsx | 29 ++ .../DataSourcePermissions.test.tsx | 77 ++++ .../datasources/DataSourcePermissions.tsx | 12 +- .../DataSourcePermissionsList.test.tsx | 32 ++ .../datasources/DataSourcePermissionsList.tsx | 2 +- .../datasources/__mocks__/dataSourcesMocks.ts | 31 +- .../AddDataSourcePermissions.test.tsx.snap | 179 +++++++++ .../DataSourcePermissions.test.tsx.snap | 92 +++++ .../DataSourcePermissionsList.test.tsx.snap | 342 ++++++++++++++++++ public/app/types/datasources.ts | 11 +- 10 files changed, 793 insertions(+), 14 deletions(-) create mode 100644 public/app/features/datasources/AddDataSourcePermissions.test.tsx create mode 100644 public/app/features/datasources/DataSourcePermissions.test.tsx create mode 100644 public/app/features/datasources/DataSourcePermissionsList.test.tsx create mode 100644 public/app/features/datasources/__snapshots__/AddDataSourcePermissions.test.tsx.snap create mode 100644 public/app/features/datasources/__snapshots__/DataSourcePermissions.test.tsx.snap create mode 100644 public/app/features/datasources/__snapshots__/DataSourcePermissionsList.test.tsx.snap diff --git a/public/app/features/datasources/AddDataSourcePermissions.test.tsx b/public/app/features/datasources/AddDataSourcePermissions.test.tsx new file mode 100644 index 00000000000..facd71b51b7 --- /dev/null +++ b/public/app/features/datasources/AddDataSourcePermissions.test.tsx @@ -0,0 +1,29 @@ +import React from 'react'; +import { shallow } from 'enzyme'; +import { AddDataSourcePermissions, Props } from './AddDataSourcePermissions'; +import { AclTarget } from '../../types/acl'; + +const setup = () => { + const props: Props = { + onAddPermission: jest.fn(), + onCancel: jest.fn(), + }; + + return shallow(); +}; + +describe('Render', () => { + it('should render component', () => { + const wrapper = setup(); + + expect(wrapper).toMatchSnapshot(); + }); + + it('should render user picker', () => { + const wrapper = setup(); + + wrapper.instance().setState({ type: AclTarget.User }); + + expect(wrapper).toMatchSnapshot(); + }); +}); diff --git a/public/app/features/datasources/DataSourcePermissions.test.tsx b/public/app/features/datasources/DataSourcePermissions.test.tsx new file mode 100644 index 00000000000..8e21e5760d4 --- /dev/null +++ b/public/app/features/datasources/DataSourcePermissions.test.tsx @@ -0,0 +1,77 @@ +import React from 'react'; +import { shallow } from 'enzyme'; +import { DataSourcePermissions, Props } from './DataSourcePermissions'; +import { DataSourcePermission, DataSourcePermissionDTO } from 'app/types'; +import { AclTarget, dashboardPermissionLevels } from '../../types/acl'; + +const setup = (propOverrides?: object) => { + const props: Props = { + dataSourcePermission: {} as DataSourcePermissionDTO, + pageId: 1, + addDataSourcePermission: jest.fn(), + enableDataSourcePermissions: jest.fn(), + disableDataSourcePermissions: jest.fn(), + loadDataSourcePermissions: jest.fn(), + removeDataSourcePermission: jest.fn(), + }; + + Object.assign(props, propOverrides); + + const wrapper = shallow(); + const instance = wrapper.instance() as DataSourcePermissions; + + return { + wrapper, + instance, + }; +}; + +describe('Render', () => { + it('should render component', () => { + const { wrapper } = setup(); + + expect(wrapper).toMatchSnapshot(); + }); + + it('should render permissions enabled', () => { + const { wrapper } = setup({ + dataSourcePermission: { + enabled: true, + datasourceId: 1, + permissions: [] as DataSourcePermission[], + }, + }); + + expect(wrapper).toMatchSnapshot(); + }); +}); + +describe('Functions', () => { + describe('on add permissions', () => { + const { instance } = setup(); + + it('should add permissions for team', () => { + const mockState = { + permission: dashboardPermissionLevels[0].value, + teamId: 1, + type: AclTarget.Team, + }; + + instance.onAddPermission(mockState); + + expect(instance.props.addDataSourcePermission).toHaveBeenCalledWith(1, { teamId: 1, permission: 1 }); + }); + + it('should add permissions for user', () => { + const mockState = { + permission: dashboardPermissionLevels[0].value, + userId: 1, + type: AclTarget.User, + }; + + instance.onAddPermission(mockState); + + expect(instance.props.addDataSourcePermission).toHaveBeenCalledWith(1, { userId: 1, permission: 1 }); + }); + }); +}); diff --git a/public/app/features/datasources/DataSourcePermissions.tsx b/public/app/features/datasources/DataSourcePermissions.tsx index 506d6fb89b7..1ea20c97933 100644 --- a/public/app/features/datasources/DataSourcePermissions.tsx +++ b/public/app/features/datasources/DataSourcePermissions.tsx @@ -11,11 +11,11 @@ import { loadDataSourcePermissions, removeDataSourcePermission, } from './state/actions'; -import { DataSourcePermission } from 'app/types'; +import { DataSourcePermissionDTO } from 'app/types'; import { getRouteParamsId } from '../../core/selectors/location'; export interface Props { - dataSourcePermission: { enabled: boolean; datasouceId: number; permissions: DataSourcePermission[] }; + dataSourcePermission: DataSourcePermissionDTO; pageId: number; addDataSourcePermission: typeof addDataSourcePermission; enableDataSourcePermissions: typeof enableDataSourcePermissions; @@ -64,17 +64,13 @@ export class DataSourcePermissions extends PureComponent { const { pageId, addDataSourcePermission } = this.props; const data = { permission: state.permission, - userId: 0, - teamId: 0, }; if (state.type === AclTarget.Team) { - data.teamId = state.teamId; + addDataSourcePermission(pageId, Object.assign(data, { teamId: state.teamId })); } else if (state.type === AclTarget.User) { - data.userId = state.userId; + addDataSourcePermission(pageId, Object.assign(data, { userId: state.userId })); } - - addDataSourcePermission(pageId, data); }; onRemovePermission = item => { diff --git a/public/app/features/datasources/DataSourcePermissionsList.test.tsx b/public/app/features/datasources/DataSourcePermissionsList.test.tsx new file mode 100644 index 00000000000..f89ada9ce39 --- /dev/null +++ b/public/app/features/datasources/DataSourcePermissionsList.test.tsx @@ -0,0 +1,32 @@ +import React from 'react'; +import { shallow } from 'enzyme'; +import { DataSourcePermissionsList, Props } from './DataSourcePermissionsList'; +import { DataSourcePermission } from '../../types'; +import { getMockDataSourcePermissionsTeam, getMockDataSourcePermissionsUser } from './__mocks__/dataSourcesMocks'; + +const setup = (propOverrides?: object) => { + const props: Props = { + items: [] as DataSourcePermission[], + onRemoveItem: jest.fn(), + }; + + Object.assign(props, propOverrides); + + return shallow(); +}; + +describe('Render', () => { + it('should render component', () => { + const wrapper = setup(); + + expect(wrapper).toMatchSnapshot(); + }); + + it('should render items', () => { + const wrapper = setup({ + items: [getMockDataSourcePermissionsUser(), getMockDataSourcePermissionsTeam()], + }); + + expect(wrapper).toMatchSnapshot(); + }); +}); diff --git a/public/app/features/datasources/DataSourcePermissionsList.tsx b/public/app/features/datasources/DataSourcePermissionsList.tsx index cd933dab13e..5b7aae6047e 100644 --- a/public/app/features/datasources/DataSourcePermissionsList.tsx +++ b/public/app/features/datasources/DataSourcePermissionsList.tsx @@ -3,7 +3,7 @@ import { DataSourcePermission } from '../../types'; import { dataSourceAclLevels, DataSourcePermissionLevel } from '../../types/acl'; import DescriptionPicker from '../../core/components/Picker/DescriptionPicker'; -interface Props { +export interface Props { items: DataSourcePermission[]; onRemoveItem: (item) => void; } diff --git a/public/app/features/datasources/__mocks__/dataSourcesMocks.ts b/public/app/features/datasources/__mocks__/dataSourcesMocks.ts index 97819a18c82..b7e237cdb50 100644 --- a/public/app/features/datasources/__mocks__/dataSourcesMocks.ts +++ b/public/app/features/datasources/__mocks__/dataSourcesMocks.ts @@ -1,4 +1,4 @@ -import { DataSource } from 'app/types'; +import { DataSource, DataSourcePermission } from 'app/types'; export const getMockDataSources = (amount: number): DataSource[] => { const dataSources = []; @@ -43,3 +43,32 @@ export const getMockDataSource = (): DataSource => { user: '', }; }; + +export const getMockDataSourcePermissionsUser = (): DataSourcePermission => { + return { + created: '2018-10-10T16:50:45+02:00', + datasourceId: 1, + id: 2, + permission: 1, + permissionName: 'Query', + updated: '2018-10-10T16:50:45+02:00', + userAvatarUrl: '/avatar/926aa85c6bcefa0b4deca3223f337ae1', + userEmail: 'test@test.com', + userId: 3, + userLogin: 'testUser', + }; +}; + +export const getMockDataSourcePermissionsTeam = (): DataSourcePermission => { + return { + created: '2018-10-10T16:57:09+02:00', + datasourceId: 1, + id: 6, + permission: 1, + permissionName: 'Query', + team: 'A-team', + teamAvatarUrl: '/avatar/93c0801b955cbd443a8cfa91a401d7bc', + teamId: 1, + updated: '2018-10-10T16:57:09+02:00', + }; +}; diff --git a/public/app/features/datasources/__snapshots__/AddDataSourcePermissions.test.tsx.snap b/public/app/features/datasources/__snapshots__/AddDataSourcePermissions.test.tsx.snap new file mode 100644 index 00000000000..6c8b6f2205c --- /dev/null +++ b/public/app/features/datasources/__snapshots__/AddDataSourcePermissions.test.tsx.snap @@ -0,0 +1,179 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Render should render component 1`] = ` +
+ +
+
+ Add Permission For +
+
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+
+`; + +exports[`Render should render user picker 1`] = ` +
+ +
+
+ Add Permission For +
+
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+
+`; diff --git a/public/app/features/datasources/__snapshots__/DataSourcePermissions.test.tsx.snap b/public/app/features/datasources/__snapshots__/DataSourcePermissions.test.tsx.snap new file mode 100644 index 00000000000..2ea7bff53d9 --- /dev/null +++ b/public/app/features/datasources/__snapshots__/DataSourcePermissions.test.tsx.snap @@ -0,0 +1,92 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Render should render component 1`] = ` +
+
+

+ Permissions +

+
+
+
+
+ Permissions not enabled for this data source. +
+ +
+ + ProTip: + + Only admins will be able to query the data source after you enable permissions. +
+
+
+`; + +exports[`Render should render permissions enabled 1`] = ` +
+
+

+ Permissions +

+
+ + +
+
+ + + + +
+
+`; diff --git a/public/app/features/datasources/__snapshots__/DataSourcePermissionsList.test.tsx.snap b/public/app/features/datasources/__snapshots__/DataSourcePermissionsList.test.tsx.snap new file mode 100644 index 00000000000..1425a9c0921 --- /dev/null +++ b/public/app/features/datasources/__snapshots__/DataSourcePermissionsList.test.tsx.snap @@ -0,0 +1,342 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Render should render component 1`] = ` + + + + + + + + + + +
+ + + Admin + + (Role) + + + + Can + +
+ +
+
+ +
+`; + +exports[`Render should render items 1`] = ` + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + Admin + + (Role) + + + + Can + +
+ +
+
+ +
+ + + + testUser + + + + (User) + + + + Can + +
+ +
+
+ +
+ + + + A-team + + + + (Team) + + + + Can + +
+ +
+
+ +
+`; diff --git a/public/app/types/datasources.ts b/public/app/types/datasources.ts index 7e62978e642..37c5122c06e 100644 --- a/public/app/types/datasources.ts +++ b/public/app/types/datasources.ts @@ -4,14 +4,17 @@ import { Plugin } from './plugins'; export interface DataSourcePermission { id: number; datasourceId: number; - userId: number; - userLogin: string; - userEmail: string; - userAvatarUrl: string; permission: number; permissionName: string; created: string; updated: string; + userId?: number; + userLogin?: string; + userEmail?: string; + userAvatarUrl?: string; + teamId?: number; + teamAvatarUrl?: string; + team?: string; } export interface DataSourcePermissionDTO { From ec9ed5c830af52541a7585d0aafb03687ceedd27 Mon Sep 17 00:00:00 2001 From: Leonard Gram Date: Wed, 10 Oct 2018 16:03:06 +0200 Subject: [PATCH 20/30] Removes unused code. --- pkg/api/datasources.go | 27 +++++---------------------- 1 file changed, 5 insertions(+), 22 deletions(-) diff --git a/pkg/api/datasources.go b/pkg/api/datasources.go index e23f691ca2b..46c5a41cdf8 100644 --- a/pkg/api/datasources.go +++ b/pkg/api/datasources.go @@ -22,16 +22,19 @@ func GetDataSources(c *m.ReqContext) Response { Datasources: query.Result, } + datasources := []*m.DataSource{} if err := bus.Dispatch(&dsFilterQuery); err != nil { if err != bus.ErrHandlerNotFound { return Error(500, "Could not get datasources", err) } - dsFilterQuery.Result = query.Result + datasources = query.Result + } else { + datasources = dsFilterQuery.Result } result := make(dtos.DataSourceList, 0) - for _, ds := range dsFilterQuery.Result { + for _, ds := range datasources { dsItem := dtos.DataSourceListItemDTO{ OrgId: ds.OrgId, Id: ds.Id, @@ -62,26 +65,6 @@ func GetDataSources(c *m.ReqContext) Response { return JSON(200, &result) } -func hasRequiredDatasourcePermission(dsId int64, permission m.DsPermissionType, user *m.SignedInUser) Response { - query := m.HasRequiredDataSourcePermissionQuery{ - Id: dsId, - User: user, - RequiredPermission: permission, - } - - if err := bus.Dispatch(&query); err != nil { - if err == bus.ErrHandlerNotFound { - return nil - } - if err == m.ErrDataSourceAccessDenied { - return Error(403, err.Error(), nil) - } - return Error(500, "Failed to check data source permissions", err) - } - - return nil -} - func GetDataSourceById(c *m.ReqContext) Response { query := m.GetDataSourceByIdQuery{ Id: c.ParamsInt64(":id"), From d360d8cebcc96501174cfd500b82c2bfccd9a400 Mon Sep 17 00:00:00 2001 From: Leonard Gram Date: Thu, 11 Oct 2018 11:29:14 +0200 Subject: [PATCH 21/30] Requests for ds via backend blocked for users without permissions. --- pkg/api/dataproxy.go | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/pkg/api/dataproxy.go b/pkg/api/dataproxy.go index eddfb884f8f..6aedc051ab7 100644 --- a/pkg/api/dataproxy.go +++ b/pkg/api/dataproxy.go @@ -2,6 +2,7 @@ package api import ( "fmt" + "github.com/pkg/errors" "time" "github.com/grafana/grafana/pkg/api/pluginproxy" @@ -14,6 +15,20 @@ import ( const HeaderNameNoBackendCache = "X-Grafana-NoCache" func (hs *HTTPServer) getDatasourceFromCache(id int64, c *m.ReqContext) (*m.DataSource, error) { + userPermissionsQuery := m.GetDataSourcePermissionsForUserQuery{ + User: c.SignedInUser, + } + if err := bus.Dispatch(&userPermissionsQuery); err != nil { + if err != bus.ErrHandlerNotFound { + return nil, err + } + } else { + permissionType, exists := userPermissionsQuery.Result[id] + if exists && permissionType != m.DsPermissionQuery { + return nil, errors.New("User not allowed to access datasource") + } + } + nocache := c.Req.Header.Get(HeaderNameNoBackendCache) == "true" cacheKey := fmt.Sprintf("ds-%d", id) @@ -38,7 +53,10 @@ func (hs *HTTPServer) getDatasourceFromCache(id int64, c *m.ReqContext) (*m.Data func (hs *HTTPServer) ProxyDataSourceRequest(c *m.ReqContext) { c.TimeRequest(metrics.M_DataSource_ProxyReq_Timer) - ds, err := hs.getDatasourceFromCache(c.ParamsInt64(":id"), c) + dsId := c.ParamsInt64(":id") + ds, err := hs.getDatasourceFromCache(dsId, c) + hs.log.Debug("We are in the ds proxy", "dsId", dsId) + if err != nil { c.JsonApiErr(500, "Unable to load datasource meta data", err) return From 8ab2d8b0b9abb135fa002748cbc6a1042e160cb4 Mon Sep 17 00:00:00 2001 From: Peter Holmberg Date: Thu, 11 Oct 2018 15:32:13 +0200 Subject: [PATCH 22/30] fix mutability bug, removed unused constructor --- public/app/core/components/Picker/DescriptionPicker.tsx | 5 ----- .../app/features/datasources/DataSourcePermissionsList.tsx | 6 +++--- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/public/app/core/components/Picker/DescriptionPicker.tsx b/public/app/core/components/Picker/DescriptionPicker.tsx index 2e53d096e08..d3528062221 100644 --- a/public/app/core/components/Picker/DescriptionPicker.tsx +++ b/public/app/core/components/Picker/DescriptionPicker.tsx @@ -17,11 +17,6 @@ export interface OptionWithDescription { } class DescriptionPicker extends Component { - constructor(props) { - super(props); - this.state = {}; - } - render() { const { optionsWithDesc, onSelected, value, disabled, className } = this.props; diff --git a/public/app/features/datasources/DataSourcePermissionsList.tsx b/public/app/features/datasources/DataSourcePermissionsList.tsx index 5b7aae6047e..635f2cd87e8 100644 --- a/public/app/features/datasources/DataSourcePermissionsList.tsx +++ b/public/app/features/datasources/DataSourcePermissionsList.tsx @@ -1,6 +1,6 @@ import React, { PureComponent } from 'react'; -import { DataSourcePermission } from '../../types'; -import { dataSourceAclLevels, DataSourcePermissionLevel } from '../../types/acl'; +import { DataSourcePermission } from 'app/types'; +import { dataSourceAclLevels, DataSourcePermissionLevel } from 'app/types/acl'; import DescriptionPicker from '../../core/components/Picker/DescriptionPicker'; export interface Props { @@ -41,7 +41,7 @@ export class DataSourcePermissionsList extends PureComponent { render() { const { items } = this.props; - const permissionLevels = dataSourceAclLevels; + const permissionLevels = [...dataSourceAclLevels]; permissionLevels.push({ value: DataSourcePermissionLevel.Admin, label: 'Admin', description: '' }); return ( From 6a35eda8626861eae527560523de1d9f2c9bb07b Mon Sep 17 00:00:00 2001 From: Peter Holmberg Date: Thu, 11 Oct 2018 15:55:33 +0200 Subject: [PATCH 23/30] removed unsused function --- public/app/features/datasources/NewDataSourcePage.tsx | 3 --- 1 file changed, 3 deletions(-) diff --git a/public/app/features/datasources/NewDataSourcePage.tsx b/public/app/features/datasources/NewDataSourcePage.tsx index 527ecf6db83..c6eaa893d97 100644 --- a/public/app/features/datasources/NewDataSourcePage.tsx +++ b/public/app/features/datasources/NewDataSourcePage.tsx @@ -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, }; From c9721bf8cc5d85b69e241965a04578520c0a6d76 Mon Sep 17 00:00:00 2001 From: Peter Holmberg Date: Fri, 12 Oct 2018 08:54:37 +0200 Subject: [PATCH 24/30] fix route issue --- public/app/features/datasources/state/navModel.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/app/features/datasources/state/navModel.ts b/public/app/features/datasources/state/navModel.ts index 47eadb82376..e0b6b39588e 100644 --- a/public/app/features/datasources/state/navModel.ts +++ b/public/app/features/datasources/state/navModel.ts @@ -15,7 +15,7 @@ export function buildNavModel(dataSource: DataSource, pluginMeta: PluginMeta): N icon: 'fa fa-fw fa-sliders', id: `datasource-settings-${dataSource.id}`, text: 'Settings', - url: `datasources/edit/${dataSource.id}/settings`, + url: `datasources/edit/${dataSource.id}`, }, ], }; From 97a3a4f33adbd3f71b593a1c0bfa57308672dbb6 Mon Sep 17 00:00:00 2001 From: Leonard Gram Date: Fri, 12 Oct 2018 15:13:23 +0200 Subject: [PATCH 25/30] removes debug log. --- pkg/api/dataproxy.go | 1 - 1 file changed, 1 deletion(-) diff --git a/pkg/api/dataproxy.go b/pkg/api/dataproxy.go index 6aedc051ab7..3bb2f236129 100644 --- a/pkg/api/dataproxy.go +++ b/pkg/api/dataproxy.go @@ -55,7 +55,6 @@ func (hs *HTTPServer) ProxyDataSourceRequest(c *m.ReqContext) { dsId := c.ParamsInt64(":id") ds, err := hs.getDatasourceFromCache(dsId, c) - hs.log.Debug("We are in the ds proxy", "dsId", dsId) if err != nil { c.JsonApiErr(500, "Unable to load datasource meta data", err) From 1329c70965981ef598506679925f0f5dfff20b47 Mon Sep 17 00:00:00 2001 From: Leonard Gram Date: Tue, 16 Oct 2018 14:22:50 +0200 Subject: [PATCH 26/30] codestyle --- pkg/api/datasources.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/api/datasources.go b/pkg/api/datasources.go index 46c5a41cdf8..e9eb78fbe13 100644 --- a/pkg/api/datasources.go +++ b/pkg/api/datasources.go @@ -22,7 +22,7 @@ func GetDataSources(c *m.ReqContext) Response { Datasources: query.Result, } - datasources := []*m.DataSource{} + var datasources []*m.DataSource if err := bus.Dispatch(&dsFilterQuery); err != nil { if err != bus.ErrHandlerNotFound { return Error(500, "Could not get datasources", err) From 66311a065cb0ad15217615ceadd6a0d01b387b83 Mon Sep 17 00:00:00 2001 From: Peter Holmberg Date: Tue, 16 Oct 2018 15:36:33 +0200 Subject: [PATCH 27/30] removing datasource permissions states from grafana --- .../datasources/__mocks__/dataSourcesMocks.ts | 31 +---------- .../app/features/datasources/state/actions.ts | 54 +------------------ .../features/datasources/state/reducers.ts | 6 +-- public/app/store/configureStore.ts | 10 +++- public/app/types/datasources.ts | 23 -------- public/app/types/index.ts | 4 +- 6 files changed, 13 insertions(+), 115 deletions(-) diff --git a/public/app/features/datasources/__mocks__/dataSourcesMocks.ts b/public/app/features/datasources/__mocks__/dataSourcesMocks.ts index b7e237cdb50..97819a18c82 100644 --- a/public/app/features/datasources/__mocks__/dataSourcesMocks.ts +++ b/public/app/features/datasources/__mocks__/dataSourcesMocks.ts @@ -1,4 +1,4 @@ -import { DataSource, DataSourcePermission } from 'app/types'; +import { DataSource } from 'app/types'; export const getMockDataSources = (amount: number): DataSource[] => { const dataSources = []; @@ -43,32 +43,3 @@ export const getMockDataSource = (): DataSource => { user: '', }; }; - -export const getMockDataSourcePermissionsUser = (): DataSourcePermission => { - return { - created: '2018-10-10T16:50:45+02:00', - datasourceId: 1, - id: 2, - permission: 1, - permissionName: 'Query', - updated: '2018-10-10T16:50:45+02:00', - userAvatarUrl: '/avatar/926aa85c6bcefa0b4deca3223f337ae1', - userEmail: 'test@test.com', - userId: 3, - userLogin: 'testUser', - }; -}; - -export const getMockDataSourcePermissionsTeam = (): DataSourcePermission => { - return { - created: '2018-10-10T16:57:09+02:00', - datasourceId: 1, - id: 6, - permission: 1, - permissionName: 'Query', - team: 'A-team', - teamAvatarUrl: '/avatar/93c0801b955cbd443a8cfa91a401d7bc', - teamId: 1, - updated: '2018-10-10T16:57:09+02:00', - }; -}; diff --git a/public/app/features/datasources/state/actions.ts b/public/app/features/datasources/state/actions.ts index 664ed840928..bb8fce8424a 100644 --- a/public/app/features/datasources/state/actions.ts +++ b/public/app/features/datasources/state/actions.ts @@ -1,5 +1,5 @@ import { ThunkAction } from 'redux-thunk'; -import { DataSource, DataSourcePermissionDTO, Plugin, StoreState } from 'app/types'; +import { DataSource, Plugin, StoreState } from 'app/types'; import { getBackendSrv } from '../../../core/services/backend_srv'; import { LayoutMode } from '../../../core/components/LayoutSelector/LayoutSelector'; import { updateLocation, updateNavIndex, UpdateNavIndexAction } from '../../../core/actions'; @@ -11,7 +11,6 @@ export enum ActionTypes { LoadDataSourceTypes = 'LOAD_DATA_SOURCE_TYPES', LoadDataSource = 'LOAD_DATA_SOURCE', LoadDataSourceMeta = 'LOAD_DATA_SOURCE_META', - LoadDataSourcePermissions = 'LOAD_DATA_SOURCE_PERMISSIONS', SetDataSourcesSearchQuery = 'SET_DATA_SOURCES_SEARCH_QUERY', SetDataSourcesLayoutMode = 'SET_DATA_SOURCES_LAYOUT_MODE', SetDataSourceTypeSearchQuery = 'SET_DATA_SOURCE_TYPE_SEARCH_QUERY', @@ -52,11 +51,6 @@ export interface LoadDataSourceMetaAction { payload: Plugin; } -export interface LoadDataSourcePermissionsAction { - type: ActionTypes.LoadDataSourcePermissions; - payload: DataSourcePermissionDTO; -} - const dataSourcesLoaded = (dataSources: DataSource[]): LoadDataSourcesAction => ({ type: ActionTypes.LoadDataSources, payload: dataSources, @@ -77,13 +71,6 @@ const dataSourceTypesLoaded = (dataSourceTypes: Plugin[]): LoadDataSourceTypesAc payload: dataSourceTypes, }); -const dataSourcePermissionsLoaded = ( - dataSourcePermission: DataSourcePermissionDTO -): LoadDataSourcePermissionsAction => ({ - type: ActionTypes.LoadDataSourcePermissions, - payload: dataSourcePermission, -}); - export const setDataSourcesSearchQuery = (searchQuery: string): SetDataSourcesSearchQueryAction => ({ type: ActionTypes.SetDataSourcesSearchQuery, payload: searchQuery, @@ -108,8 +95,7 @@ export type Action = | SetDataSourceTypeSearchQueryAction | LoadDataSourceAction | UpdateNavIndexAction - | LoadDataSourceMetaAction - | LoadDataSourcePermissionsAction; + | LoadDataSourceMetaAction; type ThunkResult = ThunkAction; @@ -159,42 +145,6 @@ export function loadDataSourceTypes(): ThunkResult { }; } -export function loadDataSourcePermissions(id: number): ThunkResult { - return async dispatch => { - const response = await getBackendSrv().get(`/api/datasources/${id}/permissions`); - dispatch(dataSourcePermissionsLoaded(response)); - }; -} - -export function enableDataSourcePermissions(id: number): ThunkResult { - return async dispatch => { - await getBackendSrv().post(`/api/datasources/${id}/enable-permissions`, {}); - dispatch(loadDataSourcePermissions(id)); - }; -} - -export function disableDataSourcePermissions(id: number): ThunkResult { - return async dispatch => { - await getBackendSrv().post(`/api/datasources/${id}/disable-permissions`, {}); - dispatch(loadDataSourcePermissions(id)); - }; -} - -export function addDataSourcePermission(id: number, data: object): ThunkResult { - return async dispatch => { - await getBackendSrv().post(`/api/datasources/${id}/permissions`, data); - - dispatch(loadDataSourcePermissions(id)); - }; -} - -export function removeDataSourcePermission(id: number, permissionId: number): ThunkResult { - return async dispatch => { - await getBackendSrv().delete(`/api/datasources/${id}/permissions/${permissionId}`); - dispatch(loadDataSourcePermissions(id)); - }; -} - export function nameExits(dataSources, name) { return ( dataSources.filter(dataSource => { diff --git a/public/app/features/datasources/state/reducers.ts b/public/app/features/datasources/state/reducers.ts index 98c1e485364..7e235f5ea0a 100644 --- a/public/app/features/datasources/state/reducers.ts +++ b/public/app/features/datasources/state/reducers.ts @@ -1,4 +1,4 @@ -import { DataSource, DataSourcePermissionDTO, DataSourcesState, Plugin } from 'app/types'; +import { DataSource, DataSourcesState, Plugin } from 'app/types'; import { Action, ActionTypes } from './actions'; import { LayoutModes } from '../../../core/components/LayoutSelector/LayoutSelector'; @@ -11,7 +11,6 @@ const initialState: DataSourcesState = { dataSourceTypes: [] as Plugin[], dataSourceTypeSearchQuery: '', dataSourceMeta: {} as Plugin, - dataSourcePermission: {} as DataSourcePermissionDTO, hasFetched: false, }; @@ -37,9 +36,6 @@ export const dataSourcesReducer = (state = initialState, action: Action): DataSo case ActionTypes.LoadDataSourceMeta: return { ...state, dataSourceMeta: action.payload }; - - case ActionTypes.LoadDataSourcePermissions: - return { ...state, dataSourcePermission: action.payload }; } return state; diff --git a/public/app/store/configureStore.ts b/public/app/store/configureStore.ts index 0a93a4baa0f..ccd027a0b6d 100644 --- a/public/app/store/configureStore.ts +++ b/public/app/store/configureStore.ts @@ -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()))); diff --git a/public/app/types/datasources.ts b/public/app/types/datasources.ts index 47261b976f4..8e1991dcd9f 100644 --- a/public/app/types/datasources.ts +++ b/public/app/types/datasources.ts @@ -1,28 +1,6 @@ import { LayoutMode } from '../core/components/LayoutSelector/LayoutSelector'; import { Plugin } from './plugins'; -export interface DataSourcePermission { - id: number; - datasourceId: number; - permission: number; - permissionName: string; - created: string; - updated: string; - userId?: number; - userLogin?: string; - userEmail?: string; - userAvatarUrl?: string; - teamId?: number; - teamAvatarUrl?: string; - team?: string; -} - -export interface DataSourcePermissionDTO { - datasourceId: number; - enabled: boolean; - permissions: DataSourcePermission[]; -} - export interface DataSource { id: number; orgId: number; @@ -49,6 +27,5 @@ export interface DataSourcesState { dataSourceTypes: Plugin[]; dataSource: DataSource; dataSourceMeta: Plugin; - dataSourcePermission: DataSourcePermissionDTO; hasFetched: boolean; } diff --git a/public/app/types/index.ts b/public/app/types/index.ts index eee34c291af..26f15d582ac 100644 --- a/public/app/types/index.ts +++ b/public/app/types/index.ts @@ -7,7 +7,7 @@ import { DashboardState } from './dashboard'; import { DashboardAcl, OrgRole, PermissionLevel } from './acl'; import { ApiKey, ApiKeysState, NewApiKey } from './apiKeys'; import { Invitee, OrgUser, User, UsersState } from './user'; -import { DataSource, DataSourcePermissionDTO, DataSourcePermission, DataSourcesState } from './datasources'; +import { DataSource, DataSourcesState } from './datasources'; import { PluginMeta, Plugin, PluginsState } from './plugins'; export { @@ -41,8 +41,6 @@ export { Plugin, PluginsState, DataSourcesState, - DataSourcePermissionDTO, - DataSourcePermission, Invitee, OrgUser, User, From dd2fb7961f60d3cae99f8e2d2bba55017b11cfb9 Mon Sep 17 00:00:00 2001 From: Peter Holmberg Date: Wed, 17 Oct 2018 10:37:56 +0200 Subject: [PATCH 28/30] removed snaps --- .../AddDataSourcePermissions.test.tsx.snap | 175 ---------- .../DataSourcePermissionsList.test.tsx.snap | 327 ------------------ 2 files changed, 502 deletions(-) delete mode 100644 public/app/features/datasources/__snapshots__/AddDataSourcePermissions.test.tsx.snap delete mode 100644 public/app/features/datasources/__snapshots__/DataSourcePermissionsList.test.tsx.snap diff --git a/public/app/features/datasources/__snapshots__/AddDataSourcePermissions.test.tsx.snap b/public/app/features/datasources/__snapshots__/AddDataSourcePermissions.test.tsx.snap deleted file mode 100644 index dab46c7609e..00000000000 --- a/public/app/features/datasources/__snapshots__/AddDataSourcePermissions.test.tsx.snap +++ /dev/null @@ -1,175 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Render should render component 1`] = ` -
- -
-
- Add Permission For -
-
-
- -
-
- -
-
- -
-
- -
-
-
-
-`; - -exports[`Render should render user picker 1`] = ` -
- -
-
- Add Permission For -
-
-
- -
-
- -
-
- -
-
- -
-
-
-
-`; diff --git a/public/app/features/datasources/__snapshots__/DataSourcePermissionsList.test.tsx.snap b/public/app/features/datasources/__snapshots__/DataSourcePermissionsList.test.tsx.snap deleted file mode 100644 index dba51823549..00000000000 --- a/public/app/features/datasources/__snapshots__/DataSourcePermissionsList.test.tsx.snap +++ /dev/null @@ -1,327 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Render should render component 1`] = ` - - - - - - - - - - -
- - - Admin - - (Role) - - - - Can - -
- -
-
- -
-`; - -exports[`Render should render items 1`] = ` - - - - - - - - - - - - - - - - - - - - - - - - -
- - - Admin - - (Role) - - - - Can - -
- -
-
- -
- - - - testUser - - - - (User) - - - - Can - -
- -
-
- -
- - - - A-team - - - - (Team) - - - - Can - -
- -
-
- -
-`; From b7b0ce0107b76627adb999c74762373c8aa77ff5 Mon Sep 17 00:00:00 2001 From: Peter Holmberg Date: Wed, 17 Oct 2018 11:07:17 +0200 Subject: [PATCH 29/30] remove addpermissions component --- .../datasources/AddDataSourcePermissions.tsx | 122 ------------------ 1 file changed, 122 deletions(-) delete mode 100644 public/app/features/datasources/AddDataSourcePermissions.tsx diff --git a/public/app/features/datasources/AddDataSourcePermissions.tsx b/public/app/features/datasources/AddDataSourcePermissions.tsx deleted file mode 100644 index fb5832e88b3..00000000000 --- a/public/app/features/datasources/AddDataSourcePermissions.tsx +++ /dev/null @@ -1,122 +0,0 @@ -import React, { PureComponent } from 'react'; -import { UserPicker } from 'app/core/components/Picker/UserPicker'; -import { Team, TeamPicker } from 'app/core/components/Picker/TeamPicker'; -import DescriptionPicker, { OptionWithDescription } from 'app/core/components/Picker/DescriptionPicker'; -import { dataSourceAclLevels, AclTarget, DataSourcePermissionLevel } from 'app/types/acl'; -import { User } from 'app/types'; - -export interface Props { - onAddPermission: (state) => void; - onCancel: () => void; -} - -interface State { - userId: number; - teamId: number; - type: AclTarget; - permission: DataSourcePermissionLevel; -} - -export class AddDataSourcePermissions extends PureComponent { - cleanState = () => ({ - userId: 0, - teamId: 0, - type: AclTarget.Team, - permission: DataSourcePermissionLevel.Query, - }); - - state = this.cleanState(); - - isValid() { - switch (this.state.type) { - case AclTarget.Team: - return this.state.teamId > 0; - case AclTarget.User: - return this.state.userId > 0; - } - return true; - } - - onTeamSelected = (team: Team) => { - this.setState({ teamId: team ? team.id : 0 }); - }; - - onUserSelected = (user: User) => { - this.setState({ userId: user ? user.id : 0 }); - }; - - onPermissionChanged = (permission: OptionWithDescription) => { - this.setState({ permission: permission.value }); - }; - - onTypeChanged = event => { - const type = event.target.value as AclTarget; - - this.setState({ type: type, userId: 0, teamId: 0 }); - }; - - onSubmit = async event => { - event.preventDefault(); - - await this.props.onAddPermission(this.state); - this.setState(this.cleanState()); - }; - - render() { - const { onCancel } = this.props; - const { type } = this.state; - - const pickerClassName = 'width-20'; - const aclTargets = [{ value: AclTarget.Team, text: 'Team' }, { value: AclTarget.User, text: 'User' }]; - - return ( -
- -
-
Add Permission For
-
-
- -
- {type === AclTarget.User && ( -
- -
- )} - - {type === AclTarget.Team && ( -
- -
- )} -
- -
-
- -
-
-
-
- ); - } -} - -export default AddDataSourcePermissions; From 3245227016e8f60a9171f37791a4d6e4edaa8c82 Mon Sep 17 00:00:00 2001 From: Leonard Gram Date: Wed, 17 Oct 2018 15:58:52 +0200 Subject: [PATCH 30/30] permissions: cleanup. --- pkg/api/datasources.go | 18 +----------------- pkg/models/datasource.go | 10 ++-------- 2 files changed, 3 insertions(+), 25 deletions(-) diff --git a/pkg/api/datasources.go b/pkg/api/datasources.go index e9eb78fbe13..e7614614076 100644 --- a/pkg/api/datasources.go +++ b/pkg/api/datasources.go @@ -17,24 +17,8 @@ func GetDataSources(c *m.ReqContext) Response { return Error(500, "Failed to query datasources", err) } - dsFilterQuery := m.DatasourcesPermissionFilterQuery{ - User: c.SignedInUser, - Datasources: query.Result, - } - - var datasources []*m.DataSource - if err := bus.Dispatch(&dsFilterQuery); err != nil { - if err != bus.ErrHandlerNotFound { - return Error(500, "Could not get datasources", err) - } - - datasources = query.Result - } else { - datasources = dsFilterQuery.Result - } - result := make(dtos.DataSourceList, 0) - for _, ds := range datasources { + for _, ds := range query.Result { dsItem := dtos.DataSourceListItemDTO{ OrgId: ds.OrgId, Id: ds.Id, diff --git a/pkg/models/datasource.go b/pkg/models/datasource.go index 488fb2fe1fa..b71d17ec0d1 100644 --- a/pkg/models/datasource.go +++ b/pkg/models/datasource.go @@ -195,8 +195,8 @@ type GetDataSourceByNameQuery struct { type DsPermissionType int const ( - DsPermissionQuery DsPermissionType = 1 << iota - DsPermissionNoAccess + DsPermissionNoAccess DsPermissionType = iota + DsPermissionQuery ) func (p DsPermissionType) String() string { @@ -207,12 +207,6 @@ func (p DsPermissionType) String() string { return names[int(p)] } -type HasRequiredDataSourcePermissionQuery struct { - Id int64 - User *SignedInUser - RequiredPermission DsPermissionType -} - type GetDataSourcePermissionsForUserQuery struct { User *SignedInUser Result map[int64]DsPermissionType