2019-11-04 07:47:20 +00:00
|
|
|
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';
|
2019-11-29 07:17:00 +00:00
|
|
|
import { Utils } from '../../utils/utils';
|
2019-11-04 07:47:20 +00:00
|
|
|
import toaster from '../toast';
|
|
|
|
import copy from 'copy-to-clipboard';
|
|
|
|
import Loading from '../loading';
|
2021-10-09 06:17:06 +00:00
|
|
|
import OpIcon from '../op-icon';
|
2019-11-04 07:47:20 +00:00
|
|
|
|
|
|
|
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);
|
2019-11-29 07:17:00 +00:00
|
|
|
toaster.success(gettext('API token is copied to the clipboard.'));
|
2019-11-04 07:47:20 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
render() {
|
|
|
|
let item = this.props.item;
|
|
|
|
|
|
|
|
return (
|
2021-10-09 06:17:06 +00:00
|
|
|
<tr onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave} tabIndex="0" onFocus={this.onMouseEnter}>
|
2019-11-04 07:47:20 +00:00
|
|
|
<td className="name">{item.app_name}</td>
|
|
|
|
<td>
|
|
|
|
<RepoAPITokenPermissionEditor
|
|
|
|
isTextMode={true}
|
|
|
|
isEditIconShow={this.state.isOperationShow}
|
|
|
|
currentPermission={item.permission}
|
|
|
|
onPermissionChanged={this.onUpdateAPIToken}
|
|
|
|
/>
|
|
|
|
</td>
|
|
|
|
<td>
|
2019-11-29 07:17:00 +00:00
|
|
|
<span>{item.api_token}</span>
|
|
|
|
{this.state.isOperationShow &&
|
2021-10-09 06:17:06 +00:00
|
|
|
<OpIcon
|
|
|
|
className="far fa-copy action-icon"
|
|
|
|
op={this.onCopyAPIToken}
|
|
|
|
title={gettext('Copy')}
|
|
|
|
/>
|
2019-11-29 07:17:00 +00:00
|
|
|
}
|
2019-11-04 07:47:20 +00:00
|
|
|
</td>
|
|
|
|
<td>
|
2021-10-09 06:17:06 +00:00
|
|
|
<OpIcon
|
2019-11-04 07:47:20 +00:00
|
|
|
className={`sf2-icon-x3 action-icon ${this.state.isOperationShow ? '' : 'hide'}`}
|
2021-10-09 06:17:06 +00:00
|
|
|
op={this.onDeleteAPIToken}
|
2019-11-04 07:47:20 +00:00
|
|
|
title={gettext('Delete')}
|
|
|
|
/>
|
|
|
|
</td>
|
|
|
|
</tr>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
APITokenItem.propTypes = apiTokenItemPropTypes;
|
|
|
|
|
|
|
|
const propTypes = {
|
|
|
|
repo: PropTypes.object.isRequired,
|
|
|
|
onRepoAPITokenToggle: PropTypes.func.isRequired,
|
|
|
|
};
|
|
|
|
|
|
|
|
class RepoAPITokenDialog extends React.Component {
|
|
|
|
constructor(props) {
|
|
|
|
super(props);
|
|
|
|
this.state = {
|
|
|
|
apiTokenList: [],
|
2019-11-04 09:37:27 +00:00
|
|
|
permission: 'rw',
|
2019-11-04 07:47:20 +00:00
|
|
|
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 (
|
|
|
|
<APITokenItem
|
|
|
|
key={index}
|
|
|
|
item={item}
|
|
|
|
deleteAPIToken={this.deleteAPIToken}
|
|
|
|
updateAPIToken={this.updateAPIToken}
|
|
|
|
/>
|
|
|
|
);
|
|
|
|
});
|
|
|
|
|
2019-11-29 07:17:00 +00:00
|
|
|
const thead = (
|
|
|
|
<thead>
|
|
|
|
<tr>
|
2020-04-04 06:32:11 +00:00
|
|
|
<th width="20%">{gettext('App Name')}</th>
|
2019-11-29 07:17:00 +00:00
|
|
|
<th width="20%">{gettext('Permission')}</th>
|
|
|
|
<th width="48%">API Token</th>
|
2020-04-04 06:32:11 +00:00
|
|
|
<th width="12%"></th>
|
2019-11-29 07:17:00 +00:00
|
|
|
</tr>
|
|
|
|
</thead>
|
|
|
|
);
|
2019-11-04 07:47:20 +00:00
|
|
|
return (
|
|
|
|
<Fragment>
|
|
|
|
{this.state.errorMsg &&
|
|
|
|
<p className="error text-center">{this.state.errorMsg}</p>
|
|
|
|
}
|
|
|
|
{!this.state.errorMsg &&
|
2020-04-04 06:32:11 +00:00
|
|
|
<Fragment>
|
|
|
|
<table className="w-xs-250">
|
2019-11-29 07:17:00 +00:00
|
|
|
{thead}
|
2019-11-04 07:47:20 +00:00
|
|
|
<tbody>
|
|
|
|
<tr>
|
|
|
|
<td>
|
|
|
|
<Input
|
|
|
|
type="text"
|
|
|
|
id="appName"
|
|
|
|
value={this.state.appName}
|
|
|
|
onChange={this.onInputChange}
|
|
|
|
onKeyDown={this.onKeyDown}
|
|
|
|
/>
|
|
|
|
</td>
|
|
|
|
<td>
|
|
|
|
<RepoAPITokenPermissionEditor
|
|
|
|
isTextMode={false}
|
|
|
|
isEditIconShow={false}
|
|
|
|
currentPermission={this.state.permission}
|
|
|
|
onPermissionChanged={this.setPermission}
|
|
|
|
/>
|
|
|
|
</td>
|
2019-11-29 07:17:00 +00:00
|
|
|
<td><span className="text-secondary">--</span></td>
|
2019-11-04 07:47:20 +00:00
|
|
|
<td>
|
|
|
|
<Button onClick={this.addAPIToken} disabled={!this.state.isSubmitBtnActive}>{gettext('Submit')}</Button>
|
|
|
|
</td>
|
|
|
|
</tr>
|
|
|
|
</tbody>
|
|
|
|
</table>
|
2020-04-04 06:32:11 +00:00
|
|
|
<div style={{minHeight: '10rem', maxHeight: '18rem'}}>
|
|
|
|
{this.state.apiTokenList.length !== 0 &&
|
|
|
|
<table className="table-thead-hidden w-xs-250">
|
|
|
|
{thead}
|
|
|
|
<tbody>
|
|
|
|
{renderAPITokenList}
|
|
|
|
</tbody>
|
|
|
|
</table>
|
|
|
|
}
|
2019-11-04 07:47:20 +00:00
|
|
|
</div>
|
2020-04-04 06:32:11 +00:00
|
|
|
{this.state.loading && <Loading/>}
|
|
|
|
</Fragment>
|
2019-11-04 07:47:20 +00:00
|
|
|
}
|
|
|
|
</Fragment>
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
render() {
|
|
|
|
let repo = this.repo;
|
|
|
|
|
2024-03-07 07:37:09 +00:00
|
|
|
const itemName = '<span class="op-target text-truncate mr-1">' + Utils.HTMLescape(repo.repo_name) + '</span>';
|
2019-11-29 07:17:00 +00:00
|
|
|
const title = gettext('{placeholder} API Token').replace('{placeholder}', itemName);
|
2019-11-04 07:47:20 +00:00
|
|
|
return (
|
|
|
|
<Modal
|
2020-04-04 06:32:11 +00:00
|
|
|
isOpen={true} style={{maxWidth: '800px'}}
|
2019-11-04 07:47:20 +00:00
|
|
|
toggle={this.props.onRepoAPITokenToggle}
|
|
|
|
>
|
|
|
|
<ModalHeader toggle={this.props.onRepoAPITokenToggle}>
|
2024-03-07 07:37:09 +00:00
|
|
|
<span dangerouslySetInnerHTML={{__html: title}} className="d-flex mw-100"></span>
|
2019-11-29 07:17:00 +00:00
|
|
|
</ModalHeader>
|
2020-04-04 06:32:11 +00:00
|
|
|
<ModalBody>
|
|
|
|
<div className="o-auto">
|
|
|
|
{this.renderContent()}
|
|
|
|
</div>
|
2019-11-04 07:47:20 +00:00
|
|
|
</ModalBody>
|
|
|
|
</Modal>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
RepoAPITokenDialog.propTypes = propTypes;
|
|
|
|
|
|
|
|
export default RepoAPITokenDialog;
|