diff --git a/ui/src/admin/containers/AdminInfluxDBPage.js b/ui/src/admin/containers/AdminInfluxDBPage.tsx similarity index 63% rename from ui/src/admin/containers/AdminInfluxDBPage.js rename to ui/src/admin/containers/AdminInfluxDBPage.tsx index 1d296dbf30..fa73c6663d 100644 --- a/ui/src/admin/containers/AdminInfluxDBPage.js +++ b/ui/src/admin/containers/AdminInfluxDBPage.tsx @@ -1,5 +1,4 @@ -import React, {Component} from 'react' -import PropTypes from 'prop-types' +import React, {PureComponent} from 'react' import {connect} from 'react-redux' import {bindActionCreators} from 'redux' import { @@ -30,16 +29,25 @@ import RolesTable from 'src/admin/components/RolesTable' import QueriesPage from 'src/admin/containers/QueriesPage' import DatabaseManagerPage from 'src/admin/containers/DatabaseManagerPage' import PageHeader from 'src/reusable_ui/components/page_layout/PageHeader' -import FancyScrollbar from 'shared/components/FancyScrollbar' -import SubSections from 'shared/components/SubSections' +import FancyScrollbar from 'src/shared/components/FancyScrollbar' +import SubSections from 'src/shared/components/SubSections' import {ErrorHandling} from 'src/shared/decorators/errors' -import {notify as notifyAction} from 'shared/actions/notifications' +import {notify as notifyAction} from 'src/shared/actions/notifications' +import { + Source, + User as InfluxDBUser, + Role as InfluxDBRole, + Permission, + RemoteDataState, +} from 'src/types' +import {SourceAuthenticationMethod} from 'src/types/sources' +import {NotificationAction} from 'src/types/notifications' import { notifyRoleNameInvalid, notifyDBUserNamePasswordInvalid, -} from 'shared/copy/notifications' +} from 'src/shared/copy/notifications' const isValidUser = user => { const minLen = 3 @@ -51,23 +59,111 @@ const isValidRole = role => { return role.name.length >= minLen } +interface User extends InfluxDBUser { + isEditing: boolean +} + +interface Role extends InfluxDBRole { + isEditing: boolean +} + +interface Props { + source: Source + users: User[] + roles: Role[] + permissions: Permission[] + loadUsers: (url: string) => void + loadRoles: (url: string) => void + loadPermissions: (url: string) => void + addUser: () => void + addRole: () => void + removeUser: (user: User) => void + removeRole: (role: Role) => void + editUser: (user: User, updates: Partial) => void + editRole: (role: Role, updates: Partial) => void + createUser: (url: string, user: User) => void + createRole: (url: string, role: Role) => void + deleteRole: (role: Role) => void + deleteUser: (user: User) => void + filterRoles: () => void + filterUsers: () => void + updateRoleUsers: (role: Role, users: User[]) => void + updateRolePermissions: (role: Role, permissions: Permission[]) => void + updateUserPermissions: (user: User, permissions: Permission[]) => void + updateUserRoles: (user: User, roles: Role[]) => void + updateUserPassword: (user: User, password: string) => void + notify: NotificationAction + params: { + tab: string + } +} + +interface State { + loading: RemoteDataState +} + @ErrorHandling -export class DisconnectedAdminInfluxDBPage extends Component { +export class AdminInfluxDBPage extends PureComponent { constructor(props) { super(props) + this.state = { + loading: RemoteDataState.NotStarted, + } } - - componentDidMount() { + public async componentDidMount() { const {source, loadUsers, loadRoles, loadPermissions} = this.props - loadUsers(source.links.users) - loadPermissions(source.links.permissions) + this.setState({loading: RemoteDataState.Loading}) + + if (source.authentication === SourceAuthenticationMethod.LDAP) { + return this.setState({loading: RemoteDataState.Done}) + } + + try { + await loadUsers(source.links.users) + await loadPermissions(source.links.permissions) + this.setState({loading: RemoteDataState.Done}) + } catch (error) { + console.error(error) + this.setState({loading: RemoteDataState.Error}) + } + if (source.links.roles) { - loadRoles(source.links.roles) + try { + await loadRoles(source.links.roles) + } catch (error) { + this.setState({loading: RemoteDataState.Error}) + console.error('could not load roles: ', error) + } } } - handleClickCreate = type => () => { + public render() { + const {source, params} = this.props + const {loading} = this.state + + return ( +
+ + + {loading === RemoteDataState.Loading ? ( +
+ ) : ( +
+ +
+ )} + +
+ ) + } + + private handleClickCreate = type => () => { if (type === 'users') { this.props.addUser() } else if (type === 'roles') { @@ -75,15 +171,15 @@ export class DisconnectedAdminInfluxDBPage extends Component { } } - handleEditUser = (user, updates) => { + private handleEditUser = (user, updates) => { this.props.editUser(user, updates) } - handleEditRole = (role, updates) => { + private handleEditRole = (role, updates) => { this.props.editRole(role, updates) } - handleSaveUser = async user => { + private handleSaveUser = async user => { const {notify} = this.props if (!isValidUser(user)) { notify(notifyDBUserNamePasswordInvalid()) @@ -96,7 +192,7 @@ export class DisconnectedAdminInfluxDBPage extends Component { } } - handleSaveRole = async role => { + private handleSaveRole = async role => { const {notify} = this.props if (!isValidRole(role)) { notify(notifyRoleNameInvalid()) @@ -109,43 +205,43 @@ export class DisconnectedAdminInfluxDBPage extends Component { } } - handleCancelEditUser = user => { + private handleCancelEditUser = user => { this.props.removeUser(user) } - handleCancelEditRole = role => { + private handleCancelEditRole = role => { this.props.removeRole(role) } - handleDeleteRole = role => { + private handleDeleteRole = role => { this.props.deleteRole(role) } - handleDeleteUser = user => { + private handleDeleteUser = user => { this.props.deleteUser(user) } - handleUpdateRoleUsers = (role, users) => { + private handleUpdateRoleUsers = (role, users) => { this.props.updateRoleUsers(role, users) } - handleUpdateRolePermissions = (role, permissions) => { + private handleUpdateRolePermissions = (role, permissions) => { this.props.updateRolePermissions(role, permissions) } - handleUpdateUserPermissions = (user, permissions) => { + private handleUpdateUserPermissions = (user, permissions) => { this.props.updateUserPermissions(user, permissions) } - handleUpdateUserRoles = (user, roles) => { + private handleUpdateUserRoles = (user, roles) => { this.props.updateUserRoles(user, roles) } - handleUpdateUserPassword = (user, password) => { + private handleUpdateUserPassword = (user, password) => { this.props.updateUserPassword(user, password) } - getAdminSubSections = () => { + private get adminSubSections() { const { users, roles, @@ -157,8 +253,7 @@ export class DisconnectedAdminInfluxDBPage extends Component { const hasRoles = !!source.links.roles const globalPermissions = permissions.find(p => p.scope === 'all') const allowed = globalPermissions ? globalPermissions.allowed : [] - - return [ + const sections = [ { url: 'databases', name: 'Databases', @@ -216,69 +311,15 @@ export class DisconnectedAdminInfluxDBPage extends Component { component: , }, ] + + if (source.authentication === SourceAuthenticationMethod.LDAP) { + return sections.filter( + s => s.name === 'Queries' || s.name === 'Databases' + ) + } + + return sections } - - render() { - const {users, source, params} = this.props - - return ( -
- - - {users ? ( -
- -
- ) : ( -
- )} - -
- ) - } -} - -const {arrayOf, func, shape, string} = PropTypes - -DisconnectedAdminInfluxDBPage.propTypes = { - source: shape({ - id: string.isRequired, - links: shape({ - users: string.isRequired, - }), - }).isRequired, - users: arrayOf(shape()), - roles: arrayOf(shape()), - permissions: arrayOf(shape()), - loadUsers: func, - loadRoles: func, - loadPermissions: func, - addUser: func, - addRole: func, - removeUser: func, - removeRole: func, - editUser: func, - editRole: func, - createUser: func, - createRole: func, - deleteRole: func, - deleteUser: func, - filterRoles: func, - filterUsers: func, - updateRoleUsers: func, - updateRolePermissions: func, - updateUserPermissions: func, - updateUserRoles: func, - updateUserPassword: func, - notify: func.isRequired, - params: shape({ - tab: string, - }).isRequired, } const mapStateToProps = ({adminInfluxDB: {users, roles, permissions}}) => ({ @@ -317,6 +358,4 @@ const mapDispatchToProps = dispatch => ({ notify: bindActionCreators(notifyAction, dispatch), }) -export default connect(mapStateToProps, mapDispatchToProps)( - DisconnectedAdminInfluxDBPage -) +export default connect(mapStateToProps, mapDispatchToProps)(AdminInfluxDBPage) diff --git a/ui/src/admin/reducers/influxdb.js b/ui/src/admin/reducers/influxdb.js index 81056d8cea..d2edd6ab48 100644 --- a/ui/src/admin/reducers/influxdb.js +++ b/ui/src/admin/reducers/influxdb.js @@ -8,7 +8,7 @@ import { import uuid from 'uuid' const initialState = { - users: null, + users: [], roles: [], permissions: [], queries: [], diff --git a/ui/src/types/auth.ts b/ui/src/types/auth.ts index e87cb52385..8cdd377706 100644 --- a/ui/src/types/auth.ts +++ b/ui/src/types/auth.ts @@ -12,6 +12,39 @@ export interface Me { role: Role } +export enum InfluxDBPermissions { + All = 'ALL', + NoPermissions = 'NoPermissions', + ViewAdmin = 'ViewAdmin', + ViewChronograf = 'ViewChronograf', + CreateDatabase = 'CreateDatabase', + CreateUserAndRole = 'CreateUserAndRole', + AddRemoveNode = 'AddRemoveNode', + DropDatabase = 'DropDatabase', + DropData = 'DropData', + ReadData = 'ReadData', + WriteData = 'WriteData', + Rebalance = 'Rebalance', + ManageShard = 'ManageShard', + ManageContinuousQuery = 'ManageContinuousQuery', + ManageQuery = 'ManageQuery', + ManageSubscription = 'ManageSubscription', + Monitor = 'Monitor', + CopyShard = 'CopyShard', + KapacitorAPI = 'KapacitorAPI', + KapacitorConfigAPI = 'KapacitorConfigAPI', +} + +export enum InfluxDBPermissionScope { + All = 'all', + Database = 'database', +} + +export interface Permission { + scope: string + allowed: InfluxDBPermissions[] +} + export interface Role { name: string organization: string diff --git a/ui/src/types/index.ts b/ui/src/types/index.ts index 72e2dba203..8d90df5d26 100644 --- a/ui/src/types/index.ts +++ b/ui/src/types/index.ts @@ -1,6 +1,6 @@ import {LayoutCell, LayoutQuery} from './layouts' import {Service, NewService} from './services' -import {AuthLinks, Organization, Role, User, Me} from './auth' +import {AuthLinks, Organization, Role, Permission, User, Me} from './auth' import {Cell, CellQuery, Legend, Axes, Dashboard, CellType} from './dashboards' import { Template, @@ -28,7 +28,12 @@ import { TagValues, } from './queries' import {AlertRule, Kapacitor, Task, RuleValues} from './kapacitor' -import {NewSource, Source, SourceLinks} from './sources' +import { + NewSource, + Source, + SourceLinks, + SourceAuthenticationMethod, +} from './sources' import {DropdownAction, DropdownItem, Constructable} from './shared' import { Notification, @@ -53,6 +58,7 @@ export { Role, User, Organization, + Permission, Constructable, Template, TemplateQuery, @@ -79,6 +85,7 @@ export { NewSource, Source, SourceLinks, + SourceAuthenticationMethod, DropdownAction, DropdownItem, TimeRange, diff --git a/ui/src/types/notifications.ts b/ui/src/types/notifications.ts index 10f1b556c2..d5056f4411 100644 --- a/ui/src/types/notifications.ts +++ b/ui/src/types/notifications.ts @@ -7,5 +7,4 @@ export interface Notification { } export type NotificationFunc = (message: string) => Notification - export type NotificationAction = (message: Notification) => void diff --git a/ui/src/types/sources.ts b/ui/src/types/sources.ts index b3c90c59c0..4984af21a0 100644 --- a/ui/src/types/sources.ts +++ b/ui/src/types/sources.ts @@ -2,6 +2,13 @@ import {Kapacitor, Service} from './' export type NewSource = Pick> +export enum SourceAuthenticationMethod { + LDAP = 'ldap', + Basic = 'basic', + Shared = 'shared', + Unknown = 'unknown', +} + export interface Source { id: string name: string @@ -21,6 +28,7 @@ export interface Source { kapacitors?: Kapacitor[] // this field does not exist on the server type for Source and is added in the client in the reducer for loading kapacitors. text?: string // added client-side for dropdowns services?: Service[] + authentication: SourceAuthenticationMethod } export interface SourceLinks { diff --git a/ui/test/admin/containers/AdminInfluxDBPage.test.tsx b/ui/test/admin/containers/AdminInfluxDBPage.test.tsx index 559b1772ce..b2591f0478 100644 --- a/ui/test/admin/containers/AdminInfluxDBPage.test.tsx +++ b/ui/test/admin/containers/AdminInfluxDBPage.test.tsx @@ -1,23 +1,43 @@ import React from 'react' import {shallow} from 'enzyme' -import {DisconnectedAdminInfluxDBPage} from 'src/admin/containers/AdminInfluxDBPage' +import {AdminInfluxDBPage} from 'src/admin/containers/AdminInfluxDBPage' import PageHeader from 'src/reusable_ui/components/page_layout/PageHeader' import Title from 'src/reusable_ui/components/page_layout/PageHeaderTitle' import {source} from 'test/resources' describe('AdminInfluxDBPage', () => { - it('should render the approriate header text', () => { + it('should render the appropriate header text', () => { const props = { source, + addUser: () => {}, loadUsers: () => {}, loadRoles: () => {}, loadPermissions: () => {}, notify: () => {}, params: {tab: ''}, + users: [], + roles: [], + permissions: [], + addRole: () => {}, + removeUser: () => {}, + removeRole: () => {}, + editUser: () => {}, + editRole: () => {}, + createUser: () => {}, + createRole: () => {}, + deleteRole: () => {}, + deleteUser: () => {}, + filterRoles: () => {}, + filterUsers: () => {}, + updateRoleUsers: () => {}, + updateRolePermissions: () => {}, + updateUserPermissions: () => {}, + updateUserRoles: () => {}, + updateUserPassword: () => {}, } - const wrapper = shallow() + const wrapper = shallow() const pageTitle = wrapper .find(PageHeader) diff --git a/ui/test/fixtures/index.ts b/ui/test/fixtures/index.ts index 11a4deb204..d0ed3fc26e 100644 --- a/ui/test/fixtures/index.ts +++ b/ui/test/fixtures/index.ts @@ -1,6 +1,7 @@ import {interval} from 'src/shared/constants' import { Source, + SourceAuthenticationMethod, CellQuery, SourceLinks, Cell, @@ -46,6 +47,7 @@ export const source: Source = { defaultRP: '', links: sourceLinks, insecureSkipVerify: false, + authentication: SourceAuthenticationMethod.Basic, } export const queryConfig: QueryConfig = { diff --git a/ui/test/resources.ts b/ui/test/resources.ts index 09fecabf4a..84edd01129 100644 --- a/ui/test/resources.ts +++ b/ui/test/resources.ts @@ -1,5 +1,14 @@ -import {Source, Template, Dashboard, Cell, CellType} from 'src/types' -import {SourceLinks, TemplateType, TemplateValueType} from 'src/types' +import { + Source, + SourceAuthenticationMethod, + Template, + Dashboard, + Cell, + CellType, + SourceLinks, + TemplateType, + TemplateValueType, +} from 'src/types' export const role = { name: '', @@ -47,6 +56,7 @@ export const source: Source = { role: 'viewer', defaultRP: '', links: sourceLinks, + authentication: SourceAuthenticationMethod.Basic, } export const timeRange = {