ActAsModal.js 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275
  1. /*
  2. * Copyright (C) 2017 - present Instructure, Inc.
  3. *
  4. * This file is part of Canvas.
  5. *
  6. * Canvas is free software: you can redistribute it and/or modify it under
  7. * the terms of the GNU Affero General Public License as published by the Free
  8. * Software Foundation, version 3 of the License.
  9. *
  10. * Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
  11. * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  12. * A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
  13. * details.
  14. *
  15. * You should have received a copy of the GNU Affero General Public License along
  16. * with this program. If not, see <http://www.gnu.org/licenses/>.
  17. */
  18. import React from 'react'
  19. import keycode from 'keycode'
  20. import I18n from 'i18n!act_as'
  21. import Modal, {ModalHeader, ModalBody} from 'instructure-ui/lib/components/Modal'
  22. import ScreenReaderContent from 'instructure-ui/lib/components/ScreenReaderContent'
  23. import Container from 'instructure-ui/lib/components/Container'
  24. import Typography from 'instructure-ui/lib/components/Typography'
  25. import Button from 'instructure-ui/lib/components/Button'
  26. import Avatar from 'instructure-ui/lib/components/Avatar'
  27. import Spinner from 'instructure-ui/lib/components/Spinner'
  28. import Table from 'instructure-ui/lib/components/Table'
  29. import ActAsMask from './ActAsMask'
  30. import ActAsPanda from './ActAsPanda'
  31. export default class ActAsModal extends React.Component {
  32. static propTypes = {
  33. user: React.PropTypes.shape({
  34. name: React.PropTypes.string,
  35. short_name: React.PropTypes.string,
  36. id: React.PropTypes.oneOfType([React.PropTypes.number, React.PropTypes.string]),
  37. avatar_image_url: React.PropTypes.string,
  38. sortable_name: React.PropTypes.string,
  39. email: React.PropTypes.string,
  40. pseudonyms: React.PropTypes.arrayOf(React.PropTypes.shape({
  41. login_id: React.PropTypes.oneOfType([React.PropTypes.number, React.PropTypes.string]),
  42. sis_id: React.PropTypes.oneOfType([React.PropTypes.number, React.PropTypes.string]),
  43. integration_id: React.PropTypes.oneOfType([React.PropTypes.number, React.PropTypes.string])
  44. }))
  45. }).isRequired
  46. }
  47. constructor (props) {
  48. super(props)
  49. this.state = {
  50. isLoading: false
  51. }
  52. this._button = null
  53. }
  54. componentWillMount () {
  55. if (window.location.href === document.referrer) {
  56. this.setState({isLoading: true})
  57. window.location.href = '/'
  58. }
  59. }
  60. handleModalRequestClose = () => {
  61. const defaultUrl = '/'
  62. if (!document.referrer) {
  63. window.location.href = defaultUrl
  64. } else {
  65. const currentPage = window.location.href
  66. window.history.back()
  67. // if we go nowhere, modal was opened in new tab,
  68. // and we return to the dashboard by default
  69. setTimeout(() => {
  70. if (window.location.href === currentPage) {
  71. window.location.href = defaultUrl
  72. }
  73. }, 1000)
  74. }
  75. this.setState({isLoading: true})
  76. }
  77. handleClick = (e) => {
  78. if (e.keyCode && (e.keyCode === keycode.codes.space || e.keyCode === keycode.codes.enter)) {
  79. // for the data to post correctly, we need an actual click
  80. // on enter and space press, we simulate a click event and return
  81. e.target.click()
  82. return
  83. }
  84. this.setState({isLoading: true})
  85. }
  86. renderInfoTable (caption, renderRows) {
  87. return (
  88. <Table caption={<ScreenReaderContent>{caption}</ScreenReaderContent>}>
  89. <thead>
  90. <tr>
  91. <th><ScreenReaderContent>{I18n.t('Category')}</ScreenReaderContent></th>
  92. <th><ScreenReaderContent>{I18n.t('User information')}</ScreenReaderContent></th>
  93. </tr>
  94. </thead>
  95. {renderRows()}
  96. </Table>
  97. )
  98. }
  99. renderUserInfoRows = () => {
  100. const user = this.props.user
  101. return (
  102. <tbody>
  103. {this.renderUserRow(I18n.t('Full Name:'), user.name)}
  104. {this.renderUserRow(I18n.t('Display Name:'), user.short_name)}
  105. {this.renderUserRow(I18n.t('Sortable Name:'), user.sortable_name)}
  106. {this.renderUserRow(I18n.t('Default Email:'), user.email)}
  107. </tbody>
  108. )
  109. }
  110. renderLoginInfoRows = pseudonym => (
  111. <tbody>
  112. {this.renderUserRow(I18n.t('Login ID:'), pseudonym.login_id)}
  113. {this.renderUserRow(I18n.t('SIS ID:'), pseudonym.sis_id)}
  114. {this.renderUserRow(I18n.t('Integration ID:'), pseudonym.integration_id)}
  115. </tbody>
  116. )
  117. renderUserRow (category, info) {
  118. return (
  119. <tr>
  120. <td>
  121. <Typography size="small">{category}</Typography>
  122. </td>
  123. <td>
  124. <Container
  125. as="div"
  126. textAlign="end"
  127. >
  128. <Typography
  129. size="small"
  130. weight="bold"
  131. >
  132. {info}
  133. </Typography>
  134. </Container>
  135. </td>
  136. </tr>
  137. )
  138. }
  139. render () {
  140. const user = this.props.user
  141. return (
  142. <span>
  143. <Modal
  144. onDismiss={this.handleModalRequestClose}
  145. transition="fade"
  146. size="fullscreen"
  147. label={I18n.t('Act as User')}
  148. closeButtonLabel={I18n.t('Close')}
  149. applicationElement={() => document.getElementById('application')}
  150. open
  151. >
  152. <ModalHeader>
  153. <Typography size="large">
  154. {I18n.t('Act as User')}
  155. </Typography>
  156. </ModalHeader>
  157. <ModalBody>
  158. {this.state.isLoading ?
  159. <div className="ActAs__loading">
  160. <Spinner title={I18n.t('Loading')} />
  161. </div>
  162. :
  163. <div className="ActAs__body">
  164. <div className="ActAs__svgContainer">
  165. <div className="ActAs__svg">
  166. <ActAsPanda />
  167. </div>
  168. <div className="ActAs__svg">
  169. <ActAsMask />
  170. </div>
  171. </div>
  172. <div className="ActAs__text">
  173. <Container
  174. as="div"
  175. size="small"
  176. >
  177. <Container
  178. as="div"
  179. textAlign="center"
  180. padding="0 0 x-small 0"
  181. >
  182. <Typography
  183. size="x-large"
  184. weight="light"
  185. >
  186. {I18n.t('Act as %{name}', { name: user.short_name })}
  187. </Typography>
  188. </Container>
  189. <Container
  190. as="div"
  191. textAlign="center"
  192. >
  193. <Typography
  194. lineHeight="condensed"
  195. size="small"
  196. >
  197. {I18n.t('"Act as" is essentially logging in as this user ' +
  198. 'without a password. You will be able to take any action ' +
  199. 'as if you were this user, and from other users\' points ' +
  200. 'of views, it will be as if this user performed them. However, ' +
  201. 'audit logs record that you were the one who performed the ' +
  202. 'actions on behalf of this user.'
  203. )}
  204. </Typography>
  205. </Container>
  206. <Container
  207. as="div"
  208. textAlign="center"
  209. >
  210. <Avatar
  211. name={user.short_name}
  212. src={user.avatar_image_url}
  213. size="small"
  214. margin="medium 0 x-small 0"
  215. />
  216. </Container>
  217. <Container
  218. as="div"
  219. textAlign="center"
  220. >
  221. {this.renderInfoTable(I18n.t('User details'), this.renderUserInfoRows)}
  222. </Container>
  223. {user.pseudonyms.map(pseudonym => (
  224. <Container
  225. as="div"
  226. textAlign="center"
  227. margin="large 0 0 0"
  228. key={pseudonym.login_id}
  229. >
  230. {this.renderInfoTable(I18n.t('Login info'), () => this.renderLoginInfoRows(pseudonym))}
  231. </Container>
  232. )
  233. )}
  234. <Container
  235. as="div"
  236. textAlign="center"
  237. >
  238. <Button
  239. variant="primary"
  240. href={`/users/${user.id}/masquerade`}
  241. data-method="post"
  242. onClick={this.handleClick}
  243. margin="large 0 0 0"
  244. >
  245. {I18n.t('Proceed')}
  246. </Button>
  247. </Container>
  248. </Container>
  249. </div>
  250. </div>
  251. }
  252. </ModalBody>
  253. </Modal>
  254. </span>
  255. )
  256. }
  257. }