mirror of
https://github.com/haiwen/seahub.git
synced 2025-09-19 18:29:23 +00:00
Ocm (#4640)
* ocm share local and inter-server api, ocm share page (#4335) * ocm share * optimize code * ocm repo (#4341) * ocm repo * hide share if not repo owner * optimize code * fix All refresh page, hide upload if is r perm * update permission check * update return status code * update code * add receive user drop share
This commit is contained in:
121
frontend/src/pages/share-with-ocm/remote-dir-content.js
Normal file
121
frontend/src/pages/share-with-ocm/remote-dir-content.js
Normal file
@@ -0,0 +1,121 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Link } from '@reach/router';
|
||||
import moment from 'moment';
|
||||
import { gettext } from '../../utils/constants';
|
||||
import { Utils } from '../../utils/utils';
|
||||
import Loading from '../../components/loading';
|
||||
|
||||
class DirentItem extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
isOpIconShown: false
|
||||
};
|
||||
}
|
||||
|
||||
handleMouseOver = () => {
|
||||
this.setState({
|
||||
isOpIconShown: true
|
||||
});
|
||||
}
|
||||
|
||||
handleMouseOut = () => {
|
||||
this.setState({
|
||||
isOpIconShown: false
|
||||
});
|
||||
}
|
||||
|
||||
openFolder = () => {
|
||||
this.props.openFolder(this.props.dirent);
|
||||
}
|
||||
|
||||
downloadDirent = (e) => {
|
||||
e.preventDefault();
|
||||
this.props.downloadDirent(this.props.dirent);
|
||||
}
|
||||
|
||||
render () {
|
||||
let { isOpIconShown } = this.state;
|
||||
let { dirent } = this.props;
|
||||
let iconUrl = Utils.getDirentIcon(dirent);
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<tr onMouseEnter={this.handleMouseOver} onMouseLeave={this.handleMouseOut}>
|
||||
<td className="text-center"><img src={iconUrl} width="24" alt='' /></td>
|
||||
<td>
|
||||
{dirent.is_file ?
|
||||
dirent.name :
|
||||
<Link to="#" onClick={this.openFolder}>{dirent.name}</Link>
|
||||
}
|
||||
</td>
|
||||
<td>
|
||||
{isOpIconShown && dirent.is_file &&
|
||||
<a href="#" className="op-icon sf2-icon-download" title={gettext('Download')} onClick={this.downloadDirent}></a>
|
||||
}
|
||||
</td>
|
||||
<td>{Utils.bytesToSize(dirent.size)}</td>
|
||||
<td>{moment(dirent.mtime).fromNow()}</td>
|
||||
</tr>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const propTypes = {
|
||||
direntList: PropTypes.array.isRequired
|
||||
};
|
||||
|
||||
class DirContent extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
render() {
|
||||
let { loading, errorMsg, direntList } = this.props;
|
||||
|
||||
if (loading) {
|
||||
return <Loading />;
|
||||
}
|
||||
|
||||
if (errorMsg) {
|
||||
return <p className="error text-center mt-4">{errorMsg}</p>;
|
||||
}
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<table className="table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="5%">{/*icon*/}</th>
|
||||
<th width="55%">{gettext('Name')}</th>
|
||||
<th width="10%">{/*operation*/}</th>
|
||||
<th width="15%">{gettext('Size')}</th>
|
||||
<th width="15%">{gettext('Last Update')}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{direntList.map((dirent, index) => {
|
||||
return <DirentItem
|
||||
key={index}
|
||||
dirent={dirent}
|
||||
openFolder={this.props.openFolder}
|
||||
deleteDirent={this.props.deleteDirent}
|
||||
downloadDirent={this.props.downloadDirent}
|
||||
fromSystemRepo={this.props.fromSystemRepo}
|
||||
/>;
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
DirContent.propTypes = propTypes;
|
||||
|
||||
export default DirContent;
|
70
frontend/src/pages/share-with-ocm/remote-dir-path.js
Normal file
70
frontend/src/pages/share-with-ocm/remote-dir-path.js
Normal file
@@ -0,0 +1,70 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Link } from '@reach/router';
|
||||
import { siteRoot, gettext } from '../../utils/constants';
|
||||
import { Utils } from '../../utils/utils';
|
||||
|
||||
const propTypes = {
|
||||
repoName: PropTypes.string.isRequired,
|
||||
currentPath: PropTypes.string.isRequired,
|
||||
onPathClick: PropTypes.func.isRequired,
|
||||
onTabNavClick: PropTypes.func.isRequired,
|
||||
repoID: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
class DirPath extends React.Component {
|
||||
|
||||
onPathClick = (e) => {
|
||||
let path = Utils.getEventData(e, 'path');
|
||||
this.props.onPathClick(path);
|
||||
}
|
||||
|
||||
turnPathToLink = (path) => {
|
||||
path = path[path.length - 1] === '/' ? path.slice(0, path.length - 1) : path;
|
||||
let pathList = path.split('/');
|
||||
let nodePath = '';
|
||||
let pathElem = pathList.map((item, index) => {
|
||||
if (item === '') {
|
||||
return;
|
||||
}
|
||||
if (index === (pathList.length - 1)) {
|
||||
return (
|
||||
<Fragment key={index}>
|
||||
<span className="path-split">/</span>
|
||||
<span className="path-file-name">{item}</span>
|
||||
</Fragment>
|
||||
);
|
||||
} else {
|
||||
nodePath += '/' + item;
|
||||
return (
|
||||
<Fragment key={index} >
|
||||
<span className="path-split">/</span>
|
||||
<a className="path-link" data-path={nodePath} onClick={this.onPathClick}>{item}</a>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
});
|
||||
return pathElem;
|
||||
}
|
||||
|
||||
render() {
|
||||
let { currentPath, repoName } = this.props;
|
||||
let pathElem = this.turnPathToLink(currentPath);
|
||||
|
||||
return (
|
||||
<div className="path-container">
|
||||
<Link to={siteRoot + 'shared-with-ocm/'} className="normal" onClick={(e) => this.props.onTabNavClick('shared-with-ocm')}>{gettext('All')}</Link>
|
||||
<span className="path-split">/</span>
|
||||
{(currentPath === '/' || currentPath === '') ?
|
||||
<span className="path-repo-name">{repoName}</span>:
|
||||
<a className="path-link" data-path="/" onClick={this.onPathClick}>{repoName}</a>
|
||||
}
|
||||
{pathElem}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
DirPath.propTypes = propTypes;
|
||||
|
||||
export default DirPath;
|
30
frontend/src/pages/share-with-ocm/remote-dir-topbar.js
Normal file
30
frontend/src/pages/share-with-ocm/remote-dir-topbar.js
Normal file
@@ -0,0 +1,30 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import Account from '../../components/common/account';
|
||||
|
||||
const propTypes = {
|
||||
children: PropTypes.object
|
||||
};
|
||||
|
||||
class MainPanelTopbar extends Component {
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className={`main-panel-north ${this.props.children ? 'border-left-show' : ''}`}>
|
||||
<div className="cur-view-toolbar">
|
||||
<span className="sf2-icon-menu side-nav-toggle hidden-md-up d-md-none" title="Side Nav Menu"></span>
|
||||
<div className="operation">
|
||||
{this.props.children}
|
||||
</div>
|
||||
</div>
|
||||
<div className="common-toolbar">
|
||||
<Account isAdminPanel={false} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
MainPanelTopbar.propTypes = propTypes;
|
||||
|
||||
export default MainPanelTopbar;
|
188
frontend/src/pages/share-with-ocm/remote-dir-view.js
Normal file
188
frontend/src/pages/share-with-ocm/remote-dir-view.js
Normal file
@@ -0,0 +1,188 @@
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import { Button } from 'reactstrap';
|
||||
import { post } from 'axios';
|
||||
import { Utils } from '../../utils/utils';
|
||||
import { seafileAPI } from '../../utils/seafile-api';
|
||||
import { loginUrl, siteRoot, gettext } from '../../utils/constants';
|
||||
import toaster from '../../components/toast';
|
||||
import MainPanelTopbar from './remote-dir-topbar';
|
||||
import DirPathBar from './remote-dir-path';
|
||||
import DirContent from './remote-dir-content';
|
||||
|
||||
class Dirent {
|
||||
constructor(obj) {
|
||||
this.name = obj.name;
|
||||
this.mtime = obj.mtime;
|
||||
this.size = obj.size;
|
||||
this.is_file = obj.type === 'file';
|
||||
}
|
||||
|
||||
isDir() {
|
||||
return !this.is_file;
|
||||
}
|
||||
}
|
||||
|
||||
class DirView extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.fileInput = React.createRef();
|
||||
this.state = {
|
||||
loading: true,
|
||||
errorMsg: '',
|
||||
repoName: '',
|
||||
path: '',
|
||||
direntList: [],
|
||||
isNewFolderDialogOpen: false,
|
||||
userPerm: '',
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
this.loadDirentList('/');
|
||||
}
|
||||
|
||||
onPathClick = (path) => {
|
||||
this.loadDirentList(path);
|
||||
}
|
||||
|
||||
openFolder = (dirent) => {
|
||||
let direntPath = Utils.joinPath(this.state.path, dirent.name);
|
||||
if (!dirent.is_file) {
|
||||
this.loadDirentList(direntPath);
|
||||
}
|
||||
}
|
||||
|
||||
loadDirentList = (path) => {
|
||||
const { providerID, repoID } = this.props;
|
||||
seafileAPI.listOCMRepoDir(providerID, repoID, path).then(res => {
|
||||
const { repo_name: repoName, dirent_list, user_perm } = res.data;
|
||||
let direntList = [];
|
||||
dirent_list.forEach(item => {
|
||||
let dirent = new Dirent(item);
|
||||
direntList.push(dirent);
|
||||
});
|
||||
this.setState({
|
||||
loading: false,
|
||||
repoName: repoName,
|
||||
direntList: direntList,
|
||||
path: path,
|
||||
userPerm: user_perm,
|
||||
}, () => {
|
||||
let url =`${siteRoot}remote-library/${providerID}/${repoID}/${repoName}${Utils.encodePath(path)}`;
|
||||
window.history.replaceState({url: url, path: path}, path, url);
|
||||
});
|
||||
}).catch((error) => {
|
||||
if (error.response) {
|
||||
if (error.response.status == 403) {
|
||||
this.setState({
|
||||
loading: false,
|
||||
errorMsg: gettext('Permission denied')
|
||||
});
|
||||
location.href = `${loginUrl}?next=${encodeURIComponent(location.href)}`;
|
||||
} else {
|
||||
this.setState({
|
||||
loading: false,
|
||||
errorMsg: gettext('Error')
|
||||
});
|
||||
}
|
||||
} else {
|
||||
this.setState({
|
||||
loading: false,
|
||||
errorMsg: gettext('Please check the network.')
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
downloadDirent = (dirent) => {
|
||||
let path = Utils.joinPath(this.state.path, dirent.name);
|
||||
seafileAPI.getOCMRepoDownloadURL(this.props.providerID, this.props.repoID, path).then(res => {
|
||||
location.href = res.data;
|
||||
}).catch((err) => {
|
||||
let errMessage = Utils.getErrorMsg(err);
|
||||
toaster.danger(errMessage);
|
||||
});
|
||||
}
|
||||
|
||||
openFileInput = () => {
|
||||
this.fileInput.current.click();
|
||||
}
|
||||
|
||||
onFileInputChange = () => {
|
||||
if (!this.fileInput.current.files.length) {
|
||||
return;
|
||||
}
|
||||
const file = this.fileInput.current.files[0];
|
||||
|
||||
let { path } = this.state;
|
||||
let { providerID, repoID } = this.props;
|
||||
seafileAPI.getOCMRepoUploadURL(providerID, repoID, path).then(res => {
|
||||
let formData = new FormData();
|
||||
formData.append('parent_dir', path);
|
||||
formData.append('file', file);
|
||||
post(res.data, formData).then(res => {
|
||||
const fileObj = res.data[0];
|
||||
let newDirent = new Dirent({
|
||||
'type': 'file',
|
||||
'name': fileObj.name,
|
||||
'size': fileObj.size,
|
||||
'mtime': (new Date()).getTime()
|
||||
});
|
||||
let direntList = this.state.direntList;
|
||||
const dirs = direntList.filter(item => { return !item.is_file; });
|
||||
direntList.splice(dirs.length, 0, newDirent);
|
||||
this.setState({
|
||||
direntList: direntList
|
||||
});
|
||||
});
|
||||
}).catch((err) => {
|
||||
let errMessage = Utils.getErrorMsg(err);
|
||||
toaster.danger(errMessage);
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const { loading, errorMsg,
|
||||
repoName, direntList, path, userPerm } = this.state;
|
||||
const { repoID } = this.props;
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<MainPanelTopbar>
|
||||
<Fragment>
|
||||
<input className="d-none" type="file" onChange={this.onFileInputChange} ref={this.fileInput} />
|
||||
{userPerm === 'rw' &&
|
||||
<Button className="operation-item" onClick={this.openFileInput}>{gettext('Upload')}</Button>
|
||||
}
|
||||
</Fragment>
|
||||
</MainPanelTopbar>
|
||||
<div className="main-panel-center flex-row">
|
||||
<div className="cur-view-container">
|
||||
<div className="cur-view-path align-items-center">
|
||||
<DirPathBar
|
||||
repoID={repoID}
|
||||
repoName={repoName}
|
||||
currentPath={path}
|
||||
onPathClick={this.onPathClick}
|
||||
onTabNavClick={this.props.onTabNavClick}
|
||||
/>
|
||||
</div>
|
||||
<div className="cur-view-content">
|
||||
<DirContent
|
||||
loading={loading}
|
||||
errorMsg={errorMsg}
|
||||
direntList={direntList}
|
||||
openFolder={this.openFolder}
|
||||
deleteDirent={this.deleteDirent}
|
||||
downloadDirent={this.downloadDirent}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default DirView;
|
203
frontend/src/pages/share-with-ocm/shared-with-ocm.js
Normal file
203
frontend/src/pages/share-with-ocm/shared-with-ocm.js
Normal file
@@ -0,0 +1,203 @@
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import moment from 'moment';
|
||||
import { Link } from '@reach/router';
|
||||
import { gettext, siteRoot } from '../../utils/constants';
|
||||
import { seafileAPI } from '../../utils/seafile-api';
|
||||
import { Utils } from '../../utils/utils';
|
||||
import toaster from '../../components/toast';
|
||||
import Loading from '../../components/loading';
|
||||
import EmptyTip from '../../components/empty-tip';
|
||||
|
||||
class Content extends Component {
|
||||
|
||||
render() {
|
||||
const { loading, errorMsg, items } = this.props;
|
||||
|
||||
const emptyTip = (
|
||||
<EmptyTip>
|
||||
<h2>{gettext('No libraries have been shared with you')}</h2>
|
||||
<p>{gettext('No libraries have been shared directly with you. You can find more shared libraries at "Shared with groups".')}</p>
|
||||
</EmptyTip>
|
||||
);
|
||||
|
||||
if (loading) {
|
||||
return <Loading />;
|
||||
} else if (errorMsg) {
|
||||
return <p className="error text-center">{errorMsg}</p>;
|
||||
} else {
|
||||
const table = (
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="4%"></th>
|
||||
<th width="20%">{gettext('Name')}</th>
|
||||
<th width="20%">{gettext('Shared from')}</th>
|
||||
<th width="26%">{gettext('At site')}</th>
|
||||
<th width="20%">{gettext('Time')}</th>
|
||||
<th width="10%">{/* operations */}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{items.map((item, index) => {
|
||||
return <Item
|
||||
key={index}
|
||||
item={item}
|
||||
deleteShare={this.props.deleteShare}
|
||||
/>;
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
);
|
||||
|
||||
return items.length ? table : emptyTip;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Content.propTypes = {
|
||||
loading: PropTypes.bool.isRequired,
|
||||
errorMsg: PropTypes.string.isRequired,
|
||||
items: PropTypes.array.isRequired,
|
||||
};
|
||||
|
||||
class Item extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
showOpIcon: false,
|
||||
isOpMenuOpen: false // for mobile
|
||||
};
|
||||
}
|
||||
|
||||
toggleOpMenu = () => {
|
||||
this.setState({
|
||||
isOpMenuOpen: !this.state.isOpMenuOpen
|
||||
});
|
||||
}
|
||||
|
||||
handleMouseOver = () => {
|
||||
this.setState({
|
||||
showOpIcon: true
|
||||
});
|
||||
}
|
||||
|
||||
handleMouseOut = () => {
|
||||
this.setState({
|
||||
showOpIcon: false
|
||||
});
|
||||
}
|
||||
|
||||
deleteShare = () => {
|
||||
this.props.deleteShare(this.props.item);
|
||||
}
|
||||
|
||||
render() {
|
||||
const item = this.props.item;
|
||||
|
||||
item.icon_url = Utils.getLibIconUrl(item);
|
||||
item.icon_title = Utils.getLibIconTitle(item);
|
||||
item.url = `${siteRoot}#shared-libs/lib/${item.repo_id}/`;
|
||||
|
||||
let shareRepoUrl =`${siteRoot}remote-library/${this.props.item.provider_id}/${this.props.item.repo_id}/${Utils.encodePath(this.props.item.repo_name)}/`;
|
||||
let iconVisibility = this.state.showOpIcon ? '' : ' invisible';
|
||||
let deleteIcon = `action-icon sf2-icon-x3 ${iconVisibility ? 'invisible' : ''}`;
|
||||
return (
|
||||
<Fragment>
|
||||
<tr onMouseOver={this.handleMouseOver} onMouseOut={this.handleMouseOut}>
|
||||
<td><img src={item.icon_url} title={item.icon_title} alt={item.icon_title} width="24" /></td>
|
||||
<td><Link to={shareRepoUrl}>{item.repo_name}</Link></td>
|
||||
<td>{item.from_user}</td>
|
||||
<td>{item.from_server_url}</td>
|
||||
<td title={moment(item.last_modified).format('llll')}>{moment(item.ctime).fromNow()}</td>
|
||||
<td>
|
||||
<a href="#" className={deleteIcon} title={gettext('Remove')} onClick={this.deleteShare}></a>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Item.propTypes = {
|
||||
item: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
class SharedWithOCM extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
loading: true,
|
||||
errorMsg: '',
|
||||
items: [],
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
seafileAPI.listOCMSharesReceived().then((res) => {
|
||||
this.setState({
|
||||
loading: false,
|
||||
items: res.data.ocm_share_received_list
|
||||
});
|
||||
}).catch((error) => {
|
||||
if (error.response) {
|
||||
if (error.response.status == 403) {
|
||||
this.setState({
|
||||
loading: false,
|
||||
errorMsg: gettext('Permission denied')
|
||||
});
|
||||
} else {
|
||||
this.setState({
|
||||
loading: false,
|
||||
errorMsg: gettext('Error')
|
||||
});
|
||||
}
|
||||
} else {
|
||||
this.setState({
|
||||
loading: false,
|
||||
errorMsg: gettext('Please check the network.')
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
deleteShare = (item) => {
|
||||
let { id } = item;
|
||||
seafileAPI.deleteOCMShareReceived(id).then((res) => {
|
||||
toaster.success(gettext('delete success.'));
|
||||
let items = this.state.items.filter(item => {
|
||||
return item.id != id;
|
||||
});
|
||||
this.setState({items: items});
|
||||
}).catch(error => {
|
||||
let errMessage = Utils.getErrorMsg(error);
|
||||
toaster.danger(errMessage);
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Fragment>
|
||||
<div className="main-panel-center">
|
||||
<div className="cur-view-container">
|
||||
<div className="cur-view-path">
|
||||
<h3 className="sf-heading m-0">{gettext('Shared from other servers')}</h3>
|
||||
</div>
|
||||
<div className="cur-view-content">
|
||||
<Content
|
||||
loading={this.state.loading}
|
||||
errorMsg={this.state.errorMsg}
|
||||
items={this.state.items}
|
||||
deleteShare={this.deleteShare}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default SharedWithOCM;
|
Reference in New Issue
Block a user