ProjectDetailsContent.jsx 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318
  1. /*
  2. Copyright (C) 2017 Cloudbase Solutions SRL
  3. This program is free software: you can redistribute it and/or modify
  4. it under the terms of the GNU Affero General Public License as
  5. published by the Free Software Foundation, either version 3 of the
  6. License, or (at your option) any later version.
  7. This program is distributed in the hope that it will be useful,
  8. but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  10. GNU Affero General Public License for more details.
  11. You should have received a copy of the GNU Affero General Public License
  12. along with this program. If not, see <http://www.gnu.org/licenses/>.
  13. */
  14. // @flow
  15. import React from 'react'
  16. import { observer } from 'mobx-react'
  17. import styled, { css } from 'styled-components'
  18. import AlertModal from '../../organisms/AlertModal'
  19. import Table from '../../molecules/Table'
  20. import CopyValue from '../../atoms/CopyValue'
  21. import CopyMultilineValue from '../../atoms/CopyMultilineValue'
  22. import StatusImage from '../../atoms/StatusImage'
  23. import DropdownLink from '../../molecules/DropdownLink'
  24. import Button from '../../atoms/Button'
  25. import type { Project, RoleAssignment, Role } from '../../../types/Project'
  26. import type { User } from '../../../types/User'
  27. import StyleProps from '../../styleUtils/StyleProps'
  28. import Palette from '../../styleUtils/Palette'
  29. const Wrapper = styled.div`
  30. ${StyleProps.exactWidth(StyleProps.contentWidth)}
  31. margin: 0 auto;
  32. padding-left: 126px;
  33. `
  34. const Info = styled.div`
  35. display: flex;
  36. flex-wrap: wrap;
  37. margin-top: 32px;
  38. margin-left: -32px;
  39. `
  40. const Field = styled.div`
  41. ${StyleProps.exactWidth('calc(50% - 32px)')}
  42. margin-bottom: 32px;
  43. margin-left: 32px;
  44. `
  45. const Value = styled.div``
  46. const Label = styled.div`
  47. font-size: 10px;
  48. font-weight: ${StyleProps.fontWeights.medium};
  49. color: ${Palette.grayscale[3]};
  50. text-transform: uppercase;
  51. margin-bottom: 3px;
  52. `
  53. const LoadingWrapper = styled.div`
  54. display: flex;
  55. justify-content: center;
  56. width: 100%;
  57. margin: 32px 0 64px 0;
  58. `
  59. const TableStyled = styled(Table)`
  60. margin-top: 42px;
  61. margin-bottom: 32px;
  62. `
  63. const Buttons = styled.div`
  64. margin-top: 64px;
  65. display: flex;
  66. justify-content: space-between;
  67. `
  68. const UserColumn = styled.div`
  69. ${props => props.disabled ? css`color: ${Palette.grayscale[3]};` : ''}
  70. `
  71. const UserName = styled.a`
  72. ${props => props.disabled ? css`opacity: 0.7;` : ''}
  73. color: ${Palette.primary};
  74. text-decoration: none;
  75. `
  76. const ButtonsColumn = styled.div`
  77. display: flex;
  78. flex-direction: column;
  79. button {
  80. margin-bottom: 16px;
  81. &:last-child {
  82. margin-bottom: 0;
  83. }
  84. }
  85. `
  86. type Props = {
  87. project: ?Project,
  88. loading: boolean,
  89. users: User[],
  90. usersLoading: boolean,
  91. roleAssignments: RoleAssignment[],
  92. roles: Role[],
  93. loggedUserId: string,
  94. onEnableUser: (user: User) => void,
  95. onRemoveUser: (user: User) => void,
  96. onUserRoleChange: (user: User, roleId: string, toggled: boolean) => void,
  97. onAddMemberClick: () => void,
  98. onDeleteClick: () => void,
  99. }
  100. type State = {
  101. showRemoveUserAlert: boolean,
  102. }
  103. const testName = 'pdContent'
  104. @observer
  105. class ProjectDetailsContent extends React.Component<Props, State> {
  106. state = {
  107. showRemoveUserAlert: false,
  108. }
  109. selectedUser: ?User
  110. handleRemoveUserAction(user: User) {
  111. this.selectedUser = user
  112. this.setState({ showRemoveUserAlert: true })
  113. }
  114. handleUserAction(user: User, item: { label: string, value: string }) {
  115. switch (item.value) {
  116. case 'enable':
  117. this.props.onEnableUser(user)
  118. break
  119. case 'remove':
  120. this.handleRemoveUserAction(user)
  121. break
  122. default:
  123. break
  124. }
  125. }
  126. handleRemoveUserConfirmation() {
  127. if (this.selectedUser) {
  128. this.props.onRemoveUser(this.selectedUser)
  129. }
  130. this.setState({ showRemoveUserAlert: false })
  131. }
  132. handleCloseRemoveUserConfirmation() {
  133. this.setState({ showRemoveUserAlert: false })
  134. }
  135. renderLoading() {
  136. return (
  137. <LoadingWrapper>
  138. <StatusImage />
  139. </LoadingWrapper>
  140. )
  141. }
  142. renderButtons() {
  143. if (this.props.loading) return null
  144. return (
  145. <Buttons>
  146. <ButtonsColumn>
  147. <Button
  148. onClick={this.props.onAddMemberClick}
  149. >Add Member</Button>
  150. </ButtonsColumn>
  151. <ButtonsColumn>
  152. <Button
  153. alert
  154. hollow
  155. onClick={() => { this.props.onDeleteClick() }}
  156. >Delete Project</Button>
  157. </ButtonsColumn>
  158. </Buttons>
  159. )
  160. }
  161. renderInfo() {
  162. if (this.props.loading || !this.props.project) {
  163. return null
  164. }
  165. const project = this.props.project
  166. return (
  167. <Info>
  168. <Field>
  169. <Label>Name</Label>
  170. {this.renderValue(project.name, 'name')}
  171. </Field>
  172. <Field>
  173. <Label>Description</Label>
  174. {project.description ? <CopyMultilineValue value={project.description} /> : <Value>-</Value>}
  175. </Field>
  176. <Field>
  177. <Label>ID</Label>
  178. {this.renderValue(project.id, 'id')}
  179. </Field>
  180. <Field>
  181. <Label>Enabled</Label>
  182. <Value>{project.enabled ? 'Yes' : 'No'}</Value>
  183. </Field>
  184. </Info>
  185. )
  186. }
  187. renderUsers() {
  188. if (this.props.usersLoading || this.props.loading) {
  189. return null
  190. }
  191. const rows = []
  192. const actions = user => [
  193. {
  194. label: `${user.enabled ? 'Disable' : 'Enable'} User`,
  195. value: 'enable',
  196. }, {
  197. label: 'Remove',
  198. value: 'remove',
  199. },
  200. ]
  201. let getUserRoles = user => {
  202. let projectId = this.props.project ? this.props.project.id : ''
  203. let roles = this.props.roleAssignments
  204. .filter(a => a.scope.project.id === projectId)
  205. .filter(a => a.user.id === user.id)
  206. .map(a => { return { value: a.role.id, label: a.role.name } })
  207. return roles
  208. }
  209. let allRoles = this.props.roles
  210. .filter(r => r.name !== 'key-manager:service-admin')
  211. .map(r => { return { value: r.id, label: r.name } })
  212. this.props.users.forEach(user => {
  213. let userActions = actions(user)
  214. let userRoles = getUserRoles(user)
  215. const columns = [
  216. <UserName
  217. data-test-id={`pdContent-users-${user.name}`}
  218. disabled={!user.enabled}
  219. href={`#/user/${user.id}`}
  220. >{user.name}</UserName>,
  221. <DropdownLink
  222. data-test-id={`${testName}-roles-${user.name}`}
  223. width="214px"
  224. getLabel={() => userRoles.length > 0 ? userRoles.map(r => r.label).join(', ') : 'No roles'}
  225. selectedItems={userRoles.map(r => r.value)}
  226. listWidth="120px"
  227. multipleSelection
  228. items={allRoles}
  229. labelStyle={{ color: Palette.grayscale[4] }}
  230. disabled={!user.enabled}
  231. style={{ opacity: user.enabled ? 1 : 0.7 }}
  232. onChange={item => {
  233. this.props.onUserRoleChange(user, item.value, !userRoles.find(i => i.value === item.value))
  234. }}
  235. />,
  236. <UserColumn disabled={!user.enabled}>{user.enabled ? 'Enabled' : 'Disabled'}</UserColumn>,
  237. <DropdownLink
  238. data-test-id={`${testName}-actions-${user.name}`}
  239. noCheckmark
  240. width="82px"
  241. items={userActions}
  242. selectedItem=""
  243. selectItemLabel="Actions"
  244. listWidth="120px"
  245. onChange={item => { this.handleUserAction(user, item) }}
  246. disabled={user.id === this.props.loggedUserId}
  247. style={{ opacity: user.id === this.props.loggedUserId ? 0.7 : 1 }}
  248. itemStyle={item => `color: ${item.value === 'remove' ? Palette.alert : Palette.black};`}
  249. />,
  250. ]
  251. rows.push(columns)
  252. })
  253. return (
  254. <TableStyled
  255. data-test-id={`${testName}-members`}
  256. header={['Member', 'Roles', 'Status', '']}
  257. items={rows}
  258. noItemsLabel="No members available!"
  259. columnsStyle={[css`color: ${Palette.black};`]}
  260. />
  261. )
  262. }
  263. renderValue(value: string, dataTestId: string) {
  264. return value !== '-' ? (
  265. <CopyValue
  266. data-test-id={`${testName}-${dataTestId}`}
  267. value={value}
  268. maxWidth="90%"
  269. />
  270. ) : <Value>{value}</Value>
  271. }
  272. render() {
  273. return (
  274. <Wrapper>
  275. {this.renderInfo()}
  276. {this.props.loading ? this.renderLoading() : null}
  277. {this.renderUsers()}
  278. {!this.props.loading && this.props.usersLoading ? this.renderLoading() : null}
  279. {this.renderButtons()}
  280. {this.state.showRemoveUserAlert ? (
  281. <AlertModal
  282. isOpen
  283. title="Remove User?"
  284. message="Are you sure you want to remove this user from the project?"
  285. extraMessage=" "
  286. onConfirmation={() => { this.handleRemoveUserConfirmation() }}
  287. onRequestClose={() => { this.handleCloseRemoveUserConfirmation() }}
  288. />
  289. ) : null}
  290. </Wrapper>
  291. )
  292. }
  293. }
  294. export default ProjectDetailsContent