mirror of
https://github.com/haiwen/seahub.git
synced 2025-09-06 17:33:18 +00:00
Rate limit (#5169)
* set upload/download rate limit by user role * admin set user upload/download rate limit * update Co-authored-by: lian <lian@seafile.com>
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Alert, Modal, ModalHeader, ModalBody, ModalFooter, Button, Form, FormGroup, Input, InputGroup, InputGroupAddon, InputGroupText } from 'reactstrap';
|
||||
import { gettext } from '../../../utils/constants';
|
||||
import { Utils } from '../../../utils/utils';
|
||||
|
||||
const propTypes = {
|
||||
uploadOrDownload: PropTypes.string.isRequired,
|
||||
toggle: PropTypes.func.isRequired,
|
||||
updateUploadDownloadRateLimit: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
class SysAdminSetUploadDownloadRateLimitDialog extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
rateLimit: '',
|
||||
isSubmitBtnActive: false
|
||||
};
|
||||
}
|
||||
|
||||
toggle = () => {
|
||||
this.props.toggle();
|
||||
}
|
||||
|
||||
handleRateLimitChange = (e) => {
|
||||
const value = e.target.value;
|
||||
this.setState({
|
||||
rateLimit: value,
|
||||
isSubmitBtnActive: value.trim() != ''
|
||||
});
|
||||
}
|
||||
|
||||
handleKeyPress = (e) => {
|
||||
if (e.key == 'Enter') {
|
||||
this.handleSubmit();
|
||||
e.preventDefault();
|
||||
}
|
||||
}
|
||||
|
||||
handleSubmit = () => {
|
||||
this.props.updateUploadDownloadRateLimit(this.props.uploadOrDownload, this.state.rateLimit.trim());
|
||||
this.toggle();
|
||||
}
|
||||
|
||||
render() {
|
||||
const { rateLimit, isSubmitBtnActive } = this.state;
|
||||
return (
|
||||
<Modal isOpen={true} toggle={this.toggle}>
|
||||
<ModalHeader toggle={this.toggle}>{this.props.uploadOrDownload == "upload" ? gettext('Set Upload Rate Limit') : gettext('Set Download Rate Limit')}</ModalHeader>
|
||||
<ModalBody>
|
||||
<Form>
|
||||
<FormGroup>
|
||||
<InputGroup>
|
||||
<Input
|
||||
type="text"
|
||||
className="form-control"
|
||||
value={rateLimit}
|
||||
onKeyPress={this.handleKeyPress}
|
||||
onChange={this.handleRateLimitChange}
|
||||
/>
|
||||
<InputGroupAddon addonType="append">
|
||||
<InputGroupText>kB/s</InputGroupText>
|
||||
</InputGroupAddon>
|
||||
</InputGroup>
|
||||
<p className="small text-secondary mt-2 mb-2">
|
||||
{gettext('An integer that is greater than or equal to 0.')}
|
||||
<br />
|
||||
{gettext('Tip: 0 means default limit')}
|
||||
</p>
|
||||
</FormGroup>
|
||||
</Form>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button color="secondary" onClick={this.toggle}>{gettext('Cancel')}</Button>
|
||||
<Button color="primary" onClick={this.handleSubmit} disabled={!isSubmitBtnActive}>{gettext('Submit')}</Button>
|
||||
</ModalFooter>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
SysAdminSetUploadDownloadRateLimitDialog.propTypes = propTypes;
|
||||
|
||||
export default SysAdminSetUploadDownloadRateLimitDialog;
|
@@ -6,6 +6,7 @@ import { gettext } from '../../../utils/constants';
|
||||
import toaster from '../../../components/toast';
|
||||
import Loading from '../../../components/loading';
|
||||
import SysAdminSetQuotaDialog from '../../../components/dialog/sysadmin-dialog/set-quota';
|
||||
import SysAdminSetUploadDownloadRateLimitDialog from '../../../components/dialog/sysadmin-dialog/set-upload-download-rate-limit';
|
||||
import SysAdminUpdateUserDialog from '../../../components/dialog/sysadmin-dialog/update-user';
|
||||
import MainPanelTopbar from '../main-panel-topbar';
|
||||
import Nav from './user-nav';
|
||||
@@ -20,6 +21,8 @@ class Content extends Component {
|
||||
currentKey: '',
|
||||
dialogTitle: '',
|
||||
isSetQuotaDialogOpen: false,
|
||||
isSetUserUploadRateLimitDialogOpen: false,
|
||||
isSetUserDownloadRateLimitDialogOpen: false,
|
||||
isUpdateUserDialogOpen: false
|
||||
};
|
||||
}
|
||||
@@ -28,10 +31,27 @@ class Content extends Component {
|
||||
this.setState({isSetQuotaDialogOpen: !this.state.isSetQuotaDialogOpen});
|
||||
}
|
||||
|
||||
toggleSetUserUploadRateLimitDialog = () => {
|
||||
this.setState({isSetUserUploadRateLimitDialogOpen: !this.state.isSetUserUploadRateLimitDialogOpen});
|
||||
}
|
||||
|
||||
toggleSetUserDownloadRateLimitDialog = () => {
|
||||
this.setState({isSetUserDownloadRateLimitDialogOpen: !this.state.isSetUserDownloadRateLimitDialogOpen});
|
||||
}
|
||||
|
||||
updateQuota = (value) => {
|
||||
this.props.updateUser('quota_total', value);
|
||||
}
|
||||
|
||||
updateUploadDownloadRateLimit = (uploadOrDownload, value) => {
|
||||
if (uploadOrDownload == 'upload'){
|
||||
this.props.updateUser('upload_rate_limit', value);
|
||||
}
|
||||
if (uploadOrDownload == 'download'){
|
||||
this.props.updateUser('download_rate_limit', value);
|
||||
}
|
||||
}
|
||||
|
||||
toggleDialog = (key, dialogTitle) => {
|
||||
this.setState({
|
||||
currentKey: key,
|
||||
@@ -84,7 +104,8 @@ class Content extends Component {
|
||||
const user = this.props.userInfo;
|
||||
const {
|
||||
currentKey, dialogTitle,
|
||||
isSetQuotaDialogOpen, isUpdateUserDialogOpen
|
||||
isSetQuotaDialogOpen, isUpdateUserDialogOpen,
|
||||
isSetUserUploadRateLimitDialogOpen, isSetUserDownloadRateLimitDialogOpen
|
||||
} = this.state;
|
||||
return (
|
||||
<Fragment>
|
||||
@@ -134,6 +155,18 @@ class Content extends Component {
|
||||
{this.showEditIcon(this.toggleSetQuotaDialog)}
|
||||
</dd>
|
||||
|
||||
<dt className="info-item-heading">{gettext('Upload Rate Limit')}</dt>
|
||||
<dd className="info-item-content">
|
||||
{user.upload_rate_limit > 0 ? user.upload_rate_limit + ' kB/s' : '--'}
|
||||
{this.showEditIcon(this.toggleSetUserUploadRateLimitDialog)}
|
||||
</dd>
|
||||
|
||||
<dt className="info-item-heading">{gettext('Download Rate Limit')}</dt>
|
||||
<dd className="info-item-content">
|
||||
{user.download_rate_limit > 0 ? user.download_rate_limit + ' kB/s' : '--'}
|
||||
{this.showEditIcon(this.toggleSetUserDownloadRateLimitDialog)}
|
||||
</dd>
|
||||
|
||||
{twoFactorAuthEnabled &&
|
||||
<Fragment>
|
||||
<dt className="info-item-heading">{gettext('Two-Factor Authentication')}</dt>
|
||||
@@ -163,6 +196,20 @@ class Content extends Component {
|
||||
toggle={this.toggleSetQuotaDialog}
|
||||
/>
|
||||
}
|
||||
{isSetUserUploadRateLimitDialogOpen &&
|
||||
<SysAdminSetUploadDownloadRateLimitDialog
|
||||
uploadOrDownload="upload"
|
||||
updateUploadDownloadRateLimit={this.updateUploadDownloadRateLimit}
|
||||
toggle={this.toggleSetUserUploadRateLimitDialog}
|
||||
/>
|
||||
}
|
||||
{isSetUserDownloadRateLimitDialogOpen &&
|
||||
<SysAdminSetUploadDownloadRateLimitDialog
|
||||
uploadOrDownload="download"
|
||||
updateUploadDownloadRateLimit={this.updateUploadDownloadRateLimit}
|
||||
toggle={this.toggleSetUserDownloadRateLimitDialog}
|
||||
/>
|
||||
}
|
||||
{isUpdateUserDialogOpen &&
|
||||
<SysAdminUpdateUserDialog
|
||||
dialogTitle={dialogTitle}
|
||||
|
@@ -37,7 +37,7 @@ from seahub.utils import is_valid_username2, is_org_context, \
|
||||
IS_EMAIL_CONFIGURED, send_html_email, get_site_name, \
|
||||
gen_shared_link, gen_shared_upload_link
|
||||
|
||||
from seahub.utils.file_size import get_file_size_unit
|
||||
from seahub.utils.file_size import get_file_size_unit, byte_to_kb
|
||||
from seahub.utils.timeutils import timestamp_to_isoformat_timestr, \
|
||||
datetime_to_isoformat_timestr
|
||||
from seahub.utils.user_permissions import get_user_role
|
||||
@@ -180,7 +180,9 @@ def create_user_info(request, email, role, nickname, contact_email, quota_total_
|
||||
|
||||
|
||||
def update_user_info(request, user, password, is_active, is_staff, role,
|
||||
nickname, login_id, contact_email, reference_id, quota_total_mb, institution_name):
|
||||
nickname, login_id, contact_email, reference_id,
|
||||
quota_total_mb, institution_name,
|
||||
upload_rate_limit, download_rate_limit):
|
||||
|
||||
# update basic user info
|
||||
if is_active is not None:
|
||||
@@ -239,6 +241,12 @@ def update_user_info(request, user, password, is_active, is_staff, role,
|
||||
logger.error(e)
|
||||
seafile_api.set_user_quota(email, -1)
|
||||
|
||||
if upload_rate_limit is not None:
|
||||
seafile_api.set_user_upload_rate_limit(email, upload_rate_limit * 1000)
|
||||
|
||||
if download_rate_limit is not None:
|
||||
seafile_api.set_user_download_rate_limit(email, download_rate_limit * 1000)
|
||||
|
||||
|
||||
def get_user_info(email):
|
||||
|
||||
@@ -995,6 +1003,9 @@ class AdminUser(APIView):
|
||||
|
||||
user_info = get_user_info(email)
|
||||
user_info['avatar_url'], _, _ = api_avatar_url(email, avatar_size)
|
||||
if is_pro_version():
|
||||
user_info['upload_rate_limit'] = byte_to_kb(seafile_api.get_user_upload_rate_limit(email))
|
||||
user_info['download_rate_limit'] = byte_to_kb(seafile_api.get_user_download_rate_limit(email))
|
||||
|
||||
last_login_obj = UserLastLogin.objects.get_by_username(email)
|
||||
if last_login_obj:
|
||||
@@ -1106,6 +1117,30 @@ class AdminUser(APIView):
|
||||
error_msg = 'Institution %s does not exist' % institution
|
||||
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
|
||||
|
||||
upload_rate_limit = request.data.get("upload_rate_limit", None)
|
||||
if upload_rate_limit:
|
||||
try:
|
||||
upload_rate_limit = int(upload_rate_limit)
|
||||
except ValueError:
|
||||
error_msg = _('Must be an integer that is greater than or equal to 0.')
|
||||
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
|
||||
|
||||
if upload_rate_limit < 0:
|
||||
error_msg = _('Must be an integer that is greater than or equal to 0.')
|
||||
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
|
||||
|
||||
download_rate_limit = request.data.get("download_rate_limit", None)
|
||||
if download_rate_limit:
|
||||
try:
|
||||
download_rate_limit = int(download_rate_limit)
|
||||
except ValueError:
|
||||
error_msg = _('Must be an integer that is greater than or equal to 0.')
|
||||
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
|
||||
|
||||
if download_rate_limit < 0:
|
||||
error_msg = _('Must be an integer that is greater than or equal to 0.')
|
||||
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
|
||||
|
||||
# query user info
|
||||
try:
|
||||
user_obj = User.objects.get(email=email)
|
||||
@@ -1114,9 +1149,20 @@ class AdminUser(APIView):
|
||||
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
|
||||
|
||||
try:
|
||||
update_user_info(request, user=user_obj, password=password, is_active=is_active, is_staff=is_staff,
|
||||
role=role, nickname=name, login_id=login_id, contact_email=contact_email,
|
||||
reference_id=reference_id, quota_total_mb=quota_total_mb, institution_name=institution)
|
||||
update_user_info(request,
|
||||
user=user_obj,
|
||||
password=password,
|
||||
is_active=is_active,
|
||||
is_staff=is_staff,
|
||||
role=role,
|
||||
nickname=name,
|
||||
login_id=login_id,
|
||||
contact_email=contact_email,
|
||||
reference_id=reference_id,
|
||||
quota_total_mb=quota_total_mb,
|
||||
institution_name=institution,
|
||||
upload_rate_limit=upload_rate_limit,
|
||||
download_rate_limit=download_rate_limit)
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
error_msg = 'Internal Server Error'
|
||||
@@ -1147,6 +1193,9 @@ class AdminUser(APIView):
|
||||
|
||||
user_info = get_user_info(email)
|
||||
user_info['update_status_tip'] = update_status_tip
|
||||
if is_pro_version():
|
||||
user_info['upload_rate_limit'] = byte_to_kb(seafile_api.get_user_upload_rate_limit(email))
|
||||
user_info['download_rate_limit'] = byte_to_kb(seafile_api.get_user_download_rate_limit(email))
|
||||
|
||||
return Response(user_info)
|
||||
|
||||
|
@@ -2,7 +2,10 @@
|
||||
from copy import deepcopy
|
||||
import logging
|
||||
|
||||
from seaserv import seafile_api
|
||||
|
||||
from django.conf import settings
|
||||
from seahub.utils import is_pro_version
|
||||
from seahub.constants import DEFAULT_USER, GUEST_USER, \
|
||||
DEFAULT_ADMIN, SYSTEM_ADMIN, DAILY_ADMIN, AUDIT_ADMIN
|
||||
|
||||
@@ -43,6 +46,8 @@ DEFAULT_ENABLED_ROLE_PERMISSIONS = {
|
||||
'storage_ids': [],
|
||||
'role_quota': '',
|
||||
'can_publish_repo': True,
|
||||
'upload_rate_limit': 0,
|
||||
'download_rate_limit': 0,
|
||||
},
|
||||
GUEST_USER: {
|
||||
'can_add_repo': False,
|
||||
@@ -62,6 +67,8 @@ DEFAULT_ENABLED_ROLE_PERMISSIONS = {
|
||||
'storage_ids': [],
|
||||
'role_quota': '',
|
||||
'can_publish_repo': False,
|
||||
'upload_rate_limit': 0,
|
||||
'download_rate_limit': 0,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -74,6 +81,18 @@ ENABLED_ROLE_PERMISSIONS = merge_roles(
|
||||
DEFAULT_ENABLED_ROLE_PERMISSIONS, custom_role_permissions
|
||||
)
|
||||
|
||||
if is_pro_version():
|
||||
for role, permissions in ENABLED_ROLE_PERMISSIONS.items():
|
||||
|
||||
upload_rate_limit = permissions.get('upload_rate_limit', 0)
|
||||
if upload_rate_limit >= 0:
|
||||
seafile_api.set_role_upload_rate_limit(role, upload_rate_limit * 1000)
|
||||
|
||||
download_rate_limit = permissions.get('download_rate_limit', 0)
|
||||
if download_rate_limit >= 0:
|
||||
seafile_api.set_role_download_rate_limit(role, download_rate_limit * 1000)
|
||||
|
||||
|
||||
# role permission for administraror
|
||||
|
||||
# 1, Admin without a role or with a role of `default_admin` can view ALL pages.
|
||||
|
@@ -15,6 +15,7 @@ UNIT_GIB = 'gib'
|
||||
UNIT_TIB = 'tib'
|
||||
UNIT_PIB = 'pib'
|
||||
|
||||
|
||||
def get_file_size_unit(unit_type):
|
||||
"""
|
||||
File size unit according to https://en.wikipedia.org/wiki/Kibibyte.
|
||||
@@ -40,6 +41,7 @@ def get_file_size_unit(unit_type):
|
||||
|
||||
return table.get(unit_type)
|
||||
|
||||
|
||||
def get_quota_from_string(quota_str):
|
||||
quota_str = quota_str.lower()
|
||||
if quota_str.endswith('g'):
|
||||
@@ -51,6 +53,20 @@ def get_quota_from_string(quota_str):
|
||||
|
||||
return quota
|
||||
|
||||
|
||||
def byte_to_kb(byte):
|
||||
|
||||
if byte < 0:
|
||||
return ''
|
||||
|
||||
try:
|
||||
unit = get_file_size_unit(UNIT_KB)
|
||||
return round(float(byte)/unit, 2)
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
return ''
|
||||
|
||||
|
||||
def byte_to_mb(byte):
|
||||
|
||||
if byte < 0:
|
||||
|
@@ -11,4 +11,4 @@ class UtilsTest(BaseTestCase):
|
||||
assert DEFAULT_USER in get_available_roles()
|
||||
|
||||
def test_get_enabled_role_permissions_by_role(self):
|
||||
assert len(list(get_enabled_role_permissions_by_role(DEFAULT_USER).keys())) == 17
|
||||
assert len(list(get_enabled_role_permissions_by_role(DEFAULT_USER).keys())) == 19
|
||||
|
Reference in New Issue
Block a user