ProjectDetailsContent.tsx 9.1 KB

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