1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-09-09 02:42:47 +00:00

Org export log (#6652)

* org admin export log

* optimize code

* optimize code

* optimize code

* update

* update code

* update

---------

Co-authored-by: 孙永强 <11704063+s-yongqiang@user.noreply.gitee.com>
Co-authored-by: r350178982 <32759763+r350178982@users.noreply.github.com>
This commit is contained in:
awu0403
2024-09-02 18:07:40 +08:00
committed by GitHub
parent b4059f9bf7
commit 670ee89f24
14 changed files with 357 additions and 52 deletions

View File

@@ -0,0 +1,158 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Button, Modal, ModalHeader, ModalBody, ModalFooter, FormGroup, Label, Input, Alert } from 'reactstrap';
import { gettext, siteRoot, orgID } from '../../utils/constants';
import { orgAdminAPI } from '../../utils/org-admin-api';
import { userAPI } from '../../utils/user-api';
import toaster from '../../components/toast';
import { Utils } from '../../utils/utils';
import moment from 'moment';
class OrgLogsExportExcelDialog extends React.Component {
constructor(props) {
super(props);
this.state = {
loading: true,
startDateStr: '',
endDateStr: '',
errMsg: '',
taskId: '',
};
}
downloadExcel = () => {
if (!this.isValidDateStr()) {
return;
}
switch (this.props.logType) {
case 'fileaudit':
this.orgAdminExportLogs('fileaudit');
break;
case 'file-update':
this.orgAdminExportLogs('fileupdate');
break;
case 'perm-audit':
this.orgAdminExportLogs('permaudit');
break;
}
};
orgAdminExportLogs = (logType) => {
let { startDateStr, endDateStr } = this.state;
let task_id = '';
orgAdminAPI.orgAdminExportLogsExcel(orgID, startDateStr, endDateStr, logType).then(res => {
task_id = res.data.task_id;
this.setState({
taskId: task_id
});
this.props.toggle();
return userAPI.queryIOStatus(task_id);
}).then(res => {
if (res.data.is_finished === true) {
location.href = siteRoot + 'api/v2.1/org/admin/log/export-excel/?task_id=' + task_id + '&log_type=' + logType;
} else {
this.timer = setInterval(() => {
userAPI.queryIOStatus(task_id).then(res => {
if (res.data.is_finished === true) {
clearInterval(this.timer);
location.href = siteRoot + 'api/v2.1/org/admin/log/export-excel/?task_id=' + task_id + '&log_type=' + logType;
}
}).catch(err => {
clearInterval(this.timer);
toaster.danger(gettext('Failed to export. Please check whether the size of table attachments exceeds the limit.'));
});
}, 1000);
}
}).catch(error => {
this.props.toggle();
if (error.response && error.response.status === 500) {
const error_msg = error.response.data ? error.response.data['error_msg'] : null;
if (error_msg && error_msg !== 'Internal Server Error') {
toaster.danger(error_msg);
} else {
toaster.danger(gettext('Internal Server Error'));
}
} else {
let errMessage = Utils.getErrorMsg(error);
toaster.danger(errMessage);
}
});
};
isValidDateStr = () => {
let { startDateStr, endDateStr } = this.state;
if (moment(startDateStr, 'YYYY-MM-DD', true).isValid() &&
moment(endDateStr, 'YYYY-MM-DD', true).isValid() &&
moment(startDateStr).isBefore(endDateStr)
) {
return true;
} else {
this.setState({
errMsg: gettext('Date Invalid.')
});
return false;
}
};
handleStartChange = (e) => {
const startDateStr = e.target.value.trim();
this.setState({
startDateStr: startDateStr,
errMsg: ''
});
};
handleEndChange = (e) => {
const endDateStr = e.target.value.trim();
this.setState({
endDateStr: endDateStr,
errMsg: '',
});
};
render() {
return (
<Modal isOpen={true} toggle={this.props.toggle} autoFocus={false}>
<ModalHeader toggle={this.props.toggle}>{gettext('Choose date')}</ModalHeader>
<ModalBody>
<FormGroup>
<Label>{gettext('Start date')}</Label>
<Input
value={this.state.startDateStr}
onChange={this.handleStartChange}
placeholder='yyyy-mm-dd'
autoFocus={true}
/>
</FormGroup>
<FormGroup>
<Label>{gettext('End date')}</Label>
<Input
value={this.state.endDateStr}
onChange={this.handleEndChange}
placeholder='yyyy-mm-dd'
/>
</FormGroup>
{this.state.errMsg &&
<Alert className="mt-2" color="danger">
{gettext(this.state.errMsg)}
</Alert>
}
</ModalBody>
<ModalFooter>
<Button color="secondary" onClick={this.props.toggle}>{gettext('Cancel')}</Button>
<Button color="primary" onClick={this.downloadExcel}>{gettext('Submit')}</Button>
</ModalFooter>
</Modal>
);
}
}
const propTypes = {
toggle: PropTypes.func.isRequired,
logType: PropTypes.string.isRequired,
};
OrgLogsExportExcelDialog.propTypes = propTypes;
export default OrgLogsExportExcelDialog;

View File

@@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
import { Button, Modal, ModalHeader, ModalBody, ModalFooter, FormGroup, Label, Input, Alert } from 'reactstrap';
import { gettext, siteRoot } from '../../../utils/constants';
import { systemAdminAPI } from '../../../utils/system-admin-api';
import { userAPI } from '../../../utils/user-api';
import toaster from '../../../components/toast';
import { Utils } from '../../../utils/utils';
import moment from 'moment';
@@ -49,23 +50,20 @@ class LogsExportExcelDialog extends React.Component {
taskId: task_id
});
this.props.toggle();
return systemAdminAPI.queryAsyncOperationExportExcel(task_id);
return userAPI.queryIOStatus(task_id);
}).then(res => {
if (res.data.is_finished === true) {
location.href = siteRoot + 'sys/log/export-excel/?task_id=' + task_id + '&log_type=' + logType;
} else {
this.timer = setInterval(() => {
systemAdminAPI.queryAsyncOperationExportExcel(task_id).then(res => {
userAPI.queryIOStatus(task_id).then(res => {
if (res.data.is_finished === true) {
this.setState({ isFinished: true });
clearInterval(this.timer);
location.href = siteRoot + 'sys/log/export-excel/?task_id=' + task_id + '&log_type=' + logType;
}
}).catch(err => {
if (this.state.isFinished === false) {
clearInterval(this.timer);
toaster.danger(gettext('Failed to export. Please check whether the size of table attachments exceeds the limit.'));
}
});
}, 1000);
}

View File

@@ -73,7 +73,14 @@ class Org extends React.Component {
if (location.href.indexOf(`${siteRoot}org/departmentadmin`) != -1) {
currentTab = 'departmentadmin';
}
this.setState({ currentTab: currentTab });
if (location.href.indexOf(`${siteRoot}org/logadmin/`) != -1) {
if (currentTab === 'logadmin') {
currentTab = 'fileaudit';
}
}
this.setState({
currentTab: currentTab
});
}
onCloseSidePanel = () => {

View File

@@ -3,28 +3,53 @@ import PropTypes from 'prop-types';
import { Link } from '@gatsbyjs/reach-router';
import { siteRoot, gettext } from '../../utils/constants';
import MainPanelTopbar from './main-panel-topbar';
import { Button } from 'reactstrap';
import ModalPortal from '../../components/modal-portal';
import OrgLogsExportExcelDialog from '../../components/dialog/org-admin-logs-export-excel-dialog';
class OrgLogs extends Component {
constructor(props) {
super(props);
this.state = {
isExportExcelDialogOpen: false,
logType: '',
};
}
componentDidMount() {
let href = window.location.href.split('/');
let logtype = href[href.length - 2];
if (logtype === 'logadmin') {
logtype = 'fileaudit';
}
this.setState({ logType: logtype });
}
toggleExportExcelDialog = () => {
this.setState({ isExportExcelDialogOpen: !this.state.isExportExcelDialogOpen });
};
tabItemClick = (param) => {
this.setState({
logType: param
});
this.props.tabItemClick(param);
};
render() {
const { isExportExcelDialogOpen, logType } = this.state;
return (
<Fragment>
<MainPanelTopbar/>
<MainPanelTopbar>
<Button className="btn btn-secondary operation-item" onClick={this.toggleExportExcelDialog}>{gettext('Export Excel')}</Button>
</MainPanelTopbar>
<div className="main-panel-center flex-row">
<div className="cur-view-container">
<div className="cur-view-container h-100">
<div className="cur-view-path org-user-nav">
<ul className="nav">
<li className="nav-item" onClick={() => this.tabItemClick('logadmin')}>
<li className="nav-item" onClick={() => this.tabItemClick('fileaudit')}>
<Link
className={`nav-link ${this.props.currentTab === 'logadmin' ? 'active' : ''}`}
className={`nav-link ${this.props.currentTab === 'fileaudit' ? 'active' : ''}`}
to={siteRoot + 'org/logadmin/'} title={gettext('File Access')}>{gettext('File Access')}
</Link>
</li>
@@ -42,9 +67,19 @@ class OrgLogs extends Component {
</li>
</ul>
</div>
<div className="h-100 o-auto">
{this.props.children}
</div>
</div>
</div>
{isExportExcelDialogOpen &&
<ModalPortal>
<OrgLogsExportExcelDialog
logType={logType}
toggle={this.toggleExportExcelDialog}
/>
</ModalPortal>
}
</Fragment>
);
}

View File

@@ -95,8 +95,8 @@ class SidePanel extends React.Component {
<span className="nav-text">{gettext('Links')}</span>
</Link>
</li>
<li className={`nav-item ${this.getActiveClass('logadmin') || this.getActiveClass('file-update') || this.getActiveClass('perm-audit')}`}>
<Link className={`nav-link ellipsis ${this.getActiveClass('logadmin') || this.getActiveClass('file-update') || this.getActiveClass('perm-audit')}`} to={siteRoot + 'org/logadmin/'} onClick={() => this.tabItemClick('logadmin')} >
<li className={`nav-item ${this.getActiveClass('fileaudit') || this.getActiveClass('file-update') || this.getActiveClass('perm-audit')}`}>
<Link className={`nav-link ellipsis ${this.getActiveClass('fileaudit') || this.getActiveClass('file-update') || this.getActiveClass('perm-audit')}`} to={siteRoot + 'org/logadmin/'} onClick={() => this.tabItemClick('fileaudit')} >
<span className="sf2-icon-clock"></span>
<span className="nav-text">{gettext('Logs')}</span>
</Link>

View File

@@ -49,6 +49,16 @@ class OrgAdminAPI {
return this.req.post(url);
}
orgAdminExportLogsExcel(orgID, start, end, logType) {
const url = this.server + '/api/v2.1/org/' + orgID + '/admin/logs/export-excel/';
const params = {
start: start,
end: end,
logType: logType
};
return this.req.get(url, { params: params });
}
}
let orgAdminAPI = new OrgAdminAPI();

View File

@@ -76,11 +76,6 @@ class SystemAdminAPI {
return this.req.get(url, { params: params });
}
queryAsyncOperationExportExcel(task_id) {
const url = this.server + '/api/v2.1/query-export-status/?task_id=' + task_id;
return this.req.get(url);
}
adminGroup2Department(groupID) {
const url = this.server + '/api/v2.1/admin/groups/' + groupID + '/group-to-department/';
return this.req.post(url);

View File

@@ -42,6 +42,11 @@ class UserAPI {
};
return this.req.post(url, data);
}
queryIOStatus(task_id) {
const url = this.server + '/api/v2.1/query-io-status/?task_id=' + task_id;
return this.req.get(url);
}
}
let userAPI = new UserAPI();

View File

@@ -1,7 +1,4 @@
import os
import logging
import time
import json
from shutil import rmtree
from django.http import FileResponse
from rest_framework.authentication import SessionAuthentication
@@ -9,6 +6,7 @@ from rest_framework.permissions import IsAdminUser
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework import status
from rest_framework.decorators import api_view
from urllib.parse import quote
from seahub.api2.authentication import TokenAuthentication
@@ -39,34 +37,11 @@ class SysLogsExport(APIView):
return Response(res_data)
class FileLogsExportStatus(APIView):
authentication_classes = (TokenAuthentication, SessionAuthentication)
permission_classes = (IsAdminUser, IsProVersion)
throttle_classes = (UserRateThrottle,)
def get(self, request):
"""
Get task status by task id
"""
task_id = request.GET.get('task_id', '')
if not task_id:
error_msg = 'task_id invalid.'
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
resp = event_export_status(task_id)
if resp.status_code == 500:
logger.error('query export status error: %s, %s' % (task_id, resp.content))
return api_error(500, 'Internal Server Error')
if not resp.status_code == 200:
return api_error(resp.status_code, resp.content)
is_finished = json.loads(resp.content)['is_finished']
return Response({'is_finished': is_finished})
@login_required
@sys_staff_required
@api_view(('GET',))
def sys_log_export_excel(request):
task_id = request.GET.get('task_id', None)
log_type = request.GET.get('log_type', None)

View File

@@ -0,0 +1,41 @@
import logging
import json
from rest_framework.authentication import SessionAuthentication
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework import status
from seahub.api2.authentication import TokenAuthentication
from seahub.api2.endpoints.utils import event_export_status
from seahub.api2.permissions import IsProVersion
from seahub.api2.throttling import UserRateThrottle
from seahub.api2.utils import api_error
logger = logging.getLogger(__name__)
class SeahubIOStatus(APIView):
authentication_classes = (TokenAuthentication, SessionAuthentication)
permission_classes = (IsProVersion,)
throttle_classes = (UserRateThrottle,)
def get(self, request):
"""
Get task status by task id
"""
task_id = request.GET.get('task_id', '')
if not task_id:
error_msg = 'task_id invalid.'
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
resp = event_export_status(task_id)
if resp.status_code == 500:
logger.error('query export status error: %s, %s' % (task_id, resp.content))
return api_error(500, 'Internal Server Error')
if not resp.status_code == 200:
return api_error(resp.status_code, resp.content)
is_finished = json.loads(resp.content)['is_finished']
return Response({'is_finished': is_finished})

View File

@@ -296,13 +296,16 @@ def format_date(start, end):
return start_timestamp, end_timestamp
def export_logs_to_excel(start, end, log_type):
def export_logs_to_excel(start, end, log_type, org_id=None):
start_time, end_time = format_date(start, end)
payload = {'exp': int(time.time()) + 300, }
token = jwt.encode(payload, SECRET_KEY, algorithm='HS256')
headers = {"Authorization": "Token %s" % token}
url = urljoin(SEAFEVENTS_SERVER_URL, '/add-init-export-log-task')
params = {'start_time': start_time, 'end_time': end_time, 'log_type': log_type}
if not org_id:
url = urljoin(SEAFEVENTS_SERVER_URL, '/add-export-log-task')
else:
url = urljoin(SEAFEVENTS_SERVER_URL, '/add-org-export-log-task')
params = {'start_time': start_time, 'end_time': end_time, 'log_type': log_type, 'org_id': org_id}
resp = requests.get(url, params=params, headers=headers)
return json.loads(resp.content)['task_id']

View File

@@ -31,6 +31,8 @@ from .api.admin.statistics import OrgFileOperationsView, OrgTotalStorageView, \
OrgUserTrafficExcelView, OrgUserStorageExcelView
from .api.admin.saml_config import OrgSAMLConfigView, OrgVerifyDomain
from .org_logs_export import OrgLogsExport, org_log_export_excel
urlpatterns = [
path('<int:org_id>/admin/statistics/file-operations/',
@@ -101,5 +103,8 @@ urlpatterns = [
path('admin/logs/file-update/', OrgAdminLogsFileUpdate.as_view(), name='api-v2.1-org-admin-logs-file-update'),
path('admin/logs/repo-permission/', OrgAdminLogsPermAudit.as_view(), name='api-v2.1-org-admin-logs-repo-permission'),
path('<int:org_id>/admin/departments/', OrgAdminDepartments.as_view(), name='api-v2.1-org-admin-departments'),
path('<int:org_id>/admin/logs/export-excel/', OrgLogsExport.as_view(), name='api-v2.1-org-logs-export-excel'),
path('admin/log/export-excel/', org_log_export_excel, name='org_log_export_excel'),
]

View File

@@ -0,0 +1,72 @@
import os
import logging
from shutil import rmtree
from django.http import FileResponse
from rest_framework.authentication import SessionAuthentication
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework import status
from rest_framework.decorators import api_view
from urllib.parse import quote
from seahub.api2.authentication import TokenAuthentication
from seahub.api2.endpoints.utils import check_time_period_valid, export_logs_to_excel
from seahub.api2.permissions import IsProVersion
from seahub.api2.throttling import UserRateThrottle
from seahub.api2.utils import api_error
from seahub.auth.decorators import login_required
from seahub.organizations.api.permissions import IsOrgAdmin
from seahub.organizations.decorators import org_staff_required
logger = logging.getLogger(__name__)
class OrgLogsExport(APIView):
authentication_classes = (TokenAuthentication, SessionAuthentication)
permission_classes = (IsOrgAdmin, IsProVersion)
throttle_classes = (UserRateThrottle,)
def get(self, request, org_id):
start = request.GET.get('start', None)
end = request.GET.get('end', None)
log_type = request.GET.get('logType', None)
if not check_time_period_valid(start, end):
error_msg = 'Failed to export excel, invalid start or end date.'
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
task_id = export_logs_to_excel(start, end, log_type, org_id)
res_data = {'task_id': task_id}
return Response(res_data)
@login_required
@org_staff_required
@api_view(('GET',))
def org_log_export_excel(request):
task_id = request.GET.get('task_id', None)
log_type = request.GET.get('log_type', None)
if not task_id:
error_msg = 'task_id invalid.'
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
if log_type == 'fileaudit':
excel_name = 'file-access-logs.xlsx'
elif log_type == 'fileupdate':
excel_name = 'file-update-logs.xlsx'
elif log_type == 'permaudit':
excel_name = 'perm-audit-logs.xlsx'
else:
error_msg = 'log_type invalid'
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
target_dir = os.path.join('/tmp/seafile_events/', task_id)
tmp_excel_path = os.path.join(target_dir, excel_name)
if not os.path.isfile(tmp_excel_path):
return api_error(status.HTTP_400_BAD_REQUEST, excel_name + ' not found.')
response = FileResponse(open(tmp_excel_path, 'rb'), content_type='application/ms-excel', as_attachment=True)
try:
rmtree(target_dir)
except OSError:
pass
response['Content-Disposition'] = 'attachment;filename*=UTF-8\'\'' + quote(excel_name)
return response

View File

@@ -125,7 +125,7 @@ from seahub.api2.endpoints.repo_share_links import RepoShareLinks, RepoShareLink
from seahub.api2.endpoints.repo_upload_links import RepoUploadLinks, RepoUploadLink
# Admin
from seahub.api2.endpoints.admin.logs_export import SysLogsExport, FileLogsExportStatus, sys_log_export_excel
from seahub.api2.endpoints.admin.logs_export import SysLogsExport, sys_log_export_excel
from seahub.api2.endpoints.admin.abuse_reports import AdminAbuseReportsView, AdminAbuseReportView
from seahub.api2.endpoints.admin.revision_tag import AdminTaggedItemsView
from seahub.api2.endpoints.admin.login_logs import LoginLogs, AdminLoginLogs
@@ -212,6 +212,7 @@ from seahub.api2.endpoints.subscription import SubscriptionView, SubscriptionPla
from seahub.api2.endpoints.metadata_manage import MetadataRecords, MetadataManage, MetadataColumns, MetadataRecordInfo, \
MetadataViews, MetadataViewsMoveView, MetadataViewsDetailView, MetadataViewsDuplicateView
from seahub.api2.endpoints.user_list import UserListView
from seahub.api2.endpoints.seahub_io import SeahubIOStatus
urlpatterns = [
@@ -911,7 +912,7 @@ if is_pro_version():
re_path(r'^api/v2.1/admin/logs/perm-audit/$', PermAudit.as_view(), name='api-v2.1-admin-logs-perm-audit'),
re_path(r'^api/v2.1/admin/logs/export-excel/$', SysLogsExport.as_view(), name='api-v2.1-admin-logs-export-excel'),
re_path(r'^api/v2.1/query-export-status/$', FileLogsExportStatus.as_view(), name='api-v2.1-query-export-status'),
re_path(r'^api/v2.1/query-io-status/$', SeahubIOStatus.as_view(), name='api-v2.1-query-export-status'),
path('sys/log/export-excel/', sys_log_export_excel, name='sys_log_export_excel'),