mirror of
https://github.com/Prowlarr/Prowlarr.git
synced 2025-09-17 17:14:18 +02:00
Misc History Improvements
This commit is contained in:
@@ -2,255 +2,43 @@ import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import DescriptionList from 'Components/DescriptionList/DescriptionList';
|
||||
import DescriptionListItem from 'Components/DescriptionList/DescriptionListItem';
|
||||
import DescriptionListItemDescription from 'Components/DescriptionList/DescriptionListItemDescription';
|
||||
import DescriptionListItemTitle from 'Components/DescriptionList/DescriptionListItemTitle';
|
||||
import Link from 'Components/Link/Link';
|
||||
import formatDateTime from 'Utilities/Date/formatDateTime';
|
||||
import formatAge from 'Utilities/Number/formatAge';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import styles from './HistoryDetails.css';
|
||||
|
||||
function HistoryDetails(props) {
|
||||
const {
|
||||
indexer,
|
||||
eventType,
|
||||
sourceTitle,
|
||||
data,
|
||||
shortDateFormat,
|
||||
timeFormat
|
||||
data
|
||||
} = props;
|
||||
|
||||
if (eventType === 'grabbed') {
|
||||
if (eventType === 'indexerQuery') {
|
||||
const {
|
||||
indexer,
|
||||
releaseGroup,
|
||||
nzbInfoUrl,
|
||||
downloadClient,
|
||||
downloadId,
|
||||
age,
|
||||
ageHours,
|
||||
ageMinutes,
|
||||
publishedDate
|
||||
query,
|
||||
queryResults
|
||||
} = data;
|
||||
|
||||
return (
|
||||
<DescriptionList>
|
||||
<DescriptionListItem
|
||||
descriptionClassName={styles.description}
|
||||
title={translate('Name')}
|
||||
data={sourceTitle}
|
||||
title={translate('Query')}
|
||||
data={query}
|
||||
/>
|
||||
|
||||
{
|
||||
!!indexer &&
|
||||
<DescriptionListItem
|
||||
title={translate('Indexer')}
|
||||
data={indexer}
|
||||
data={indexer.name}
|
||||
/>
|
||||
}
|
||||
|
||||
{
|
||||
!!releaseGroup &&
|
||||
!!data &&
|
||||
<DescriptionListItem
|
||||
descriptionClassName={styles.description}
|
||||
title={translate('ReleaseGroup')}
|
||||
data={releaseGroup}
|
||||
/>
|
||||
}
|
||||
|
||||
{
|
||||
!!nzbInfoUrl &&
|
||||
<span>
|
||||
<DescriptionListItemTitle>
|
||||
Info URL
|
||||
</DescriptionListItemTitle>
|
||||
|
||||
<DescriptionListItemDescription>
|
||||
<Link to={nzbInfoUrl}>{nzbInfoUrl}</Link>
|
||||
</DescriptionListItemDescription>
|
||||
</span>
|
||||
}
|
||||
|
||||
{
|
||||
!!downloadClient &&
|
||||
<DescriptionListItem
|
||||
title={translate('DownloadClient')}
|
||||
data={downloadClient}
|
||||
/>
|
||||
}
|
||||
|
||||
{
|
||||
!!downloadId &&
|
||||
<DescriptionListItem
|
||||
title={translate('GrabID')}
|
||||
data={downloadId}
|
||||
/>
|
||||
}
|
||||
|
||||
{
|
||||
!!indexer &&
|
||||
<DescriptionListItem
|
||||
title={translate('AgeWhenGrabbed')}
|
||||
data={formatAge(age, ageHours, ageMinutes)}
|
||||
/>
|
||||
}
|
||||
|
||||
{
|
||||
!!publishedDate &&
|
||||
<DescriptionListItem
|
||||
title={translate('PublishedDate')}
|
||||
data={formatDateTime(publishedDate, shortDateFormat, timeFormat, { includeSeconds: true })}
|
||||
/>
|
||||
}
|
||||
</DescriptionList>
|
||||
);
|
||||
}
|
||||
|
||||
if (eventType === 'downloadFailed') {
|
||||
const {
|
||||
message
|
||||
} = data;
|
||||
|
||||
return (
|
||||
<DescriptionList>
|
||||
<DescriptionListItem
|
||||
descriptionClassName={styles.description}
|
||||
title={translate('Name')}
|
||||
data={sourceTitle}
|
||||
/>
|
||||
|
||||
{
|
||||
!!message &&
|
||||
<DescriptionListItem
|
||||
title={translate('Message')}
|
||||
data={message}
|
||||
/>
|
||||
}
|
||||
</DescriptionList>
|
||||
);
|
||||
}
|
||||
|
||||
if (eventType === 'downloadFolderImported') {
|
||||
const {
|
||||
droppedPath,
|
||||
importedPath
|
||||
} = data;
|
||||
|
||||
return (
|
||||
<DescriptionList>
|
||||
<DescriptionListItem
|
||||
descriptionClassName={styles.description}
|
||||
title={translate('Name')}
|
||||
data={sourceTitle}
|
||||
/>
|
||||
|
||||
{
|
||||
!!droppedPath &&
|
||||
<DescriptionListItem
|
||||
descriptionClassName={styles.description}
|
||||
title={translate('Source')}
|
||||
data={droppedPath}
|
||||
/>
|
||||
}
|
||||
|
||||
{
|
||||
!!importedPath &&
|
||||
<DescriptionListItem
|
||||
descriptionClassName={styles.description}
|
||||
title={translate('ImportedTo')}
|
||||
data={importedPath}
|
||||
/>
|
||||
}
|
||||
</DescriptionList>
|
||||
);
|
||||
}
|
||||
|
||||
if (eventType === 'movieFileDeleted') {
|
||||
const {
|
||||
reason
|
||||
} = data;
|
||||
|
||||
let reasonMessage = '';
|
||||
|
||||
switch (reason) {
|
||||
case 'Manual':
|
||||
reasonMessage = 'File was deleted by via UI';
|
||||
break;
|
||||
case 'MissingFromDisk':
|
||||
reasonMessage = 'Prowlarr was unable to find the file on disk so it was removed';
|
||||
break;
|
||||
case 'Upgrade':
|
||||
reasonMessage = 'File was deleted to import an upgrade';
|
||||
break;
|
||||
default:
|
||||
reasonMessage = '';
|
||||
}
|
||||
|
||||
return (
|
||||
<DescriptionList>
|
||||
<DescriptionListItem
|
||||
title={translate('Name')}
|
||||
data={sourceTitle}
|
||||
/>
|
||||
|
||||
<DescriptionListItem
|
||||
title={translate('Reason')}
|
||||
data={reasonMessage}
|
||||
/>
|
||||
</DescriptionList>
|
||||
);
|
||||
}
|
||||
|
||||
if (eventType === 'movieFileRenamed') {
|
||||
const {
|
||||
sourcePath,
|
||||
sourceRelativePath,
|
||||
path,
|
||||
relativePath
|
||||
} = data;
|
||||
|
||||
return (
|
||||
<DescriptionList>
|
||||
<DescriptionListItem
|
||||
title={translate('SourcePath')}
|
||||
data={sourcePath}
|
||||
/>
|
||||
|
||||
<DescriptionListItem
|
||||
title={translate('SourceRelativePath')}
|
||||
data={sourceRelativePath}
|
||||
/>
|
||||
|
||||
<DescriptionListItem
|
||||
title={translate('DestinationPath')}
|
||||
data={path}
|
||||
/>
|
||||
|
||||
<DescriptionListItem
|
||||
title={translate('DestinationRelativePath')}
|
||||
data={relativePath}
|
||||
/>
|
||||
</DescriptionList>
|
||||
);
|
||||
}
|
||||
|
||||
if (eventType === 'downloadIgnored') {
|
||||
const {
|
||||
message
|
||||
} = data;
|
||||
|
||||
return (
|
||||
<DescriptionList>
|
||||
<DescriptionListItem
|
||||
descriptionClassName={styles.description}
|
||||
title={translate('Name')}
|
||||
data={sourceTitle}
|
||||
/>
|
||||
|
||||
{
|
||||
!!message &&
|
||||
<DescriptionListItem
|
||||
title={translate('Message')}
|
||||
data={message}
|
||||
title={'Query Results'}
|
||||
data={queryResults}
|
||||
/>
|
||||
}
|
||||
</DescriptionList>
|
||||
@@ -262,15 +50,15 @@ function HistoryDetails(props) {
|
||||
<DescriptionListItem
|
||||
descriptionClassName={styles.description}
|
||||
title={translate('Name')}
|
||||
data={sourceTitle}
|
||||
data={data.query}
|
||||
/>
|
||||
</DescriptionList>
|
||||
);
|
||||
}
|
||||
|
||||
HistoryDetails.propTypes = {
|
||||
indexer: PropTypes.object.isRequired,
|
||||
eventType: PropTypes.string.isRequired,
|
||||
sourceTitle: PropTypes.string.isRequired,
|
||||
data: PropTypes.object.isRequired,
|
||||
shortDateFormat: PropTypes.string.isRequired,
|
||||
timeFormat: PropTypes.string.isRequired
|
||||
|
@@ -14,18 +14,8 @@ import styles from './HistoryDetailsModal.css';
|
||||
|
||||
function getHeaderTitle(eventType) {
|
||||
switch (eventType) {
|
||||
case 'grabbed':
|
||||
return 'Grabbed';
|
||||
case 'downloadFailed':
|
||||
return 'Download Failed';
|
||||
case 'downloadFolderImported':
|
||||
return 'Movie Imported';
|
||||
case 'movieFileDeleted':
|
||||
return 'Movie File Deleted';
|
||||
case 'movieFileRenamed':
|
||||
return 'Movie File Renamed';
|
||||
case 'downloadIgnored':
|
||||
return 'Download Ignored';
|
||||
case 'indexerQuery':
|
||||
return 'Indexer Query';
|
||||
default:
|
||||
return 'Unknown';
|
||||
}
|
||||
@@ -35,7 +25,7 @@ function HistoryDetailsModal(props) {
|
||||
const {
|
||||
isOpen,
|
||||
eventType,
|
||||
sourceTitle,
|
||||
indexer,
|
||||
data,
|
||||
isMarkingAsFailed,
|
||||
shortDateFormat,
|
||||
@@ -57,7 +47,7 @@ function HistoryDetailsModal(props) {
|
||||
<ModalBody>
|
||||
<HistoryDetails
|
||||
eventType={eventType}
|
||||
sourceTitle={sourceTitle}
|
||||
indexer={indexer}
|
||||
data={data}
|
||||
shortDateFormat={shortDateFormat}
|
||||
timeFormat={timeFormat}
|
||||
@@ -91,7 +81,7 @@ function HistoryDetailsModal(props) {
|
||||
HistoryDetailsModal.propTypes = {
|
||||
isOpen: PropTypes.bool.isRequired,
|
||||
eventType: PropTypes.string.isRequired,
|
||||
sourceTitle: PropTypes.string.isRequired,
|
||||
indexer: PropTypes.object.isRequired,
|
||||
data: PropTypes.object.isRequired,
|
||||
isMarkingAsFailed: PropTypes.bool.isRequired,
|
||||
shortDateFormat: PropTypes.string.isRequired,
|
||||
|
@@ -7,20 +7,8 @@ import styles from './HistoryEventTypeCell.css';
|
||||
|
||||
function getIconName(eventType) {
|
||||
switch (eventType) {
|
||||
case 'grabbed':
|
||||
return icons.DOWNLOADING;
|
||||
case 'movieFolderImported':
|
||||
return icons.DRIVE;
|
||||
case 'downloadFolderImported':
|
||||
return icons.DOWNLOADED;
|
||||
case 'downloadFailed':
|
||||
return icons.DOWNLOADING;
|
||||
case 'movieFileDeleted':
|
||||
return icons.DELETE;
|
||||
case 'movieFileRenamed':
|
||||
return icons.ORGANIZE;
|
||||
case 'downloadIgnored':
|
||||
return icons.IGNORE;
|
||||
case 'indexerQuery':
|
||||
return icons.SEARCH;
|
||||
default:
|
||||
return icons.UNKNOWN;
|
||||
}
|
||||
@@ -35,31 +23,19 @@ function getIconKind(eventType) {
|
||||
}
|
||||
}
|
||||
|
||||
function getTooltip(eventType, data) {
|
||||
function getTooltip(eventType, data, indexer) {
|
||||
switch (eventType) {
|
||||
case 'grabbed':
|
||||
return `Movie grabbed from ${data.indexer} and sent to ${data.downloadClient}`;
|
||||
case 'movieFolderImported':
|
||||
return 'Movie imported from movie folder';
|
||||
case 'downloadFolderImported':
|
||||
return 'Movie downloaded successfully and picked up from download client';
|
||||
case 'downloadFailed':
|
||||
return 'Movie download failed';
|
||||
case 'movieFileDeleted':
|
||||
return 'Movie file deleted';
|
||||
case 'movieFileRenamed':
|
||||
return 'Movie file renamed';
|
||||
case 'downloadIgnored':
|
||||
return 'Movie Download Ignored';
|
||||
case 'indexerQuery':
|
||||
return `Query "${data.query}" sent to ${indexer.name}`;
|
||||
default:
|
||||
return 'Unknown event';
|
||||
}
|
||||
}
|
||||
|
||||
function HistoryEventTypeCell({ eventType, data }) {
|
||||
function HistoryEventTypeCell({ eventType, data, indexer }) {
|
||||
const iconName = getIconName(eventType);
|
||||
const iconKind = getIconKind(eventType);
|
||||
const tooltip = getTooltip(eventType, data);
|
||||
const tooltip = getTooltip(eventType, data, indexer);
|
||||
|
||||
return (
|
||||
<TableRowCell
|
||||
@@ -76,7 +52,8 @@ function HistoryEventTypeCell({ eventType, data }) {
|
||||
|
||||
HistoryEventTypeCell.propTypes = {
|
||||
eventType: PropTypes.string.isRequired,
|
||||
data: PropTypes.object
|
||||
data: PropTypes.object,
|
||||
indexer: PropTypes.object
|
||||
};
|
||||
|
||||
HistoryEventTypeCell.defaultProps = {
|
||||
|
@@ -1,4 +1,4 @@
|
||||
.downloadClient {
|
||||
.query {
|
||||
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
||||
|
||||
width: 120px;
|
||||
|
@@ -48,9 +48,8 @@ class HistoryRow extends Component {
|
||||
|
||||
render() {
|
||||
const {
|
||||
movie,
|
||||
indexer,
|
||||
eventType,
|
||||
sourceTitle,
|
||||
date,
|
||||
data,
|
||||
isMarkingAsFailed,
|
||||
@@ -60,7 +59,7 @@ class HistoryRow extends Component {
|
||||
onMarkAsFailedPress
|
||||
} = this.props;
|
||||
|
||||
if (!movie) {
|
||||
if (!indexer) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -81,6 +80,7 @@ class HistoryRow extends Component {
|
||||
return (
|
||||
<HistoryEventTypeCell
|
||||
key={name}
|
||||
indexer={indexer}
|
||||
eventType={eventType}
|
||||
data={data}
|
||||
/>
|
||||
@@ -93,7 +93,40 @@ class HistoryRow extends Component {
|
||||
key={name}
|
||||
className={styles.indexer}
|
||||
>
|
||||
{movie.name}
|
||||
{indexer.name}
|
||||
</TableRowCell>
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'successful') {
|
||||
return (
|
||||
<TableRowCell
|
||||
key={name}
|
||||
className={styles.indexer}
|
||||
>
|
||||
{data.successful}
|
||||
</TableRowCell>
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'elapsedTime') {
|
||||
return (
|
||||
<TableRowCell
|
||||
key={name}
|
||||
className={styles.indexer}
|
||||
>
|
||||
{`${data.elapsedTime}ms`}
|
||||
</TableRowCell>
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'query') {
|
||||
return (
|
||||
<TableRowCell
|
||||
key={name}
|
||||
className={styles.query}
|
||||
>
|
||||
{data.query}
|
||||
</TableRowCell>
|
||||
);
|
||||
}
|
||||
@@ -128,8 +161,8 @@ class HistoryRow extends Component {
|
||||
<HistoryDetailsModal
|
||||
isOpen={this.state.isDetailsModalOpen}
|
||||
eventType={eventType}
|
||||
sourceTitle={sourceTitle}
|
||||
data={data}
|
||||
indexer={indexer}
|
||||
isMarkingAsFailed={isMarkingAsFailed}
|
||||
shortDateFormat={shortDateFormat}
|
||||
timeFormat={timeFormat}
|
||||
@@ -143,10 +176,9 @@ class HistoryRow extends Component {
|
||||
}
|
||||
|
||||
HistoryRow.propTypes = {
|
||||
movieId: PropTypes.number,
|
||||
movie: PropTypes.object.isRequired,
|
||||
indexerId: PropTypes.number,
|
||||
indexer: PropTypes.object.isRequired,
|
||||
eventType: PropTypes.string.isRequired,
|
||||
sourceTitle: PropTypes.string.isRequired,
|
||||
date: PropTypes.string.isRequired,
|
||||
data: PropTypes.object.isRequired,
|
||||
isMarkingAsFailed: PropTypes.bool,
|
||||
|
@@ -11,9 +11,9 @@ function createMapStateToProps() {
|
||||
return createSelector(
|
||||
createIndexerSelector(),
|
||||
createUISettingsSelector(),
|
||||
(movie, uiSettings) => {
|
||||
(indexer, uiSettings) => {
|
||||
return {
|
||||
movie,
|
||||
indexer,
|
||||
shortDateFormat: uiSettings.shortDateFormat,
|
||||
timeFormat: uiSettings.timeFormat
|
||||
};
|
||||
|
@@ -1,25 +0,0 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import Modal from 'Components/Modal/Modal';
|
||||
import EditMovieModalContentConnector from './EditMovieModalContentConnector';
|
||||
|
||||
function EditMovieModal({ isOpen, onModalClose, ...otherProps }) {
|
||||
return (
|
||||
<Modal
|
||||
isOpen={isOpen}
|
||||
onModalClose={onModalClose}
|
||||
>
|
||||
<EditMovieModalContentConnector
|
||||
{...otherProps}
|
||||
onModalClose={onModalClose}
|
||||
/>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
EditMovieModal.propTypes = {
|
||||
isOpen: PropTypes.bool.isRequired,
|
||||
onModalClose: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default EditMovieModal;
|
@@ -1,39 +0,0 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { clearPendingChanges } from 'Store/Actions/baseActions';
|
||||
import EditMovieModal from './EditMovieModal';
|
||||
|
||||
const mapDispatchToProps = {
|
||||
clearPendingChanges
|
||||
};
|
||||
|
||||
class EditMovieModalConnector extends Component {
|
||||
|
||||
//
|
||||
// Listeners
|
||||
|
||||
onModalClose = () => {
|
||||
this.props.clearPendingChanges({ section: 'movies' });
|
||||
this.props.onModalClose();
|
||||
}
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
return (
|
||||
<EditMovieModal
|
||||
{...this.props}
|
||||
onModalClose={this.onModalClose}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
EditMovieModalConnector.propTypes = {
|
||||
onModalClose: PropTypes.func.isRequired,
|
||||
clearPendingChanges: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default connect(undefined, mapDispatchToProps)(EditMovieModalConnector);
|
@@ -1,5 +0,0 @@
|
||||
.deleteButton {
|
||||
composes: button from '~Components/Link/Button.css';
|
||||
|
||||
margin-right: auto;
|
||||
}
|
@@ -1,180 +0,0 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import Form from 'Components/Form/Form';
|
||||
import FormGroup from 'Components/Form/FormGroup';
|
||||
import FormInputGroup from 'Components/Form/FormInputGroup';
|
||||
import FormLabel from 'Components/Form/FormLabel';
|
||||
import Button from 'Components/Link/Button';
|
||||
import SpinnerButton from 'Components/Link/SpinnerButton';
|
||||
import ModalBody from 'Components/Modal/ModalBody';
|
||||
import ModalContent from 'Components/Modal/ModalContent';
|
||||
import ModalFooter from 'Components/Modal/ModalFooter';
|
||||
import ModalHeader from 'Components/Modal/ModalHeader';
|
||||
import { inputTypes, kinds } from 'Helpers/Props';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import styles from './EditMovieModalContent.css';
|
||||
|
||||
class EditMovieModalContent extends Component {
|
||||
|
||||
//
|
||||
// Lifecycle
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
|
||||
this.state = {
|
||||
isConfirmMoveModalOpen: false
|
||||
};
|
||||
}
|
||||
|
||||
//
|
||||
// Listeners
|
||||
|
||||
onSavePress = () => {
|
||||
const {
|
||||
isPathChanging,
|
||||
onSavePress
|
||||
} = this.props;
|
||||
|
||||
if (isPathChanging && !this.state.isConfirmMoveModalOpen) {
|
||||
this.setState({ isConfirmMoveModalOpen: true });
|
||||
} else {
|
||||
this.setState({ isConfirmMoveModalOpen: false });
|
||||
|
||||
onSavePress(false);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
const {
|
||||
title,
|
||||
item,
|
||||
isSaving,
|
||||
originalPath,
|
||||
onInputChange,
|
||||
onModalClose,
|
||||
onDeleteMoviePress,
|
||||
...otherProps
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
monitored,
|
||||
qualityProfileId,
|
||||
minimumAvailability,
|
||||
// Id,
|
||||
path,
|
||||
tags
|
||||
} = item;
|
||||
|
||||
return (
|
||||
<ModalContent onModalClose={onModalClose}>
|
||||
<ModalHeader>
|
||||
{translate('Edit')} - {title}
|
||||
</ModalHeader>
|
||||
|
||||
<ModalBody>
|
||||
<Form
|
||||
{...otherProps}
|
||||
>
|
||||
<FormGroup>
|
||||
<FormLabel>{translate('Monitored')}</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="monitored"
|
||||
helpText={translate('MonitoredHelpText')}
|
||||
{...monitored}
|
||||
onChange={onInputChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup>
|
||||
<FormLabel>{translate('MinimumAvailability')}</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.AVAILABILITY_SELECT}
|
||||
name="minimumAvailability"
|
||||
{...minimumAvailability}
|
||||
onChange={onInputChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup>
|
||||
<FormLabel>{translate('QualityProfile')}</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.QUALITY_PROFILE_SELECT}
|
||||
name="qualityProfileId"
|
||||
{...qualityProfileId}
|
||||
onChange={onInputChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup>
|
||||
<FormLabel>{translate('Path')}</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.PATH}
|
||||
name="path"
|
||||
{...path}
|
||||
onChange={onInputChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup>
|
||||
<FormLabel>{translate('Tags')}</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.TAG}
|
||||
name="tags"
|
||||
{...tags}
|
||||
onChange={onInputChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
</Form>
|
||||
</ModalBody>
|
||||
|
||||
<ModalFooter>
|
||||
<Button
|
||||
className={styles.deleteButton}
|
||||
kind={kinds.DANGER}
|
||||
onPress={onDeleteMoviePress}
|
||||
>
|
||||
{translate('Delete')}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
onPress={onModalClose}
|
||||
>
|
||||
{translate('Cancel')}
|
||||
</Button>
|
||||
|
||||
<SpinnerButton
|
||||
isSpinning={isSaving}
|
||||
onPress={this.onSavePress}
|
||||
>
|
||||
{translate('Save')}
|
||||
</SpinnerButton>
|
||||
</ModalFooter>
|
||||
</ModalContent>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
EditMovieModalContent.propTypes = {
|
||||
indexerId: PropTypes.number.isRequired,
|
||||
title: PropTypes.string.isRequired,
|
||||
item: PropTypes.object.isRequired,
|
||||
isSaving: PropTypes.bool.isRequired,
|
||||
isPathChanging: PropTypes.bool.isRequired,
|
||||
originalPath: PropTypes.string.isRequired,
|
||||
onInputChange: PropTypes.func.isRequired,
|
||||
onSavePress: PropTypes.func.isRequired,
|
||||
onModalClose: PropTypes.func.isRequired,
|
||||
onDeleteMoviePress: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default EditMovieModalContent;
|
@@ -1,115 +0,0 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import { saveMovie, setMovieValue } from 'Store/Actions/movieActions';
|
||||
import createIndexerSelector from 'Store/Selectors/createIndexerSelector';
|
||||
import selectSettings from 'Store/Selectors/selectSettings';
|
||||
import EditMovieModalContent from './EditMovieModalContent';
|
||||
|
||||
function createIsPathChangingSelector() {
|
||||
return createSelector(
|
||||
(state) => state.movies.pendingChanges,
|
||||
createIndexerSelector(),
|
||||
(pendingChanges, movie) => {
|
||||
const path = pendingChanges.path;
|
||||
|
||||
if (path == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return movie.path !== path;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
(state) => state.movies,
|
||||
createIndexerSelector(),
|
||||
createIsPathChangingSelector(),
|
||||
(moviesState, movie, isPathChanging) => {
|
||||
const {
|
||||
isSaving,
|
||||
saveError,
|
||||
pendingChanges
|
||||
} = moviesState;
|
||||
|
||||
const movieSettings = {
|
||||
monitored: movie.monitored,
|
||||
qualityProfileId: movie.qualityProfileId,
|
||||
minimumAvailability: movie.minimumAvailability,
|
||||
path: movie.path,
|
||||
tags: movie.tags
|
||||
};
|
||||
|
||||
const settings = selectSettings(movieSettings, pendingChanges, saveError);
|
||||
|
||||
return {
|
||||
title: movie.title,
|
||||
isSaving,
|
||||
saveError,
|
||||
isPathChanging,
|
||||
originalPath: movie.path,
|
||||
item: settings.settings,
|
||||
...settings
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
const mapDispatchToProps = {
|
||||
dispatchSetMovieValue: setMovieValue,
|
||||
dispatchSaveMovie: saveMovie
|
||||
};
|
||||
|
||||
class EditMovieModalContentConnector extends Component {
|
||||
|
||||
//
|
||||
// Lifecycle
|
||||
|
||||
componentDidUpdate(prevProps, prevState) {
|
||||
if (prevProps.isSaving && !this.props.isSaving && !this.props.saveError) {
|
||||
this.props.onModalClose();
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Listeners
|
||||
|
||||
onInputChange = ({ name, value }) => {
|
||||
this.props.dispatchSetMovieValue({ name, value });
|
||||
}
|
||||
|
||||
onSavePress = (moveFiles) => {
|
||||
this.props.dispatchSaveMovie({
|
||||
id: this.props.indexerId,
|
||||
moveFiles
|
||||
});
|
||||
}
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
return (
|
||||
<EditMovieModalContent
|
||||
{...this.props}
|
||||
onInputChange={this.onInputChange}
|
||||
onSavePress={this.onSavePress}
|
||||
onMoveMoviePress={this.onMoveMoviePress}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
EditMovieModalContentConnector.propTypes = {
|
||||
indexerId: PropTypes.number,
|
||||
isSaving: PropTypes.bool.isRequired,
|
||||
saveError: PropTypes.object,
|
||||
dispatchSetMovieValue: PropTypes.func.isRequired,
|
||||
dispatchSaveMovie: PropTypes.func.isRequired,
|
||||
onModalClose: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default connect(createMapStateToProps, mapDispatchToProps)(EditMovieModalContentConnector);
|
@@ -47,6 +47,15 @@ function MovieIndexSortMenu(props) {
|
||||
{translate('Added')}
|
||||
</SortMenuItem>
|
||||
|
||||
<SortMenuItem
|
||||
name="priority"
|
||||
sortKey={sortKey}
|
||||
sortDirection={sortDirection}
|
||||
onPress={onSortSelect}
|
||||
>
|
||||
{'Priority'}
|
||||
</SortMenuItem>
|
||||
|
||||
<SortMenuItem
|
||||
name="protocol"
|
||||
sortKey={sortKey}
|
||||
|
@@ -1,14 +0,0 @@
|
||||
.progress {
|
||||
composes: container from '~Components/ProgressBar.css';
|
||||
|
||||
border-radius: 0;
|
||||
background-color: #5b5b5b;
|
||||
color: $white;
|
||||
transition: width 200ms ease;
|
||||
}
|
||||
|
||||
.progressBar {
|
||||
composes: progressBar from '~Components/ProgressBar.css';
|
||||
|
||||
transition: width 200ms ease;
|
||||
}
|
@@ -1,65 +0,0 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import ProgressBar from 'Components/ProgressBar';
|
||||
import { sizes } from 'Helpers/Props';
|
||||
import getProgressBarKind from 'Utilities/Movie/getProgressBarKind';
|
||||
import getQueueStatusText from 'Utilities/Movie/getQueueStatusText';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import styles from './MovieIndexProgressBar.css';
|
||||
|
||||
function MovieIndexProgressBar(props) {
|
||||
const {
|
||||
monitored,
|
||||
status,
|
||||
hasFile,
|
||||
isAvailable,
|
||||
posterWidth,
|
||||
detailedProgressBar,
|
||||
queueStatus,
|
||||
queueState
|
||||
} = props;
|
||||
|
||||
const progress = 100;
|
||||
const queueStatusText = getQueueStatusText(queueStatus, queueState);
|
||||
let movieStatus = (status === 'released' && hasFile) ? 'downloaded' : status;
|
||||
|
||||
if (movieStatus === 'deleted') {
|
||||
movieStatus = 'Missing';
|
||||
|
||||
if (hasFile) {
|
||||
movieStatus = 'Downloaded';
|
||||
}
|
||||
} else if (hasFile) {
|
||||
movieStatus = 'Downloaded';
|
||||
} else if (isAvailable && !hasFile) {
|
||||
movieStatus = 'Missing';
|
||||
} else {
|
||||
movieStatus = 'NotAvailable';
|
||||
}
|
||||
|
||||
return (
|
||||
<ProgressBar
|
||||
className={styles.progressBar}
|
||||
containerClassName={styles.progress}
|
||||
progress={progress}
|
||||
kind={getProgressBarKind(status, monitored, hasFile, isAvailable, queueStatusText)}
|
||||
size={detailedProgressBar ? sizes.MEDIUM : sizes.SMALL}
|
||||
showText={detailedProgressBar}
|
||||
width={posterWidth}
|
||||
text={(queueStatusText) ? queueStatusText : translate(movieStatus)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
MovieIndexProgressBar.propTypes = {
|
||||
monitored: PropTypes.bool.isRequired,
|
||||
hasFile: PropTypes.bool.isRequired,
|
||||
isAvailable: PropTypes.bool.isRequired,
|
||||
status: PropTypes.string.isRequired,
|
||||
posterWidth: PropTypes.number.isRequired,
|
||||
detailedProgressBar: PropTypes.bool.isRequired,
|
||||
queueStatus: PropTypes.string,
|
||||
queueState: PropTypes.string
|
||||
};
|
||||
|
||||
export default MovieIndexProgressBar;
|
9
frontend/src/Indexer/Index/Table/IndexerStatusCell.css
Normal file
9
frontend/src/Indexer/Index/Table/IndexerStatusCell.css
Normal file
@@ -0,0 +1,9 @@
|
||||
.status {
|
||||
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
||||
|
||||
width: 60px;
|
||||
}
|
||||
|
||||
.statusIcon {
|
||||
width: 20px !important;
|
||||
}
|
44
frontend/src/Indexer/Index/Table/IndexerStatusCell.js
Normal file
44
frontend/src/Indexer/Index/Table/IndexerStatusCell.js
Normal file
@@ -0,0 +1,44 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import Icon from 'Components/Icon';
|
||||
import VirtualTableRowCell from 'Components/Table/Cells/TableRowCell';
|
||||
import { icons } from 'Helpers/Props';
|
||||
import styles from './IndexerStatusCell.css';
|
||||
|
||||
function IndexerStatusCell(props) {
|
||||
const {
|
||||
className,
|
||||
enabled,
|
||||
component: Component,
|
||||
...otherProps
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<Component
|
||||
className={className}
|
||||
{...otherProps}
|
||||
>
|
||||
{
|
||||
!enabled &&
|
||||
<Icon
|
||||
className={styles.statusIcon}
|
||||
name={icons.BLACKLIST}
|
||||
title={enabled ? 'Indexer is Enabled' : 'Indexer is Disabled'}
|
||||
/>
|
||||
}
|
||||
</Component>
|
||||
);
|
||||
}
|
||||
|
||||
IndexerStatusCell.propTypes = {
|
||||
className: PropTypes.string.isRequired,
|
||||
enabled: PropTypes.bool.isRequired,
|
||||
component: PropTypes.elementType
|
||||
};
|
||||
|
||||
IndexerStatusCell.defaultProps = {
|
||||
className: styles.status,
|
||||
component: VirtualTableRowCell
|
||||
};
|
||||
|
||||
export default IndexerStatusCell;
|
@@ -10,6 +10,7 @@
|
||||
flex: 4 0 110px;
|
||||
}
|
||||
|
||||
.priority,
|
||||
.privacy,
|
||||
.protocol {
|
||||
composes: headerCell from '~Components/Table/VirtualTableHeaderCell.css';
|
||||
|
@@ -17,6 +17,7 @@
|
||||
flex: 4 0 110px;
|
||||
}
|
||||
|
||||
.priority,
|
||||
.protocol,
|
||||
.privacy {
|
||||
composes: cell;
|
||||
|
@@ -5,11 +5,14 @@ import IconButton from 'Components/Link/IconButton';
|
||||
import RelativeDateCellConnector from 'Components/Table/Cells/RelativeDateCellConnector';
|
||||
import VirtualTableRowCell from 'Components/Table/Cells/VirtualTableRowCell';
|
||||
import VirtualTableSelectCell from 'Components/Table/Cells/VirtualTableSelectCell';
|
||||
import { icons, kinds } from 'Helpers/Props';
|
||||
import TagListConnector from 'Components/TagListConnector';
|
||||
import { icons } from 'Helpers/Props';
|
||||
import DeleteIndexerModal from 'Indexer/Delete/DeleteIndexerModal';
|
||||
import EditIndexerModalConnector from 'Settings/Indexers/Indexers/EditIndexerModalConnector';
|
||||
import titleCase from 'Utilities/String/titleCase';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import CapabilitiesLabel from './CapabilitiesLabel';
|
||||
import IndexerStatusCell from './IndexerStatusCell';
|
||||
import ProtocolLabel from './ProtocolLabel';
|
||||
import styles from './MovieIndexRow.css';
|
||||
|
||||
@@ -61,8 +64,10 @@ class MovieIndexRow extends Component {
|
||||
enableRss,
|
||||
enableAutomaticSearch,
|
||||
enableInteractiveSearch,
|
||||
tags,
|
||||
protocol,
|
||||
privacy,
|
||||
priority,
|
||||
added,
|
||||
capabilities,
|
||||
columns,
|
||||
@@ -103,28 +108,13 @@ class MovieIndexRow extends Component {
|
||||
|
||||
if (column.name === 'status') {
|
||||
return (
|
||||
<VirtualTableRowCell
|
||||
<IndexerStatusCell
|
||||
key={column.name}
|
||||
className={styles[column.name]}
|
||||
>
|
||||
{
|
||||
enableRss || enableAutomaticSearch || enableInteractiveSearch ?
|
||||
<Label kind={kinds.SUCCESS}>
|
||||
{'Enabled'}
|
||||
</Label>:
|
||||
null
|
||||
}
|
||||
{
|
||||
!enableRss && !enableAutomaticSearch && !enableInteractiveSearch ?
|
||||
<Label
|
||||
kind={kinds.DISABLED}
|
||||
outline={true}
|
||||
>
|
||||
{translate('Disabled')}
|
||||
</Label> :
|
||||
null
|
||||
}
|
||||
</VirtualTableRowCell>
|
||||
enabled={enableRss || enableAutomaticSearch || enableInteractiveSearch}
|
||||
status={status}
|
||||
component={VirtualTableRowCell}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -146,12 +136,23 @@ class MovieIndexRow extends Component {
|
||||
className={styles[column.name]}
|
||||
>
|
||||
<Label>
|
||||
{privacy}
|
||||
{titleCase(privacy)}
|
||||
</Label>
|
||||
</VirtualTableRowCell>
|
||||
);
|
||||
}
|
||||
|
||||
if (column.name === 'priority') {
|
||||
return (
|
||||
<VirtualTableRowCell
|
||||
key={column.name}
|
||||
className={styles[column.name]}
|
||||
>
|
||||
{priority}
|
||||
</VirtualTableRowCell>
|
||||
);
|
||||
}
|
||||
|
||||
if (column.name === 'protocol') {
|
||||
return (
|
||||
<VirtualTableRowCell
|
||||
@@ -189,6 +190,19 @@ class MovieIndexRow extends Component {
|
||||
);
|
||||
}
|
||||
|
||||
if (column.name === 'tags') {
|
||||
return (
|
||||
<VirtualTableRowCell
|
||||
key={column.name}
|
||||
className={styles[column.name]}
|
||||
>
|
||||
<TagListConnector
|
||||
tags={tags}
|
||||
/>
|
||||
</VirtualTableRowCell>
|
||||
);
|
||||
}
|
||||
|
||||
if (column.name === 'actions') {
|
||||
return (
|
||||
<VirtualTableRowCell
|
||||
@@ -234,6 +248,7 @@ MovieIndexRow.propTypes = {
|
||||
id: PropTypes.number.isRequired,
|
||||
protocol: PropTypes.string.isRequired,
|
||||
privacy: PropTypes.string.isRequired,
|
||||
priority: PropTypes.number.isRequired,
|
||||
name: PropTypes.string.isRequired,
|
||||
enableRss: PropTypes.bool.isRequired,
|
||||
enableAutomaticSearch: PropTypes.bool.isRequired,
|
||||
@@ -248,4 +263,8 @@ MovieIndexRow.propTypes = {
|
||||
onSelectedChange: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
MovieIndexRow.defaultProps = {
|
||||
tags: []
|
||||
};
|
||||
|
||||
export default MovieIndexRow;
|
||||
|
@@ -2,7 +2,7 @@ import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import FilterMenu from 'Components/Menu/FilterMenu';
|
||||
import { align } from 'Helpers/Props';
|
||||
import MovieIndexFilterModalConnector from 'Indexer/Index/MovieIndexFilterModalConnector';
|
||||
import SearchIndexFilterModalConnector from 'Search/SearchIndexFilterModalConnector';
|
||||
|
||||
function SearchIndexFilterMenu(props) {
|
||||
const {
|
||||
@@ -20,7 +20,7 @@ function SearchIndexFilterMenu(props) {
|
||||
selectedFilterKey={selectedFilterKey}
|
||||
filters={filters}
|
||||
customFilters={customFilters}
|
||||
filterModalConnectorComponent={MovieIndexFilterModalConnector}
|
||||
filterModalConnectorComponent={SearchIndexFilterModalConnector}
|
||||
onFilterSelect={onFilterSelect}
|
||||
/>
|
||||
);
|
||||
|
24
frontend/src/Search/SearchIndexFilterModalConnector.js
Normal file
24
frontend/src/Search/SearchIndexFilterModalConnector.js
Normal file
@@ -0,0 +1,24 @@
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import FilterModal from 'Components/Filter/FilterModal';
|
||||
import { setReleasesFilter } from 'Store/Actions/releaseActions';
|
||||
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
(state) => state.releases.items,
|
||||
(state) => state.releases.filterBuilderProps,
|
||||
(sectionItems, filterBuilderProps) => {
|
||||
return {
|
||||
sectionItems,
|
||||
filterBuilderProps,
|
||||
customFilterType: 'releases'
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
const mapDispatchToProps = {
|
||||
dispatchSetFilter: setReleasesFilter
|
||||
};
|
||||
|
||||
export default connect(createMapStateToProps, mapDispatchToProps)(FilterModal);
|
@@ -15,17 +15,7 @@ function NotificationEventItems(props) {
|
||||
} = props;
|
||||
|
||||
const {
|
||||
onGrab,
|
||||
onDownload,
|
||||
onUpgrade,
|
||||
onRename,
|
||||
onDelete,
|
||||
onHealthIssue,
|
||||
supportsOnGrab,
|
||||
supportsOnDownload,
|
||||
supportsOnUpgrade,
|
||||
supportsOnRename,
|
||||
supportsOnDelete,
|
||||
supportsOnHealthIssue,
|
||||
includeHealthWarnings
|
||||
} = item;
|
||||
@@ -39,64 +29,6 @@ function NotificationEventItems(props) {
|
||||
link="https://github.com/Prowlarr/Prowlarr/wiki/Connections"
|
||||
/>
|
||||
<div className={styles.events}>
|
||||
<div>
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="onGrab"
|
||||
helpText={translate('OnGrabHelpText')}
|
||||
isDisabled={!supportsOnGrab.value}
|
||||
{...onGrab}
|
||||
onChange={onInputChange}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="onDownload"
|
||||
helpText={translate('OnDownloadHelpText')}
|
||||
isDisabled={!supportsOnDownload.value}
|
||||
{...onDownload}
|
||||
onChange={onInputChange}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{
|
||||
onDownload.value &&
|
||||
<div>
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="onUpgrade"
|
||||
helpText={translate('OnUpgradeHelpText')}
|
||||
isDisabled={!supportsOnUpgrade.value}
|
||||
{...onUpgrade}
|
||||
onChange={onInputChange}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div>
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="onRename"
|
||||
helpText={translate('OnRenameHelpText')}
|
||||
isDisabled={!supportsOnRename.value}
|
||||
{...onRename}
|
||||
onChange={onInputChange}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="onDelete"
|
||||
helpText={translate('OnDeleteHelpText')}
|
||||
isDisabled={!supportsOnDelete.value}
|
||||
{...onDelete}
|
||||
onChange={onInputChange}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
|
@@ -37,7 +37,13 @@ export const defaultState = {
|
||||
{
|
||||
name: 'indexer',
|
||||
label: 'Indexer',
|
||||
isSortable: true,
|
||||
isSortable: false,
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'query',
|
||||
label: 'Query',
|
||||
isSortable: false,
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
@@ -46,6 +52,18 @@ export const defaultState = {
|
||||
isSortable: true,
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'successful',
|
||||
label: 'Successful',
|
||||
isSortable: false,
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'elapsedTime',
|
||||
label: 'Elapsed Time',
|
||||
isSortable: false,
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'details',
|
||||
columnLabel: translate('Details'),
|
||||
|
@@ -68,6 +68,12 @@ export const defaultState = {
|
||||
isSortable: true,
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'priority',
|
||||
label: translate('Priority'),
|
||||
isSortable: true,
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'added',
|
||||
label: translate('Added'),
|
||||
@@ -115,6 +121,17 @@ export const defaultState = {
|
||||
type: filterBuilderTypes.DATE,
|
||||
valueType: filterBuilderValueTypes.DATE
|
||||
},
|
||||
{
|
||||
name: 'priority',
|
||||
label: 'Priority',
|
||||
type: filterBuilderTypes.NUMBER
|
||||
},
|
||||
{
|
||||
name: 'protocol',
|
||||
label: 'Protocol',
|
||||
type: filterBuilderTypes.EXACT,
|
||||
valueType: filterBuilderValueTypes.PROTOCOL
|
||||
},
|
||||
{
|
||||
name: 'tags',
|
||||
label: translate('Tags'),
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import { createAction } from 'redux-actions';
|
||||
import Icon from 'Components/Icon';
|
||||
import { filterBuilderTypes, filterBuilderValueTypes, filterTypes, icons, sortDirections } from 'Helpers/Props';
|
||||
import { filterBuilderTypes, filterBuilderValueTypes, icons, sortDirections } from 'Helpers/Props';
|
||||
import { createThunk, handleThunks } from 'Store/thunks';
|
||||
import createAjaxRequest from 'Utilities/createAjaxRequest';
|
||||
import translate from 'Utilities/String/translate';
|
||||
@@ -100,17 +100,6 @@ export const defaultState = {
|
||||
return releaseWeight + 1000000;
|
||||
}
|
||||
|
||||
return releaseWeight;
|
||||
},
|
||||
|
||||
rejections: function(item, direction) {
|
||||
const rejections = item.rejections;
|
||||
const releaseWeight = item.releaseWeight;
|
||||
|
||||
if (rejections.length !== 0) {
|
||||
return releaseWeight + 1000000;
|
||||
}
|
||||
|
||||
return releaseWeight;
|
||||
}
|
||||
},
|
||||
@@ -123,50 +112,6 @@ export const defaultState = {
|
||||
}
|
||||
],
|
||||
|
||||
filterPredicates: {
|
||||
quality: function(item, value, type) {
|
||||
const qualityId = item.quality.quality.id;
|
||||
|
||||
if (type === filterTypes.EQUAL) {
|
||||
return qualityId === value;
|
||||
}
|
||||
|
||||
if (type === filterTypes.NOT_EQUAL) {
|
||||
return qualityId !== value;
|
||||
}
|
||||
|
||||
// Default to false
|
||||
return false;
|
||||
},
|
||||
|
||||
rejectionCount: function(item, value, type) {
|
||||
const rejectionCount = item.rejections.length;
|
||||
|
||||
switch (type) {
|
||||
case filterTypes.EQUAL:
|
||||
return rejectionCount === value;
|
||||
|
||||
case filterTypes.GREATER_THAN:
|
||||
return rejectionCount > value;
|
||||
|
||||
case filterTypes.GREATER_THAN_OR_EQUAL:
|
||||
return rejectionCount >= value;
|
||||
|
||||
case filterTypes.LESS_THAN:
|
||||
return rejectionCount < value;
|
||||
|
||||
case filterTypes.LESS_THAN_OR_EQUAL:
|
||||
return rejectionCount <= value;
|
||||
|
||||
case filterTypes.NOT_EQUAL:
|
||||
return rejectionCount !== value;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
filterBuilderProps: [
|
||||
{
|
||||
name: 'title',
|
||||
@@ -204,17 +149,6 @@ export const defaultState = {
|
||||
name: 'peers',
|
||||
label: translate('Peers'),
|
||||
type: filterBuilderTypes.NUMBER
|
||||
},
|
||||
{
|
||||
name: 'quality',
|
||||
label: translate('Quality'),
|
||||
type: filterBuilderTypes.EXACT,
|
||||
valueType: filterBuilderValueTypes.QUALITY
|
||||
},
|
||||
{
|
||||
name: 'rejectionCount',
|
||||
label: translate('RejectionCount'),
|
||||
type: filterBuilderTypes.NUMBER
|
||||
}
|
||||
],
|
||||
selectedFilterKey: 'all'
|
||||
@@ -235,7 +169,7 @@ export const SET_RELEASES_SORT = 'releases/setReleasesSort';
|
||||
export const CLEAR_RELEASES = 'releases/clearReleases';
|
||||
export const GRAB_RELEASE = 'releases/grabRelease';
|
||||
export const UPDATE_RELEASE = 'releases/updateRelease';
|
||||
export const SET_RELEASES_FILTER = 'releases/setMovieReleasesFilter';
|
||||
export const SET_RELEASES_FILTER = 'releases/setReleasesFilter';
|
||||
export const SET_RELEASES_TABLE_OPTION = 'releases/setReleasesTableOption';
|
||||
|
||||
//
|
||||
|
@@ -1,5 +1,9 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using NzbDrone.Core.Indexers;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
using NzbDrone.Core.ThingiProvider.Events;
|
||||
|
||||
namespace NzbDrone.Core.Applications
|
||||
{
|
||||
@@ -10,7 +14,7 @@ namespace NzbDrone.Core.Applications
|
||||
void DeleteAllForApp(int appId);
|
||||
}
|
||||
|
||||
public class AppIndexerMapService : IAppIndexerMapService
|
||||
public class AppIndexerMapService : IAppIndexerMapService, IHandle<ProviderDeletedEvent<IApplication>>
|
||||
{
|
||||
private readonly IAppIndexerMapRepository _appIndexerMapRepository;
|
||||
|
||||
@@ -33,5 +37,10 @@ namespace NzbDrone.Core.Applications
|
||||
{
|
||||
return _appIndexerMapRepository.Insert(appIndexerMap);
|
||||
}
|
||||
|
||||
public void Handle(ProviderDeletedEvent<IApplication> message)
|
||||
{
|
||||
_appIndexerMapRepository.DeleteAllForApp(message.ProviderId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -14,7 +14,6 @@ namespace NzbDrone.Core.Datastore.Migration
|
||||
|
||||
Create.TableForModel("History")
|
||||
.WithColumn("IndexerId").AsInt32()
|
||||
.WithColumn("SourceTitle").AsString()
|
||||
.WithColumn("Date").AsDateTime()
|
||||
.WithColumn("Data").AsString()
|
||||
.WithColumn("EventType").AsInt32().Nullable()
|
||||
|
@@ -12,7 +12,6 @@ namespace NzbDrone.Core.History
|
||||
}
|
||||
|
||||
public int IndexerId { get; set; }
|
||||
public string SourceTitle { get; set; }
|
||||
public DateTime Date { get; set; }
|
||||
public HistoryEventType EventType { get; set; }
|
||||
public Dictionary<string, string> Data { get; set; }
|
||||
|
@@ -86,10 +86,14 @@ namespace NzbDrone.Core.History
|
||||
{
|
||||
Date = DateTime.UtcNow,
|
||||
IndexerId = message.IndexerId,
|
||||
EventType = HistoryEventType.IndexerQuery,
|
||||
SourceTitle = message.Query
|
||||
EventType = HistoryEventType.IndexerQuery
|
||||
};
|
||||
|
||||
history.Data.Add("ElapsedTime", message.Time.ToString());
|
||||
history.Data.Add("Query", message.Query.SceneTitles.FirstOrDefault() ?? string.Empty);
|
||||
history.Data.Add("Successful", message.Successful.ToString());
|
||||
history.Data.Add("QueryResults", message.Results.HasValue ? message.Results.ToString() : null);
|
||||
|
||||
_historyRepository.Insert(history);
|
||||
}
|
||||
|
||||
|
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using NLog;
|
||||
@@ -53,10 +54,15 @@ namespace NzbDrone.Core.IndexerSearch
|
||||
{
|
||||
var spec = new TSpec()
|
||||
{
|
||||
InteractiveSearch = interactiveSearch
|
||||
InteractiveSearch = interactiveSearch,
|
||||
SceneTitles = new List<string>()
|
||||
};
|
||||
|
||||
spec.SceneTitles = new List<string> { query };
|
||||
if (query.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
spec.SceneTitles.Add(query);
|
||||
}
|
||||
|
||||
spec.IndexerIds = indexerIds;
|
||||
|
||||
return spec;
|
||||
@@ -86,21 +92,25 @@ namespace NzbDrone.Core.IndexerSearch
|
||||
|
||||
taskList.Add(taskFactory.StartNew(() =>
|
||||
{
|
||||
var sw = Stopwatch.StartNew();
|
||||
try
|
||||
{
|
||||
var indexerReports = searchAction(indexerLocal);
|
||||
|
||||
_eventAggregator.PublishEvent(new IndexerQueryEvent(indexer.Definition.Id, criteriaBase.QueryTitles.Join(", ")));
|
||||
|
||||
lock (reports)
|
||||
{
|
||||
reports.AddRange(indexerReports);
|
||||
}
|
||||
|
||||
_eventAggregator.PublishEvent(new IndexerQueryEvent(indexer.Definition.Id, criteriaBase, sw.ElapsedMilliseconds, true, indexerReports.Count()));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_eventAggregator.PublishEvent(new IndexerQueryEvent(indexer.Definition.Id, criteriaBase, sw.ElapsedMilliseconds, false));
|
||||
_logger.Error(e, "Error while searching for {0}", criteriaBase);
|
||||
}
|
||||
|
||||
sw.Stop();
|
||||
}).LogExceptions());
|
||||
}
|
||||
|
||||
|
@@ -1,16 +1,23 @@
|
||||
using NzbDrone.Common.Messaging;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
|
||||
namespace NzbDrone.Core.Indexers
|
||||
{
|
||||
public class IndexerQueryEvent : IEvent
|
||||
{
|
||||
public int IndexerId { get; set; }
|
||||
public string Query { get; set; }
|
||||
public SearchCriteriaBase Query { get; set; }
|
||||
public long Time { get; set; }
|
||||
public bool Successful { get; set; }
|
||||
public int? Results { get; set; }
|
||||
|
||||
public IndexerQueryEvent(int indexerId, string query)
|
||||
public IndexerQueryEvent(int indexerId, SearchCriteriaBase query, long time, bool successful, int? results = null)
|
||||
{
|
||||
IndexerId = indexerId;
|
||||
Query = query;
|
||||
Time = time;
|
||||
Successful = successful;
|
||||
Results = results;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,16 +1,13 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using NzbDrone.Core.History;
|
||||
using NzbDrone.Core.Languages;
|
||||
using Prowlarr.Http.REST;
|
||||
|
||||
namespace Prowlarr.Api.V1.History
|
||||
{
|
||||
public class HistoryResource : RestResource
|
||||
{
|
||||
public int MovieId { get; set; }
|
||||
public string SourceTitle { get; set; }
|
||||
public bool QualityCutoffNotMet { get; set; }
|
||||
public int IndexerId { get; set; }
|
||||
public DateTime Date { get; set; }
|
||||
public string DownloadId { get; set; }
|
||||
|
||||
@@ -32,8 +29,7 @@ namespace Prowlarr.Api.V1.History
|
||||
{
|
||||
Id = model.Id,
|
||||
|
||||
MovieId = model.IndexerId,
|
||||
SourceTitle = model.SourceTitle,
|
||||
IndexerId = model.IndexerId,
|
||||
|
||||
//QualityCutoffNotMet
|
||||
Date = model.Date,
|
||||
|
Reference in New Issue
Block a user