mirror of
https://github.com/Prowlarr/Prowlarr.git
synced 2025-09-17 17:14:18 +02:00
Add support for Applications
This commit is contained in:
@@ -6,6 +6,7 @@ import Switch from 'Components/Router/Switch';
|
|||||||
import HistoryConnector from 'History/HistoryConnector';
|
import HistoryConnector from 'History/HistoryConnector';
|
||||||
import IndexerIndexConnector from 'Indexer/Index/IndexerIndexConnector';
|
import IndexerIndexConnector from 'Indexer/Index/IndexerIndexConnector';
|
||||||
import SearchIndexConnector from 'Search/SearchIndexConnector';
|
import SearchIndexConnector from 'Search/SearchIndexConnector';
|
||||||
|
import ApplicationSettings from 'Settings/Applications/ApplicationSettings';
|
||||||
import GeneralSettingsConnector from 'Settings/General/GeneralSettingsConnector';
|
import GeneralSettingsConnector from 'Settings/General/GeneralSettingsConnector';
|
||||||
import IndexerSettingsConnector from 'Settings/Indexers/IndexerSettingsConnector';
|
import IndexerSettingsConnector from 'Settings/Indexers/IndexerSettingsConnector';
|
||||||
import NotificationSettings from 'Settings/Notifications/NotificationSettings';
|
import NotificationSettings from 'Settings/Notifications/NotificationSettings';
|
||||||
@@ -28,7 +29,7 @@ function AppRoutes(props) {
|
|||||||
return (
|
return (
|
||||||
<Switch>
|
<Switch>
|
||||||
{/*
|
{/*
|
||||||
Movies
|
Indexers
|
||||||
*/}
|
*/}
|
||||||
|
|
||||||
<Route
|
<Route
|
||||||
@@ -87,6 +88,11 @@ function AppRoutes(props) {
|
|||||||
component={Settings}
|
component={Settings}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<Route
|
||||||
|
path="/settings/applications"
|
||||||
|
component={ApplicationSettings}
|
||||||
|
/>
|
||||||
|
|
||||||
<Route
|
<Route
|
||||||
path="/settings/indexers"
|
path="/settings/indexers"
|
||||||
component={IndexerSettingsConnector}
|
component={IndexerSettingsConnector}
|
||||||
|
22
frontend/src/Settings/Applications/ApplicationSettings.js
Normal file
22
frontend/src/Settings/Applications/ApplicationSettings.js
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import PageContent from 'Components/Page/PageContent';
|
||||||
|
import PageContentBody from 'Components/Page/PageContentBody';
|
||||||
|
import SettingsToolbarConnector from 'Settings/SettingsToolbarConnector';
|
||||||
|
import translate from 'Utilities/String/translate';
|
||||||
|
import ApplicationsConnector from './Applications/ApplicationsConnector';
|
||||||
|
|
||||||
|
function ApplicationSettings() {
|
||||||
|
return (
|
||||||
|
<PageContent title={translate('Applications')}>
|
||||||
|
<SettingsToolbarConnector
|
||||||
|
showSave={false}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<PageContentBody>
|
||||||
|
<ApplicationsConnector />
|
||||||
|
</PageContentBody>
|
||||||
|
</PageContent>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ApplicationSettings;
|
@@ -0,0 +1,44 @@
|
|||||||
|
.application {
|
||||||
|
composes: card from '~Components/Card.css';
|
||||||
|
|
||||||
|
position: relative;
|
||||||
|
width: 300px;
|
||||||
|
height: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.underlay {
|
||||||
|
@add-mixin cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.overlay {
|
||||||
|
@add-mixin linkOverlay;
|
||||||
|
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.name {
|
||||||
|
text-align: center;
|
||||||
|
font-weight: lighter;
|
||||||
|
font-size: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions {
|
||||||
|
margin-top: 20px;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.presetsMenu {
|
||||||
|
composes: menu from '~Components/Menu/Menu.css';
|
||||||
|
|
||||||
|
display: inline-block;
|
||||||
|
margin: 0 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.presetsMenuButton {
|
||||||
|
composes: button from '~Components/Link/Button.css';
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
margin-left: 5px;
|
||||||
|
content: '\25BE';
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,111 @@
|
|||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import Button from 'Components/Link/Button';
|
||||||
|
import Link from 'Components/Link/Link';
|
||||||
|
import Menu from 'Components/Menu/Menu';
|
||||||
|
import MenuContent from 'Components/Menu/MenuContent';
|
||||||
|
import { sizes } from 'Helpers/Props';
|
||||||
|
import translate from 'Utilities/String/translate';
|
||||||
|
import AddApplicationPresetMenuItem from './AddApplicationPresetMenuItem';
|
||||||
|
import styles from './AddApplicationItem.css';
|
||||||
|
|
||||||
|
class AddApplicationItem extends Component {
|
||||||
|
|
||||||
|
//
|
||||||
|
// Listeners
|
||||||
|
|
||||||
|
onApplicationSelect = () => {
|
||||||
|
const {
|
||||||
|
implementation
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
this.props.onApplicationSelect({ implementation });
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Render
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
implementation,
|
||||||
|
implementationName,
|
||||||
|
infoLink,
|
||||||
|
presets,
|
||||||
|
onApplicationSelect
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
const hasPresets = !!presets && !!presets.length;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={styles.application}
|
||||||
|
>
|
||||||
|
<Link
|
||||||
|
className={styles.underlay}
|
||||||
|
onPress={this.onApplicationSelect}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className={styles.overlay}>
|
||||||
|
<div className={styles.name}>
|
||||||
|
{implementationName}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={styles.actions}>
|
||||||
|
{
|
||||||
|
hasPresets &&
|
||||||
|
<span>
|
||||||
|
<Button
|
||||||
|
size={sizes.SMALL}
|
||||||
|
onPress={this.onApplicationSelect}
|
||||||
|
>
|
||||||
|
Custom
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Menu className={styles.presetsMenu}>
|
||||||
|
<Button
|
||||||
|
className={styles.presetsMenuButton}
|
||||||
|
size={sizes.SMALL}
|
||||||
|
>
|
||||||
|
Presets
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<MenuContent>
|
||||||
|
{
|
||||||
|
presets.map((preset) => {
|
||||||
|
return (
|
||||||
|
<AddApplicationPresetMenuItem
|
||||||
|
key={preset.name}
|
||||||
|
name={preset.name}
|
||||||
|
implementation={implementation}
|
||||||
|
onPress={onApplicationSelect}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</MenuContent>
|
||||||
|
</Menu>
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
|
||||||
|
<Button
|
||||||
|
to={infoLink}
|
||||||
|
size={sizes.SMALL}
|
||||||
|
>
|
||||||
|
{translate('MoreInfo')}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AddApplicationItem.propTypes = {
|
||||||
|
implementation: PropTypes.string.isRequired,
|
||||||
|
implementationName: PropTypes.string.isRequired,
|
||||||
|
infoLink: PropTypes.string.isRequired,
|
||||||
|
presets: PropTypes.arrayOf(PropTypes.object),
|
||||||
|
onApplicationSelect: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AddApplicationItem;
|
@@ -0,0 +1,25 @@
|
|||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React from 'react';
|
||||||
|
import Modal from 'Components/Modal/Modal';
|
||||||
|
import AddApplicationModalContentConnector from './AddApplicationModalContentConnector';
|
||||||
|
|
||||||
|
function AddApplicationModal({ isOpen, onModalClose, ...otherProps }) {
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
isOpen={isOpen}
|
||||||
|
onModalClose={onModalClose}
|
||||||
|
>
|
||||||
|
<AddApplicationModalContentConnector
|
||||||
|
{...otherProps}
|
||||||
|
onModalClose={onModalClose}
|
||||||
|
/>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
AddApplicationModal.propTypes = {
|
||||||
|
isOpen: PropTypes.bool.isRequired,
|
||||||
|
onModalClose: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AddApplicationModal;
|
@@ -0,0 +1,5 @@
|
|||||||
|
.applications {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
@@ -0,0 +1,88 @@
|
|||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import Button from 'Components/Link/Button';
|
||||||
|
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||||
|
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 translate from 'Utilities/String/translate';
|
||||||
|
import AddApplicationItem from './AddApplicationItem';
|
||||||
|
import styles from './AddApplicationModalContent.css';
|
||||||
|
|
||||||
|
class AddApplicationModalContent extends Component {
|
||||||
|
|
||||||
|
//
|
||||||
|
// Render
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
isSchemaFetching,
|
||||||
|
isSchemaPopulated,
|
||||||
|
schemaError,
|
||||||
|
schema,
|
||||||
|
onApplicationSelect,
|
||||||
|
onModalClose
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ModalContent onModalClose={onModalClose}>
|
||||||
|
<ModalHeader>
|
||||||
|
Add Application
|
||||||
|
</ModalHeader>
|
||||||
|
|
||||||
|
<ModalBody>
|
||||||
|
{
|
||||||
|
isSchemaFetching &&
|
||||||
|
<LoadingIndicator />
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
!isSchemaFetching && !!schemaError &&
|
||||||
|
<div>
|
||||||
|
{translate('UnableToAddANewApplicationPleaseTryAgain')}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
isSchemaPopulated && !schemaError &&
|
||||||
|
<div>
|
||||||
|
<div className={styles.applications}>
|
||||||
|
{
|
||||||
|
schema.map((application) => {
|
||||||
|
return (
|
||||||
|
<AddApplicationItem
|
||||||
|
key={application.implementation}
|
||||||
|
implementation={application.implementation}
|
||||||
|
{...application}
|
||||||
|
onApplicationSelect={onApplicationSelect}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</ModalBody>
|
||||||
|
<ModalFooter>
|
||||||
|
<Button
|
||||||
|
onPress={onModalClose}
|
||||||
|
>
|
||||||
|
{translate('Close')}
|
||||||
|
</Button>
|
||||||
|
</ModalFooter>
|
||||||
|
</ModalContent>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AddApplicationModalContent.propTypes = {
|
||||||
|
isSchemaFetching: PropTypes.bool.isRequired,
|
||||||
|
isSchemaPopulated: PropTypes.bool.isRequired,
|
||||||
|
schemaError: PropTypes.object,
|
||||||
|
schema: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
|
onApplicationSelect: PropTypes.func.isRequired,
|
||||||
|
onModalClose: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AddApplicationModalContent;
|
@@ -0,0 +1,70 @@
|
|||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { createSelector } from 'reselect';
|
||||||
|
import { fetchApplicationSchema, selectApplicationSchema } from 'Store/Actions/settingsActions';
|
||||||
|
import AddApplicationModalContent from './AddApplicationModalContent';
|
||||||
|
|
||||||
|
function createMapStateToProps() {
|
||||||
|
return createSelector(
|
||||||
|
(state) => state.settings.applications,
|
||||||
|
(applications) => {
|
||||||
|
const {
|
||||||
|
isSchemaFetching,
|
||||||
|
isSchemaPopulated,
|
||||||
|
schemaError,
|
||||||
|
schema
|
||||||
|
} = applications;
|
||||||
|
|
||||||
|
return {
|
||||||
|
isSchemaFetching,
|
||||||
|
isSchemaPopulated,
|
||||||
|
schemaError,
|
||||||
|
schema
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const mapDispatchToProps = {
|
||||||
|
fetchApplicationSchema,
|
||||||
|
selectApplicationSchema
|
||||||
|
};
|
||||||
|
|
||||||
|
class AddApplicationModalContentConnector extends Component {
|
||||||
|
|
||||||
|
//
|
||||||
|
// Lifecycle
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.props.fetchApplicationSchema();
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Listeners
|
||||||
|
|
||||||
|
onApplicationSelect = ({ implementation, name }) => {
|
||||||
|
this.props.selectApplicationSchema({ implementation, presetName: name });
|
||||||
|
this.props.onModalClose({ applicationSelected: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Render
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<AddApplicationModalContent
|
||||||
|
{...this.props}
|
||||||
|
onApplicationSelect={this.onApplicationSelect}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AddApplicationModalContentConnector.propTypes = {
|
||||||
|
fetchApplicationSchema: PropTypes.func.isRequired,
|
||||||
|
selectApplicationSchema: PropTypes.func.isRequired,
|
||||||
|
onModalClose: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
export default connect(createMapStateToProps, mapDispatchToProps)(AddApplicationModalContentConnector);
|
@@ -0,0 +1,49 @@
|
|||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import MenuItem from 'Components/Menu/MenuItem';
|
||||||
|
|
||||||
|
class AddApplicationPresetMenuItem extends Component {
|
||||||
|
|
||||||
|
//
|
||||||
|
// Listeners
|
||||||
|
|
||||||
|
onPress = () => {
|
||||||
|
const {
|
||||||
|
name,
|
||||||
|
implementation
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
this.props.onPress({
|
||||||
|
name,
|
||||||
|
implementation
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Render
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
name,
|
||||||
|
implementation,
|
||||||
|
...otherProps
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MenuItem
|
||||||
|
{...otherProps}
|
||||||
|
onPress={this.onPress}
|
||||||
|
>
|
||||||
|
{name}
|
||||||
|
</MenuItem>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AddApplicationPresetMenuItem.propTypes = {
|
||||||
|
name: PropTypes.string.isRequired,
|
||||||
|
implementation: PropTypes.string.isRequired,
|
||||||
|
onPress: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AddApplicationPresetMenuItem;
|
@@ -0,0 +1,19 @@
|
|||||||
|
.application {
|
||||||
|
composes: card from '~Components/Card.css';
|
||||||
|
|
||||||
|
width: 290px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.name {
|
||||||
|
@add-mixin truncate;
|
||||||
|
|
||||||
|
margin-bottom: 20px;
|
||||||
|
font-weight: 300;
|
||||||
|
font-size: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.enabled {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
margin-top: 5px;
|
||||||
|
}
|
123
frontend/src/Settings/Applications/Applications/Application.js
Normal file
123
frontend/src/Settings/Applications/Applications/Application.js
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import Card from 'Components/Card';
|
||||||
|
import Label from 'Components/Label';
|
||||||
|
import ConfirmModal from 'Components/Modal/ConfirmModal';
|
||||||
|
import { kinds } from 'Helpers/Props';
|
||||||
|
import translate from 'Utilities/String/translate';
|
||||||
|
import EditApplicationModalConnector from './EditApplicationModalConnector';
|
||||||
|
import styles from './Application.css';
|
||||||
|
|
||||||
|
class Application extends Component {
|
||||||
|
|
||||||
|
//
|
||||||
|
// Lifecycle
|
||||||
|
|
||||||
|
constructor(props, context) {
|
||||||
|
super(props, context);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
isEditApplicationModalOpen: false,
|
||||||
|
isDeleteApplicationModalOpen: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Listeners
|
||||||
|
|
||||||
|
onEditApplicationPress = () => {
|
||||||
|
this.setState({ isEditApplicationModalOpen: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
onEditApplicationModalClose = () => {
|
||||||
|
this.setState({ isEditApplicationModalOpen: false });
|
||||||
|
}
|
||||||
|
|
||||||
|
onDeleteApplicationPress = () => {
|
||||||
|
this.setState({
|
||||||
|
isEditApplicationModalOpen: false,
|
||||||
|
isDeleteApplicationModalOpen: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onDeleteApplicationModalClose= () => {
|
||||||
|
this.setState({ isDeleteApplicationModalOpen: false });
|
||||||
|
}
|
||||||
|
|
||||||
|
onConfirmDeleteApplication = () => {
|
||||||
|
this.props.onConfirmDeleteApplication(this.props.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Render
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
syncLevel
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card
|
||||||
|
className={styles.application}
|
||||||
|
overlayContent={true}
|
||||||
|
onPress={this.onEditApplicationPress}
|
||||||
|
>
|
||||||
|
<div className={styles.name}>
|
||||||
|
{name}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{
|
||||||
|
syncLevel === 'addOnly' &&
|
||||||
|
<Label kind={kinds.WARNING}>
|
||||||
|
Add Only
|
||||||
|
</Label>
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
syncLevel === 'fullSync' &&
|
||||||
|
<Label kind={kinds.SUCCESS}>
|
||||||
|
Full Sync
|
||||||
|
</Label>
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
syncLevel === 'disabled' &&
|
||||||
|
<Label
|
||||||
|
kind={kinds.DISABLED}
|
||||||
|
outline={true}
|
||||||
|
>
|
||||||
|
Disabled
|
||||||
|
</Label>
|
||||||
|
}
|
||||||
|
|
||||||
|
<EditApplicationModalConnector
|
||||||
|
id={id}
|
||||||
|
isOpen={this.state.isEditApplicationModalOpen}
|
||||||
|
onModalClose={this.onEditApplicationModalClose}
|
||||||
|
onDeleteApplicationPress={this.onDeleteApplicationPress}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<ConfirmModal
|
||||||
|
isOpen={this.state.isDeleteApplicationModalOpen}
|
||||||
|
kind={kinds.DANGER}
|
||||||
|
title={translate('DeleteApplication')}
|
||||||
|
message={translate('DeleteApplicationMessageText', [name])}
|
||||||
|
confirmLabel={translate('Delete')}
|
||||||
|
onConfirm={this.onConfirmDeleteApplication}
|
||||||
|
onCancel={this.onDeleteApplicationModalClose}
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Application.propTypes = {
|
||||||
|
id: PropTypes.number.isRequired,
|
||||||
|
name: PropTypes.string.isRequired,
|
||||||
|
syncLevel: PropTypes.string.isRequired,
|
||||||
|
onConfirmDeleteApplication: PropTypes.func
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Application;
|
@@ -0,0 +1,20 @@
|
|||||||
|
.applications {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.addApplication {
|
||||||
|
composes: application from '~./Application.css';
|
||||||
|
|
||||||
|
background-color: $cardAlternateBackgroundColor;
|
||||||
|
color: $gray;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.center {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 5px 20px 0;
|
||||||
|
border: 1px solid $borderColor;
|
||||||
|
border-radius: 4px;
|
||||||
|
background-color: $white;
|
||||||
|
}
|
115
frontend/src/Settings/Applications/Applications/Applications.js
Normal file
115
frontend/src/Settings/Applications/Applications/Applications.js
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import Card from 'Components/Card';
|
||||||
|
import FieldSet from 'Components/FieldSet';
|
||||||
|
import Icon from 'Components/Icon';
|
||||||
|
import PageSectionContent from 'Components/Page/PageSectionContent';
|
||||||
|
import { icons } from 'Helpers/Props';
|
||||||
|
import translate from 'Utilities/String/translate';
|
||||||
|
import AddApplicationModal from './AddApplicationModal';
|
||||||
|
import Application from './Application';
|
||||||
|
import EditApplicationModalConnector from './EditApplicationModalConnector';
|
||||||
|
import styles from './Applications.css';
|
||||||
|
|
||||||
|
class Applications extends Component {
|
||||||
|
|
||||||
|
//
|
||||||
|
// Lifecycle
|
||||||
|
|
||||||
|
constructor(props, context) {
|
||||||
|
super(props, context);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
isAddApplicationModalOpen: false,
|
||||||
|
isEditApplicationModalOpen: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Listeners
|
||||||
|
|
||||||
|
onAddApplicationPress = () => {
|
||||||
|
this.setState({ isAddApplicationModalOpen: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
onAddApplicationModalClose = ({ applicationSelected = false } = {}) => {
|
||||||
|
this.setState({
|
||||||
|
isAddApplicationModalOpen: false,
|
||||||
|
isEditApplicationModalOpen: applicationSelected
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onEditApplicationModalClose = () => {
|
||||||
|
this.setState({ isEditApplicationModalOpen: false });
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Render
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
items,
|
||||||
|
onConfirmDeleteApplication,
|
||||||
|
...otherProps
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
const {
|
||||||
|
isAddApplicationModalOpen,
|
||||||
|
isEditApplicationModalOpen
|
||||||
|
} = this.state;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FieldSet legend={translate('Applications')}>
|
||||||
|
<PageSectionContent
|
||||||
|
errorMessage="Unable to load application list"
|
||||||
|
{...otherProps}
|
||||||
|
>
|
||||||
|
<div className={styles.applications}>
|
||||||
|
{
|
||||||
|
items.map((item) => {
|
||||||
|
return (
|
||||||
|
<Application
|
||||||
|
key={item.id}
|
||||||
|
{...item}
|
||||||
|
onConfirmDeleteApplication={onConfirmDeleteApplication}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
<Card
|
||||||
|
className={styles.addApplication}
|
||||||
|
onPress={this.onAddApplicationPress}
|
||||||
|
>
|
||||||
|
<div className={styles.center}>
|
||||||
|
<Icon
|
||||||
|
name={icons.ADD}
|
||||||
|
size={45}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<AddApplicationModal
|
||||||
|
isOpen={isAddApplicationModalOpen}
|
||||||
|
onModalClose={this.onAddApplicationModalClose}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<EditApplicationModalConnector
|
||||||
|
isOpen={isEditApplicationModalOpen}
|
||||||
|
onModalClose={this.onEditApplicationModalClose}
|
||||||
|
/>
|
||||||
|
</PageSectionContent>
|
||||||
|
</FieldSet>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Applications.propTypes = {
|
||||||
|
isFetching: PropTypes.bool.isRequired,
|
||||||
|
error: PropTypes.object,
|
||||||
|
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
|
onConfirmDeleteApplication: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Applications;
|
@@ -0,0 +1,56 @@
|
|||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { createSelector } from 'reselect';
|
||||||
|
import { deleteApplication, fetchApplications } from 'Store/Actions/settingsActions';
|
||||||
|
import createSortedSectionSelector from 'Store/Selectors/createSortedSectionSelector';
|
||||||
|
import sortByName from 'Utilities/Array/sortByName';
|
||||||
|
import Applications from './Applications';
|
||||||
|
|
||||||
|
function createMapStateToProps() {
|
||||||
|
return createSelector(
|
||||||
|
createSortedSectionSelector('settings.applications', sortByName),
|
||||||
|
(applications) => applications
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const mapDispatchToProps = {
|
||||||
|
fetchApplications,
|
||||||
|
deleteApplication
|
||||||
|
};
|
||||||
|
|
||||||
|
class ApplicationsConnector extends Component {
|
||||||
|
|
||||||
|
//
|
||||||
|
// Lifecycle
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.props.fetchApplications();
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Listeners
|
||||||
|
|
||||||
|
onConfirmDeleteApplication = (id) => {
|
||||||
|
this.props.deleteApplication({ id });
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Render
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<Applications
|
||||||
|
{...this.props}
|
||||||
|
onConfirmDeleteApplication={this.onConfirmDeleteApplication}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ApplicationsConnector.propTypes = {
|
||||||
|
fetchApplications: PropTypes.func.isRequired,
|
||||||
|
deleteApplication: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
export default connect(createMapStateToProps, mapDispatchToProps)(ApplicationsConnector);
|
@@ -0,0 +1,27 @@
|
|||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React from 'react';
|
||||||
|
import Modal from 'Components/Modal/Modal';
|
||||||
|
import { sizes } from 'Helpers/Props';
|
||||||
|
import EditApplicationModalContentConnector from './EditApplicationModalContentConnector';
|
||||||
|
|
||||||
|
function EditApplicationModal({ isOpen, onModalClose, ...otherProps }) {
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
size={sizes.MEDIUM}
|
||||||
|
isOpen={isOpen}
|
||||||
|
onModalClose={onModalClose}
|
||||||
|
>
|
||||||
|
<EditApplicationModalContentConnector
|
||||||
|
{...otherProps}
|
||||||
|
onModalClose={onModalClose}
|
||||||
|
/>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
EditApplicationModal.propTypes = {
|
||||||
|
isOpen: PropTypes.bool.isRequired,
|
||||||
|
onModalClose: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
export default EditApplicationModal;
|
@@ -0,0 +1,65 @@
|
|||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { clearPendingChanges } from 'Store/Actions/baseActions';
|
||||||
|
import { cancelSaveApplication, cancelTestApplication } from 'Store/Actions/settingsActions';
|
||||||
|
import EditApplicationModal from './EditApplicationModal';
|
||||||
|
|
||||||
|
function createMapDispatchToProps(dispatch, props) {
|
||||||
|
const section = 'settings.applications';
|
||||||
|
|
||||||
|
return {
|
||||||
|
dispatchClearPendingChanges() {
|
||||||
|
dispatch(clearPendingChanges({ section }));
|
||||||
|
},
|
||||||
|
|
||||||
|
dispatchCancelTestApplication() {
|
||||||
|
dispatch(cancelTestApplication({ section }));
|
||||||
|
},
|
||||||
|
|
||||||
|
dispatchCancelSaveApplication() {
|
||||||
|
dispatch(cancelSaveApplication({ section }));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
class EditApplicationModalConnector extends Component {
|
||||||
|
|
||||||
|
//
|
||||||
|
// Listeners
|
||||||
|
|
||||||
|
onModalClose = () => {
|
||||||
|
this.props.dispatchClearPendingChanges();
|
||||||
|
this.props.dispatchCancelTestApplication();
|
||||||
|
this.props.dispatchCancelSaveApplication();
|
||||||
|
this.props.onModalClose();
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Render
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
dispatchClearPendingChanges,
|
||||||
|
dispatchCancelTestApplication,
|
||||||
|
dispatchCancelSaveApplication,
|
||||||
|
...otherProps
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<EditApplicationModal
|
||||||
|
{...otherProps}
|
||||||
|
onModalClose={this.onModalClose}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
EditApplicationModalConnector.propTypes = {
|
||||||
|
onModalClose: PropTypes.func.isRequired,
|
||||||
|
dispatchClearPendingChanges: PropTypes.func.isRequired,
|
||||||
|
dispatchCancelTestApplication: PropTypes.func.isRequired,
|
||||||
|
dispatchCancelSaveApplication: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
export default connect(null, createMapDispatchToProps)(EditApplicationModalConnector);
|
@@ -0,0 +1,11 @@
|
|||||||
|
.deleteButton {
|
||||||
|
composes: button from '~Components/Link/Button.css';
|
||||||
|
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message {
|
||||||
|
composes: alert from '~Components/Alert.css';
|
||||||
|
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
@@ -0,0 +1,194 @@
|
|||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React from 'react';
|
||||||
|
import Alert from 'Components/Alert';
|
||||||
|
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 ProviderFieldFormGroup from 'Components/Form/ProviderFieldFormGroup';
|
||||||
|
import Button from 'Components/Link/Button';
|
||||||
|
import SpinnerErrorButton from 'Components/Link/SpinnerErrorButton';
|
||||||
|
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||||
|
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 './EditApplicationModalContent.css';
|
||||||
|
|
||||||
|
const syncLevelOptions = [
|
||||||
|
{ key: 'disabled', value: 'Disabled' },
|
||||||
|
{ key: 'addOnly', value: 'Add Only' },
|
||||||
|
{ key: 'fullSync', value: 'Full Sync' }
|
||||||
|
];
|
||||||
|
|
||||||
|
function EditApplicationModalContent(props) {
|
||||||
|
const {
|
||||||
|
advancedSettings,
|
||||||
|
isFetching,
|
||||||
|
error,
|
||||||
|
isSaving,
|
||||||
|
isTesting,
|
||||||
|
saveError,
|
||||||
|
item,
|
||||||
|
onInputChange,
|
||||||
|
onFieldChange,
|
||||||
|
onModalClose,
|
||||||
|
onSavePress,
|
||||||
|
onTestPress,
|
||||||
|
onDeleteApplicationPress,
|
||||||
|
...otherProps
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
const {
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
syncLevel,
|
||||||
|
tags,
|
||||||
|
fields,
|
||||||
|
message
|
||||||
|
} = item;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ModalContent onModalClose={onModalClose}>
|
||||||
|
<ModalHeader>
|
||||||
|
{`${id ? 'Edit' : 'Add'} Application`}
|
||||||
|
</ModalHeader>
|
||||||
|
|
||||||
|
<ModalBody>
|
||||||
|
{
|
||||||
|
isFetching &&
|
||||||
|
<LoadingIndicator />
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
!isFetching && !!error &&
|
||||||
|
<div>
|
||||||
|
{translate('UnableToAddANewApplicationPleaseTryAgain')}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
!isFetching && !error &&
|
||||||
|
<Form {...otherProps}>
|
||||||
|
{
|
||||||
|
!!message &&
|
||||||
|
<Alert
|
||||||
|
className={styles.message}
|
||||||
|
kind={message.value.type}
|
||||||
|
>
|
||||||
|
{message.value.message}
|
||||||
|
</Alert>
|
||||||
|
}
|
||||||
|
|
||||||
|
<FormGroup>
|
||||||
|
<FormLabel>{translate('Name')}</FormLabel>
|
||||||
|
|
||||||
|
<FormInputGroup
|
||||||
|
type={inputTypes.TEXT}
|
||||||
|
name="name"
|
||||||
|
{...name}
|
||||||
|
onChange={onInputChange}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
|
||||||
|
<FormGroup>
|
||||||
|
<FormLabel>{'Sync Level'}</FormLabel>
|
||||||
|
|
||||||
|
<FormInputGroup
|
||||||
|
type={inputTypes.SELECT}
|
||||||
|
values={syncLevelOptions}
|
||||||
|
name="syncLevel"
|
||||||
|
helpText={'Sync Level'}
|
||||||
|
{...syncLevel}
|
||||||
|
onChange={onInputChange}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
|
||||||
|
<FormGroup>
|
||||||
|
<FormLabel>{translate('Tags')}</FormLabel>
|
||||||
|
|
||||||
|
<FormInputGroup
|
||||||
|
type={inputTypes.TAG}
|
||||||
|
name="tags"
|
||||||
|
helpText={translate('TagsHelpText')}
|
||||||
|
{...tags}
|
||||||
|
onChange={onInputChange}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
|
||||||
|
{
|
||||||
|
fields.map((field) => {
|
||||||
|
return (
|
||||||
|
<ProviderFieldFormGroup
|
||||||
|
key={field.name}
|
||||||
|
advancedSettings={advancedSettings}
|
||||||
|
provider="application"
|
||||||
|
providerData={item}
|
||||||
|
section="settings.applications"
|
||||||
|
{...field}
|
||||||
|
onChange={onFieldChange}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
</Form>
|
||||||
|
}
|
||||||
|
</ModalBody>
|
||||||
|
<ModalFooter>
|
||||||
|
{
|
||||||
|
id &&
|
||||||
|
<Button
|
||||||
|
className={styles.deleteButton}
|
||||||
|
kind={kinds.DANGER}
|
||||||
|
onPress={onDeleteApplicationPress}
|
||||||
|
>
|
||||||
|
{translate('Delete')}
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
|
||||||
|
<SpinnerErrorButton
|
||||||
|
isSpinning={isTesting}
|
||||||
|
error={saveError}
|
||||||
|
onPress={onTestPress}
|
||||||
|
>
|
||||||
|
{translate('Test')}
|
||||||
|
</SpinnerErrorButton>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
onPress={onModalClose}
|
||||||
|
>
|
||||||
|
{translate('Cancel')}
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<SpinnerErrorButton
|
||||||
|
isSpinning={isSaving}
|
||||||
|
error={saveError}
|
||||||
|
onPress={onSavePress}
|
||||||
|
>
|
||||||
|
{translate('Save')}
|
||||||
|
</SpinnerErrorButton>
|
||||||
|
</ModalFooter>
|
||||||
|
</ModalContent>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
EditApplicationModalContent.propTypes = {
|
||||||
|
advancedSettings: PropTypes.bool.isRequired,
|
||||||
|
isFetching: PropTypes.bool.isRequired,
|
||||||
|
error: PropTypes.object,
|
||||||
|
isSaving: PropTypes.bool.isRequired,
|
||||||
|
isTesting: PropTypes.bool.isRequired,
|
||||||
|
saveError: PropTypes.object,
|
||||||
|
item: PropTypes.object.isRequired,
|
||||||
|
onInputChange: PropTypes.func.isRequired,
|
||||||
|
onFieldChange: PropTypes.func.isRequired,
|
||||||
|
onModalClose: PropTypes.func.isRequired,
|
||||||
|
onSavePress: PropTypes.func.isRequired,
|
||||||
|
onTestPress: PropTypes.func.isRequired,
|
||||||
|
onDeleteApplicationPress: PropTypes.func
|
||||||
|
};
|
||||||
|
|
||||||
|
export default EditApplicationModalContent;
|
@@ -0,0 +1,88 @@
|
|||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { createSelector } from 'reselect';
|
||||||
|
import { saveApplication, setApplicationFieldValue, setApplicationValue, testApplication } from 'Store/Actions/settingsActions';
|
||||||
|
import createProviderSettingsSelector from 'Store/Selectors/createProviderSettingsSelector';
|
||||||
|
import EditApplicationModalContent from './EditApplicationModalContent';
|
||||||
|
|
||||||
|
function createMapStateToProps() {
|
||||||
|
return createSelector(
|
||||||
|
(state) => state.settings.advancedSettings,
|
||||||
|
createProviderSettingsSelector('applications'),
|
||||||
|
(advancedSettings, application) => {
|
||||||
|
return {
|
||||||
|
advancedSettings,
|
||||||
|
...application
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const mapDispatchToProps = {
|
||||||
|
setApplicationValue,
|
||||||
|
setApplicationFieldValue,
|
||||||
|
saveApplication,
|
||||||
|
testApplication
|
||||||
|
};
|
||||||
|
|
||||||
|
class EditApplicationModalContentConnector extends Component {
|
||||||
|
|
||||||
|
//
|
||||||
|
// Lifecycle
|
||||||
|
|
||||||
|
componentDidUpdate(prevProps, prevState) {
|
||||||
|
if (prevProps.isSaving && !this.props.isSaving && !this.props.saveError) {
|
||||||
|
this.props.onModalClose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Listeners
|
||||||
|
|
||||||
|
onInputChange = ({ name, value }) => {
|
||||||
|
this.props.setApplicationValue({ name, value });
|
||||||
|
}
|
||||||
|
|
||||||
|
onFieldChange = ({ name, value }) => {
|
||||||
|
this.props.setApplicationFieldValue({ name, value });
|
||||||
|
}
|
||||||
|
|
||||||
|
onSavePress = () => {
|
||||||
|
this.props.saveApplication({ id: this.props.id });
|
||||||
|
}
|
||||||
|
|
||||||
|
onTestPress = () => {
|
||||||
|
this.props.testApplication({ id: this.props.id });
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Render
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<EditApplicationModalContent
|
||||||
|
{...this.props}
|
||||||
|
onSavePress={this.onSavePress}
|
||||||
|
onTestPress={this.onTestPress}
|
||||||
|
onInputChange={this.onInputChange}
|
||||||
|
onFieldChange={this.onFieldChange}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
EditApplicationModalContentConnector.propTypes = {
|
||||||
|
id: PropTypes.number,
|
||||||
|
isFetching: PropTypes.bool.isRequired,
|
||||||
|
isSaving: PropTypes.bool.isRequired,
|
||||||
|
saveError: PropTypes.object,
|
||||||
|
item: PropTypes.object.isRequired,
|
||||||
|
setApplicationValue: PropTypes.func,
|
||||||
|
setApplicationFieldValue: PropTypes.func,
|
||||||
|
saveApplication: PropTypes.func,
|
||||||
|
testApplication: PropTypes.func,
|
||||||
|
onModalClose: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
export default connect(createMapStateToProps, mapDispatchToProps)(EditApplicationModalContentConnector);
|
115
frontend/src/Store/Actions/Settings/applications.js
Normal file
115
frontend/src/Store/Actions/Settings/applications.js
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
import { createAction } from 'redux-actions';
|
||||||
|
import createFetchHandler from 'Store/Actions/Creators/createFetchHandler';
|
||||||
|
import createFetchSchemaHandler from 'Store/Actions/Creators/createFetchSchemaHandler';
|
||||||
|
import createRemoveItemHandler from 'Store/Actions/Creators/createRemoveItemHandler';
|
||||||
|
import createSaveProviderHandler, { createCancelSaveProviderHandler } from 'Store/Actions/Creators/createSaveProviderHandler';
|
||||||
|
import createTestProviderHandler, { createCancelTestProviderHandler } from 'Store/Actions/Creators/createTestProviderHandler';
|
||||||
|
import createSetProviderFieldValueReducer from 'Store/Actions/Creators/Reducers/createSetProviderFieldValueReducer';
|
||||||
|
import createSetSettingValueReducer from 'Store/Actions/Creators/Reducers/createSetSettingValueReducer';
|
||||||
|
import { createThunk } from 'Store/thunks';
|
||||||
|
import selectProviderSchema from 'Utilities/State/selectProviderSchema';
|
||||||
|
|
||||||
|
//
|
||||||
|
// Variables
|
||||||
|
|
||||||
|
const section = 'settings.applications';
|
||||||
|
|
||||||
|
//
|
||||||
|
// Actions Types
|
||||||
|
|
||||||
|
export const FETCH_APPLICATIONS = 'settings/applications/fetchApplications';
|
||||||
|
export const FETCH_APPLICATION_SCHEMA = 'settings/applications/fetchApplicationSchema';
|
||||||
|
export const SELECT_APPLICATION_SCHEMA = 'settings/applications/selectApplicationSchema';
|
||||||
|
export const SET_APPLICATION_VALUE = 'settings/applications/setApplicationValue';
|
||||||
|
export const SET_APPLICATION_FIELD_VALUE = 'settings/applications/setApplicationFieldValue';
|
||||||
|
export const SAVE_APPLICATION = 'settings/applications/saveApplication';
|
||||||
|
export const CANCEL_SAVE_APPLICATION = 'settings/applications/cancelSaveApplication';
|
||||||
|
export const DELETE_APPLICATION = 'settings/applications/deleteApplication';
|
||||||
|
export const TEST_APPLICATION = 'settings/applications/testApplication';
|
||||||
|
export const CANCEL_TEST_APPLICATION = 'settings/applications/cancelTestApplication';
|
||||||
|
|
||||||
|
//
|
||||||
|
// Action Creators
|
||||||
|
|
||||||
|
export const fetchApplications = createThunk(FETCH_APPLICATIONS);
|
||||||
|
export const fetchApplicationSchema = createThunk(FETCH_APPLICATION_SCHEMA);
|
||||||
|
export const selectApplicationSchema = createAction(SELECT_APPLICATION_SCHEMA);
|
||||||
|
|
||||||
|
export const saveApplication = createThunk(SAVE_APPLICATION);
|
||||||
|
export const cancelSaveApplication = createThunk(CANCEL_SAVE_APPLICATION);
|
||||||
|
export const deleteApplication = createThunk(DELETE_APPLICATION);
|
||||||
|
export const testApplication = createThunk(TEST_APPLICATION);
|
||||||
|
export const cancelTestApplication = createThunk(CANCEL_TEST_APPLICATION);
|
||||||
|
|
||||||
|
export const setApplicationValue = createAction(SET_APPLICATION_VALUE, (payload) => {
|
||||||
|
return {
|
||||||
|
section,
|
||||||
|
...payload
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
export const setApplicationFieldValue = createAction(SET_APPLICATION_FIELD_VALUE, (payload) => {
|
||||||
|
return {
|
||||||
|
section,
|
||||||
|
...payload
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
//
|
||||||
|
// Details
|
||||||
|
|
||||||
|
export default {
|
||||||
|
|
||||||
|
//
|
||||||
|
// State
|
||||||
|
|
||||||
|
defaultState: {
|
||||||
|
isFetching: false,
|
||||||
|
isPopulated: false,
|
||||||
|
error: null,
|
||||||
|
isSchemaFetching: false,
|
||||||
|
isSchemaPopulated: false,
|
||||||
|
schemaError: null,
|
||||||
|
schema: [],
|
||||||
|
selectedSchema: {},
|
||||||
|
isSaving: false,
|
||||||
|
saveError: null,
|
||||||
|
isTesting: false,
|
||||||
|
items: [],
|
||||||
|
pendingChanges: {}
|
||||||
|
},
|
||||||
|
|
||||||
|
//
|
||||||
|
// Action Handlers
|
||||||
|
|
||||||
|
actionHandlers: {
|
||||||
|
[FETCH_APPLICATIONS]: createFetchHandler(section, '/applications'),
|
||||||
|
[FETCH_APPLICATION_SCHEMA]: createFetchSchemaHandler(section, '/applications/schema'),
|
||||||
|
|
||||||
|
[SAVE_APPLICATION]: createSaveProviderHandler(section, '/applications'),
|
||||||
|
[CANCEL_SAVE_APPLICATION]: createCancelSaveProviderHandler(section),
|
||||||
|
[DELETE_APPLICATION]: createRemoveItemHandler(section, '/applications'),
|
||||||
|
[TEST_APPLICATION]: createTestProviderHandler(section, '/applications'),
|
||||||
|
[CANCEL_TEST_APPLICATION]: createCancelTestProviderHandler(section)
|
||||||
|
},
|
||||||
|
|
||||||
|
//
|
||||||
|
// Reducers
|
||||||
|
|
||||||
|
reducers: {
|
||||||
|
[SET_APPLICATION_VALUE]: createSetSettingValueReducer(section),
|
||||||
|
[SET_APPLICATION_FIELD_VALUE]: createSetProviderFieldValueReducer(section),
|
||||||
|
|
||||||
|
[SELECT_APPLICATION_SCHEMA]: (state, { payload }) => {
|
||||||
|
return selectProviderSchema(state, section, payload, (selectedSchema) => {
|
||||||
|
selectedSchema.onGrab = selectedSchema.supportsOnGrab;
|
||||||
|
selectedSchema.onDownload = selectedSchema.supportsOnDownload;
|
||||||
|
selectedSchema.onUpgrade = selectedSchema.supportsOnUpgrade;
|
||||||
|
selectedSchema.onRename = selectedSchema.supportsOnRename;
|
||||||
|
|
||||||
|
return selectedSchema;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
@@ -1,6 +1,7 @@
|
|||||||
import { createAction } from 'redux-actions';
|
import { createAction } from 'redux-actions';
|
||||||
import { handleThunks } from 'Store/thunks';
|
import { handleThunks } from 'Store/thunks';
|
||||||
import createHandleActions from './Creators/createHandleActions';
|
import createHandleActions from './Creators/createHandleActions';
|
||||||
|
import applications from './Settings/applications';
|
||||||
import general from './Settings/general';
|
import general from './Settings/general';
|
||||||
import indexerFlags from './Settings/indexerFlags';
|
import indexerFlags from './Settings/indexerFlags';
|
||||||
import indexerOptions from './Settings/indexerOptions';
|
import indexerOptions from './Settings/indexerOptions';
|
||||||
@@ -13,6 +14,7 @@ export * from './Settings/indexerFlags';
|
|||||||
export * from './Settings/indexerOptions';
|
export * from './Settings/indexerOptions';
|
||||||
export * from './Settings/languages';
|
export * from './Settings/languages';
|
||||||
export * from './Settings/notifications';
|
export * from './Settings/notifications';
|
||||||
|
export * from './Settings/applications';
|
||||||
export * from './Settings/ui';
|
export * from './Settings/ui';
|
||||||
|
|
||||||
//
|
//
|
||||||
@@ -31,6 +33,7 @@ export const defaultState = {
|
|||||||
indexerOptions: indexerOptions.defaultState,
|
indexerOptions: indexerOptions.defaultState,
|
||||||
languages: languages.defaultState,
|
languages: languages.defaultState,
|
||||||
notifications: notifications.defaultState,
|
notifications: notifications.defaultState,
|
||||||
|
applications: applications.defaultState,
|
||||||
ui: ui.defaultState
|
ui: ui.defaultState
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -57,6 +60,7 @@ export const actionHandlers = handleThunks({
|
|||||||
...indexerOptions.actionHandlers,
|
...indexerOptions.actionHandlers,
|
||||||
...languages.actionHandlers,
|
...languages.actionHandlers,
|
||||||
...notifications.actionHandlers,
|
...notifications.actionHandlers,
|
||||||
|
...applications.actionHandlers,
|
||||||
...ui.actionHandlers
|
...ui.actionHandlers
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -74,6 +78,7 @@ export const reducers = createHandleActions({
|
|||||||
...indexerOptions.reducers,
|
...indexerOptions.reducers,
|
||||||
...languages.reducers,
|
...languages.reducers,
|
||||||
...notifications.reducers,
|
...notifications.reducers,
|
||||||
|
...applications.reducers,
|
||||||
...ui.reducers
|
...ui.reducers
|
||||||
|
|
||||||
}, defaultState, section);
|
}, defaultState, section);
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "prowlarr",
|
"name": "prowlarr",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"description": "Prowlarr is a Indexer/Tracker manager for with seamless integration for your favorite PVR apps",
|
"description": "Prowlarr is an Indexer/Tracker manager with seamless integration for your favorite PVR apps",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "gulp build",
|
"build": "gulp build",
|
||||||
"start": "gulp watch",
|
"start": "gulp watch",
|
||||||
|
48
src/NzbDrone.Core/Applications/ApplicationBase.cs
Normal file
48
src/NzbDrone.Core/Applications/ApplicationBase.cs
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using FluentValidation.Results;
|
||||||
|
using NzbDrone.Core.ThingiProvider;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Applications
|
||||||
|
{
|
||||||
|
public abstract class ApplicationBase<TSettings> : IApplications
|
||||||
|
where TSettings : IProviderConfig, new()
|
||||||
|
{
|
||||||
|
public abstract string Name { get; }
|
||||||
|
|
||||||
|
public Type ConfigContract => typeof(TSettings);
|
||||||
|
|
||||||
|
public virtual ProviderMessage Message => null;
|
||||||
|
|
||||||
|
public ProviderDefinition Definition { get; set; }
|
||||||
|
public abstract ValidationResult Test();
|
||||||
|
|
||||||
|
protected TSettings Settings => (TSettings)Definition.Settings;
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return GetType().Name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual IEnumerable<ProviderDefinition> DefaultDefinitions
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var config = (IProviderConfig)new TSettings();
|
||||||
|
|
||||||
|
yield return new ApplicationDefinition
|
||||||
|
{
|
||||||
|
Name = GetType().Name,
|
||||||
|
SyncLevel = ApplicationSyncLevel.AddOnly,
|
||||||
|
Implementation = GetType().Name,
|
||||||
|
Settings = config
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual object RequestAction(string action, IDictionary<string, string> query)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
11
src/NzbDrone.Core/Applications/ApplicationDefinition.cs
Normal file
11
src/NzbDrone.Core/Applications/ApplicationDefinition.cs
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
using NzbDrone.Core.ThingiProvider;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Applications
|
||||||
|
{
|
||||||
|
public class ApplicationDefinition : ProviderDefinition
|
||||||
|
{
|
||||||
|
public ApplicationSyncLevel SyncLevel { get; set; }
|
||||||
|
|
||||||
|
public override bool Enable => SyncLevel == ApplicationSyncLevel.AddOnly || SyncLevel == ApplicationSyncLevel.FullSync;
|
||||||
|
}
|
||||||
|
}
|
21
src/NzbDrone.Core/Applications/ApplicationFactory.cs
Normal file
21
src/NzbDrone.Core/Applications/ApplicationFactory.cs
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using NLog;
|
||||||
|
using NzbDrone.Common.Composition;
|
||||||
|
using NzbDrone.Core.Messaging.Events;
|
||||||
|
using NzbDrone.Core.ThingiProvider;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Applications
|
||||||
|
{
|
||||||
|
public interface IApplicationsFactory : IProviderFactory<IApplications, ApplicationDefinition>
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ApplicationFactory : ProviderFactory<IApplications, ApplicationDefinition>, IApplicationsFactory
|
||||||
|
{
|
||||||
|
public ApplicationFactory(IApplicationsRepository providerRepository, IEnumerable<IApplications> providers, IContainer container, IEventAggregator eventAggregator, Logger logger)
|
||||||
|
: base(providerRepository, providers, container, eventAggregator, logger)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
24
src/NzbDrone.Core/Applications/ApplicationRepository.cs
Normal file
24
src/NzbDrone.Core/Applications/ApplicationRepository.cs
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
using NzbDrone.Core.Datastore;
|
||||||
|
using NzbDrone.Core.Messaging.Events;
|
||||||
|
using NzbDrone.Core.ThingiProvider;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Applications
|
||||||
|
{
|
||||||
|
public interface IApplicationsRepository : IProviderRepository<ApplicationDefinition>
|
||||||
|
{
|
||||||
|
void UpdateSettings(ApplicationDefinition model);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ApplicationRepository : ProviderRepository<ApplicationDefinition>, IApplicationsRepository
|
||||||
|
{
|
||||||
|
public ApplicationRepository(IMainDatabase database, IEventAggregator eventAggregator)
|
||||||
|
: base(database, eventAggregator)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateSettings(ApplicationDefinition model)
|
||||||
|
{
|
||||||
|
SetFields(model, m => m.Settings);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
16
src/NzbDrone.Core/Applications/ApplicationService.cs
Normal file
16
src/NzbDrone.Core/Applications/ApplicationService.cs
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
using NLog;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Applications
|
||||||
|
{
|
||||||
|
public class ApplicationService
|
||||||
|
{
|
||||||
|
private readonly IApplicationsFactory _applicationsFactory;
|
||||||
|
private readonly Logger _logger;
|
||||||
|
|
||||||
|
public ApplicationService(IApplicationsFactory applicationsFactory, Logger logger)
|
||||||
|
{
|
||||||
|
_applicationsFactory = applicationsFactory;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
9
src/NzbDrone.Core/Applications/ApplicationSyncLevel.cs
Normal file
9
src/NzbDrone.Core/Applications/ApplicationSyncLevel.cs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
namespace NzbDrone.Core.Applications
|
||||||
|
{
|
||||||
|
public enum ApplicationSyncLevel
|
||||||
|
{
|
||||||
|
Disabled,
|
||||||
|
AddOnly,
|
||||||
|
FullSync
|
||||||
|
}
|
||||||
|
}
|
8
src/NzbDrone.Core/Applications/IApplications.cs
Normal file
8
src/NzbDrone.Core/Applications/IApplications.cs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
using NzbDrone.Core.ThingiProvider;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Applications
|
||||||
|
{
|
||||||
|
public interface IApplications : IProvider
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
27
src/NzbDrone.Core/Applications/Radarr/Radarr.cs
Normal file
27
src/NzbDrone.Core/Applications/Radarr/Radarr.cs
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using FluentValidation.Results;
|
||||||
|
using NzbDrone.Common.Extensions;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Applications.Radarr
|
||||||
|
{
|
||||||
|
public class Radarr : ApplicationBase<RadarrSettings>
|
||||||
|
{
|
||||||
|
public override string Name => "Radarr";
|
||||||
|
|
||||||
|
private readonly IRadarrV3Proxy _radarrV3Proxy;
|
||||||
|
|
||||||
|
public Radarr(IRadarrV3Proxy radarrV3Proxy)
|
||||||
|
{
|
||||||
|
_radarrV3Proxy = radarrV3Proxy;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override ValidationResult Test()
|
||||||
|
{
|
||||||
|
var failures = new List<ValidationFailure>();
|
||||||
|
|
||||||
|
failures.AddIfNotNull(_radarrV3Proxy.Test(Settings));
|
||||||
|
|
||||||
|
return new ValidationResult(failures);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
36
src/NzbDrone.Core/Applications/Radarr/RadarrSettings.cs
Normal file
36
src/NzbDrone.Core/Applications/Radarr/RadarrSettings.cs
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
using FluentValidation;
|
||||||
|
using NzbDrone.Core.Annotations;
|
||||||
|
using NzbDrone.Core.ThingiProvider;
|
||||||
|
using NzbDrone.Core.Validation;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Applications.Radarr
|
||||||
|
{
|
||||||
|
public class RadarrSettingsValidator : AbstractValidator<RadarrSettings>
|
||||||
|
{
|
||||||
|
public RadarrSettingsValidator()
|
||||||
|
{
|
||||||
|
RuleFor(c => c.BaseUrl).IsValidUrl();
|
||||||
|
RuleFor(c => c.ApiKey).NotEmpty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class RadarrSettings : IProviderConfig
|
||||||
|
{
|
||||||
|
private static readonly RadarrSettingsValidator Validator = new RadarrSettingsValidator();
|
||||||
|
|
||||||
|
public RadarrSettings()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
[FieldDefinition(0, Label = "Radarr Server", HelpText = "Radarr server URL, including http(s):// and port if needed")]
|
||||||
|
public string BaseUrl { get; set; }
|
||||||
|
|
||||||
|
[FieldDefinition(1, Label = "ApiKey", Privacy = PrivacyLevel.ApiKey, HelpText = "The ApiKey generated by Radarr in Settings/General")]
|
||||||
|
public string ApiKey { get; set; }
|
||||||
|
|
||||||
|
public NzbDroneValidationResult Validate()
|
||||||
|
{
|
||||||
|
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
7
src/NzbDrone.Core/Applications/Radarr/RadarrStatus.cs
Normal file
7
src/NzbDrone.Core/Applications/Radarr/RadarrStatus.cs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
namespace NzbDrone.Core.Applications.Radarr
|
||||||
|
{
|
||||||
|
public class RadarrStatus
|
||||||
|
{
|
||||||
|
public string Version { get; set; }
|
||||||
|
}
|
||||||
|
}
|
78
src/NzbDrone.Core/Applications/Radarr/RadarrV3Proxy.cs
Normal file
78
src/NzbDrone.Core/Applications/Radarr/RadarrV3Proxy.cs
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
using System;
|
||||||
|
using System.Net;
|
||||||
|
using FluentValidation.Results;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using NLog;
|
||||||
|
using NzbDrone.Common.Extensions;
|
||||||
|
using NzbDrone.Common.Http;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Applications.Radarr
|
||||||
|
{
|
||||||
|
public interface IRadarrV3Proxy
|
||||||
|
{
|
||||||
|
ValidationFailure Test(RadarrSettings settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class RadarrV3Proxy : IRadarrV3Proxy
|
||||||
|
{
|
||||||
|
private readonly IHttpClient _httpClient;
|
||||||
|
private readonly Logger _logger;
|
||||||
|
|
||||||
|
public RadarrV3Proxy(IHttpClient httpClient, Logger logger)
|
||||||
|
{
|
||||||
|
_httpClient = httpClient;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public RadarrStatus GetStatus(RadarrSettings settings)
|
||||||
|
{
|
||||||
|
return Execute<RadarrStatus>("/api/v3/system/status", settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ValidationFailure Test(RadarrSettings settings)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
GetStatus(settings);
|
||||||
|
}
|
||||||
|
catch (HttpException ex)
|
||||||
|
{
|
||||||
|
if (ex.Response.StatusCode == HttpStatusCode.Unauthorized)
|
||||||
|
{
|
||||||
|
_logger.Error(ex, "API Key is invalid");
|
||||||
|
return new ValidationFailure("ApiKey", "API Key is invalid");
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.Error(ex, "Unable to send test message");
|
||||||
|
return new ValidationFailure("ApiKey", "Unable to send test message");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.Error(ex, "Unable to send test message");
|
||||||
|
return new ValidationFailure("", "Unable to send test message");
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private TResource Execute<TResource>(string resource, RadarrSettings settings)
|
||||||
|
where TResource : new()
|
||||||
|
{
|
||||||
|
if (settings.BaseUrl.IsNullOrWhiteSpace() || settings.ApiKey.IsNullOrWhiteSpace())
|
||||||
|
{
|
||||||
|
return new TResource();
|
||||||
|
}
|
||||||
|
|
||||||
|
var baseUrl = settings.BaseUrl.TrimEnd('/');
|
||||||
|
|
||||||
|
var request = new HttpRequestBuilder(baseUrl).Resource(resource).Accept(HttpAccept.Json)
|
||||||
|
.SetHeader("X-Api-Key", settings.ApiKey).Build();
|
||||||
|
|
||||||
|
var response = _httpClient.Get(request);
|
||||||
|
|
||||||
|
var results = JsonConvert.DeserializeObject<TResource>(response.Content);
|
||||||
|
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -52,7 +52,9 @@ namespace NzbDrone.Core.Datastore.Migration
|
|||||||
.WithColumn("Name").AsString().Unique()
|
.WithColumn("Name").AsString().Unique()
|
||||||
.WithColumn("Implementation").AsString()
|
.WithColumn("Implementation").AsString()
|
||||||
.WithColumn("Settings").AsString().Nullable()
|
.WithColumn("Settings").AsString().Nullable()
|
||||||
.WithColumn("ConfigContract").AsString().Nullable();
|
.WithColumn("ConfigContract").AsString().Nullable()
|
||||||
|
.WithColumn("SyncLevel").AsInt32()
|
||||||
|
.WithColumn("Tags").AsString().Nullable();
|
||||||
|
|
||||||
Create.TableForModel("Tags")
|
Create.TableForModel("Tags")
|
||||||
.WithColumn("Label").AsString().Unique();
|
.WithColumn("Label").AsString().Unique();
|
||||||
|
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Dapper;
|
using Dapper;
|
||||||
using NzbDrone.Common.Reflection;
|
using NzbDrone.Common.Reflection;
|
||||||
|
using NzbDrone.Core.Applications;
|
||||||
using NzbDrone.Core.Authentication;
|
using NzbDrone.Core.Authentication;
|
||||||
using NzbDrone.Core.Configuration;
|
using NzbDrone.Core.Configuration;
|
||||||
using NzbDrone.Core.CustomFilters;
|
using NzbDrone.Core.CustomFilters;
|
||||||
@@ -51,6 +52,9 @@ namespace NzbDrone.Core.Datastore
|
|||||||
.Ignore(x => x.ImplementationName)
|
.Ignore(x => x.ImplementationName)
|
||||||
.Ignore(i => i.SupportsOnHealthIssue);
|
.Ignore(i => i.SupportsOnHealthIssue);
|
||||||
|
|
||||||
|
Mapper.Entity<ApplicationDefinition>("Applications").RegisterModel()
|
||||||
|
.Ignore(x => x.ImplementationName);
|
||||||
|
|
||||||
Mapper.Entity<History.History>("History").RegisterModel();
|
Mapper.Entity<History.History>("History").RegisterModel();
|
||||||
|
|
||||||
Mapper.Entity<Log>("Logs").RegisterModel();
|
Mapper.Entity<Log>("Logs").RegisterModel();
|
||||||
|
14
src/Prowlarr.Api.V1/Applications/ApplicationModule.cs
Normal file
14
src/Prowlarr.Api.V1/Applications/ApplicationModule.cs
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
using NzbDrone.Core.Applications;
|
||||||
|
|
||||||
|
namespace Prowlarr.Api.V1.Application
|
||||||
|
{
|
||||||
|
public class ApplicationModule : ProviderModuleBase<ApplicationResource, IApplications, ApplicationDefinition>
|
||||||
|
{
|
||||||
|
public static readonly ApplicationResourceMapper ResourceMapper = new ApplicationResourceMapper();
|
||||||
|
|
||||||
|
public ApplicationModule(ApplicationFactory applicationsFactory)
|
||||||
|
: base(applicationsFactory, "applications", ResourceMapper)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
41
src/Prowlarr.Api.V1/Applications/ApplicationResource.cs
Normal file
41
src/Prowlarr.Api.V1/Applications/ApplicationResource.cs
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
using NzbDrone.Core.Applications;
|
||||||
|
|
||||||
|
namespace Prowlarr.Api.V1.Application
|
||||||
|
{
|
||||||
|
public class ApplicationResource : ProviderResource
|
||||||
|
{
|
||||||
|
public ApplicationSyncLevel SyncLevel { get; set; }
|
||||||
|
public string TestCommand { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ApplicationResourceMapper : ProviderResourceMapper<ApplicationResource, ApplicationDefinition>
|
||||||
|
{
|
||||||
|
public override ApplicationResource ToResource(ApplicationDefinition definition)
|
||||||
|
{
|
||||||
|
if (definition == null)
|
||||||
|
{
|
||||||
|
return default;
|
||||||
|
}
|
||||||
|
|
||||||
|
var resource = base.ToResource(definition);
|
||||||
|
|
||||||
|
resource.SyncLevel = definition.SyncLevel;
|
||||||
|
|
||||||
|
return resource;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override ApplicationDefinition ToModel(ApplicationResource resource)
|
||||||
|
{
|
||||||
|
if (resource == null)
|
||||||
|
{
|
||||||
|
return default;
|
||||||
|
}
|
||||||
|
|
||||||
|
var definition = base.ToModel(resource);
|
||||||
|
|
||||||
|
definition.SyncLevel = resource.SyncLevel;
|
||||||
|
|
||||||
|
return definition;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user