From 4f9dcb344ba85159c6429267517dea11e87d56fc Mon Sep 17 00:00:00 2001 From: lian Date: Thu, 12 Jan 2023 16:56:31 +0800 Subject: [PATCH] Rate limit (#5169) * set upload/download rate limit by user role * admin set user upload/download rate limit * update Co-authored-by: lian --- .../set-upload-download-rate-limit.js | 86 +++++++++++++++++++ .../src/pages/sys-admin/users/user-info.js | 49 ++++++++++- seahub/api2/endpoints/admin/users.py | 59 +++++++++++-- seahub/role_permissions/settings.py | 19 ++++ seahub/utils/file_size.py | 16 ++++ tests/seahub/role_permissions/test_utils.py | 2 +- 6 files changed, 224 insertions(+), 7 deletions(-) create mode 100644 frontend/src/components/dialog/sysadmin-dialog/set-upload-download-rate-limit.js diff --git a/frontend/src/components/dialog/sysadmin-dialog/set-upload-download-rate-limit.js b/frontend/src/components/dialog/sysadmin-dialog/set-upload-download-rate-limit.js new file mode 100644 index 0000000000..fedd3b526c --- /dev/null +++ b/frontend/src/components/dialog/sysadmin-dialog/set-upload-download-rate-limit.js @@ -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 ( + + {this.props.uploadOrDownload == "upload" ? gettext('Set Upload Rate Limit') : gettext('Set Download Rate Limit')} + +
+ + + + + kB/s + + +

+ {gettext('An integer that is greater than or equal to 0.')} +
+ {gettext('Tip: 0 means default limit')} +

+
+
+
+ + + + +
+ ); + } +} + +SysAdminSetUploadDownloadRateLimitDialog.propTypes = propTypes; + +export default SysAdminSetUploadDownloadRateLimitDialog; diff --git a/frontend/src/pages/sys-admin/users/user-info.js b/frontend/src/pages/sys-admin/users/user-info.js index 2941d30c9c..b061608d31 100644 --- a/frontend/src/pages/sys-admin/users/user-info.js +++ b/frontend/src/pages/sys-admin/users/user-info.js @@ -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 ( @@ -134,6 +155,18 @@ class Content extends Component { {this.showEditIcon(this.toggleSetQuotaDialog)} +
{gettext('Upload Rate Limit')}
+
+ {user.upload_rate_limit > 0 ? user.upload_rate_limit + ' kB/s' : '--'} + {this.showEditIcon(this.toggleSetUserUploadRateLimitDialog)} +
+ +
{gettext('Download Rate Limit')}
+
+ {user.download_rate_limit > 0 ? user.download_rate_limit + ' kB/s' : '--'} + {this.showEditIcon(this.toggleSetUserDownloadRateLimitDialog)} +
+ {twoFactorAuthEnabled &&
{gettext('Two-Factor Authentication')}
@@ -163,6 +196,20 @@ class Content extends Component { toggle={this.toggleSetQuotaDialog} /> } + {isSetUserUploadRateLimitDialogOpen && + + } + {isSetUserDownloadRateLimitDialogOpen && + + } {isUpdateUserDialogOpen && = 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. diff --git a/seahub/utils/file_size.py b/seahub/utils/file_size.py index 2c73125468..89061e1e0f 100644 --- a/seahub/utils/file_size.py +++ b/seahub/utils/file_size.py @@ -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: diff --git a/tests/seahub/role_permissions/test_utils.py b/tests/seahub/role_permissions/test_utils.py index 91191907ec..a24bfd8d6b 100644 --- a/tests/seahub/role_permissions/test_utils.py +++ b/tests/seahub/role_permissions/test_utils.py @@ -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