Second UI Pass, Testing now works and other little things.

Leonardo Galli 8 years ago
parent ad26e48408
commit 76a42b28f3

@ -34,6 +34,7 @@ using NzbDrone.Core.Extras.Metadata.Files;
using NzbDrone.Core.Extras.Others;
using NzbDrone.Core.Extras.Subtitles;
using NzbDrone.Core.Messaging.Commands;
using NzbDrone.Core.NetImport;
namespace NzbDrone.Core.Datastore
{
@ -55,6 +56,10 @@ namespace NzbDrone.Core.Datastore
.Ignore(i => i.SupportsRss)
.Ignore(i => i.SupportsSearch);
Mapper.Entity<NetImportDefinition>().RegisterDefinition("NetImport")
.Ignore(i => i.Enable)
.Ignore(i => i.ConfigContract);
Mapper.Entity<NotificationDefinition>().RegisterDefinition("Notifications")
.Ignore(i => i.SupportsOnGrab)
.Ignore(i => i.SupportsOnDownload)

@ -10,6 +10,7 @@ using NzbDrone.Core.Configuration;
using NzbDrone.Core.Http.CloudFlare;
using NzbDrone.Core.Indexers.Exceptions;
using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.NetImport.Exceptions;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.ThingiProvider;
@ -230,13 +231,65 @@ namespace NzbDrone.Core.NetImport
protected override void Test(List<ValidationFailure> failures)
{
throw new NotImplementedException();
failures.AddIfNotNull(TestConnection());
}
protected virtual ValidationFailure TestConnection()
{
throw new NotImplementedException();
try
{
var parser = GetParser();
var generator = GetRequestGenerator();
var releases = FetchPage(generator.GetMovies().GetAllTiers().First().First(), parser);
if (releases.Empty())
{
return new ValidationFailure(string.Empty, "No results were returned from your list, please check your settings.");
}
}
catch (ApiKeyException)
{
_logger.Warn("List returned result for RSS URL, API Key appears to be invalid");
return new ValidationFailure("ApiKey", "Invalid API Key");
}
catch (RequestLimitReachedException)
{
_logger.Warn("Request limit reached");
}
catch (CloudFlareCaptchaException ex)
{
if (ex.IsExpired)
{
return new ValidationFailure("CaptchaToken", "CloudFlare CAPTCHA token expired, please Refresh.");
}
else
{
return new ValidationFailure("CaptchaToken", "Site protected by CloudFlare CAPTCHA. Valid CAPTCHA token required.");
}
}
catch (UnsupportedFeedException ex)
{
_logger.Warn(ex, "List feed is not supported");
return new ValidationFailure(string.Empty, "List feed is not supported: " + ex.Message);
}
catch (NetImportException ex)
{
_logger.Warn(ex, "Unable to connect to list");
return new ValidationFailure(string.Empty, "Unable to connect to indexer. " + ex.Message);
}
catch (Exception ex)
{
_logger.Warn(ex, "Unable to connect to list");
return new ValidationFailure(string.Empty, "Unable to connect to list, check the log for more details");
}
return null;
}
}
}

@ -27,7 +27,7 @@ namespace NzbDrone.Core.NetImport.RSSImport
get
{
var config = (RSSImportSettings)new RSSImportSettings();
config.Link = "https://rss.imdb.com/list/YOURLISTID";
config.Link = "http://rss.imdb.com/list/YOURLISTID";
yield return new NetImportDefinition
{

@ -1,9 +0,0 @@
var ThingyAddCollectionView = require('../../ThingyAddCollectionView');
var ThingyHeaderGroupView = require('../../ThingyHeaderGroupView');
var AddItemView = require('./IndexerAddItemView');
module.exports = ThingyAddCollectionView.extend({
itemView : ThingyHeaderGroupView.extend({ itemView : AddItemView }),
itemViewContainer : '.add-indexer .items',
template : 'Settings/Indexers/Add/IndexerAddCollectionViewTemplate'
});

@ -1,18 +0,0 @@
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h3>Add Indexer</h3>
</div>
<div class="modal-body">
<div class="alert alert-info">
Radarr supports any indexer that uses the Newznab standard, as well as other indexers listed below.<br/>
For more information on the individual indexers, click on the info buttons.
</div>
<div class="add-indexer add-thingies">
<ul class="items"></ul>
</div>
</div>
<div class="modal-footer">
<button class="btn" data-dismiss="modal">Close</button>
</div>
</div>

@ -1,52 +0,0 @@
var _ = require('underscore');
var $ = require('jquery');
var AppLayout = require('../../../AppLayout');
var Marionette = require('marionette');
var EditView = require('../Edit/IndexerEditView');
module.exports = Marionette.ItemView.extend({
template : 'Settings/Indexers/Add/IndexerAddItemViewTemplate',
tagName : 'li',
className : 'add-thingy-item',
events : {
'click .x-preset' : '_addPreset',
'click' : '_add'
},
initialize : function(options) {
this.targetCollection = options.targetCollection;
},
_addPreset : function(e) {
var presetName = $(e.target).closest('.x-preset').attr('data-id');
var presetData = _.where(this.model.get('presets'), { name : presetName })[0];
this.model.set(presetData);
this._openEdit();
},
_add : function(e) {
if ($(e.target).closest('.btn,.btn-group').length !== 0 && $(e.target).closest('.x-custom').length === 0) {
return;
}
this._openEdit();
},
_openEdit : function() {
this.model.set({
id : undefined,
enableRss : this.model.get('supportsRss'),
enableSearch : this.model.get('supportsSearch')
});
var editView = new EditView({
model : this.model,
targetCollection : this.targetCollection
});
AppLayout.modalRegion.show(editView);
}
});

@ -1,30 +0,0 @@
<div class="add-thingy">
<div>
{{implementationName}}
</div>
<div class="pull-right">
{{#if_gt presets.length compare=0}}
<button class="btn btn-xs btn-default x-custom">
Custom
</button>
<div class="btn-group">
<button class="btn btn-xs btn-default dropdown-toggle" data-toggle="dropdown">
Presets
<span class="caret"></span>
</button>
<ul class="dropdown-menu">
{{#each presets}}
<li class="x-preset" data-id="{{name}}">
<a>{{name}}</a>
</li>
{{/each}}
</ul>
</div>
{{/if_gt}}
{{#if infoLink}}
<a class="btn btn-xs btn-default x-info" href="{{infoLink}}">
<i class="icon-sonarr-form-info"/>
</a>
{{/if}}
</div>
</div>

@ -0,0 +1,9 @@
var ThingyAddCollectionView = require('../../ThingyAddCollectionView');
var ThingyHeaderGroupView = require('../../ThingyHeaderGroupView');
var AddItemView = require('./NetImportAddItemView');
module.exports = ThingyAddCollectionView.extend({
itemView : ThingyHeaderGroupView.extend({ itemView : AddItemView }),
itemViewContainer : '.add-indexer .items',
template : 'Settings/NetImport/Add/NetImportAddCollectionViewTemplate'
});

@ -0,0 +1,18 @@
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h3>Add List</h3>
</div>
<div class="modal-body">
<div class="alert alert-info">
Radarr supports any RSS movie lists as well as the one stated below.<br/>
For more information on the individual lists, click on the info buttons.
</div>
<div class="add-indexer add-thingies">
<ul class="items"></ul>
</div>
</div>
<div class="modal-footer">
<button class="btn" data-dismiss="modal">Close</button>
</div>
</div>

@ -0,0 +1,52 @@
var _ = require('underscore');
var $ = require('jquery');
var AppLayout = require('../../../AppLayout');
var Marionette = require('marionette');
var EditView = require('../Edit/NetImportEditView');
module.exports = Marionette.ItemView.extend({
template : 'Settings/NetImport/Add/NetImportAddItemViewTemplate',
tagName : 'li',
className : 'add-thingy-item',
events : {
'click .x-preset' : '_addPreset',
'click' : '_add'
},
initialize : function(options) {
this.targetCollection = options.targetCollection;
},
_addPreset : function(e) {
var presetName = $(e.target).closest('.x-preset').attr('data-id');
var presetData = _.where(this.model.get('presets'), { name : presetName })[0];
this.model.set(presetData);
this._openEdit();
},
_add : function(e) {
if ($(e.target).closest('.btn,.btn-group').length !== 0 && $(e.target).closest('.x-custom').length === 0) {
return;
}
this._openEdit();
},
_openEdit : function() {
this.model.set({
id : undefined,
enableRss : this.model.get('supportsRss'),
enableSearch : this.model.get('supportsSearch')
});
var editView = new EditView({
model : this.model,
targetCollection : this.targetCollection
});
AppLayout.modalRegion.show(editView);
}
});

@ -0,0 +1,30 @@
<div class="add-thingy">
<div>
{{implementationName}}
</div>
<div class="pull-right">
{{#if_gt presets.length compare=0}}
<button class="btn btn-xs btn-default x-custom">
Custom
</button>
<div class="btn-group">
<button class="btn btn-xs btn-default dropdown-toggle" data-toggle="dropdown">
Presets
<span class="caret"></span>
</button>
<ul class="dropdown-menu">
{{#each presets}}
<li class="x-preset" data-id="{{name}}">
<a>{{name}}</a>
</li>
{{/each}}
</ul>
</div>
{{/if_gt}}
{{#if infoLink}}
<a class="btn btn-xs btn-default x-info" href="{{infoLink}}">
<i class="icon-sonarr-form-info"/>
</a>
{{/if}}
</div>
</div>

@ -2,7 +2,7 @@ var _ = require('underscore');
var AppLayout = require('../../../AppLayout');
var Backbone = require('backbone');
var SchemaCollection = require('../NetImportCollection');
var AddCollectionView = require('./IndexerAddCollectionView');
var AddCollectionView = require('./NetImportAddCollectionView');
module.exports = {
open : function(collection) {

@ -1,122 +0,0 @@
var _ = require('underscore');
var $ = require('jquery');
var vent = require('vent');
var Marionette = require('marionette');
var DeleteView = require('../Delete/IndexerDeleteView');
var AsModelBoundView = require('../../../Mixins/AsModelBoundView');
var AsValidatedView = require('../../../Mixins/AsValidatedView');
var AsEditModalView = require('../../../Mixins/AsEditModalView');
require('../../../Form/FormBuilder');
require('../../../Mixins/AutoComplete');
require('bootstrap');
var view = Marionette.ItemView.extend({
template : 'Settings/Indexers/Edit/IndexerEditViewTemplate',
events : {
'click .x-back' : '_back',
'click .x-captcha-refresh' : '_onRefreshCaptcha'
},
_deleteView : DeleteView,
initialize : function(options) {
this.targetCollection = options.targetCollection;
},
_onAfterSave : function() {
this.targetCollection.add(this.model, { merge : true });
vent.trigger(vent.Commands.CloseModalCommand);
},
_onAfterSaveAndAdd : function() {
this.targetCollection.add(this.model, { merge : true });
require('../Add/IndexerSchemaModal').open(this.targetCollection);
},
_back : function() {
if (this.model.isNew()) {
this.model.destroy();
}
require('../Add/IndexerSchemaModal').open(this.targetCollection);
},
_onRefreshCaptcha : function(event) {
var self = this;
var target = $(event.target).parents('.input-group');
this.ui.indicator.show();
this.model.requestAction("checkCaptcha")
.then(function(result) {
if (!result.captchaRequest) {
self.model.setFieldValue('CaptchaToken', '');
return result;
}
return self._showCaptcha(target, result.captchaRequest);
})
.always(function() {
self.ui.indicator.hide();
});
},
_showCaptcha : function(target, captchaRequest) {
var self = this;
var widget = $('<div class="g-recaptcha"></div>').insertAfter(target);
return this._loadRecaptchaWidget(widget[0], captchaRequest.siteKey, captchaRequest.secretToken)
.then(function(captchaResponse) {
target.parents('.form-group').removeAllErrors();
widget.remove();
var queryParams = {
responseUrl : captchaRequest.responseUrl,
ray : captchaRequest.ray,
captchaResponse: captchaResponse
};
return self.model.requestAction("getCaptchaCookie", queryParams);
})
.then(function(response) {
self.model.setFieldValue('CaptchaToken', response.captchaToken);
});
},
_loadRecaptchaWidget : function(widget, sitekey, stoken) {
var promise = $.Deferred();
var renderWidget = function() {
window.grecaptcha.render(widget, {
'sitekey' : sitekey,
'stoken' : stoken,
'callback' : promise.resolve
});
};
if (window.grecaptcha) {
renderWidget();
} else {
window.grecaptchaLoadCallback = function() {
delete window.grecaptchaLoadCallback;
renderWidget();
};
$.getScript('https://www.google.com/recaptcha/api.js?onload=grecaptchaLoadCallback&render=explicit')
.fail(function() { promise.reject(); });
}
return promise;
}
});
AsModelBoundView.call(view);
AsValidatedView.call(view);
AsEditModalView.call(view);
module.exports = view;

@ -1,92 +0,0 @@
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" aria-hidden="true" data-dismiss="modal">&times;</button>
{{#if id}}
<h3>Edit - {{implementationName}}</h3>
{{else}}
<h3>Add - {{implementationName}}</h3>
{{/if}}
</div>
<div class="modal-body indexer-modal">
<div class="form-horizontal">
<div class="form-group">
<label class="col-sm-3 control-label">Name</label>
<div class="col-sm-5">
<input type="text" name="name" class="form-control"/>
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label">Enable RSS Sync</label>
<div class="col-sm-5">
<div class="input-group">
<label class="checkbox toggle well">
<input type="checkbox" name="enableRss" {{#unless supportsRss}}disabled="disabled"{{/unless}}/>
<p>
<span>Yes</span>
<span>No</span>
</p>
<div class="btn btn-primary slide-button"/>
</label>
{{#unless supportsRss}}
<span class="help-inline-checkbox">
<i class="icon-sonarr-form-warning" title="" data-original-title="RSS is not supported with this indexer"></i>
</span>
{{/unless}}
</div>
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label">Enable Search</label>
<div class="col-sm-5">
<div class="input-group">
<label class="checkbox toggle well">
<input type="checkbox" name="enableSearch" {{#unless supportsSearch}}disabled="disabled"{{/unless}}/>
<p>
<span>Yes</span>
<span>No</span>
</p>
<div class="btn btn-primary slide-button"/>
</label>
{{#unless supportsSearch}}
<span class="help-inline-checkbox">
<i class="icon-sonarr-form-warning" title="" data-original-title="Search is not supported with this indexer"></i>
</span>
{{/unless}}
</div>
</div>
</div>
{{formBuilder}}
</div>
</div>
<div class="modal-footer">
{{#if id}}
<button class="btn btn-danger pull-left x-delete">Delete</button>
{{else}}
<button class="btn pull-left x-back">Back</button>
{{/if}}
<span class="indicator x-indicator"><i class="icon-sonarr-spinner fa-spin"></i></span>
<button class="btn x-test">test <i class="x-test-icon icon-sonarr-test"/></button>
<button class="btn" data-dismiss="modal">Cancel</button>
<div class="btn-group">
<button class="btn btn-primary x-save">Save</button>
<button class="btn btn-icon-only btn-primary dropdown-toggle" data-toggle="dropdown">
<span class="caret"></span>
</button>
<ul class="dropdown-menu">
<li class="save-and-add x-save-and-add">
save and add
</li>
</ul>
</div>
</div>
</div>

@ -0,0 +1,122 @@
var _ = require('underscore');
var $ = require('jquery');
var vent = require('vent');
var Marionette = require('marionette');
var DeleteView = require('../Delete/IndexerDeleteView');
var AsModelBoundView = require('../../../Mixins/AsModelBoundView');
var AsValidatedView = require('../../../Mixins/AsValidatedView');
var AsEditModalView = require('../../../Mixins/AsEditModalView');
require('../../../Form/FormBuilder');
require('../../../Mixins/AutoComplete');
require('bootstrap');
var view = Marionette.ItemView.extend({
template : 'Settings/NetImport/Edit/NetImportEditViewTemplate',
events : {
'click .x-back' : '_back',
'click .x-captcha-refresh' : '_onRefreshCaptcha'
},
_deleteView : DeleteView,
initialize : function(options) {
this.targetCollection = options.targetCollection;
},
_onAfterSave : function() {
this.targetCollection.add(this.model, { merge : true });
vent.trigger(vent.Commands.CloseModalCommand);
},
_onAfterSaveAndAdd : function() {
this.targetCollection.add(this.model, { merge : true });
require('../Add/NetImportSchemaModal').open(this.targetCollection);
},
_back : function() {
if (this.model.isNew()) {
this.model.destroy();
}
require('../Add/NetImportSchemaModal').open(this.targetCollection);
},
_onRefreshCaptcha : function(event) {
var self = this;
var target = $(event.target).parents('.input-group');
this.ui.indicator.show();
this.model.requestAction("checkCaptcha")
.then(function(result) {
if (!result.captchaRequest) {
self.model.setFieldValue('CaptchaToken', '');
return result;
}
return self._showCaptcha(target, result.captchaRequest);
})
.always(function() {
self.ui.indicator.hide();
});
},
_showCaptcha : function(target, captchaRequest) {
var self = this;
var widget = $('<div class="g-recaptcha"></div>').insertAfter(target);
return this._loadRecaptchaWidget(widget[0], captchaRequest.siteKey, captchaRequest.secretToken)
.then(function(captchaResponse) {
target.parents('.form-group').removeAllErrors();
widget.remove();
var queryParams = {
responseUrl : captchaRequest.responseUrl,
ray : captchaRequest.ray,
captchaResponse: captchaResponse
};
return self.model.requestAction("getCaptchaCookie", queryParams);
})
.then(function(response) {
self.model.setFieldValue('CaptchaToken', response.captchaToken);
});
},
_loadRecaptchaWidget : function(widget, sitekey, stoken) {
var promise = $.Deferred();
var renderWidget = function() {
window.grecaptcha.render(widget, {
'sitekey' : sitekey,
'stoken' : stoken,
'callback' : promise.resolve
});
};
if (window.grecaptcha) {
renderWidget();
} else {
window.grecaptchaLoadCallback = function() {
delete window.grecaptchaLoadCallback;
renderWidget();
};
$.getScript('https://www.google.com/recaptcha/api.js?onload=grecaptchaLoadCallback&render=explicit')
.fail(function() { promise.reject(); });
}
return promise;
}
});
AsModelBoundView.call(view);
AsValidatedView.call(view);
AsEditModalView.call(view);
module.exports = view;

@ -0,0 +1,67 @@
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" aria-hidden="true" data-dismiss="modal">&times;</button>
{{#if id}}
<h3>Edit - {{implementationName}}</h3>
{{else}}
<h3>Add - {{implementationName}}</h3>
{{/if}}
</div>
<div class="modal-body indexer-modal">
<div class="form-horizontal">
<div class="form-group">
<label class="col-sm-3 control-label">Name</label>
<div class="col-sm-5">
<input type="text" name="name" class="form-control"/>
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label">Enable Automatic Sync</label>
<div class="col-sm-5">
<div class="input-group">
<label class="checkbox toggle well">
<input type="checkbox" name="enableAutomatic" />
<p>
<span>Yes</span>
<span>No</span>
</p>
<div class="btn btn-primary slide-button"/>
</label>
<span class="help-inline-checkbox">
<i class="icon-sonarr-form-warning" title="" data-original-title="New movies found by this list are automatically added to your collection."></i>
</span>
</div>
</div>
</div>
{{formBuilder}}
</div>
</div>
<div class="modal-footer">
{{#if id}}
<button class="btn btn-danger pull-left x-delete">Delete</button>
{{else}}
<button class="btn pull-left x-back">Back</button>
{{/if}}
<span class="indicator x-indicator"><i class="icon-sonarr-spinner fa-spin"></i></span>
<button class="btn x-test">test <i class="x-test-icon icon-sonarr-test"/></button>
<button class="btn" data-dismiss="modal">Cancel</button>
<div class="btn-group">
<button class="btn btn-primary x-save">Save</button>
<button class="btn btn-icon-only btn-primary dropdown-toggle" data-toggle="dropdown">
<span class="caret"></span>
</button>
<ul class="dropdown-menu">
<li class="save-and-add x-save-and-add">
save and add
</li>
</ul>
</div>
</div>
</div>

@ -1,6 +1,6 @@
var Marionette = require('marionette');
var ItemView = require('./NetImportItemView');
var SchemaModal = require('./Add/IndexerSchemaModal');
var SchemaModal = require('./Add/NetImportSchemaModal');
module.exports = Marionette.CompositeView.extend({
itemView : ItemView,

@ -1,6 +1,6 @@
var AppLayout = require('../../AppLayout');
var Marionette = require('marionette');
var EditView = require('./Edit/IndexerEditView');
var EditView = require('./Edit/NetImportEditView');
module.exports = Marionette.ItemView.extend({
template : 'Settings/NetImport/NetImportItemViewTemplate',

@ -1,7 +1,7 @@
var Marionette = require('marionette');
var NetImportCollection = require('./NetImportCollection');
var CollectionView = require('./NetImportCollectionView');
var OptionsView = require('./Options/IndexerOptionsView');
var OptionsView = require('./Options/NetImportOptionsView');
module.exports = Marionette.Layout.extend({
template : 'Settings/NetImport/NetImportLayoutTemplate',

@ -1,40 +0,0 @@
<fieldset>
<legend>Options</legend>
<div class="form-group">
<label class="col-sm-3 control-label">Minimum Age</label>
<div class="col-sm-1 col-sm-push-2 help-inline">
<i class="icon-sonarr-form-info" title="Usenet only: Minimum age in minutes of NZBs before they are grabbed. Use this to give new releases time to propagate to your usenet provider."/>
</div>
<div class="col-sm-2 col-sm-pull-1">
<input type="number" min="0" name="minimumAge" class="form-control"/>
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label">Retention</label>
<div class="col-sm-1 col-sm-push-2 help-inline">
<i class="icon-sonarr-form-info" title="Usenet only: Set to zero to set to unlimited"/>
</div>
<div class="col-sm-2 col-sm-pull-1">
<input type="number" min="0" name="retention" class="form-control"/>
</div>
</div>
<div class="form-group advanced-setting">
<label class="col-sm-3 control-label">RSS Sync Interval</label>
<div class="col-sm-1 col-sm-push-2 help-inline">
<i class="icon-sonarr-form-warning" title="This will apply to all indexers, please follow the rules set forth by them"/>
<i class="icon-sonarr-form-info" title="Interval in minutes. Set to zero to disable (this will stop all automatic release grabbing)"/>
</div>
<div class="col-sm-2 col-sm-pull-1">
<input type="number" name="rssSyncInterval" class="form-control" min="0" max="120"/>
</div>
</div>
</fieldset>

@ -3,7 +3,7 @@ var AsModelBoundView = require('../../../Mixins/AsModelBoundView');
var AsValidatedView = require('../../../Mixins/AsValidatedView');
var view = Marionette.ItemView.extend({
template : 'Settings/Indexers/Options/IndexerOptionsViewTemplate'
template : 'Settings/NetImport/Options/NetImportOptionsViewTemplate'
});
AsModelBoundView.call(view);

@ -0,0 +1,16 @@
<fieldset>
<legend>Options</legend>
<div class="form-group">
<label class="col-sm-3 control-label">List Update Interval</label>
<div class="col-sm-1 col-sm-push-2 help-inline">
<i class="icon-sonarr-form-warning" title="This will apply to all lists, please follow the rules set forth by them."/>
<i class="icon-sonarr-form-info" title="Interval in minutes."/>
</div>
<div class="col-sm-2 col-sm-pull-1">
<input type="number" name="rssSyncInterval" class="form-control" min="0" max="1440"/>
</div>
</div>
</fieldset>
Loading…
Cancel
Save