1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-09-17 07:41:26 +00:00

Illegal report by react (#3415)

* illegal report

1, add illegal report at shared file page
2, list all illegal reports at admin page

* add ENABLE_SHARE_LINK_REPORT_ILLEGAL setting

* UserRateThrottle -> AnonRateThrottle

* use to_python_boolean

* frontend by React

* remove illegal report dialog in shared dir view

* add migrations dir

* add illegal_reports migrations

* rename illegal to abuse in api

* rename illegal to abuse in test

* rename illegal to abuse in share file view

* rename illegal to abuse in react

* rename illegal to abuse in Backbone

* add enableShareLinkReportAbuse in templates

* add ReportAbuse

* update ReportAbuse

* update ReportAbuse urls

* update ReportAbuse api-js

* sysadmin_react_app.html

* sysadmin.py

* fix

* fix

* fix

* can not abuse own file

* Contact Information is required.

* fix review

* remove repo icon
This commit is contained in:
sniper-py
2019-11-05 17:46:06 +08:00
committed by Daniel Pan
parent d95da0bff1
commit 37b743fe3a
31 changed files with 1114 additions and 43 deletions

View File

@@ -0,0 +1,102 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Button, Form, FormGroup, Label, Input, Modal, ModalHeader, ModalBody, ModalFooter, Alert } from 'reactstrap';
import { gettext } from '../../utils/constants';
import { seafileAPI } from '../../utils/seafile-api';
import toaster from '../toast';
const propTypes = {
sharedToken: PropTypes.string.isRequired,
filePath: PropTypes.string.isRequired,
toggleAddAbuseReportDialog: PropTypes.func.isRequired,
isAddAbuseReportDialogOpen: PropTypes.bool.isRequired,
contactEmail: PropTypes.string.isRequired,
};
class AddAbuseReportDialog extends React.Component {
constructor(props) {
super(props);
this.state = {
abuseType: 'copyright',
description: '',
reporter: this.props.contactEmail,
errMessage: '',
};
}
onAbuseReport = () => {
if (!this.state.reporter) {
this.setState({
errMessage: gettext('Contact information is required.')
});
return;
}
seafileAPI.addAbuseReport(this.props.sharedToken, this.state.abuseType, this.state.description, this.state.reporter, this.props.filePath).then((res) => {
this.props.toggleAddAbuseReportDialog();
toaster.success(gettext('Success'), {duration: 2});
}).catch((error) => {
if (error.response) {
this.setState({
errMessage: error.response.data.error_msg
});
}
});
};
onAbuseTypeChange = (event) => {
let type = event.target.value;
if (type === this.state.abuseType) {
return;
}
this.setState({abuseType: type});
};
setReporter = (event) => {
let reporter = event.target.value.trim();
this.setState({reporter: reporter});
};
setDescription = (event) => {
let desc = event.target.value.trim();
this.setState({description: desc});
};
render() {
return (
<Modal isOpen={this.props.isAddAbuseReportDialogOpen} toggle={this.props.toggleAddAbuseReportDialog}>
<ModalHeader toggle={this.props.toggleAddAbuseReportDialog}>{gettext('Report Abuse')}</ModalHeader>
<ModalBody>
<Form>
<FormGroup>
<Label for="abuse-type-select">{gettext('Abuse Type')}</Label>
<Input type="select" id="abuse-type-select" onChange={(event) => this.onAbuseTypeChange(event)}>
<option value='copyright'>{gettext('Copyright Infringement')}</option>
<option value='virus'>{gettext('Virus')}</option>
<option value='abuse_content'>{gettext('Abuse Content')}</option>
<option value='other'>{gettext('Other')}</option>
</Input>
</FormGroup>
<FormGroup>
<Label>{gettext("Contact Information")}</Label>
<Input type="text" value={this.state.reporter} onChange={(event) => this.setReporter(event)}/>
</FormGroup>
<FormGroup>
<Label>{gettext("Description")}</Label>
<Input type="textarea" onChange={(event) => this.setDescription(event)}/>
</FormGroup>
</Form>
{this.state.errMessage && <Alert color="danger">{this.state.errMessage}</Alert>}
</ModalBody>
<ModalFooter>
<Button color="secondary" onClick={this.props.toggleAddAbuseReportDialog}>{gettext('Cancel')}</Button>
<Button color="primary" onClick={this.onAbuseReport}>{gettext('Submit')}</Button>
</ModalFooter>
</Modal>
);
}
}
AddAbuseReportDialog.propTypes = propTypes;
export default AddAbuseReportDialog;

View File

@@ -5,6 +5,7 @@ import { gettext, siteRoot, mediaUrl, logoPath, logoWidth, logoHeight, siteTitle
import { Button } from 'reactstrap';
import { Utils } from '../../utils/utils';
import SaveSharedFileDialog from '../dialog/save-shared-file-dialog';
import AddAbuseReportDialog from '../../components/dialog/add-abuse-report-dialog';
import toaster from '../toast';
import watermark from 'watermark-dom';
@@ -15,14 +16,16 @@ const propTypes = {
};
let loginUser = window.app.pageOptions.name;
const { repoID, sharedToken, trafficOverLimit, fileName, fileSize, sharedBy, siteName, enableWatermark, canDownload, zipped, filePath } = window.shared.pageOptions;
let contactEmail = window.app.pageOptions.contactEmail;
const { repoID, sharedToken, trafficOverLimit, fileName, fileSize, sharedBy, siteName, enableWatermark, canDownload, zipped, filePath, enableShareLinkReportAbuse } = window.shared.pageOptions;
class SharedFileView extends React.Component {
constructor(props) {
super(props);
this.state = {
showSaveSharedFileDialog: false
showSaveSharedFileDialog: false,
isAddAbuseReportDialogOpen: false
};
}
@@ -44,6 +47,12 @@ class SharedFileView extends React.Component {
});
}
toggleAddAbuseReportDialog = () => {
this.setState({
isAddAbuseReportDialogOpen: !this.state.isAddAbuseReportDialogOpen
});
}
componentDidMount() {
if (trafficOverLimit) {
toaster.danger(gettext('File download is disabled: the share link traffic of owner is used up.'), {
@@ -106,18 +115,21 @@ class SharedFileView extends React.Component {
<p className="share-by ellipsis">{gettext('Shared by:')}{' '}{sharedBy}</p>
}
</div>
{canDownload &&
<div className="float-right">
{(loginUser && loginUser !== sharedBy) &&
<Button color="secondary" id="save"
onClick={this.handleSaveSharedFileDialog}>{gettext('Save as ...')}
</Button>
}{' '}
{!trafficOverLimit &&
<div className="float-right">
{(canDownload && loginUser && (loginUser !== sharedBy)) &&
<Button color="secondary" id="save"
onClick={this.handleSaveSharedFileDialog}>{gettext('Save as ...')}
</Button>
}{' '}
{(canDownload && !trafficOverLimit) &&
<a href={`?${zipped ? 'p=' + encodeURIComponent(filePath) + '&' : ''}dl=1`} className="btn btn-success">{gettext('Download')}({Utils.bytesToSize(fileSize)})</a>
}
</div>
}
}{' '}
{(enableShareLinkReportAbuse && (loginUser !== sharedBy)) &&
<Button
onClick={this.toggleAddAbuseReportDialog}>{gettext('Report Abuse')}
</Button>
}
</div>
</div>
{this.props.content}
</div>
@@ -129,6 +141,15 @@ class SharedFileView extends React.Component {
handleSaveSharedFile={this.handleSaveSharedFile}
/>
}
{(this.state.isAddAbuseReportDialogOpen && enableShareLinkReportAbuse) &&
<AddAbuseReportDialog
sharedToken={sharedToken}
filePath={filePath}
toggleAddAbuseReportDialog={this.toggleAddAbuseReportDialog}
isAddAbuseReportDialogOpen={this.state.isAddAbuseReportDialogOpen}
contactEmail={contactEmail}
/>
}
</div>
);
}

View File

@@ -0,0 +1,124 @@
import React, { Component, Fragment } from 'react';
import Account from '../../components/common/account';
import { gettext, siteRoot, mediaUrl } from '../../utils/constants';
import { Utils } from '../../utils/utils';
import { seafileAPI } from '../../utils/seafile-api';
import toaster from '../../components/toast';
import moment from 'moment';
class AbuseReports extends Component {
constructor(props) {
super(props);
this.state = {
abuseReportList: [],
};
}
listAbuseReports = () => {
seafileAPI.sysAdminListAbuseReports().then((res) => {
this.setState({
abuseReportList: res.data.abuse_report_list,
});
}).catch((error) => {
this.handleError(error);
});
};
updateAbuseReport = (handled, abuseReportId) => {
seafileAPI.sysAdminUpdateAbuseReport(handled, abuseReportId).then((res) => {
const abuseReportList = this.state.abuseReportList.map((item, index) => {
if (item.id === abuseReportId) {
item.handled = res.data.handled;
}
return item;
});
this.setState({
abuseReportList: abuseReportList,
});
}).catch((error) => {
this.handleError(error);
});
};
handleError = (e) => {
if (e.response) {
toaster.danger(e.response.data.error_msg || e.response.data.detail || gettext('Error'), {duration: 3});
} else {
toaster.danger(gettext('Please check the network.'), {duration: 3});
}
};
componentDidMount() {
this.listAbuseReports();
}
render() {
const isDesktop = Utils.isDesktop();
const AbuseReportList = this.state.abuseReportList.map((item, index) => {
const handled = (!item.handled).toString();
const abuseReportId = item.id;
const fileUrl = siteRoot + 'lib/' + item.repo_id + '/file' + item.file_path;
return (
<tr key={index}>
<td>{item.repo_name}</td>
<td><a href={fileUrl} target="_blank">{item.file_path}</a></td>
<td>{item.reporter}</td>
<td>{item.abuse_type}</td>
<td>{item.description}</td>
<td>{moment(item.time).format('YYYY-MM-DD')}</td>
<td><p onClick={this.updateAbuseReport.bind(this, handled, abuseReportId)}
className="op-target ellipsis ellipsis-op-target cursor-pointer"
>{gettext(item.handled.toString())}</p></td>
</tr>
);
});
return (
<Fragment>
<div className="main-panel-north">
<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">
<div className="cur-view-path">
<h3 className="sf-heading">{gettext('Abuse Reports')}</h3>
</div>
<div className="cur-view-content">
<table className={`table-hover${isDesktop ? '' : ' table-thead-hidden'}`}>
<thead>
{isDesktop ?
<tr>
<th width="20%">{gettext("Library")}</th>
<th width="20%">{gettext("File")}</th>
<th width="10%">{gettext("Reporter")}</th>
<th width="15%">{gettext("Abuse Type")}</th>
<th width="20%">{gettext("Description")}</th>
<th width="10%">{gettext("Time")}</th>
<th width="5%">{gettext("Handled")}</th>
</tr>
:
<tr>
<th width="92%"></th>
<th width="8%"></th>
</tr>
}
</thead>
<tbody>
{AbuseReportList}
</tbody>
</table>
</div>
</div>
</div>
</Fragment>
);
}
}
export default AbuseReports;

View File

@@ -10,6 +10,7 @@ import Info from './info';
import DesktopDevices from './devices/desktop-devices';
import MobileDevices from './devices/mobile-devices';
import DeviceErrors from './devices/devices-errors';
import AbuseReports from './abuse-reports';
import Users from './users/users';
import AdminUsers from './users/admin-users';
@@ -202,6 +203,7 @@ class SysAdmin extends React.Component {
currentTab={currentTab}
tabItemClick={this.tabItemClick}
/>
<AbuseReports path={siteRoot + 'sys/abuse-reports'} />
</Router>
</MainPanel>
</div>

View File

@@ -5,7 +5,8 @@ import Logo from '../../components/logo';
import { gettext, siteRoot, isPro, isDefaultAdmin, canViewSystemInfo, canViewStatistic,
canConfigSystem, canManageLibrary, canManageUser, canManageGroup, canViewUserLog,
canViewAdminLog, constanceEnabled, multiTenancy, multiInstitution, sysadminExtraEnabled,
enableGuestInvitation, enableTermsAndConditions, enableFileScan, enableWorkWeixin } from '../../utils/constants';
enableGuestInvitation, enableTermsAndConditions, enableFileScan, enableWorkWeixin,
enableShareLinkReportAbuse } from '../../utils/constants';
const propTypes = {
isSidePanelClosed: PropTypes.bool.isRequired,
@@ -247,6 +248,18 @@ class SidePanel extends React.Component {
</Link>
</li>
}
{isDefaultAdmin && enableShareLinkReportAbuse &&
<li className="nav-item">
<Link
className={`nav-link ellipsis ${this.getActiveClass('abuse-reports')}`}
to={siteRoot + 'sys/abuse-reports/'}
onClick={() => this.props.tabItemClick('abuse-reports')}
>
<span className="sf2-icon-monitor" aria-hidden="true"></span>
<span className="nav-text">{gettext('Abuse Reports')}</span>
</Link>
</li>
}
</ul>
</div>
</div>

View File

@@ -44,8 +44,8 @@ class FileContent extends React.Component {
let imageUrl = innerNode.data.src;
const re = new RegExp(serviceURL + '/lib/' + repoID +'/file.*raw=1');
// different repo
// different repo
if (!re.test(imageUrl)) {
return;
}
@@ -78,7 +78,7 @@ class FileContent extends React.Component {
return (
<div className="shared-file-view-body">
<div className="md-view">
<MarkdownViewer
<MarkdownViewer
markdownContent={this.state.markdownContent}
showTOC={false}
serviceURL={serviceURL}
@@ -95,4 +95,4 @@ class FileContent extends React.Component {
ReactDOM.render (
<SharedFileViewMarkdown />,
document.getElementById('wrapper')
);
);

View File

@@ -126,3 +126,4 @@ export const canViewAdminLog = window.sysadmin ? window.sysadmin.pageOptions.adm
export const enableWorkWeixin = window.sysadmin ? window.sysadmin.pageOptions.enable_work_weixin : '';
export const enableSysAdminViewRepo = window.sysadmin ? window.sysadmin.pageOptions.enableSysAdminViewRepo : '';
export const haveLDAP = window.sysadmin ? window.sysadmin.pageOptions.haveLDAP : '';
export const enableShareLinkReportAbuse = window.sysadmin ? window.sysadmin.pageOptions.enable_share_link_report_abuse : '';