1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-09-20 02:48:51 +00:00

Merge branch '10.0'

This commit is contained in:
lian
2023-08-22 09:59:35 +08:00
5 changed files with 251 additions and 46 deletions

View File

@@ -36,7 +36,6 @@ class SelectEditor extends React.Component {
}
componentDidMount() {
document.addEventListener('click', this.onHideSelect);
this.setOptions();
}
@@ -78,10 +77,6 @@ class SelectEditor extends React.Component {
this.setOptions();
}
componentWillUnmount() {
document.removeEventListener('click', this.onHideSelect);
}
onEditPermission = (e) => {
e.preventDefault();
e.nativeEvent.stopImmediatePropagation();
@@ -102,7 +97,7 @@ class SelectEditor extends React.Component {
e.nativeEvent.stopImmediatePropagation();
}
onHideSelect = () => {
onMenuClose = () => {
this.setState({isEditing: false});
this.props.toggleItemFreezed && this.props.toggleItemFreezed(false);
}
@@ -149,6 +144,7 @@ class SelectEditor extends React.Component {
menuPosition={'fixed'}
menuPortalTarget={document.querySelector('#wrapper')}
styles={MenuSelectStyle}
onMenuClose={this.onMenuClose}
menuShouldScrollIntoView
/>
}

View File

@@ -43,22 +43,32 @@ class LinkItem extends React.Component {
return link.slice(0, 9) + '...' + link.slice(length-5);
}
onDeleteIconClicked = (e) => {
e.preventDefault();
e.stopPropagation();
this.toggleDeleteShareLinkDialog();
}
toggleDeleteShareLinkDialog = () => {
this.setState({isDeleteShareLinkDialogOpen: !this.state.isDeleteShareLinkDialogOpen});
}
copyLink = (e) => {
onCopyIconClicked = (e) => {
e.preventDefault();
e.stopPropagation();
const { item } = this.props;
copy(item.link);
toaster.success(gettext('Share link is copied to the clipboard.'));
}
viewDetails = (e) => {
e.preventDefault();
clickItem = (e) => {
this.props.showLinkDetails(this.props.item);
}
onCheckboxClicked = (e) => {
e.stopPropagation();
}
toggleSelectLink = (e) => {
const { item } = this.props;
this.props.toggleSelectLink(item, e.target.checked);
@@ -76,12 +86,23 @@ class LinkItem extends React.Component {
const currentPermission = Utils.getShareLinkPermissionStr(permissions);
return (
<Fragment>
<tr onMouseOver={this.onMouseOver} onMouseOut={this.onMouseOut} className={isSelected ? 'tr-highlight' : ''}>
<tr
onClick={this.clickItem}
onMouseOver={this.onMouseOver}
onMouseOut={this.onMouseOut}
className={`cursor-pointer ${isSelected ? 'tr-highlight' : ''}`}
>
<td className="text-center">
<input type="checkbox" checked={isSelected} onChange={this.toggleSelectLink} className="vam" />
<input
type="checkbox"
checked={isSelected}
className="vam"
onClick={this.onCheckboxClicked}
onChange={this.toggleSelectLink}
/>
</td>
<td>
<a href="#" onClick={this.viewDetails} className="text-inherit">{this.cutLink(link)}</a>
{this.cutLink(link)}
</td>
<td>
{(isPro && permissions) && (
@@ -98,9 +119,8 @@ class LinkItem extends React.Component {
{expire_date ? moment(expire_date).format('YYYY-MM-DD HH:mm') : '--'}
</td>
<td>
<a href="#" role="button" onClick={this.copyLink} className={`sf2-icon-copy action-icon ${isItemOpVisible ? '' : 'invisible'}`} title={gettext('Copy')} aria-label={gettext('Copy')}></a>
<a href="#" role="button" onClick={this.viewDetails} className={`fa fa-pencil-alt attr-action-icon ${isItemOpVisible ? '' : 'invisible'}`} title={gettext('Edit')} aria-label={gettext('Edit')}></a>
<a href="#" role="button" onClick={this.toggleDeleteShareLinkDialog} className={`sf2-icon-delete action-icon ${isItemOpVisible ? '' : 'invisible'}`} title={gettext('Delete')} aria-label={gettext('Delete')}></a>
<a href="#" role="button" onClick={this.onCopyIconClicked} className={`sf2-icon-copy action-icon ${isItemOpVisible ? '' : 'invisible'}`} title={gettext('Copy')} aria-label={gettext('Copy')}></a>
<a href="#" role="button" onClick={this.onDeleteIconClicked} className={`sf2-icon-delete action-icon ${isItemOpVisible ? '' : 'invisible'}`} title={gettext('Delete')} aria-label={gettext('Delete')}></a>
</td>
</tr>
{this.state.isDeleteShareLinkDialogOpen && (

View File

@@ -80,10 +80,10 @@ class LinkList extends React.Component {
<th width="5%" className="text-center">
<input type="checkbox" checked={isAllLinksSelected} className="vam" onChange={this.toggleSelectAllLinks} />
</th>
<th width="23%">{gettext('Link')}</th>
<th width="26%">{gettext('Link')}</th>
<th width="30%">{gettext('Permission')}</th>
<th width="24%">{gettext('Expiration')}</th>
<th width="18%"></th>
<th width="25%">{gettext('Expiration')}</th>
<th width="14%"></th>
</tr>
</thead>
<tbody>

View File

@@ -1,40 +1,202 @@
from datetime import datetime
from django.db import connection
from django.core.management.base import BaseCommand
from seaserv import seafile_api
from seahub.tags.models import FileUUIDMap
from seahub.base.models import UserStarredFiles
from seahub.revision_tag.models import RevisionTags
from seahub.share.models import ExtraGroupsSharePermission, \
ExtraSharePermission, UploadLinkShare
from seahub.utils.seafile_db import get_seafile_db_name
class Command(BaseCommand):
help = "Clear invalid data when repo deleted"
def add_arguments(self, parser):
parser.add_argument('--dry-run', type=str, choices=['true', 'false'], default='false',
help='Whether to only print the log instead of actually deleting the data')
def get_seafile_repo_count(self, table_name):
db_name, error_msg = get_seafile_db_name()
if error_msg:
self.stderr.write('[%s] Failed to get seafile db name, error: %s' % (datetime.now(), error_msg))
return
sql = """SELECT COUNT(1) FROM `%s`.`%s`""" % (db_name, table_name)
try:
with connection.cursor() as cursor:
cursor.execute(sql)
repo_count = int(cursor.fetchone()[0])
except Exception as e:
self.stderr.write('[%s] Failed to count the number of %s, error: %s.' % (datetime.now(), table_name, e))
return
return repo_count
def query_seafile_repo_ids(self, count, table_name):
db_name, error_msg = get_seafile_db_name()
if error_msg:
self.stderr.write('[%s] Failed to get seafile db name, error: %s' % (datetime.now(), error_msg))
return
repo_ids = set()
sql = """SELECT repo_id FROM `%s`.`%s` LIMIT %%s, %%s""" % (db_name, table_name)
for i in range(0, count, 1000):
try:
with connection.cursor() as cursor:
cursor.execute(sql, (i, 1000))
res = cursor.fetchall()
except Exception as e:
self.stderr.write('[%s] Failed to query all repo_id from %s, error: %s.' %
(datetime.now(), table_name, e))
return
for repo_id, *_ in res:
repo_ids.add(repo_id)
return repo_ids
def get_repo_id_count(self, table_name):
self.stdout.write('[%s] Count the number of repo_id of %s.' % (datetime.now(), table_name))
sql = """SELECT COUNT(DISTINCT(repo_id)) FROM %s""" % table_name
try:
with connection.cursor() as cursor:
cursor.execute(sql)
repo_id_count = int(cursor.fetchone()[0])
except Exception as e:
self.stderr.write('[%s] Failed to count the number repo_id of %s, error: %s.' %
(datetime.now(), table_name, e))
return
self.stdout.write('[%s] The number of repo_id of %s: %s.' % (datetime.now(), table_name, repo_id_count))
return repo_id_count
def query_invalid_repo_ids(self, all_repo_ids, repo_id_count, table_name):
self.stdout.write('[%s] Start to query all repo_id of %s.' % (datetime.now(), table_name))
sql = """SELECT DISTINCT(repo_id) FROM %s LIMIT %%s, %%s""" % table_name
repo_ids = list()
invalid_repo_ids = list()
for i in range(0, repo_id_count, 1000):
try:
with connection.cursor() as cursor:
cursor.execute(sql, (i, 1000))
res = cursor.fetchall()
except Exception as e:
self.stderr.write('[%s] Failed to query repo_id from %s, error: %s.' %
(datetime.now(), table_name, e))
return
for repo_id, *_ in res:
repo_ids.append(repo_ids)
if repo_id not in all_repo_ids:
invalid_repo_ids.append(repo_id)
self.stdout.write('[%s] Successfully queried all repo_id of %s, result length: %s' %
(datetime.now(), table_name, len(repo_ids)))
self.stdout.write('[%s] The number of invalid repo_id of %s: %s' %
(datetime.now(), table_name, len(invalid_repo_ids)))
return invalid_repo_ids
def clean_up_invalid_records(self, dry_run, invalid_repo_ids, table_name):
self.stdout.write('[%s] Start to count invalid records of %s.' % (datetime.now(), table_name))
invalid_records_count = 0
if invalid_repo_ids:
count_sql = """SELECT COUNT(1) FROM %s WHERE repo_id IN %%s""" % table_name
try:
with connection.cursor() as cursor:
cursor.execute(count_sql, (invalid_repo_ids,))
invalid_records_count = int(cursor.fetchone()[0])
except Exception as e:
self.stderr.write('[%s] Failed to count invalid records of %s, error: %s.' %
(datetime.now(), table_name, e))
return False
self.stdout.write('[%s] The number of invalid records of %s: %s' %
(datetime.now(), table_name, invalid_records_count))
self.stdout.write('[%s] Start to clean up invalid records of %s...' % (datetime.now(), table_name))
if dry_run == 'false':
clean_sql = """DELETE FROM %s WHERE repo_id IN %%s LIMIT 10000""" % table_name
for i in range(0, invalid_records_count, 10000):
try:
with connection.cursor() as cursor:
cursor.execute(clean_sql, (invalid_repo_ids,))
except Exception as e:
self.stderr.write('[%s] Failed to clean up expired UploadLinkShare, error: %s.' %
(datetime.now(), e))
return False
self.stdout.write('[%s] Successfully cleaned up invalid records of %s.' % (datetime.now(), table_name))
return True
def handle(self, *args, **kwargs):
self.stdout.write('Start to get all existing repo')
self.all_repo = [repo.repo_id for repo in seafile_api.get_repo_list(-1, -1, ret_virt_repo=True)]
trash_repo = [repo.repo_id for repo in seafile_api.get_trash_repo_list(-1, -1)]
self.all_repo.extend(trash_repo)
self.stdout.write('Successly get all existing repos')
dry_run = kwargs['dry_run']
# get all exist repo_id
self.stdout.write('[%s] Count the number of exist repo.' % datetime.now())
exist_repo_count = self.get_seafile_repo_count('Repo')
if exist_repo_count is None:
return
self.stdout.write('[%s] The number of exist repo: %s.' % (datetime.now(), exist_repo_count))
# on_delete is CASCADE, so FileTag/FileComment will be deleted
self.tables = {'FileUUIDMap': FileUUIDMap, 'RevisionTags': RevisionTags,
'UserStarredFiles': UserStarredFiles,
'ExtraGroupsSharePermission': ExtraGroupsSharePermission,
'ExtraSharePermission': ExtraSharePermission,
'UploadLinkShare': UploadLinkShare}
self.stdout.write('[%s] Start to query all exist repo_id.' % datetime.now())
exist_repo_ids = self.query_seafile_repo_ids(exist_repo_count, 'Repo')
if exist_repo_ids is None:
return
self.stdout.write('[%s] Successfully queried all exist repo_id, result length: %s.' %
(datetime.now(), len(exist_repo_ids)))
for table in list(self.tables.items()):
self.clear_table(table[0], table[1])
# get all virtual repo_id
self.stdout.write('[%s] Count the number of virtual repo.' % datetime.now())
virtual_repo_count = self.get_seafile_repo_count('VirtualRepo')
if virtual_repo_count is None:
return
self.stdout.write('[%s] The number of virtual repo: %s.' % (datetime.now(), virtual_repo_count))
self.stdout.write('All invalid repo data are deleted')
self.stdout.write('[%s] Start to query all virtual repo_id.' % datetime.now())
virtual_repo_ids = self.query_seafile_repo_ids(virtual_repo_count, 'VirtualRepo')
if virtual_repo_ids is None:
return
self.stdout.write('[%s] Successfully queried all virtual repo_id, result length: %s.' %
(datetime.now(), len(virtual_repo_ids)))
def clear_table(self, table_name, table_model):
""" clear invalid data on table
table must has `repo_id` column and without foreign relationship
"""
self.stdout.write('Start to clear %s table' % table_name)
tb_repo_ids = table_model.objects.all().values_list('repo_id', flat=True)
table_model.objects.filter(repo_id__in=list(set(tb_repo_ids) - set(self.all_repo))).delete()
self.stdout.write('%s table has been clear' % table_name)
# get all trash repo_id
self.stdout.write('[%s] Count the number of trash repo.' % datetime.now())
trash_repo_count = self.get_seafile_repo_count('RepoTrash')
if trash_repo_count is None:
return
self.stdout.write('[%s] The number of trash repo: %s.' % (datetime.now(), trash_repo_count))
self.stdout.write('[%s] Start to query all trash repo_id.' % datetime.now())
trash_repo_ids = self.query_seafile_repo_ids(trash_repo_count, 'RepoTrash')
if trash_repo_ids is None:
return
self.stdout.write('[%s] Successfully queried all trash repo_id, result length: %s.' %
(datetime.now(), len(trash_repo_ids)))
all_repo_ids = exist_repo_ids | virtual_repo_ids | trash_repo_ids
self.stdout.write('[%s] The number of valid repo: %s.' % (datetime.now(), len(all_repo_ids)))
# clean up expired upload_link
self.stdout.write('[%s] Start to clean up expired upload_link...' % datetime.now())
if dry_run == 'false':
sql1 = """DELETE FROM share_uploadlinkshare WHERE expire_date < DATE_SUB(CURDATE(), INTERVAL 3 DAY)"""
try:
with connection.cursor() as cursor:
cursor.execute(sql1)
except Exception as e:
self.stderr.write('[%s] Failed to clean up expired upload_link, error: %s.' % (datetime.now(), e))
return
self.stdout.write('[%s] Successfully cleaned up expired upload_link.' % datetime.now())
# clean up invalid upload_link
repo_id_count = self.get_repo_id_count('share_uploadlinkshare')
if repo_id_count is None:
return
invalid_repo_ids = self.query_invalid_repo_ids(all_repo_ids, repo_id_count, 'share_uploadlinkshare')
if invalid_repo_ids is None:
return
clean_up_res = self.clean_up_invalid_records(dry_run, invalid_repo_ids, 'share_uploadlinkshare')
if clean_up_res is None:
return
# TODO: tags_fileuuidmap, revision_tag_revisiontags, base_userstarredfiles, share_extragroupssharepermission, share_extrasharepermission

View File

@@ -0,0 +1,27 @@
# -*- coding: utf-8 -*-
import os
import configparser
def get_seafile_db_name():
seafile_conf_dir = os.environ.get('SEAFILE_CENTRAL_CONF_DIR') or os.environ.get('SEAFILE_CONF_DIR')
if not seafile_conf_dir:
error_msg = 'Environment variable seafile_conf_dir is not define.'
return None, error_msg
seafile_conf_path = os.path.join(seafile_conf_dir, 'seafile.conf')
config = configparser.ConfigParser()
config.read(seafile_conf_path)
if not config.has_section('database'):
error_msg = 'Do not found database configuration items.'
return None, error_msg
db_type = config.get('database', 'type')
if db_type != 'mysql':
error_msg = 'Unknown database backend: %s' % db_type
return None, error_msg
db_name = config.get('database', 'db_name', fallback='seafile')
return db_name, None