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:
102
frontend/src/components/dialog/add-abuse-report-dialog.js
Normal file
102
frontend/src/components/dialog/add-abuse-report-dialog.js
Normal 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;
|
@@ -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>
|
||||
);
|
||||
}
|
||||
|
124
frontend/src/pages/sys-admin/abuse-reports.js
Normal file
124
frontend/src/pages/sys-admin/abuse-reports.js
Normal 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;
|
@@ -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>
|
||||
|
@@ -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>
|
||||
|
@@ -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')
|
||||
);
|
||||
);
|
@@ -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 : '';
|
||||
|
Reference in New Issue
Block a user