diff --git a/frontend/src/pages/sys-admin/index.js b/frontend/src/pages/sys-admin/index.js index 49bfe21a91..2807997471 100644 --- a/frontend/src/pages/sys-admin/index.js +++ b/frontend/src/pages/sys-admin/index.js @@ -6,6 +6,7 @@ import SidePanel from './side-panel'; import MainPanel from './main-panel'; import FileScanRecords from './file-scan-records'; import WorkWeixinDepartments from './work-weixin-departments'; +import Info from './info'; import '../../assets/css/fa-solid.css'; import '../../assets/css/fa-regular.css'; @@ -43,13 +44,16 @@ class SysAdmin extends React.Component { + - - { + this.setState({ + loading: false, + sysInfo: res.data + }); + }).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.') + }); + } + }); + } + + uploadLicenseFile = (e) => { + + // no file selected + if (!this.fileInput.current.files.length) { + return; + } + const file = this.fileInput.current.files[0]; + seafileAPI.uploadLicense(file).then((res) => { + let info = this.state.sysInfo; + Object.assign(info, res.data); + this.setState({ + sysInfo: info + }); + }).catch((error) => { + let errMsg = Utils.getErrorMsg(error); + toaster.danger(errMsg); + }); + } + + openFileInput = () => { + this.fileInput.current.click(); + } + + renderLicenseDescString = (license_mode, license_to, license_expiration) => { + if (license_mode == 'life-time') { + if (window.app.config.lang == 'zh-cn') { + return '永久授权给 ' + license_to + ',技术支持服务至 ' + license_expiration + ' 到期'; + } else { + return gettext('licensed to {placeholder_license_to}, upgrade service expired in {placeholder_license_expiration}') + .replace('{placeholder_license_to}', license_to).replace('{placeholder_license_expiration}', license_expiration); + } + } else { + return gettext('licensed to {placeholder_license_to}, expires on {placeholder_license_expiration}') + .replace('{placeholder_license_to}', license_to).replace('{placeholder_license_expiration}', license_expiration); + } + } + + render() { + let { with_license, license_mode, license_to, license_expiration, org_count, + repos_count, total_files_count, total_storage, total_devices_count, + current_connected_devices_count, license_maxusers, multi_tenancy_enabled, + active_users_count, users_count, groups_count } = this.state.sysInfo; + let { loading, errorMsg } = this.state; + + if (loading) { + return ; + } else if (errorMsg) { + return

{errorMsg}

; + } else { + return ( + +
+
+ +
+
+ +
+
+
+
+
+

{gettext('Info')}

+
+
+
+
{gettext('System Info')}
+ {isPro ? +
+ {gettext('Professional Edition')} + {with_license && + ' ' + this.renderLicenseDescString(license_mode, license_to, license_expiration) + }
+ {isDefaultAdmin && + + + + + } +
: +
+ {gettext('Community Edition')} + {gettext('Upgrade to Pro Edition')} +
+ } +
{gettext('Libraries')} / {gettext('Files')}
+
{repos_count} / {total_files_count}
+ +
{gettext('Storage Used')}
+
{Utils.bytesToSize(total_storage)}
+ +
{gettext('Total Devices')} / {gettext('Current Connected Devices')}
+
{total_devices_count} / {current_connected_devices_count}
+ + {isPro ? + +
{gettext('Activated Users')} / {gettext('Total Users')} / {gettext('Limits')}
+
{active_users_count}{' / '}{users_count}{' / '}{with_license ? license_maxusers : '--'}
+
: + +
{gettext('Activated Users')} / {gettext('Total Users')}
+
{active_users_count} / {users_count}
+
+ } + +
{gettext('Groups')}
+
{groups_count}
+ + {multi_tenancy_enabled && + +
{gettext('Organizations')}
+
{org_count}
+
+ } +
+
+
+
+
+ ); + } + } +} + +export default Info; diff --git a/frontend/src/pages/sys-admin/main-panel.js b/frontend/src/pages/sys-admin/main-panel.js index 2c579ea438..cc2f74afa0 100644 --- a/frontend/src/pages/sys-admin/main-panel.js +++ b/frontend/src/pages/sys-admin/main-panel.js @@ -2,7 +2,7 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; const propTypes = { - children: PropTypes.array.isRequired, + children: PropTypes.object.isRequired, }; class MainPanel extends Component { diff --git a/frontend/src/pages/sys-admin/side-panel.js b/frontend/src/pages/sys-admin/side-panel.js index 8dbb63328d..c5957f3257 100644 --- a/frontend/src/pages/sys-admin/side-panel.js +++ b/frontend/src/pages/sys-admin/side-panel.js @@ -31,12 +31,12 @@ class SidePanel extends React.Component {

{gettext('System Admin')}

    {canViewSystemInfo && -
  • - - - {gettext('Info')} - -
  • +
  • + + + {gettext('Info')} + +
  • } {isPro && canViewStatistic &&
  • diff --git a/seahub/api2/endpoints/admin/license.py b/seahub/api2/endpoints/admin/license.py index bef17a3ff9..5eb977fb24 100644 --- a/seahub/api2/endpoints/admin/license.py +++ b/seahub/api2/endpoints/admin/license.py @@ -13,7 +13,8 @@ from seahub.api2.authentication import TokenAuthentication from seahub.api2.throttling import UserRateThrottle from seahub.api2.utils import api_error from seahub.settings import LICENSE_PATH -from seahub.utils import get_file_type_and_ext +from seahub.utils import get_file_type_and_ext, is_pro_version +from seahub.utils.licenseparse import parse_license from seahub.utils.error_msg import file_type_error_msg, file_size_error_msg logger = logging.getLogger(__name__) @@ -53,4 +54,31 @@ class AdminLicense(APIView): logger.error(e) error_msg = 'Internal Server Error' return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg) - return Response({'success': True}, status=status.HTTP_200_OK) + + # get license info + is_pro = is_pro_version() + if is_pro: + license_dict = parse_license() + else: + license_dict = {} + + if license_dict: + with_license = True + try: + max_users = int(license_dict.get('MaxUsers', 3)) + except ValueError as e: + logger.error(e) + max_users = 0 + else: + with_license = False + max_users = 0 + + license_info = { + 'with_license': with_license, + 'license_expiration': license_dict.get('Expiration', ''), + 'license_mode': license_dict.get('Mode', ''), + 'license_maxusers': max_users, + 'license_to': license_dict.get('Name', ''), + } + + return Response(license_info, status=status.HTTP_200_OK) diff --git a/seahub/templates/js/sysadmin-templates.html b/seahub/templates/js/sysadmin-templates.html index a3c8739120..78963a4231 100644 --- a/seahub/templates/js/sysadmin-templates.html +++ b/seahub/templates/js/sysadmin-templates.html @@ -16,7 +16,7 @@ {% if user.admin_permissions.can_view_system_info %}
  • - {% trans "Info" %} + {% trans "Info" %}
  • {% endif %} diff --git a/seahub/urls.py b/seahub/urls.py index 36ee196059..e0206780cc 100644 --- a/seahub/urls.py +++ b/seahub/urls.py @@ -603,7 +603,6 @@ urlpatterns = [ url(r'^sys/invitationadmin/remove/$', sys_invitation_remove, name='sys_invitation_remove'), url(r'^sys/sudo/', sys_sudo_mode, name='sys_sudo_mode'), url(r'^sys/check-license/', sys_check_license, name='sys_check_license'), - url(r'^sys/work-weixin/departments/$', sysadmin_react_fake_view, name="sys_work_weixin_departments"), url(r'^useradmin/add/$', user_add, name="user_add"), url(r'^useradmin/remove/(?P[^/]+)/$', user_remove, name="user_remove"), url(r'^useradmin/removetrial/(?P[^/]+)/$', remove_trial, name="remove_trial"), @@ -620,6 +619,9 @@ urlpatterns = [ url(r'^useradmin/batchadduser/$', batch_add_user, name='batch_add_user'), url(r'^useradmin/batchadduser/example/$', batch_add_user_example, name='batch_add_user_example'), + url(r'^sys/info/$', sysadmin_react_fake_view, name="sys_info"), + url(r'^sys/work-weixin/departments/$', sysadmin_react_fake_view, name="sys_work_weixin_departments"), + url(r'^client-login/$', client_token_login, name='client_token_login'), ] diff --git a/tests/api/endpoints/admin/test_license.py b/tests/api/endpoints/admin/test_license.py index dfd30856e1..5e14f70865 100644 --- a/tests/api/endpoints/admin/test_license.py +++ b/tests/api/endpoints/admin/test_license.py @@ -26,7 +26,11 @@ class AdminLicenseTest(BaseTestCase): resp = self.client.post(url, {'license': f}) json_resp = json.loads(resp.content) - assert json_resp['success'] is True + assert json_resp['with_license'] is True + assert json_resp['license_expiration'] is not None + assert json_resp['license_mode'] is not None + assert json_resp['license_maxusers'] is not None + assert json_resp['license_to'] is not None assert os.path.exists(LICENSE_PATH) @patch.object(license_api, 'ccnet_api')