mirror of
https://github.com/haiwen/seahub.git
synced 2025-09-06 17:33:18 +00:00
clean orphan share/upload links (#5383)
* clean orphan share/upload links * update style * [share admin - links] fixup & improvements for 'deleted' tip & 'clean orphan links' --------- Co-authored-by: llj <lingjun.li1@gmail.com>
This commit is contained in:
@@ -45,8 +45,6 @@ const SharedWithOCMWrapper = MainContentWrapper(ShareWithOCM);
|
|||||||
const OCMViaWebdavWrapper = MainContentWrapper(OCMViaWebdav);
|
const OCMViaWebdavWrapper = MainContentWrapper(OCMViaWebdav);
|
||||||
const ShareAdminLibrariesWrapper = MainContentWrapper(ShareAdminLibraries);
|
const ShareAdminLibrariesWrapper = MainContentWrapper(ShareAdminLibraries);
|
||||||
const ShareAdminFoldersWrapper = MainContentWrapper(ShareAdminFolders);
|
const ShareAdminFoldersWrapper = MainContentWrapper(ShareAdminFolders);
|
||||||
const ShareAdminShareLinksWrapper = MainContentWrapper(ShareAdminShareLinks);
|
|
||||||
const ShareAdminUploadLinksWrapper = MainContentWrapper(ShareAdminUploadLinks);
|
|
||||||
|
|
||||||
class App extends Component {
|
class App extends Component {
|
||||||
|
|
||||||
@@ -259,8 +257,8 @@ class App extends Component {
|
|||||||
<LinkedDevicesWrapper path={siteRoot + 'linked-devices'} onShowSidePanel={this.onShowSidePanel} onSearchedClick={this.onSearchedClick} />
|
<LinkedDevicesWrapper path={siteRoot + 'linked-devices'} onShowSidePanel={this.onShowSidePanel} onSearchedClick={this.onSearchedClick} />
|
||||||
<ShareAdminLibrariesWrapper path={siteRoot + 'share-admin-libs'} onShowSidePanel={this.onShowSidePanel} onSearchedClick={this.onSearchedClick} />
|
<ShareAdminLibrariesWrapper path={siteRoot + 'share-admin-libs'} onShowSidePanel={this.onShowSidePanel} onSearchedClick={this.onSearchedClick} />
|
||||||
<ShareAdminFoldersWrapper path={siteRoot + 'share-admin-folders'} onShowSidePanel={this.onShowSidePanel} onSearchedClick={this.onSearchedClick} />
|
<ShareAdminFoldersWrapper path={siteRoot + 'share-admin-folders'} onShowSidePanel={this.onShowSidePanel} onSearchedClick={this.onSearchedClick} />
|
||||||
<ShareAdminShareLinksWrapper path={siteRoot + 'share-admin-share-links'} onShowSidePanel={this.onShowSidePanel} onSearchedClick={this.onSearchedClick} />
|
<ShareAdminShareLinks path={siteRoot + 'share-admin-share-links'} onShowSidePanel={this.onShowSidePanel} onSearchedClick={this.onSearchedClick} />
|
||||||
<ShareAdminUploadLinksWrapper path={siteRoot + 'share-admin-upload-links'} onShowSidePanel={this.onShowSidePanel} onSearchedClick={this.onSearchedClick} />
|
<ShareAdminUploadLinks path={siteRoot + 'share-admin-upload-links'} onShowSidePanel={this.onShowSidePanel} onSearchedClick={this.onSearchedClick} />
|
||||||
<SharedLibrariesWrapper path={siteRoot + 'shared-libs'} onShowSidePanel={this.onShowSidePanel} onSearchedClick={this.onSearchedClick} />
|
<SharedLibrariesWrapper path={siteRoot + 'shared-libs'} onShowSidePanel={this.onShowSidePanel} onSearchedClick={this.onSearchedClick} />
|
||||||
<SharedWithOCMWrapper path={siteRoot + 'shared-with-ocm'} onShowSidePanel={this.onShowSidePanel} onSearchedClick={this.onSearchedClick} />
|
<SharedWithOCMWrapper path={siteRoot + 'shared-with-ocm'} onShowSidePanel={this.onShowSidePanel} onSearchedClick={this.onSearchedClick} />
|
||||||
<OCMViaWebdavWrapper path={siteRoot + 'ocm-via-webdav'} onShowSidePanel={this.onShowSidePanel} onSearchedClick={this.onSearchedClick} />
|
<OCMViaWebdavWrapper path={siteRoot + 'ocm-via-webdav'} onShowSidePanel={this.onShowSidePanel} onSearchedClick={this.onSearchedClick} />
|
||||||
|
31
frontend/src/components/toolbar/top-toolbar.js
Normal file
31
frontend/src/components/toolbar/top-toolbar.js
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import CommonToolbar from './common-toolbar';
|
||||||
|
|
||||||
|
const propTypes = {
|
||||||
|
onShowSidePanel: PropTypes.func.isRequired,
|
||||||
|
onSearchedClick: PropTypes.func.isRequired,
|
||||||
|
searchPlaceholder: PropTypes.string,
|
||||||
|
children: PropTypes.object
|
||||||
|
};
|
||||||
|
|
||||||
|
class TopToolbar extends React.Component {
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { onShowSidePanel, onSearchedClick } = this.props;
|
||||||
|
return (
|
||||||
|
<div className="main-panel-north border-left-show">
|
||||||
|
<div className="cur-view-toolbar">
|
||||||
|
<span title="Side Nav Menu" onClick={onShowSidePanel} className="sf2-icon-menu side-nav-toggle hidden-md-up d-md-none">
|
||||||
|
</span>
|
||||||
|
{this.props.children}
|
||||||
|
</div>
|
||||||
|
<CommonToolbar searchPlaceholder={this.props.searchPlaceholder} onSearchedClick={onSearchedClick} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TopToolbar.propTypes = propTypes;
|
||||||
|
|
||||||
|
export default TopToolbar;
|
@@ -5,6 +5,7 @@ class ShareLink {
|
|||||||
this.repo_name = object.repo_name;
|
this.repo_name = object.repo_name;
|
||||||
this.path = object.path;
|
this.path = object.path;
|
||||||
this.obj_name = object.obj_name;
|
this.obj_name = object.obj_name;
|
||||||
|
this.obj_id = object.obj_id;
|
||||||
this.is_dir = object.is_dir;
|
this.is_dir = object.is_dir;
|
||||||
this.can_edit = object.can_edit;
|
this.can_edit = object.can_edit;
|
||||||
this.repo_folder_permission = object.repo_folder_permission;
|
this.repo_folder_permission = object.repo_folder_permission;
|
||||||
|
@@ -6,6 +6,7 @@ class UploadLink {
|
|||||||
this.path = object.path;
|
this.path = object.path;
|
||||||
this.link = object.link;
|
this.link = object.link;
|
||||||
this.obj_name = object.obj_name;
|
this.obj_name = object.obj_name;
|
||||||
|
this.obj_id = object.obj_id;
|
||||||
this.username = object.username;
|
this.username = object.username;
|
||||||
this.ctime = object.ctime;
|
this.ctime = object.ctime;
|
||||||
this.token = object.token;
|
this.token = object.token;
|
||||||
|
@@ -2,7 +2,7 @@ import React, { Component, Fragment } from 'react';
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { Link } from '@gatsbyjs/reach-router';
|
import { Link } from '@gatsbyjs/reach-router';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import { Dropdown, DropdownToggle, DropdownItem } from 'reactstrap';
|
import { Dropdown, DropdownToggle, DropdownItem, Button } from 'reactstrap';
|
||||||
import { seafileAPI } from '../../utils/seafile-api';
|
import { seafileAPI } from '../../utils/seafile-api';
|
||||||
import { Utils } from '../../utils/utils';
|
import { Utils } from '../../utils/utils';
|
||||||
import { isPro, gettext, siteRoot, canGenerateUploadLink } from '../../utils/constants';
|
import { isPro, gettext, siteRoot, canGenerateUploadLink } from '../../utils/constants';
|
||||||
@@ -14,6 +14,18 @@ import EmptyTip from '../../components/empty-tip';
|
|||||||
import ShareLinkPermissionSelect from '../../components/dialog/share-link-permission-select';
|
import ShareLinkPermissionSelect from '../../components/dialog/share-link-permission-select';
|
||||||
import ShareAdminLink from '../../components/dialog/share-admin-link';
|
import ShareAdminLink from '../../components/dialog/share-admin-link';
|
||||||
import SortOptionsDialog from '../../components/dialog/sort-options';
|
import SortOptionsDialog from '../../components/dialog/sort-options';
|
||||||
|
import CommonOperationConfirmationDialog from '../../components/dialog/common-operation-confirmation-dialog';
|
||||||
|
import TopToolbar from '../../components/toolbar/top-toolbar';
|
||||||
|
|
||||||
|
const contentPropTypes = {
|
||||||
|
loading: PropTypes.bool.isRequired,
|
||||||
|
errorMsg: PropTypes.string.isRequired,
|
||||||
|
items: PropTypes.array.isRequired,
|
||||||
|
sortBy: PropTypes.string.isRequired,
|
||||||
|
sortOrder: PropTypes.string.isRequired,
|
||||||
|
sortItems: PropTypes.func.isRequired,
|
||||||
|
onRemoveLink: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
class Content extends Component {
|
class Content extends Component {
|
||||||
|
|
||||||
@@ -88,6 +100,8 @@ class Content extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Content.propTypes = contentPropTypes;
|
||||||
|
|
||||||
const itemPropTypes = {
|
const itemPropTypes = {
|
||||||
item: PropTypes.object.isRequired,
|
item: PropTypes.object.isRequired,
|
||||||
isDesktop: PropTypes.bool.isRequired,
|
isDesktop: PropTypes.bool.isRequired,
|
||||||
@@ -202,6 +216,7 @@ class Item extends Component {
|
|||||||
objUrl = `${siteRoot}lib/${item.repo_id}/file${Utils.encodePath(item.path)}`;
|
objUrl = `${siteRoot}lib/${item.repo_id}/file${Utils.encodePath(item.path)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const deletedTip = item.obj_id === '' ? <span style={{color:'red'}}>{gettext('(deleted)')}</span> : null;
|
||||||
const desktopItem = (
|
const desktopItem = (
|
||||||
<tr onMouseEnter={this.handleMouseOver} onMouseLeave={this.handleMouseOut} onFocus={this.handleMouseOver}>
|
<tr onMouseEnter={this.handleMouseOver} onMouseLeave={this.handleMouseOut} onFocus={this.handleMouseOver}>
|
||||||
<td><img src={iconUrl} width="24" alt="" /></td>
|
<td><img src={iconUrl} width="24" alt="" /></td>
|
||||||
@@ -210,6 +225,7 @@ class Item extends Component {
|
|||||||
<Link to={objUrl}>{item.obj_name}</Link> :
|
<Link to={objUrl}>{item.obj_name}</Link> :
|
||||||
<a href={objUrl} target="_blank" rel="noreferrer">{item.obj_name}</a>
|
<a href={objUrl} target="_blank" rel="noreferrer">{item.obj_name}</a>
|
||||||
}
|
}
|
||||||
|
{deletedTip}
|
||||||
</td>
|
</td>
|
||||||
<td><Link to={`${siteRoot}library/${item.repo_id}/${encodeURIComponent(item.repo_name)}/`}>{item.repo_name}</Link></td>
|
<td><Link to={`${siteRoot}library/${item.repo_id}/${encodeURIComponent(item.repo_name)}/`}>{item.repo_name}</Link></td>
|
||||||
{isPro &&
|
{isPro &&
|
||||||
@@ -294,11 +310,17 @@ class Item extends Component {
|
|||||||
|
|
||||||
Item.propTypes = itemPropTypes;
|
Item.propTypes = itemPropTypes;
|
||||||
|
|
||||||
|
const propTypes = {
|
||||||
|
onShowSidePanel: PropTypes.func.isRequired,
|
||||||
|
onSearchedClick: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
class ShareAdminShareLinks extends Component {
|
class ShareAdminShareLinks extends Component {
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
|
isCleanOrphanShareLinksDialogOpen: false,
|
||||||
loading: true,
|
loading: true,
|
||||||
errorMsg: '',
|
errorMsg: '',
|
||||||
items: [],
|
items: [],
|
||||||
@@ -366,6 +388,10 @@ class ShareAdminShareLinks extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
|
this.listUserShareLinks();
|
||||||
|
}
|
||||||
|
|
||||||
|
listUserShareLinks() {
|
||||||
seafileAPI.listUserShareLinks().then((res) => {
|
seafileAPI.listUserShareLinks().then((res) => {
|
||||||
let items = res.data.map(item => {
|
let items = res.data.map(item => {
|
||||||
return new ShareLink(item);
|
return new ShareLink(item);
|
||||||
@@ -402,9 +428,31 @@ class ShareAdminShareLinks extends Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
toggleCleanOrphanShareLinksDialog = () => {
|
||||||
|
this.setState({isCleanOrphanShareLinksDialogOpen: !this.state.isCleanOrphanShareLinksDialogOpen});
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanOrphanShareLinks = () => {
|
||||||
|
seafileAPI.cleanOrphanShareLinks().then(res => {
|
||||||
|
const newItems = this.state.items.filter(item => item.obj_id !== '');
|
||||||
|
this.setState({items: newItems});
|
||||||
|
toaster.success(gettext('Successfully cleaned orphan share links.'));
|
||||||
|
}).catch(error => {
|
||||||
|
let errMessage = Utils.getErrorMsg(error);
|
||||||
|
toaster.danger(errMessage);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
|
<TopToolbar
|
||||||
|
onShowSidePanel={this.props.onShowSidePanel}
|
||||||
|
onSearchedClick={this.props.onSearchedClick}
|
||||||
|
>
|
||||||
|
|
||||||
|
<Button className="operation-item d-none d-md-block" onClick={this.toggleCleanOrphanShareLinksDialog}>{gettext('Clean orphan share links')}</Button>
|
||||||
|
</TopToolbar>
|
||||||
<div className="main-panel-center">
|
<div className="main-panel-center">
|
||||||
<div className="cur-view-container">
|
<div className="cur-view-container">
|
||||||
<div className="cur-view-path share-upload-nav">
|
<div className="cur-view-path share-upload-nav">
|
||||||
@@ -440,9 +488,20 @@ class ShareAdminShareLinks extends Component {
|
|||||||
sortItems={this.sortItems}
|
sortItems={this.sortItems}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
|
{this.state.isCleanOrphanShareLinksDialogOpen &&
|
||||||
|
<CommonOperationConfirmationDialog
|
||||||
|
title={gettext('Clean orphan share links')}
|
||||||
|
message={gettext('Are you sure you want to clean orphan share links?')}
|
||||||
|
executeOperation={this.cleanOrphanShareLinks}
|
||||||
|
confirmBtnText={gettext('Clean')}
|
||||||
|
toggleDialog={this.toggleCleanOrphanShareLinksDialog}
|
||||||
|
/>
|
||||||
|
}
|
||||||
</Fragment>
|
</Fragment>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ShareAdminShareLinks.propTypes = propTypes;
|
||||||
|
|
||||||
export default ShareAdminShareLinks;
|
export default ShareAdminShareLinks;
|
||||||
|
@@ -1,7 +1,8 @@
|
|||||||
import React, { Component, Fragment } from 'react';
|
import React, { Component, Fragment } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
import { Link } from '@gatsbyjs/reach-router';
|
import { Link } from '@gatsbyjs/reach-router';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import { Dropdown, DropdownToggle, DropdownItem } from 'reactstrap';
|
import { Dropdown, DropdownToggle, DropdownItem, Button } from 'reactstrap';
|
||||||
import { gettext, siteRoot, canGenerateShareLink } from '../../utils/constants';
|
import { gettext, siteRoot, canGenerateShareLink } from '../../utils/constants';
|
||||||
import { seafileAPI } from '../../utils/seafile-api';
|
import { seafileAPI } from '../../utils/seafile-api';
|
||||||
import { Utils } from '../../utils/utils';
|
import { Utils } from '../../utils/utils';
|
||||||
@@ -10,6 +11,15 @@ import Loading from '../../components/loading';
|
|||||||
import EmptyTip from '../../components/empty-tip';
|
import EmptyTip from '../../components/empty-tip';
|
||||||
import UploadLink from '../../models/upload-link';
|
import UploadLink from '../../models/upload-link';
|
||||||
import ShareAdminLink from '../../components/dialog/share-admin-link';
|
import ShareAdminLink from '../../components/dialog/share-admin-link';
|
||||||
|
import CommonOperationConfirmationDialog from '../../components/dialog/common-operation-confirmation-dialog';
|
||||||
|
import TopToolbar from '../../components/toolbar/top-toolbar';
|
||||||
|
|
||||||
|
const contentPropTypes = {
|
||||||
|
loading: PropTypes.bool.isRequired,
|
||||||
|
errorMsg: PropTypes.string.isRequired,
|
||||||
|
items: PropTypes.array.isRequired,
|
||||||
|
onRemoveLink: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
class Content extends Component {
|
class Content extends Component {
|
||||||
|
|
||||||
@@ -63,6 +73,14 @@ class Content extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Content.propTypes = contentPropTypes;
|
||||||
|
|
||||||
|
const itemPropTypes = {
|
||||||
|
isDesktop: PropTypes.bool.isRequired,
|
||||||
|
item: PropTypes.object.isRequired,
|
||||||
|
onRemoveLink: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
class Item extends Component {
|
class Item extends Component {
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
@@ -122,10 +140,11 @@ class Item extends Component {
|
|||||||
const repoUrl = `${siteRoot}library/${item.repo_id}/${encodeURIComponent(item.repo_name)}`;
|
const repoUrl = `${siteRoot}library/${item.repo_id}/${encodeURIComponent(item.repo_name)}`;
|
||||||
const objUrl = `${repoUrl}${Utils.encodePath(item.path)}`;
|
const objUrl = `${repoUrl}${Utils.encodePath(item.path)}`;
|
||||||
|
|
||||||
|
const deletedTip = item.obj_id === '' ? <span style={{color:'red'}}>{gettext('(deleted)')}</span> : null;
|
||||||
const desktopItem = (
|
const desktopItem = (
|
||||||
<tr onMouseOver={this.handleMouseOver} onMouseOut={this.handleMouseOut} onFocus={this.handleMouseOver}>
|
<tr onMouseOver={this.handleMouseOver} onMouseOut={this.handleMouseOut} onFocus={this.handleMouseOver}>
|
||||||
<td><img src={iconUrl} alt="" width="24" /></td>
|
<td><img src={iconUrl} alt="" width="24" /></td>
|
||||||
<td><Link to={objUrl}>{item.obj_name}</Link></td>
|
<td><Link to={objUrl}>{item.obj_name}</Link>{deletedTip}</td>
|
||||||
<td><Link to={repoUrl}>{item.repo_name}</Link></td>
|
<td><Link to={repoUrl}>{item.repo_name}</Link></td>
|
||||||
<td>{item.view_cnt}</td>
|
<td>{item.view_cnt}</td>
|
||||||
<td>{this.renderExpiration()}</td>
|
<td>{this.renderExpiration()}</td>
|
||||||
@@ -180,11 +199,19 @@ class Item extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Item.propTypes = itemPropTypes;
|
||||||
|
|
||||||
|
const propTypes = {
|
||||||
|
onShowSidePanel: PropTypes.func.isRequired,
|
||||||
|
onSearchedClick: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
class ShareAdminUploadLinks extends Component {
|
class ShareAdminUploadLinks extends Component {
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
|
isCleanOrphanUploadLinksDialogOpen: false,
|
||||||
loading: true,
|
loading: true,
|
||||||
errorMsg: '',
|
errorMsg: '',
|
||||||
items: []
|
items: []
|
||||||
@@ -192,6 +219,10 @@ class ShareAdminUploadLinks extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
|
this.listUserUploadLinks();
|
||||||
|
}
|
||||||
|
|
||||||
|
listUserUploadLinks() {
|
||||||
seafileAPI.listUserUploadLinks().then((res) => {
|
seafileAPI.listUserUploadLinks().then((res) => {
|
||||||
let items = res.data.map(item => {
|
let items = res.data.map(item => {
|
||||||
return new UploadLink(item);
|
return new UploadLink(item);
|
||||||
@@ -222,30 +253,64 @@ class ShareAdminUploadLinks extends Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
toggleCleanOrphanUploadLinksDialog = () => {
|
||||||
|
this.setState({isCleanOrphanUploadLinksDialogOpen: !this.state.isCleanOrphanUploadLinksDialogOpen});
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanOrphanUploadLinks = () => {
|
||||||
|
seafileAPI.cleanOrphanUploadLinks().then(res => {
|
||||||
|
const newItems = this.state.items.filter(item => item.obj_id !== '');
|
||||||
|
this.setState({items: newItems});
|
||||||
|
toaster.success(gettext('Successfully cleaned orphan upload links.'));
|
||||||
|
}).catch(error => {
|
||||||
|
let errMessage = Utils.getErrorMsg(error);
|
||||||
|
toaster.danger(errMessage);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div className="main-panel-center">
|
<Fragment>
|
||||||
<div className="cur-view-container">
|
<TopToolbar
|
||||||
<div className="cur-view-path share-upload-nav">
|
onShowSidePanel={this.props.onShowSidePanel}
|
||||||
<ul className="nav">
|
onSearchedClick={this.props.onSearchedClick}
|
||||||
{canGenerateShareLink && (
|
>
|
||||||
<li className="nav-item"><Link to={`${siteRoot}share-admin-share-links/`} className="nav-link">{gettext('Share Links')}</Link></li>
|
<Button className="operation-item d-none d-md-block" onClick={this.toggleCleanOrphanUploadLinksDialog}>{gettext('Clean orphan upload links')}</Button>
|
||||||
)}
|
</TopToolbar>
|
||||||
<li className="nav-item"><Link to={`${siteRoot}share-admin-upload-links/`} className="nav-link active">{gettext('Upload Links')}</Link></li>
|
<div className="main-panel-center">
|
||||||
</ul>
|
<div className="cur-view-container">
|
||||||
</div>
|
<div className="cur-view-path share-upload-nav">
|
||||||
<div className="cur-view-content">
|
<ul className="nav">
|
||||||
<Content
|
{canGenerateShareLink && (
|
||||||
loading={this.state.loading}
|
<li className="nav-item"><Link to={`${siteRoot}share-admin-share-links/`} className="nav-link">{gettext('Share Links')}</Link></li>
|
||||||
errorMsg={this.state.errorMsg}
|
)}
|
||||||
items={this.state.items}
|
<li className="nav-item"><Link to={`${siteRoot}share-admin-upload-links/`} className="nav-link active">{gettext('Upload Links')}</Link></li>
|
||||||
onRemoveLink={this.onRemoveLink}
|
</ul>
|
||||||
/>
|
</div>
|
||||||
|
<div className="cur-view-content">
|
||||||
|
<Content
|
||||||
|
loading={this.state.loading}
|
||||||
|
errorMsg={this.state.errorMsg}
|
||||||
|
items={this.state.items}
|
||||||
|
onRemoveLink={this.onRemoveLink}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
{this.state.isCleanOrphanUploadLinksDialogOpen &&
|
||||||
|
<CommonOperationConfirmationDialog
|
||||||
|
title={gettext('Clean orphan upload links')}
|
||||||
|
message={gettext('Are you sure you want to clean orphan upload links?')}
|
||||||
|
executeOperation={this.cleanOrphanUploadLinks}
|
||||||
|
confirmBtnText={gettext('Clean')}
|
||||||
|
toggleDialog={this.toggleCleanOrphanUploadLinksDialog}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
</Fragment>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ShareAdminUploadLinks.propTypes = propTypes;
|
||||||
|
|
||||||
export default ShareAdminUploadLinks;
|
export default ShareAdminUploadLinks;
|
||||||
|
@@ -73,6 +73,13 @@ def get_share_link_info(fileshare):
|
|||||||
else:
|
else:
|
||||||
obj_name = ''
|
obj_name = ''
|
||||||
|
|
||||||
|
if fileshare.s_type == 'd':
|
||||||
|
folder_path = normalize_dir_path(fileshare.path)
|
||||||
|
obj_id = seafile_api.get_dir_id_by_path(repo_id, folder_path)
|
||||||
|
else:
|
||||||
|
file_path = normalize_file_path(fileshare.path)
|
||||||
|
obj_id = seafile_api.get_file_id_by_path(repo_id, file_path)
|
||||||
|
|
||||||
if fileshare.expire_date:
|
if fileshare.expire_date:
|
||||||
expire_date = datetime_to_isoformat_timestr(fileshare.expire_date)
|
expire_date = datetime_to_isoformat_timestr(fileshare.expire_date)
|
||||||
else:
|
else:
|
||||||
@@ -89,6 +96,7 @@ def get_share_link_info(fileshare):
|
|||||||
|
|
||||||
data['path'] = path
|
data['path'] = path
|
||||||
data['obj_name'] = obj_name
|
data['obj_name'] = obj_name
|
||||||
|
data['obj_id'] = obj_id or ""
|
||||||
data['is_dir'] = True if fileshare.s_type == 'd' else False
|
data['is_dir'] = True if fileshare.s_type == 'd' else False
|
||||||
|
|
||||||
data['token'] = token
|
data['token'] = token
|
||||||
@@ -1327,3 +1335,37 @@ class ShareLinkRepoTagsTaggedFiles(APIView):
|
|||||||
filtered_tagged_files.append(tagged_file)
|
filtered_tagged_files.append(tagged_file)
|
||||||
|
|
||||||
return Response({'tagged_files': filtered_tagged_files})
|
return Response({'tagged_files': filtered_tagged_files})
|
||||||
|
|
||||||
|
|
||||||
|
class ShareLinksCleanOrphan(APIView):
|
||||||
|
|
||||||
|
authentication_classes = (TokenAuthentication, SessionAuthentication)
|
||||||
|
permission_classes = (IsAuthenticated, CanGenerateShareLink)
|
||||||
|
throttle_classes = (UserRateThrottle,)
|
||||||
|
|
||||||
|
def delete(self, request):
|
||||||
|
""" Clean orphan share links.
|
||||||
|
"""
|
||||||
|
|
||||||
|
username = request.user.username
|
||||||
|
share_links = FileShare.objects.filter(username=username)
|
||||||
|
|
||||||
|
for share_link in share_links:
|
||||||
|
|
||||||
|
repo_id = share_link.repo_id
|
||||||
|
if not seafile_api.get_repo(repo_id):
|
||||||
|
share_link.delete()
|
||||||
|
continue
|
||||||
|
|
||||||
|
if share_link.s_type == 'd':
|
||||||
|
folder_path = normalize_dir_path(share_link.path)
|
||||||
|
obj_id = seafile_api.get_dir_id_by_path(repo_id, folder_path)
|
||||||
|
else:
|
||||||
|
file_path = normalize_file_path(share_link.path)
|
||||||
|
obj_id = seafile_api.get_file_id_by_path(repo_id, file_path)
|
||||||
|
|
||||||
|
if not obj_id:
|
||||||
|
share_link.delete()
|
||||||
|
continue
|
||||||
|
|
||||||
|
return Response({'success': True})
|
||||||
|
@@ -27,6 +27,7 @@ from seahub.api2.permissions import CanGenerateUploadLink
|
|||||||
from seahub.share.models import UploadLinkShare, check_share_link_common
|
from seahub.share.models import UploadLinkShare, check_share_link_common
|
||||||
from seahub.utils import gen_shared_upload_link, gen_file_upload_url, \
|
from seahub.utils import gen_shared_upload_link, gen_file_upload_url, \
|
||||||
is_pro_version, get_password_strength_level, is_valid_password
|
is_pro_version, get_password_strength_level, is_valid_password
|
||||||
|
|
||||||
from seahub.views import check_folder_permission
|
from seahub.views import check_folder_permission
|
||||||
from seahub.utils.timeutils import datetime_to_isoformat_timestr
|
from seahub.utils.timeutils import datetime_to_isoformat_timestr
|
||||||
|
|
||||||
@@ -54,6 +55,8 @@ def get_upload_link_info(uls):
|
|||||||
else:
|
else:
|
||||||
obj_name = ''
|
obj_name = ''
|
||||||
|
|
||||||
|
obj_id = seafile_api.get_dir_id_by_path(repo_id, path)
|
||||||
|
|
||||||
if uls.ctime:
|
if uls.ctime:
|
||||||
ctime = datetime_to_isoformat_timestr(uls.ctime)
|
ctime = datetime_to_isoformat_timestr(uls.ctime)
|
||||||
else:
|
else:
|
||||||
@@ -68,6 +71,7 @@ def get_upload_link_info(uls):
|
|||||||
data['repo_name'] = repo.repo_name if repo else ''
|
data['repo_name'] = repo.repo_name if repo else ''
|
||||||
data['path'] = path
|
data['path'] = path
|
||||||
data['obj_name'] = obj_name
|
data['obj_name'] = obj_name
|
||||||
|
data['obj_id'] = obj_id or ""
|
||||||
data['view_cnt'] = uls.view_cnt
|
data['view_cnt'] = uls.view_cnt
|
||||||
data['ctime'] = ctime
|
data['ctime'] = ctime
|
||||||
data['link'] = gen_shared_upload_link(token)
|
data['link'] = gen_shared_upload_link(token)
|
||||||
@@ -475,3 +479,30 @@ class UploadLinkUpload(APIView):
|
|||||||
result = {}
|
result = {}
|
||||||
result['upload_link'] = gen_file_upload_url(token, 'upload-api')
|
result['upload_link'] = gen_file_upload_url(token, 'upload-api')
|
||||||
return Response(result)
|
return Response(result)
|
||||||
|
|
||||||
|
|
||||||
|
class UploadLinksCleanOrphan(APIView):
|
||||||
|
authentication_classes = (TokenAuthentication, SessionAuthentication)
|
||||||
|
permission_classes = (IsAuthenticated, CanGenerateUploadLink)
|
||||||
|
throttle_classes = (UserRateThrottle, )
|
||||||
|
|
||||||
|
def delete(self, request):
|
||||||
|
""" Clean orphan upload links.
|
||||||
|
"""
|
||||||
|
|
||||||
|
username = request.user.username
|
||||||
|
upload_links = UploadLinkShare.objects.filter(username=username)
|
||||||
|
|
||||||
|
for upload_link in upload_links:
|
||||||
|
|
||||||
|
repo_id = upload_link.repo_id
|
||||||
|
if not seafile_api.get_repo(repo_id):
|
||||||
|
upload_link.delete()
|
||||||
|
continue
|
||||||
|
|
||||||
|
obj_id = seafile_api.get_dir_id_by_path(repo_id, upload_link.path)
|
||||||
|
if not obj_id:
|
||||||
|
upload_link.delete()
|
||||||
|
continue
|
||||||
|
|
||||||
|
return Response({'success': True})
|
||||||
|
@@ -40,11 +40,11 @@ from seahub.api2.endpoints.search_group import SearchGroup
|
|||||||
from seahub.api2.endpoints.share_links import ShareLinks, ShareLink, \
|
from seahub.api2.endpoints.share_links import ShareLinks, ShareLink, \
|
||||||
ShareLinkOnlineOfficeLock, ShareLinkDirents, ShareLinkSaveFileToRepo, \
|
ShareLinkOnlineOfficeLock, ShareLinkDirents, ShareLinkSaveFileToRepo, \
|
||||||
ShareLinkUpload, ShareLinkUploadDone, ShareLinkSaveItemsToRepo, \
|
ShareLinkUpload, ShareLinkUploadDone, ShareLinkSaveItemsToRepo, \
|
||||||
ShareLinkRepoTags, ShareLinkRepoTagsTaggedFiles
|
ShareLinkRepoTags, ShareLinkRepoTagsTaggedFiles, ShareLinksCleanOrphan
|
||||||
from seahub.api2.endpoints.shared_folders import SharedFolders
|
from seahub.api2.endpoints.shared_folders import SharedFolders
|
||||||
from seahub.api2.endpoints.shared_repos import SharedRepos, SharedRepo
|
from seahub.api2.endpoints.shared_repos import SharedRepos, SharedRepo
|
||||||
from seahub.api2.endpoints.upload_links import UploadLinks, UploadLink, \
|
from seahub.api2.endpoints.upload_links import UploadLinks, UploadLink, \
|
||||||
UploadLinkUpload
|
UploadLinkUpload, UploadLinksCleanOrphan
|
||||||
from seahub.api2.endpoints.repos_batch import ReposBatchView, \
|
from seahub.api2.endpoints.repos_batch import ReposBatchView, \
|
||||||
ReposBatchCopyDirView, ReposBatchCreateDirView, \
|
ReposBatchCopyDirView, ReposBatchCreateDirView, \
|
||||||
ReposBatchCopyItemView, ReposBatchMoveItemView, \
|
ReposBatchCopyItemView, ReposBatchMoveItemView, \
|
||||||
@@ -341,6 +341,7 @@ urlpatterns = [
|
|||||||
|
|
||||||
## user::shared-download-links
|
## user::shared-download-links
|
||||||
url(r'^api/v2.1/share-links/$', ShareLinks.as_view(), name='api-v2.1-share-links'),
|
url(r'^api/v2.1/share-links/$', ShareLinks.as_view(), name='api-v2.1-share-links'),
|
||||||
|
url(r'^api/v2.1/share-links/clean-orphan/$', ShareLinksCleanOrphan.as_view(), name='api-v2.1-share-links-clean-orphan'),
|
||||||
url(r'^api/v2.1/share-links/(?P<token>[a-f0-9]+)/$', ShareLink.as_view(), name='api-v2.1-share-link'),
|
url(r'^api/v2.1/share-links/(?P<token>[a-f0-9]+)/$', ShareLink.as_view(), name='api-v2.1-share-link'),
|
||||||
url(r'^api/v2.1/share-links/(?P<token>[a-f0-9]+)/save-file-to-repo/$', ShareLinkSaveFileToRepo.as_view(), name='api-v2.1-share-link-save-file-to-repo'),
|
url(r'^api/v2.1/share-links/(?P<token>[a-f0-9]+)/save-file-to-repo/$', ShareLinkSaveFileToRepo.as_view(), name='api-v2.1-share-link-save-file-to-repo'),
|
||||||
url(r'^api/v2.1/share-links/(?P<token>[a-f0-9]+)/save-items-to-repo/$', ShareLinkSaveItemsToRepo.as_view(), name='api-v2.1-share-link-save-items-to-repo'),
|
url(r'^api/v2.1/share-links/(?P<token>[a-f0-9]+)/save-items-to-repo/$', ShareLinkSaveItemsToRepo.as_view(), name='api-v2.1-share-link-save-items-to-repo'),
|
||||||
@@ -355,6 +356,7 @@ urlpatterns = [
|
|||||||
|
|
||||||
## user::shared-upload-links
|
## user::shared-upload-links
|
||||||
url(r'^api/v2.1/upload-links/$', UploadLinks.as_view(), name='api-v2.1-upload-links'),
|
url(r'^api/v2.1/upload-links/$', UploadLinks.as_view(), name='api-v2.1-upload-links'),
|
||||||
|
url(r'^api/v2.1/upload-links/clean-orphan/$', UploadLinksCleanOrphan.as_view(), name='api-v2.1-upload-links-clean-orphan'),
|
||||||
url(r'^api/v2.1/upload-links/(?P<token>[a-f0-9]+)/$', UploadLink.as_view(), name='api-v2.1-upload-link'),
|
url(r'^api/v2.1/upload-links/(?P<token>[a-f0-9]+)/$', UploadLink.as_view(), name='api-v2.1-upload-link'),
|
||||||
url(r'^api/v2.1/upload-links/(?P<token>[a-f0-9]+)/upload/$', UploadLinkUpload.as_view(), name='api-v2.1-upload-link-upload'),
|
url(r'^api/v2.1/upload-links/(?P<token>[a-f0-9]+)/upload/$', UploadLinkUpload.as_view(), name='api-v2.1-upload-link-upload'),
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user