Basis of UI Update.

pull/2/head
Leonardo Galli 8 years ago
parent 2c52795822
commit 463d85e479

@ -0,0 +1,20 @@
using FluentValidation;
using NzbDrone.Api.Validation;
using NzbDrone.Core.Configuration;
namespace NzbDrone.Api.Config
{
public class NetImportConfigModule : NzbDroneConfigModule<NetImportConfigResource>
{
public NetImportConfigModule(IConfigService configService)
: base(configService)
{
}
protected override NetImportConfigResource ToResource(IConfigService model)
{
return NetImportConfigResourceMapper.ToResource(model);
}
}
}

@ -0,0 +1,19 @@
using NzbDrone.Api.REST;
using NzbDrone.Core.Configuration;
namespace NzbDrone.Api.Config
{
public class NetImportConfigResource : RestResource
{
}
public static class NetImportConfigResourceMapper
{
public static NetImportConfigResource ToResource(IConfigService model)
{
return new NetImportConfigResource
{
};
}
}
}

@ -5,7 +5,7 @@ namespace NzbDrone.Api.NetImport
public class NetImportModule : ProviderModuleBase<NetImportResource, INetImport, NetImportDefinition>
{
public NetImportModule(NetImportFactory indexerFactory)
: base(indexerFactory, "indexer")
: base(indexerFactory, "netimport")
{
}

@ -109,6 +109,8 @@
<Compile Include="ClientSchema\SelectOption.cs" />
<Compile Include="Commands\CommandModule.cs" />
<Compile Include="Commands\CommandResource.cs" />
<Compile Include="Config\NetImportConfigModule.cs" />
<Compile Include="Config\NetImportConfigResource.cs" />
<Compile Include="Extensions\AccessControlHeaders.cs" />
<Compile Include="Extensions\Pipelines\CorsPipeline.cs" />
<Compile Include="Extensions\Pipelines\RequestLoggingPipeline.cs" />
@ -121,6 +123,8 @@
<Compile Include="Movies\RenameMovieModule.cs" />
<Compile Include="Movies\RenameMovieResource.cs" />
<Compile Include="Movies\MovieEditorModule.cs" />
<Compile Include="NetImport\NetImportModule.cs" />
<Compile Include="NetImport\NetImportResource.cs" />
<Compile Include="Parse\ParseModule.cs" />
<Compile Include="Parse\ParseResource.cs" />
<Compile Include="ManualImport\ManualImportModule.cs" />

@ -125,6 +125,7 @@
<Compile Include="Datastore\Migration\119_create_netimport_table.cs" />
<Compile Include="NetImport\Exceptions\NetImportException.cs" />
<Compile Include="NetImport\HttpNetImportBase.cs" />
<Compile Include="NetImport\NetImportFactory.cs" />
<Compile Include="NetImport\IProcessNetImportResponse.cs" />
<Compile Include="NetImport\NetImportBaseSettings.cs" />
<Compile Include="NetImport\NetImportPageableRequest.cs" />

@ -0,0 +1,9 @@
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'
});

@ -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 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>

@ -0,0 +1,52 @@
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);
}
});

@ -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>

@ -0,0 +1,39 @@
var _ = require('underscore');
var AppLayout = require('../../../AppLayout');
var Backbone = require('backbone');
var SchemaCollection = require('../IndexerCollection');
var AddCollectionView = require('./IndexerAddCollectionView');
module.exports = {
open : function(collection) {
var schemaCollection = new SchemaCollection();
var originalUrl = schemaCollection.url;
schemaCollection.url = schemaCollection.url + '/schema';
schemaCollection.fetch();
schemaCollection.url = originalUrl;
var groupedSchemaCollection = new Backbone.Collection();
schemaCollection.on('sync', function() {
var groups = schemaCollection.groupBy(function(model, iterator) {
return model.get('protocol');
});
var modelCollection = _.map(groups, function(values, key, list) {
return {
"header" : key,
collection : values
};
});
groupedSchemaCollection.reset(modelCollection);
});
var view = new AddCollectionView({
collection : groupedSchemaCollection,
targetCollection : collection
});
AppLayout.modalRegion.show(view);
}
};

@ -0,0 +1,19 @@
var vent = require('vent');
var Marionette = require('marionette');
module.exports = Marionette.ItemView.extend({
template : 'Settings/Indexers/Delete/IndexerDeleteViewTemplate',
events : {
'click .x-confirm-delete' : '_delete'
},
_delete : function() {
this.model.destroy({
wait : true,
success : function() {
vent.trigger(vent.Commands.CloseModalCommand);
}
});
}
});

@ -0,0 +1,13 @@
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h3>Delete Indexer</h3>
</div>
<div class="modal-body">
<p>Are you sure you want to delete '{{name}}'?</p>
</div>
<div class="modal-footer">
<button class="btn" data-dismiss="modal">Cancel</button>
<button class="btn btn-danger x-confirm-delete">Delete</button>
</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/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;

@ -0,0 +1,92 @@
<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,25 @@
var Marionette = require('marionette');
var ItemView = require('./IndexerItemView');
var SchemaModal = require('./Add/IndexerSchemaModal');
module.exports = Marionette.CompositeView.extend({
itemView : ItemView,
itemViewContainer : '.indexer-list',
template : 'Settings/Indexers/IndexerCollectionViewTemplate',
ui : {
'addCard' : '.x-add-card'
},
events : {
'click .x-add-card' : '_openSchemaModal'
},
appendHtml : function(collectionView, itemView, index) {
collectionView.ui.addCard.parent('li').before(itemView.el);
},
_openSchemaModal : function() {
SchemaModal.open(this.collection);
}
});

@ -0,0 +1,16 @@
<fieldset>
<legend>Indexers</legend>
<div class="row">
<div class="col-md-12">
<ul class="indexer-list thingies">
<li>
<div class="indexer-item thingy add-card x-add-card">
<span class="center well">
<i class="icon-sonarr-add"/>
</span>
</div>
</li>
</ul>
</div>
</div>
</fieldset>

@ -0,0 +1,24 @@
var AppLayout = require('../../AppLayout');
var Marionette = require('marionette');
var EditView = require('./Edit/IndexerEditView');
module.exports = Marionette.ItemView.extend({
template : 'Settings/Indexers/IndexerItemViewTemplate',
tagName : 'li',
events : {
'click' : '_edit'
},
initialize : function() {
this.listenTo(this.model, 'sync', this.render);
},
_edit : function() {
var view = new EditView({
model : this.model,
targetCollection : this.model.collection
});
AppLayout.modalRegion.show(view);
}
});

@ -0,0 +1,27 @@
<div class="indexer-item thingy">
<div>
<h3>{{name}}</h3>
</div>
<div class="settings">
{{#if supportsRss}}
{{#if enableRss}}
<span class="label label-success">RSS</span>
{{else}}
<span class="label label-default">RSS</span>
{{/if}}
{{else}}
<span class="label label-default label-disabled">RSS</span>
{{/if}}
{{#if supportsSearch}}
{{#if enableSearch}}
<span class="label label-success">Search</span>
{{else}}
<span class="label label-default">Search</span>
{{/if}}
{{else}}
<span class="label label-default label-disabled">Search</span>
{{/if}}
</div>
</div>

@ -0,0 +1,30 @@
var Marionette = require('marionette');
var IndexerCollection = require('./IndexerCollection');
var CollectionView = require('./IndexerCollectionView');
var OptionsView = require('./Options/IndexerOptionsView');
var RestrictionCollection = require('./Restriction/RestrictionCollection');
var RestrictionCollectionView = require('./Restriction/RestrictionCollectionView');
module.exports = Marionette.Layout.extend({
template : 'Settings/Indexers/IndexerLayoutTemplate',
regions : {
indexers : '#x-indexers-region',
indexerOptions : '#x-indexer-options-region',
restriction : '#x-restriction-region'
},
initialize : function() {
this.indexersCollection = new IndexerCollection();
this.indexersCollection.fetch();
this.restrictionCollection = new RestrictionCollection();
this.restrictionCollection.fetch();
},
onShow : function() {
this.indexers.show(new CollectionView({ collection : this.indexersCollection }));
this.indexerOptions.show(new OptionsView({ model : this.model }));
this.restriction.show(new RestrictionCollectionView({ collection : this.restrictionCollection }));
}
});

@ -0,0 +1,5 @@
<div id="x-indexers-region"></div>
<div class="form-horizontal">
<div id="x-indexer-options-region"></div>
<div id="x-restriction-region"></div>
</div>

@ -0,0 +1,13 @@
var Backbone = require('backbone');
var NetImportModel = require('./NetImportModel');
module.exports = Backbone.Collection.extend({
model : NetImportModel,
url : window.NzbDrone.ApiRoot + '/netimport',
comparator : function(left, right, collection) {
var result = 0;
return result;
}
});

@ -0,0 +1,3 @@
var ProviderSettingsModelBase = require('../ProviderSettingsModelBase');
module.exports = ProviderSettingsModelBase.extend({});

@ -0,0 +1,7 @@
var SettingsModelBase = require('../SettingsModelBase');
module.exports = SettingsModelBase.extend({
url : window.NzbDrone.ApiRoot + '/config/netimport',
successMessage : 'Net Import settings saved',
errorMessage : 'Failed to save net import settings'
});

@ -0,0 +1,12 @@
var Marionette = require('marionette');
var AsModelBoundView = require('../../../Mixins/AsModelBoundView');
var AsValidatedView = require('../../../Mixins/AsValidatedView');
var view = Marionette.ItemView.extend({
template : 'Settings/Indexers/Options/IndexerOptionsViewTemplate'
});
AsModelBoundView.call(view);
AsValidatedView.call(view);
module.exports = view;

@ -0,0 +1,40 @@
<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>

@ -0,0 +1,7 @@
var Backbone = require('backbone');
var RestrictionModel = require('./RestrictionModel');
module.exports = Backbone.Collection.extend({
model : RestrictionModel,
url : window.NzbDrone.ApiRoot + '/Restriction'
});

@ -0,0 +1,26 @@
var AppLayout = require('../../../AppLayout');
var Marionette = require('marionette');
var RestrictionItemView = require('./RestrictionItemView');
var EditView = require('./RestrictionEditView');
require('../../../Tags/TagHelpers');
require('bootstrap');
module.exports = Marionette.CompositeView.extend({
template : 'Settings/Indexers/Restriction/RestrictionCollectionViewTemplate',
itemViewContainer : '.x-rows',
itemView : RestrictionItemView,
events : {
'click .x-add' : '_addMapping'
},
_addMapping : function() {
var model = this.collection.create({ tags : [] });
var view = new EditView({
model : model,
targetCollection : this.collection
});
AppLayout.modalRegion.show(view);
}
});

@ -0,0 +1,24 @@
<fieldset class="advanced-setting">
<legend>Restrictions</legend>
<div class="col-md-12">
<div class="rule-setting-list">
<div class="rule-setting-header x-header hidden-xs">
<div class="row">
<span class="col-sm-4">Must Contain</span>
<span class="col-sm-4">Must Not Contain</span>
<span class="col-sm-3">Tags</span>
</div>
</div>
<div class="rows x-rows">
</div>
<div class="rule-setting-footer">
<div class="pull-right">
<span class="add-rule-setting-mapping">
<i class="icon-sonarr-add x-add" title="Add new restriction" />
</span>
</div>
</div>
</div>
</div>
</fieldset>

@ -0,0 +1,19 @@
var vent = require('vent');
var Marionette = require('marionette');
module.exports = Marionette.ItemView.extend({
template : 'Settings/Indexers/Restriction/RestrictionDeleteViewTemplate',
events : {
'click .x-confirm-delete' : '_delete'
},
_delete : function() {
this.model.destroy({
wait : true,
success : function() {
vent.trigger(vent.Commands.CloseModalCommand);
}
});
}
});

@ -0,0 +1,13 @@
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h3>Delete Restriction</h3>
</div>
<div class="modal-body">
<p>Are you sure you want to delete this restriction?</p>
</div>
<div class="modal-footer">
<button class="btn" data-dismiss="modal">Cancel</button>
<button class="btn btn-danger x-confirm-delete">Delete</button>
</div>
</div>

@ -0,0 +1,55 @@
var _ = require('underscore');
var vent = require('vent');
var AppLayout = require('../../../AppLayout');
var Marionette = require('marionette');
var DeleteView = require('./RestrictionDeleteView');
var CommandController = require('../../../Commands/CommandController');
var AsModelBoundView = require('../../../Mixins/AsModelBoundView');
var AsValidatedView = require('../../../Mixins/AsValidatedView');
var AsEditModalView = require('../../../Mixins/AsEditModalView');
require('../../../Mixins/TagInput');
require('bootstrap');
require('bootstrap.tagsinput');
var view = Marionette.ItemView.extend({
template : 'Settings/Indexers/Restriction/RestrictionEditViewTemplate',
ui : {
required : '.x-required',
ignored : '.x-ignored',
tags : '.x-tags'
},
_deleteView : DeleteView,
initialize : function(options) {
this.targetCollection = options.targetCollection;
},
onRender : function() {
this.ui.required.tagsinput({
trimValue : true,
tagClass : 'label label-success'
});
this.ui.ignored.tagsinput({
trimValue : true,
tagClass : 'label label-danger'
});
this.ui.tags.tagInput({
model : this.model,
property : 'tags'
});
},
_onAfterSave : function() {
this.targetCollection.add(this.model, { merge : true });
vent.trigger(vent.Commands.CloseModalCommand);
}
});
AsModelBoundView.call(view);
AsValidatedView.call(view);
AsEditModalView.call(view);
module.exports = view;

@ -0,0 +1,60 @@
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
{{#if id}}
<h3>Edit Restriction</h3>
{{else}}
<h3>Add Restriction</h3>
{{/if}}
</div>
<div class="modal-body remotepath-mapping-modal">
<div class="form-horizontal">
<div class="form-group">
<label class="col-sm-3 control-label">Must contain</label>
<div class="col-sm-1 col-sm-push-5 help-inline">
<i class="icon-sonarr-form-info" title="The release must contain at least one of these terms (case insensitive)" />
</div>
<div class="col-sm-5 col-sm-pull-1">
<input type="text" name="required" class="form-control x-required"/>
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label">Must not contain</label>
<div class="col-sm-1 col-sm-push-5 help-inline">
<i class="icon-sonarr-form-info" title="The release will be rejected if it contains one or more of terms (case insensitive)" />
</div>
<div class="col-sm-5 col-sm-pull-1">
<input type="text" name="ignored" class="form-control x-ignored"/>
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label">Tags</label>
<div class="col-sm-1 col-sm-push-5 help-inline">
<i class="icon-sonarr-form-info" title="Restrictions will apply to series with one or more matching tags. Leave blank to apply to all series" />
</div>
<div class="col-sm-5 col-sm-pull-1">
<input type="text" class="form-control x-tags">
</div>
</div>
</div>
</div>
<div class="modal-footer">
{{#if id}}
<button class="btn btn-danger pull-left x-delete">Delete</button>
{{/if}}
<button class="btn" data-dismiss="modal">Cancel</button>
<div class="btn-group">
<button class="btn btn-primary x-save">Save</button>
</div>
</div>
</div>

@ -0,0 +1,28 @@
var AppLayout = require('../../../AppLayout');
var Marionette = require('marionette');
var EditView = require('./RestrictionEditView');
module.exports = Marionette.ItemView.extend({
template : 'Settings/Indexers/Restriction/RestrictionItemViewTemplate',
className : 'row',
ui : {
tags : '.x-tags'
},
events : {
'click .x-edit' : '_edit'
},
initialize : function() {
this.listenTo(this.model, 'sync', this.render);
},
_edit : function() {
var view = new EditView({
model : this.model,
targetCollection : this.model.collection
});
AppLayout.modalRegion.show(view);
}
});

@ -0,0 +1,12 @@
<div class="col-sm-4">
{{genericTagDisplay required 'label label-success'}}
</div>
<div class="col-sm-4">
{{genericTagDisplay ignored 'label label-danger'}}
</div>
<div class="col-sm-3">
{{tagDisplay tags}}
</div>
<div class="col-sm-1">
<div class="pull-right"><i class="icon-sonarr-edit x-edit" title="" data-original-title="Edit"></i></div>
</div>

@ -0,0 +1,4 @@
var $ = require('jquery');
var DeepModel = require('backbone.deepmodel');
module.exports = DeepModel.extend({});

@ -0,0 +1,33 @@
@import "../../Shared/Styles/clickable.less";
.indexer-list {
li {
display: inline-block;
vertical-align: top;
}
}
.indexer-item {
.clickable;
width: 290px;
height: 90px;
padding: 10px 15px;
&.add-card {
.center {
margin-top: -3px;
}
}
}
.modal-overflow {
overflow-y: visible;
}
.add-indexer {
li.add-thingy-item {
width: 33%;
}
}

@ -12,6 +12,7 @@ var QualityLayout = require('./Quality/QualityLayout');
var IndexerLayout = require('./Indexers/IndexerLayout');
var IndexerCollection = require('./Indexers/IndexerCollection');
var IndexerSettingsModel = require('./Indexers/IndexerSettingsModel');
var NetImportSettingsModel = require("./NetImport/NetImportSettingsModel");
var DownloadClientLayout = require('./DownloadClient/DownloadClientLayout');
var DownloadClientSettingsModel = require('./DownloadClient/DownloadClientSettingsModel');
var NotificationCollectionView = require('./Notifications/NotificationCollectionView');
@ -24,229 +25,245 @@ var LoadingView = require('../Shared/LoadingView');
var Config = require('../Config');
module.exports = Marionette.Layout.extend({
template : 'Settings/SettingsLayoutTemplate',
regions : {
mediaManagement : '#media-management',
profiles : '#profiles',
quality : '#quality',
indexers : '#indexers',
downloadClient : '#download-client',
notifications : '#notifications',
metadata : '#metadata',
general : '#general',
uiRegion : '#ui',
loading : '#loading-region'
},
ui : {
mediaManagementTab : '.x-media-management-tab',
profilesTab : '.x-profiles-tab',
qualityTab : '.x-quality-tab',
indexersTab : '.x-indexers-tab',
downloadClientTab : '.x-download-client-tab',
notificationsTab : '.x-notifications-tab',
metadataTab : '.x-metadata-tab',
generalTab : '.x-general-tab',
uiTab : '.x-ui-tab',
advancedSettings : '.x-advanced-settings'
},
events : {
'click .x-media-management-tab' : '_showMediaManagement',
'click .x-profiles-tab' : '_showProfiles',
'click .x-quality-tab' : '_showQuality',
'click .x-indexers-tab' : '_showIndexers',
'click .x-download-client-tab' : '_showDownloadClient',
'click .x-notifications-tab' : '_showNotifications',
'click .x-metadata-tab' : '_showMetadata',
'click .x-general-tab' : '_showGeneral',
'click .x-ui-tab' : '_showUi',
'click .x-save-settings' : '_save',
'change .x-advanced-settings' : '_toggleAdvancedSettings'
},
initialize : function(options) {
if (options.action) {
this.action = options.action.toLowerCase();
}
this.listenTo(vent, vent.Hotkeys.SaveSettings, this._save);
},
onRender : function() {
this.loading.show(new LoadingView());
var self = this;
this.mediaManagementSettings = new MediaManagementSettingsModel();
this.namingSettings = new NamingModel();
this.indexerSettings = new IndexerSettingsModel();
this.downloadClientSettings = new DownloadClientSettingsModel();
this.notificationCollection = new NotificationCollection();
this.generalSettings = new GeneralSettingsModel();
this.uiSettings = new UiSettingsModel();
Backbone.$.when(this.mediaManagementSettings.fetch(), this.namingSettings.fetch(), this.indexerSettings.fetch(), this.downloadClientSettings.fetch(),
this.notificationCollection.fetch(), this.generalSettings.fetch(), this.uiSettings.fetch()).done(function() {
if (!self.isClosed) {
self.loading.$el.hide();
self.mediaManagement.show(new MediaManagementLayout({
settings : self.mediaManagementSettings,
namingSettings : self.namingSettings
}));
self.profiles.show(new ProfileLayout());
self.quality.show(new QualityLayout());
self.indexers.show(new IndexerLayout({ model : self.indexerSettings }));
self.downloadClient.show(new DownloadClientLayout({ model : self.downloadClientSettings }));
self.notifications.show(new NotificationCollectionView({ collection : self.notificationCollection }));
self.metadata.show(new MetadataLayout());
self.general.show(new GeneralView({ model : self.generalSettings }));
self.uiRegion.show(new UiView({ model : self.uiSettings }));
}
});
this._setAdvancedSettingsState();
},
onShow : function() {
switch (this.action) {
case 'profiles':
this._showProfiles();
break;
case 'quality':
this._showQuality();
break;
case 'indexers':
this._showIndexers();
break;
case 'downloadclient':
this._showDownloadClient();
break;
case 'connect':
this._showNotifications();
break;
case 'notifications':
this._showNotifications();
break;
case 'metadata':
this._showMetadata();
break;
case 'general':
this._showGeneral();
break;
case 'ui':
this._showUi();
break;
default:
this._showMediaManagement();
}
},
_showMediaManagement : function(e) {
if (e) {
e.preventDefault();
}
this.ui.mediaManagementTab.tab('show');
this._navigate('settings/mediamanagement');
},
_showProfiles : function(e) {
if (e) {
e.preventDefault();
}
this.ui.profilesTab.tab('show');
this._navigate('settings/profiles');
},
_showQuality : function(e) {
if (e) {
e.preventDefault();
}
this.ui.qualityTab.tab('show');
this._navigate('settings/quality');
},
_showIndexers : function(e) {
if (e) {
e.preventDefault();
}
this.ui.indexersTab.tab('show');
this._navigate('settings/indexers');
},
_showDownloadClient : function(e) {
if (e) {
e.preventDefault();
}
this.ui.downloadClientTab.tab('show');
this._navigate('settings/downloadclient');
},
_showNotifications : function(e) {
if (e) {
e.preventDefault();
}
this.ui.notificationsTab.tab('show');
this._navigate('settings/connect');
},
_showMetadata : function(e) {
if (e) {
e.preventDefault();
}
this.ui.metadataTab.tab('show');
this._navigate('settings/metadata');
},
_showGeneral : function(e) {
if (e) {
e.preventDefault();
}
this.ui.generalTab.tab('show');
this._navigate('settings/general');
},
_showUi : function(e) {
if (e) {
e.preventDefault();
}
this.ui.uiTab.tab('show');
this._navigate('settings/ui');
},
_navigate : function(route) {
Backbone.history.navigate(route, {
trigger : false,
replace : true
});
},
_save : function() {
vent.trigger(vent.Commands.SaveSettings);
},
_setAdvancedSettingsState : function() {
var checked = Config.getValueBoolean(Config.Keys.AdvancedSettings);
this.ui.advancedSettings.prop('checked', checked);
if (checked) {
$('body').addClass('show-advanced-settings');
}
},
_toggleAdvancedSettings : function() {
var checked = this.ui.advancedSettings.prop('checked');
Config.setValue(Config.Keys.AdvancedSettings, checked);
if (checked) {
$('body').addClass('show-advanced-settings');
} else {
$('body').removeClass('show-advanced-settings');
}
}
});
template : 'Settings/SettingsLayoutTemplate',
regions : {
mediaManagement : '#media-management',
profiles : '#profiles',
quality : '#quality',
indexers : '#indexers',
downloadClient : '#download-client',
netImport : "#net-import",
notifications : '#notifications',
metadata : '#metadata',
general : '#general',
uiRegion : '#ui',
loading : '#loading-region'
},
ui : {
mediaManagementTab : '.x-media-management-tab',
profilesTab : '.x-profiles-tab',
qualityTab : '.x-quality-tab',
indexersTab : '.x-indexers-tab',
downloadClientTab : '.x-download-client-tab',
netImportTab : ".x-net-import-tab",
notificationsTab : '.x-notifications-tab',
metadataTab : '.x-metadata-tab',
generalTab : '.x-general-tab',
uiTab : '.x-ui-tab',
advancedSettings : '.x-advanced-settings'
},
events : {
'click .x-media-management-tab' : '_showMediaManagement',
'click .x-profiles-tab' : '_showProfiles',
'click .x-quality-tab' : '_showQuality',
'click .x-indexers-tab' : '_showIndexers',
'click .x-download-client-tab' : '_showDownloadClient',
"click .x-net-import-tab" : "_showNetImport",
'click .x-notifications-tab' : '_showNotifications',
'click .x-metadata-tab' : '_showMetadata',
'click .x-general-tab' : '_showGeneral',
'click .x-ui-tab' : '_showUi',
'click .x-save-settings' : '_save',
'change .x-advanced-settings' : '_toggleAdvancedSettings'
},
initialize : function(options) {
if (options.action) {
this.action = options.action.toLowerCase();
}
this.listenTo(vent, vent.Hotkeys.SaveSettings, this._save);
},
onRender : function() {
this.loading.show(new LoadingView());
var self = this;
this.mediaManagementSettings = new MediaManagementSettingsModel();
this.namingSettings = new NamingModel();
this.indexerSettings = new IndexerSettingsModel();
this.netImportSettings = new NetImportSettingsModel();
this.downloadClientSettings = new DownloadClientSettingsModel();
this.notificationCollection = new NotificationCollection();
this.generalSettings = new GeneralSettingsModel();
this.uiSettings = new UiSettingsModel();
Backbone.$.when(this.mediaManagementSettings.fetch(), this.namingSettings.fetch(), this.indexerSettings.fetch(), this.downloadClientSettings.fetch(),
this.notificationCollection.fetch(), this.generalSettings.fetch(), this.uiSettings.fetch(), this.netImportSettings.fetch()).done(function() {
if (!self.isClosed) {
self.loading.$el.hide();
self.mediaManagement.show(new MediaManagementLayout({
settings : self.mediaManagementSettings,
namingSettings : self.namingSettings
}));
self.profiles.show(new ProfileLayout());
self.quality.show(new QualityLayout());
self.indexers.show(new IndexerLayout({ model : self.indexerSettings }));
self.downloadClient.show(new DownloadClientLayout({ model : self.downloadClientSettings }));
self.notifications.show(new NotificationCollectionView({ collection : self.notificationCollection }));
self.metadata.show(new MetadataLayout());
self.general.show(new GeneralView({ model : self.generalSettings }));
self.uiRegion.show(new UiView({ model : self.uiSettings }));
}
});
this._setAdvancedSettingsState();
},
onShow : function() {
switch (this.action) {
case 'profiles':
this._showProfiles();
break;
case 'quality':
this._showQuality();
break;
case 'indexers':
this._showIndexers();
break;
case 'downloadclient':
this._showDownloadClient();
break;
case "netimport":
this._showNetImport();
break;
case 'connect':
this._showNotifications();
break;
case 'notifications':
this._showNotifications();
break;
case 'metadata':
this._showMetadata();
break;
case 'general':
this._showGeneral();
break;
case 'ui':
this._showUi();
break;
default:
this._showMediaManagement();
}
},
_showMediaManagement : function(e) {
if (e) {
e.preventDefault();
}
this.ui.mediaManagementTab.tab('show');
this._navigate('settings/mediamanagement');
},
_showProfiles : function(e) {
if (e) {
e.preventDefault();
}
this.ui.profilesTab.tab('show');
this._navigate('settings/profiles');
},
_showQuality : function(e) {
if (e) {
e.preventDefault();
}
this.ui.qualityTab.tab('show');
this._navigate('settings/quality');
},
_showIndexers : function(e) {
if (e) {
e.preventDefault();
}
this.ui.indexersTab.tab('show');
this._navigate('settings/indexers');
},
_showNetImport : function(e) {
if (e) {
e.preventDefault();
}
this.ui.downloadClientTab.tab('show');
this._navigate('settings/netimport');
},
_showDownloadClient : function(e) {
if (e) {
e.preventDefault();
}
this.ui.downloadClientTab.tab('show');
this._navigate('settings/downloadclient');
},
_showNotifications : function(e) {
if (e) {
e.preventDefault();
}
this.ui.notificationsTab.tab('show');
this._navigate('settings/connect');
},
_showMetadata : function(e) {
if (e) {
e.preventDefault();
}
this.ui.metadataTab.tab('show');
this._navigate('settings/metadata');
},
_showGeneral : function(e) {
if (e) {
e.preventDefault();
}
this.ui.generalTab.tab('show');
this._navigate('settings/general');
},
_showUi : function(e) {
if (e) {
e.preventDefault();
}
this.ui.uiTab.tab('show');
this._navigate('settings/ui');
},
_navigate : function(route) {
Backbone.history.navigate(route, {
trigger : false,
replace : true
});
},
_save : function() {
vent.trigger(vent.Commands.SaveSettings);
},
_setAdvancedSettingsState : function() {
var checked = Config.getValueBoolean(Config.Keys.AdvancedSettings);
this.ui.advancedSettings.prop('checked', checked);
if (checked) {
$('body').addClass('show-advanced-settings');
}
},
_toggleAdvancedSettings : function() {
var checked = this.ui.advancedSettings.prop('checked');
Config.setValue(Config.Keys.AdvancedSettings, checked);
if (checked) {
$('body').addClass('show-advanced-settings');
} else {
$('body').removeClass('show-advanced-settings');
}
}
});

@ -1,49 +1,51 @@
<ul class="nav nav-tabs nav-justified settings-tabs">
<li><a href="#media-management" class="x-media-management-tab no-router">Media Management</a></li>
<li><a href="#profiles" class="x-profiles-tab no-router">Profiles</a></li>
<li><a href="#quality" class="x-quality-tab no-router">Quality</a></li>
<li><a href="#indexers" class="x-indexers-tab no-router">Indexers</a></li>
<li><a href="#download-client" class="x-download-client-tab no-router">Download Client</a></li>
<li><a href="#notifications" class="x-notifications-tab no-router">Connect</a></li>
<li><a href="#metadata" class="x-metadata-tab no-router">Metadata</a></li>
<li><a href="#general" class="x-general-tab no-router">General</a></li>
<li><a href="#ui" class="x-ui-tab no-router">UI</a></li>
<li><a href="#media-management" class="x-media-management-tab no-router">Media Management</a></li>
<li><a href="#profiles" class="x-profiles-tab no-router">Profiles</a></li>
<li><a href="#quality" class="x-quality-tab no-router">Quality</a></li>
<li><a href="#indexers" class="x-indexers-tab no-router">Indexers</a></li>
<li><a href="#download-client" class="x-download-client-tab no-router">Download Client</a></li>
<li><a href="#net-import" class="x-net-import-tab no-router">Net Import</a></li>
<li><a href="#notifications" class="x-notifications-tab no-router">Connect</a></li>
<li><a href="#metadata" class="x-metadata-tab no-router">Metadata</a></li>
<li><a href="#general" class="x-general-tab no-router">General</a></li>
<li><a href="#ui" class="x-ui-tab no-router">UI</a></li>
</ul>
<div class="row settings-controls">
<div class="col-sm-4 col-sm-offset-7 col-md-3 col-md-offset-8">
<div class="advanced-settings-toggle">
<span class="help-inline-checkbox hidden-xs">
Advanced Settings
</span>
<label class="checkbox toggle well">
<input type="checkbox" class="x-advanced-settings"/>
<p>
<span>Shown</span>
<span>Hidden</span>
</p>
<div class="btn btn-warning slide-button"/>
</label>
<span class="help-inline-checkbox hidden-sm hidden-md hidden-lg">
Advanced Settings
</span>
</div>
</div>
<div class="col-sm-1 col-md-1">
<button class="btn btn-primary x-save-settings">Save</button>
</div>
<div class="col-sm-4 col-sm-offset-7 col-md-3 col-md-offset-8">
<div class="advanced-settings-toggle">
<span class="help-inline-checkbox hidden-xs">
Advanced Settings
</span>
<label class="checkbox toggle well">
<input type="checkbox" class="x-advanced-settings"/>
<p>
<span>Shown</span>
<span>Hidden</span>
</p>
<div class="btn btn-warning slide-button"/>
</label>
<span class="help-inline-checkbox hidden-sm hidden-md hidden-lg">
Advanced Settings
</span>
</div>
</div>
<div class="col-sm-1 col-md-1">
<button class="btn btn-primary x-save-settings">Save</button>
</div>
</div>
<div class="tab-content">
<div class="tab-pane" id="media-management"></div>
<div class="tab-pane" id="profiles"></div>
<div class="tab-pane" id="quality"></div>
<div class="tab-pane" id="indexers"></div>
<div class="tab-pane" id="download-client"></div>
<div class="tab-pane" id="notifications"></div>
<div class="tab-pane" id="metadata"></div>
<div class="tab-pane" id="general"></div>
<div class="tab-pane" id="ui"></div>
<div class="tab-pane" id="media-management"></div>
<div class="tab-pane" id="profiles"></div>
<div class="tab-pane" id="quality"></div>
<div class="tab-pane" id="indexers"></div>
<div class="tab-pane" id="download-client"></div>
<div class="tab-pane" id="net-import"></div>
<div class="tab-pane" id="notifications"></div>
<div class="tab-pane" id="metadata"></div>
<div class="tab-pane" id="general"></div>
<div class="tab-pane" id="ui"></div>
</div>
<div id="loading-region"></div>
<div id="loading-region"></div>

Loading…
Cancel
Save