1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-09-08 18:30:53 +00:00

Sys admin react reconstruct (#3918)

* sysadmin reconstruct info page

* optimize frontend code, add license post api response

* update test case

* optimize js code
This commit is contained in:
llj
2019-07-25 16:43:55 +08:00
committed by GitHub
parent 2574301005
commit de2408efdc
8 changed files with 226 additions and 14 deletions

View File

@@ -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 {
<SidePanel isSidePanelClosed={isSidePanelClosed} onCloseSidePanel={this.onCloseSidePanel} currentTab={currentTab}/>
<MainPanel>
<Router className="reach-router">
<Info
path={siteRoot + 'sys/info'}
currentTab={currentTab}
tabItemClick={this.tabItemClick}
/>
<FileScanRecords
path={siteRoot + 'sys/file-scan-records'}
currentTab={currentTab}
tabItemClick={this.tabItemClick}
/>
</Router>
<Router className="reach-router">
<WorkWeixinDepartments
path={siteRoot + 'sys/work-weixin/departments'}
currentTab={currentTab}

View File

@@ -0,0 +1,174 @@
import React, { Component, Fragment } from 'react';
import { Button } from 'reactstrap';
import { seafileAPI } from '../../utils/seafile-api';
import { gettext, isPro, isDefaultAdmin } from '../../utils/constants';
import toaster from '../../components/toast';
import { Utils } from '../../utils/utils';
import Account from '../../components/common/account';
import Loading from '../../components/loading';
class Info extends Component {
constructor(props) {
super(props);
this.fileInput = React.createRef();
this.state = {
loading: true,
errorMsg: '',
sysInfo: {}
};
}
componentDidMount () {
seafileAPI.getSysInfo().then((res) => {
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 <Loading />;
} else if (errorMsg) {
return <p className="error text-center">{errorMsg}</p>;
} else {
return (
<Fragment>
<div className="main-panel-north border-left-show">
<div className="cur-view-toolbar">
<span className="sf2-icon-menu side-nav-toggle hidden-md-up d-md-none" title="Side Nav Menu"></span>
</div>
<div className="common-toolbar">
<Account isAdminPanel={true} />
</div>
</div>
<div className="main-panel-center flex-row">
<div className="cur-view-container">
<div className="cur-view-path">
<h3 className="sf-heading">{gettext('Info')}</h3>
</div>
<div className="cur-view-content">
<dl>
<dt>{gettext('System Info')}</dt>
{isPro ?
<dd>
{gettext('Professional Edition')}
{with_license &&
' ' + this.renderLicenseDescString(license_mode, license_to, license_expiration)
}<br/>
{isDefaultAdmin &&
<Fragment>
<Button type="button" className="mt-2" onClick={this.openFileInput}>{gettext('Upload license')}</Button>
<input className="d-none" type="file" onChange={this.uploadLicenseFile} ref={this.fileInput} />
</Fragment>
}
</dd> :
<dd>
{gettext('Community Edition')}
<a className="ml-1" href="http://manual.seafile.com/deploy_pro/migrate_from_seafile_community_server.html" target="_blank">{gettext('Upgrade to Pro Edition')}</a>
</dd>
}
<dt>{gettext('Libraries')} / {gettext('Files')}</dt>
<dd>{repos_count} / {total_files_count}</dd>
<dt>{gettext('Storage Used')}</dt>
<dd>{Utils.bytesToSize(total_storage)}</dd>
<dt>{gettext('Total Devices')} / {gettext('Current Connected Devices')}</dt>
<dd>{total_devices_count} / {current_connected_devices_count}</dd>
{isPro ?
<Fragment>
<dt>{gettext('Activated Users')} / {gettext('Total Users')} / {gettext('Limits')}</dt>
<dd>{active_users_count}{' / '}{users_count}{' / '}{with_license ? license_maxusers : '--'}</dd>
</Fragment> :
<Fragment>
<dt>{gettext('Activated Users')} / {gettext('Total Users')}</dt>
<dd>{active_users_count} / {users_count}</dd>
</Fragment>
}
<dt>{gettext('Groups')}</dt>
<dd>{groups_count}</dd>
{multi_tenancy_enabled &&
<Fragment>
<dt>{gettext('Organizations')}</dt>
<dd>{org_count}</dd>
</Fragment>
}
</dl>
</div>
</div>
</div>
</Fragment>
);
}
}
}
export default Info;

View File

@@ -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 {

View File

@@ -32,10 +32,10 @@ class SidePanel extends React.Component {
<ul className="nav nav-pills flex-column nav-container">
{canViewSystemInfo &&
<li className="nav-item">
<a className='nav-link ellipsis' href={siteRoot + 'sysadmin/#dashboard/'}>
<Link className='nav-link ellipsis' to={siteRoot + 'sys/info/'}>
<span className="sf2-icon-info" aria-hidden="true"></span>
<span className="nav-text">{gettext('Info')}</span>
</a>
</Link>
</li>
}
{isPro && canViewStatistic &&

View File

@@ -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)

View File

@@ -16,7 +16,7 @@
{% if user.admin_permissions.can_view_system_info %}
<li class="tab<% if (cur_tab == 'dashboard') { %> tab-cur<% } %>">
<a href="{{ SITE_ROOT }}sysadmin/#dashboard/"><span class="sf2-icon-info"></span>{% trans "Info" %}</a>
<a href="{{ SITE_ROOT }}sys/info/"><span class="sf2-icon-info"></span>{% trans "Info" %}</a>
</li>
{% endif %}

View File

@@ -623,7 +623,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<email>[^/]+)/$', user_remove, name="user_remove"),
url(r'^useradmin/removetrial/(?P<user_or_org>[^/]+)/$', remove_trial, name="remove_trial"),
@@ -640,6 +639,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'),
]

View File

@@ -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')