1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-09-06 17:33:18 +00:00

virus scan react (#4222)

* virus scan react

* repair code

* repair code

* update seafile-js
This commit is contained in:
王健辉
2019-11-06 11:51:38 +08:00
committed by Daniel Pan
parent 37b743fe3a
commit 47c189ebcf
8 changed files with 333 additions and 15 deletions

View File

@@ -5470,7 +5470,7 @@
},
"git-up": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/git-up/-/git-up-1.2.1.tgz",
"resolved": "http://registry.npmjs.org/git-up/-/git-up-1.2.1.tgz",
"integrity": "sha1-JkSAoAax2EJhrB/gmjpRacV+oZ0=",
"requires": {
"is-ssh": "^1.0.0",
@@ -5479,7 +5479,7 @@
},
"git-url-parse": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/git-url-parse/-/git-url-parse-5.0.1.tgz",
"resolved": "http://registry.npmjs.org/git-url-parse/-/git-url-parse-5.0.1.tgz",
"integrity": "sha1-/j15xnRq4FBIz6UIyB553du6OEM=",
"requires": {
"git-up": "^1.0.0"
@@ -8217,7 +8217,7 @@
},
"node-status-codes": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/node-status-codes/-/node-status-codes-1.0.0.tgz",
"resolved": "http://registry.npmjs.org/node-status-codes/-/node-status-codes-1.0.0.tgz",
"integrity": "sha1-WuVUHQJGRdMqWPzdyc7s6nrjrC8="
},
"noop6": {
@@ -8524,7 +8524,7 @@
},
"package.json": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/package.json/-/package.json-2.0.1.tgz",
"resolved": "http://registry.npmjs.org/package.json/-/package.json-2.0.1.tgz",
"integrity": "sha1-+IYFnSpJ7QduZIg2ldc7K0bSHW0=",
"requires": {
"git-package-json": "^1.4.0",
@@ -8534,7 +8534,7 @@
"dependencies": {
"got": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/got/-/got-5.7.1.tgz",
"resolved": "http://registry.npmjs.org/got/-/got-5.7.1.tgz",
"integrity": "sha1-X4FjWmHkplifGAVp6k44FoClHzU=",
"requires": {
"create-error-class": "^3.0.1",
@@ -8556,7 +8556,7 @@
},
"package-json": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/package-json/-/package-json-2.4.0.tgz",
"resolved": "http://registry.npmjs.org/package-json/-/package-json-2.4.0.tgz",
"integrity": "sha1-DRW9Z9HLvduyyiIv8u24a8sxqLs=",
"requires": {
"got": "^5.0.0",
@@ -11386,9 +11386,9 @@
}
},
"seafile-js": {
"version": "0.2.136",
"resolved": "https://registry.npmjs.org/seafile-js/-/seafile-js-0.2.136.tgz",
"integrity": "sha512-0/MYJrLRHwU7tKawbtNVfVXG+tk1rfUiYC8fiWMfsyDDhXwXEmqGaKMJuYqDU2rpAyqgpyTZLUX43UXmZF9Kiw==",
"version": "0.2.138",
"resolved": "https://registry.npmjs.org/seafile-js/-/seafile-js-0.2.138.tgz",
"integrity": "sha512-glPINgDUw2a5q1qBX3hJPKlT2iNltvt3hQXkC6f2pQqLRdh3s8faNHP0ftD+7QnmEmXZpr1LX/6RjtdwP6U57g==",
"requires": {
"axios": "^0.18.0",
"form-data": "^2.3.2",

View File

@@ -41,7 +41,7 @@
"react-responsive": "^6.1.2",
"react-select": "^2.4.1",
"reactstrap": "^6.4.0",
"seafile-js": "^0.2.136",
"seafile-js": "^0.2.138",
"socket.io-client": "^2.2.0",
"sw-precache-webpack-plugin": "0.11.4",
"unified": "^7.0.0",

View File

@@ -60,6 +60,7 @@ import AdminLoginLogs from './admin-logs/login-logs';
import WebSettings from './web-settings/web-settings';
import Notifications from './notifications/notifications';
import FileScanRecords from './file-scan-records';
import VirusScanRecords from './virus-scan-records';
import WorkWeixinDepartments from './work-weixin-departments';
import Invitations from './invitations/invitations';
@@ -198,6 +199,11 @@ class SysAdmin extends React.Component {
currentTab={currentTab}
tabItemClick={this.tabItemClick}
/>
<VirusScanRecords
path={siteRoot + 'sys/virus-scan-records'}
currentTab={currentTab}
tabItemClick={this.tabItemClick}
/>
<WorkWeixinDepartments
path={siteRoot + 'sys/work-weixin'}
currentTab={currentTab}

View File

@@ -198,10 +198,14 @@ class SidePanel extends React.Component {
}
{isPro && isDefaultAdmin &&
<li className="nav-item">
<a className='nav-link ellipsis' href={siteRoot + 'sys/virus_scan_records/'}>
<Link
className={`nav-link ellipsis ${this.getActiveClass('virus-scan-records')}`}
to={siteRoot + 'sys/virus-scan-records/'}
onClick={() => this.props.tabItemClick('virus-scan-records')}
>
<span className="sf2-icon-security" aria-hidden="true"></span>
<span className="nav-text">{gettext('Virus Scan')}</span>
</a>
</Link>
</li>
}
{enableGuestInvitation && isDefaultAdmin &&

View File

@@ -0,0 +1,206 @@
import React, { Component, Fragment } from 'react';
import PropTypes from 'prop-types';
import { seafileAPI } from '../../utils/seafile-api';
import { gettext } from '../../utils/constants';
import toaster from '../../components/toast'
import Account from '../../components/common/account';
const recordItemPropTypes = {
record: PropTypes.object.isRequired,
};
class RecordItem extends Component {
constructor(props) {
super(props);
this.state = {
handleStatus: this.props.record.has_handle,
errorMsg: '',
};
}
deleteVirusScanRecord = () => {
seafileAPI.deleteVirusScanRecord(this.props.record.virus_id).then(() => {
this.setState({
handleStatus: !this.state.handleStatus,
});
}).catch((error) => {
if (error.response) {
this.setState({
errorMsg: error.response.data.error_msg,
});
toaster.danger(this.state.errorMsg);
}
});
}
render() {
let record = this.props.record;
return (
<tr>
<td>{record.repo_name}</td>
<td>{record.repo_owner}</td>
<td>{record.file_path}</td>
<td>
{
this.state.handleStatus ?
<span style={{color: "green"}}>{gettext('Handled')}</span> :
<a style={{color: "red", cursor: "pointer"}} onClick={this.deleteVirusScanRecord}>{gettext('Delete')}</a>
}
</td>
</tr>
);
}
}
RecordItem.propTypes = recordItemPropTypes;
const recordListPropTypes = {
loading: PropTypes.bool.isRequired,
isLoadingMore: PropTypes.bool.isRequired,
errorMsg: PropTypes.string.isRequired,
records: PropTypes.array.isRequired,
};
class RecordList extends Component {
render() {
let { loading, isLoadingMore, errorMsg, records } = this.props;
if (loading) {
return <span className="loading-icon loading-tip"></span>;
} else if (errorMsg) {
return <p className="error text-center">{errorMsg}</p>;
} else {
return (
<Fragment>
<table width="100%" className="table table-hover table-vcenter">
<thead>
<tr>
<th width="28%">{gettext('Library')}</th>
<th width="28%">{gettext('Owner')}</th>
<th width="29%">{gettext('Virus File')}</th>
<th width="15%">{gettext('Operations')}</th>
</tr>
</thead>
<tbody>
{records.map((record, index) => {
return (
<RecordItem key={index} record={record} />
);
})}
</tbody>
</table>
{isLoadingMore ? <span className="loading-icon loading-tip"></span> : ''}
</Fragment>
);
}
}
}
RecordList.propTypes = recordListPropTypes;
class VirusScanRecords extends Component {
constructor(props) {
super(props);
this.state = {
loading: true,
isLoadingMore: false,
currentPage: 1,
hasMore: true,
errorMsg: '',
records: [],
};
}
getMore() {
let currentPage = this.state.currentPage;
seafileAPI.listVirusScanRecords(currentPage).then((res) => {
this.setState({
isLoadingMore: false,
records: [...this.state.records, ...res.data.record_list],
currentPage: currentPage + 1,
hasMore: res.data.record_list.length === 0 ? false : true,
});
}).catch((error) => {
if (error.response) {
this.setState({
isLoadingMore: false,
errorMsg: error.response.data.error_msg,
});
toaster.danger(this.state.errorMsg);
}
});
}
handleScroll = (event) => {
if (!this.state.isLoadingMore && this.state.hasMore) {
const clientHeight = event.target.clientHeight;
const scrollHeight = event.target.scrollHeight;
const scrollTop = event.target.scrollTop;
const isBottom = (clientHeight + scrollTop + 1 >= scrollHeight);
if (isBottom) {
this.setState({isLoadingMore: true}, () => {
this.getMore();
});
}
}
}
componentDidMount() {
let currentPage = this.state.currentPage;
seafileAPI.listVirusScanRecords(currentPage).then((res) => {
this.setState({
loading: false,
records: res.data.record_list,
currentPage: currentPage + 1,
hasMore: true,
});
}).catch((error) => {
if (error.response) {
this.setState({
loading: false,
errorMsg: error.response.data.error_msg,
});
toaster.danger(this.state.errorMsg);
}
});
}
render() {
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">
<div className="cur-view-container" id="content-scan-records">
<div className="cur-view-path">
<h3 className="sf-heading">{gettext('Virus Scan Records')}</h3>
</div>
<div className="cur-view-content" onScroll={this.handleScroll}>
<RecordList
loading={this.state.loading}
isLoadingMore={this.state.isLoadingMore}
errorMsg={this.state.errorMsg}
records={this.state.records}
/>
</div>
</div>
</div>
</Fragment>
);
}
}
export default VirusScanRecords;

View File

@@ -0,0 +1,98 @@
# -*- coding: utf-8 -*-
import os
import logging
from rest_framework.authentication import SessionAuthentication
from rest_framework.permissions import IsAdminUser
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework import status
from seaserv import seafile_api
from seahub.api2.authentication import TokenAuthentication
from seahub.api2.throttling import UserRateThrottle
from seahub.api2.permissions import IsProVersion
from seahub.api2.utils import api_error
from seahub.utils import get_virus_record, get_virus_record_by_id, handle_virus_record
logger = logging.getLogger(__name__)
class AdminVirusScanRecords(APIView):
authentication_classes = (TokenAuthentication, SessionAuthentication)
permission_classes = (IsAdminUser, IsProVersion)
throttle_classes = (UserRateThrottle,)
def get(self, request):
"""get virus scan records
"""
try:
page = int(request.GET.get('page', ''))
except ValueError:
page = 1
try:
per_page = int(request.GET.get('per_page', ''))
except ValueError:
per_page = 25
start = (page - 1) * per_page
count = per_page
try:
virus_records = get_virus_record(start=start, limit=count)
except Exception as e:
logger.error(e)
error_msg = 'Internal Server Error'
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
record_list = list()
for virus_record in virus_records:
try:
repo = seafile_api.get_repo(virus_record.repo_id)
repo_owner = seafile_api.get_repo_owner(virus_record.repo_id)
except Exception as e:
logger.error(e)
continue
if not repo:
continue
else:
record = dict()
record["repo_name"] = repo.name
record["repo_owner"] = repo_owner
record["file_path"] = virus_record.file_path
record["has_handle"] = virus_record.has_handle
record["virus_id"] = virus_record.vid
record_list.append(record)
return Response({"record_list": record_list}, status=status.HTTP_200_OK)
class AdminVirusScanRecord(APIView):
authentication_classes = (TokenAuthentication, SessionAuthentication)
permission_classes = (IsAdminUser, IsProVersion)
throttle_classes = (UserRateThrottle,)
def delete(self, request, virus_id):
virus_record = get_virus_record_by_id(virus_id)
if not virus_record:
error_msg = 'Virus file %d not found.' % virus_record.file_path
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
parent_dir = os.path.dirname(virus_record.file_path)
filename = os.path.basename(virus_record.file_path)
try:
seafile_api.del_file(
virus_record.repo_id, parent_dir, filename, request.user.username
)
handle_virus_record(virus_id)
except Exception as e:
logger.error(e)
error_msg = 'Failed to delete, please try again later.'
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
return Response({"success": True}, status=status.HTTP_200_OK)

View File

@@ -107,7 +107,7 @@
{% if is_pro and is_default_admin %}
<li class="tab {% block cur_virus_scan %}{% endblock %}">
<a href="{{ SITE_ROOT }}sys/virus_scan_records/"><span class="sf2-icon-security"></span>{% trans "Virus Scan" %}</a>
<a href="{{ SITE_ROOT }}sys/virus-scan-records/"><span class="sf2-icon-security"></span>{% trans "Virus Scan" %}</a>
</li>
{% endif %}

View File

@@ -157,6 +157,7 @@ from seahub.api2.endpoints.admin.logs import AdminLogsLoginLogs, AdminLogsFileAc
AdminLogsSharePermissionLogs
from seahub.api2.endpoints.admin.work_weixin import AdminWorkWeixinDepartments, \
AdminWorkWeixinDepartmentMembers, AdminWorkWeixinUsersBatch, AdminWorkWeixinDepartmentsImport
from seahub.api2.endpoints.admin.virus_scan_records import AdminVirusScanRecords, AdminVirusScanRecord
from seahub.api2.endpoints.file_participants import FileParticipantsView, FileParticipantView
from seahub.api2.endpoints.repo_related_users import RepoRelatedUsersView
@@ -610,6 +611,10 @@ urlpatterns = [
## admin::file-scan-records
url(r'^api/v2.1/admin/file-scan-records/$', AdminFileScanRecords.as_view(), name='api-v2.1-admin-file-scan-records'),
# admin::virus-scan-records
url(r'^api/v2.1/admin/virus-scan-records/$', AdminVirusScanRecords.as_view(), name='api-v2.1-admin-virus-scan-records'),
url(r'^api/v2.1/admin/virus-scan-records/(?P<virus_id>\d+)/$', AdminVirusScanRecord.as_view(), name='api-v2.1-admin-virus-scan-record'),
## admin::notifications
url(r'^api/v2.1/admin/notifications/$', AdminNotificationsView.as_view(), name='api-2.1-admin-notifications'),
url(r'^api/v2.1/admin/sys-notifications/$', AdminSysNotificationsView.as_view(), name='api-2.1-admin-sys-notifications'),
@@ -751,8 +756,7 @@ if ENABLE_FILE_SCAN:
from seahub.utils import EVENTS_ENABLED
if EVENTS_ENABLED:
urlpatterns += [
url(r'^sys/virus_scan_records/$', sys_virus_scan_records, name='sys_virus_scan_records'),
url(r'^sys/virus_scan_records/delete/(?P<vid>\d+)/$', sys_delete_virus_scan_records, name='sys_delete_virus_scan_records'),
url(r'^sys/virus-scan-records/$', sysadmin_react_fake_view, name='sys_virus_scan_records'),
]
if settings.SERVE_STATIC: