diff --git a/frontend/src/components/dialog/repo-api-token-dialog.js b/frontend/src/components/dialog/repo-api-token-dialog.js new file mode 100644 index 0000000000..3578cf2776 --- /dev/null +++ b/frontend/src/components/dialog/repo-api-token-dialog.js @@ -0,0 +1,312 @@ +import React, {Fragment} from 'react'; +import PropTypes from 'prop-types'; +import {gettext} from '../../utils/constants'; +import {Modal, ModalHeader, ModalBody, Button, Input} from 'reactstrap'; +import RepoAPITokenPermissionEditor from '../select-editor/repo-api-token-permission-editor'; +import {seafileAPI} from '../../utils/seafile-api'; +import toaster from '../toast'; +import copy from 'copy-to-clipboard'; +import Loading from '../loading'; + +import '../../css/share-link-dialog.css'; + + +const apiTokenItemPropTypes = { + item: PropTypes.object.isRequired, + deleteAPIToken: PropTypes.func.isRequired, + updateAPIToken: PropTypes.func.isRequired, +}; + +class APITokenItem extends React.Component { + + constructor(props) { + super(props); + this.state = { + isOperationShow: false, + }; + } + + onMouseEnter = () => { + this.setState({isOperationShow: true}); + }; + + onMouseLeave = () => { + this.setState({isOperationShow: false}); + }; + + onDeleteAPIToken = () => { + this.props.deleteAPIToken(this.props.item.app_name); + }; + + onUpdateAPIToken = (permission) => { + this.props.updateAPIToken(this.props.item.app_name, permission); + }; + + onCopyAPIToken = () => { + let api_token = this.props.item.api_token; + copy(api_token); + toaster.success(gettext('API Token is copied to the clipboard.')); + }; + + render() { + let item = this.props.item; + + return ( + + {item.app_name} + + + + {item.api_token} + + + + + + + + ); + } +} + +APITokenItem.propTypes = apiTokenItemPropTypes; + +const propTypes = { + // currentTable: PropTypes.object.isRequired, + // onTableAPITokenToggle: PropTypes.func.isRequired, + repo: PropTypes.object.isRequired, + onRepoAPITokenToggle: PropTypes.func.isRequired, +}; + +class RepoAPITokenDialog extends React.Component { + constructor(props) { + super(props); + this.state = { + apiTokenList: [], + permission: '', + appName: '', + errorMsg: '', + loading: true, + isSubmitBtnActive: true, + }; + this.repo = this.props.repo; + } + + listAPITokens = () => { + seafileAPI.listRepoAPITokens(this.repo.repo_id).then((res) => { + this.setState({ + apiTokenList: res.data.repo_api_tokens, + loading: false, + }); + }).catch(error => { + if (error.response.status === 403) { + this.setState({ + errorMsg: gettext('Permission denied'), + }); + } else { + this.handleError(error); + } + }); + }; + + onInputChange = (e) => { + let appName = e.target.value; + this.setState({ + appName: appName, + }); + }; + + onKeyDown = (e) => { + if (e.keyCode === 13) { + e.preventDefault(); + this.addAPIToken(); + } + }; + + setPermission = (permission) => { + this.setState({permission: permission}); + }; + + addAPIToken = () => { + if (!this.state.appName) { + return; + } + + this.setState({ + isSubmitBtnActive: false, + }); + const {appName, permission, apiTokenList} = this.state; + + seafileAPI.addRepoAPIToken(this.repo.repo_id, appName, permission).then((res) => { + apiTokenList.push(res.data); + this.setState({ + apiTokenList: apiTokenList, + isSubmitBtnActive: true, + }); + }).catch(error => { + this.handleError(error); + this.setState({ + isSubmitBtnActive: true, + }); + }); + }; + + deleteAPIToken = (appName) => { + seafileAPI.deleteRepoAPIToken(this.repo.repo_id, appName).then((res) => { + const apiTokenList = this.state.apiTokenList.filter(item => { + return item.app_name !== appName; + }); + this.setState({ + apiTokenList: apiTokenList, + }); + }).catch(error => { + this.handleError(error); + }); + }; + + updateAPIToken = (appName, permission) => { + seafileAPI.updateRepoAPIToken(this.repo.repo_id, appName, permission).then((res) => { + let apiTokenList = this.state.apiTokenList.filter(item => { + if (item.app_name === appName) { + item.permission = permission; + } + return item; + }); + this.setState({ + apiTokenList: apiTokenList, + }); + }).catch(error => { + this.handleError(error); + }); + }; + + handleError = (e) => { + if (e.response) { + toaster.danger(e.response.data.error_msg || e.response.data.detail || gettext('Error'), {duration: 3}); + } else { + toaster.danger(gettext('Please check the network.'), {duration: 3}); + } + }; + + componentDidMount() { + this.listAPITokens(); + } + + renderContent = () => { + const renderAPITokenList = this.state.apiTokenList.map((item, index) => { + return ( + + ); + }); + + return ( + + {this.state.errorMsg && +
+

{this.state.errorMsg}

+
+ } + {!this.state.errorMsg && +
+ + + + + + + + + + + + + + + +
{gettext('App Name')}{gettext('Permission')}
+ + + + + +
+ {this.state.apiTokenList.length !== 0 && +
+
+ + + + + + + + + + + + {renderAPITokenList} + +
{gettext('App Name')}{gettext('Permission')}{gettext('Access Token')}
+
+
+ } + {this.state.loading && + + } +
+ } +
+ ); + }; + + render() { + // let currentTable = this.props.currentTable; + // let name = currentTable.name; + let repo = this.repo; + + return ( + + + {gettext('API Token')} {repo.repo_name} + + {this.renderContent()} + + + ); + } +} + +RepoAPITokenDialog.propTypes = propTypes; + +export default RepoAPITokenDialog; diff --git a/frontend/src/components/select-editor/repo-api-token-permission-editor.js b/frontend/src/components/select-editor/repo-api-token-permission-editor.js new file mode 100644 index 0000000000..dbc3c0c657 --- /dev/null +++ b/frontend/src/components/select-editor/repo-api-token-permission-editor.js @@ -0,0 +1,81 @@ +import React, { Fragment } from 'react'; +import PropTypes from 'prop-types'; +import Select from 'react-select'; +import { gettext } from '../../utils/constants'; + +const propTypes = { + isTextMode: PropTypes.bool.isRequired, + isEditIconShow: PropTypes.bool.isRequired, + currentPermission: PropTypes.string.isRequired, + onPermissionChanged: PropTypes.func.isRequired, +}; + +class RepoAPITokenPermissionEditor extends React.Component { + + constructor(props) { + super(props); + this.state = { + isEditing: false, + }; + this.options = [ + { value: 'rw', label:
{gettext('Read-Write')}
}, + { value: 'r', label:
{gettext('Read-Only')}
} + ]; + } + + componentDidMount() { + document.addEventListener('click', this.onHideSelect); + } + + componentWillUnmount() { + document.removeEventListener('click', this.onHideSelect); + } + + onHideSelect = () => { + this.setState({ isEditing: false }); + } + + onEditPermission = (e) => { + e.nativeEvent.stopImmediatePropagation(); + this.setState({ isEditing: true }); + } + + onPermissionChanged = (e) => { + if (e.value !== this.props.currentPermission) { + this.props.onPermissionChanged(e.value); + } + this.setState({ isEditing: false }); + } + + onSelectHandler = (e) => { + e.nativeEvent.stopImmediatePropagation(); + } + + render() { + const { currentPermission, isTextMode } = this.props; + let optionTranslation = currentPermission === 'rw' ? gettext('Read-Write') : gettext('Read-Only'); + return ( +
+ {(isTextMode && !this.state.isEditing) ? + + {optionTranslation} + {this.props.isEditIconShow && + + } + + : +