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:
18
frontend/package-lock.json
generated
18
frontend/package-lock.json
generated
@@ -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",
|
||||
|
@@ -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",
|
||||
|
@@ -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}
|
||||
|
@@ -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 &&
|
||||
|
206
frontend/src/pages/sys-admin/virus-scan-records.js
Normal file
206
frontend/src/pages/sys-admin/virus-scan-records.js
Normal 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;
|
98
seahub/api2/endpoints/admin/virus_scan_records.py
Normal file
98
seahub/api2/endpoints/admin/virus_scan_records.py
Normal 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)
|
@@ -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 %}
|
||||
|
||||
|
@@ -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:
|
||||
|
Reference in New Issue
Block a user