diff --git a/src/Jackett/Content/custom.js b/src/Jackett/Content/custom.js index 5a26e6a8f..931da5a68 100644 --- a/src/Jackett/Content/custom.js +++ b/src/Jackett/Content/custom.js @@ -1,570 +1,594 @@ -$(document).ready(function () { - $.ajaxSetup({ cache: false }); - window.jackettIsLocal = window.location.hostname === 'localhost' || - window.location.hostname === '127.0.0.1'; - - bindUIButtons(); - reloadIndexers(); - loadJackettSettings(); -}); - -function getJackettConfig(callback) { - var jqxhr = $.get("/admin/get_jackett_config", function (data) { - - callback(data); - }).fail(function () { - doNotify("Error loading Jackett settings, request to Jackett server failed", "danger", "glyphicon glyphicon-alert"); - }); -} - -function loadJackettSettings() { - getJackettConfig(function (data) { - $("#api-key-input").val(data.config.api_key); - $("#app-version").html(data.app_version); - $("#jackett-port").val(data.config.port); - $("#jackett-savedir").val(data.config.blackholedir); - $("#jackett-allowext").attr('checked', data.config.external); - var password = data.config.password; - $("#jackett-adminpwd").val(password); - if (password != null && password != '') { - $("#logoutBtn").show(); - } - }); -} - -function reloadIndexers() { - $('#indexers').hide(); - $('#indexers > .indexer').remove(); - $('#unconfigured-indexers').empty(); - var jqxhr = $.get("/admin/get_indexers", function (data) { - displayIndexers(data.items); - }).fail(function () { - doNotify("Error loading indexers, request to Jackett server failed", "danger", "glyphicon glyphicon-alert"); - }); -} - -function displayIndexers(items) { - var indexerTemplate = Handlebars.compile($("#configured-indexer").html()); - var unconfiguredIndexerTemplate = Handlebars.compile($("#unconfigured-indexer").html()); - $('#unconfigured-indexers-template').empty(); - for (var i = 0; i < items.length; i++) { - var item = items[i]; - item.torznab_host = resolveUrl("/torznab/" + item.id); - item.potato_host = resolveUrl("/potato/" + item.id); - if (item.configured) - $('#indexers').append(indexerTemplate(item)); - else - $('#unconfigured-indexers-template').append($(unconfiguredIndexerTemplate(item))); - } - - var addIndexerButton = $($('#add-indexer').html()); - addIndexerButton.appendTo($('#indexers')); - - addIndexerButton.click(function () { - $("#modals").empty(); - var dialog = $($("#select-indexer").html()); - dialog.find('#unconfigured-indexers').html($('#unconfigured-indexers-template').html()); - $("#modals").append(dialog); - dialog.modal("show"); - $('.indexer-setup').each(function (i, btn) { - var $btn = $(btn); - var id = $btn.data("id"); - $btn.click(function () { - $('#select-indexer-modal').modal('hide').on('hidden.bs.modal', function (e) { - displayIndexerSetup(id); - }); - }); - }); - }); - - $('#indexers').fadeIn(); - prepareSetupButtons(); - prepareTestButtons(); - prepareDeleteButtons(); -} - -function prepareDeleteButtons() { - $(".indexer-button-delete").each(function (i, btn) { - var $btn = $(btn); - var id = $btn.data("id"); - $btn.click(function () { - var jqxhr = $.post("/admin/delete_indexer", JSON.stringify({ indexer: id }), function (data) { - if (data.result == "error") { - doNotify("Delete error for " + id + "\n" + data.error, "danger", "glyphicon glyphicon-alert"); - } - else { - doNotify("Deleted " + id, "success", "glyphicon glyphicon-ok"); - } - }).fail(function () { - doNotify("Error deleting indexer, request to Jackett server error", "danger", "glyphicon glyphicon-alert"); - }).always(function () { - reloadIndexers(); - }); - }); - }); -} - -function prepareSetupButtons() { - $('.indexer-setup').each(function (i, btn) { - var $btn = $(btn); - var id = $btn.data("id"); - $btn.click(function () { - displayIndexerSetup(id); - }); - }); -} - -function prepareTestButtons() { - $(".indexer-button-test").each(function (i, btn) { - var $btn = $(btn); - var id = $btn.data("id"); - $btn.click(function () { - doNotify("Test started for " + id, "info", "glyphicon glyphicon-transfer"); - var jqxhr = $.post("/admin/test_indexer", JSON.stringify({ indexer: id }), function (data) { - if (data.result == "error") { - doNotify("Test failed for " + id + ": \n" + data.error, "danger", "glyphicon glyphicon-alert"); - } - else { - doNotify("Test successful for " + id, "success", "glyphicon glyphicon-ok"); - } - }).fail(function () { - doNotify("Error testing indexer, request to Jackett server error", "danger", "glyphicon glyphicon-alert"); - }); - }); - }); -} - -function displayIndexerSetup(id) { - - var jqxhr = $.post("/admin/get_config_form", JSON.stringify({ indexer: id }), function (data) { - if (data.result == "error") { - doNotify("Error: " + data.error, "danger", "glyphicon glyphicon-alert"); - return; - } - - populateSetupForm(id, data.name, data.config, data.caps); - - }).fail(function () { - doNotify("Request to Jackett server failed", "danger", "glyphicon glyphicon-alert"); - }); - - $("#select-indexer-modal").modal("hide"); -} - -function populateConfigItems(configForm, config) { - // Set flag so we show fields named password as a password input - for (var i = 0; i < config.length; i++) { - config[i].ispassword = config[i].id.toLowerCase() === 'password'; - } - var $formItemContainer = configForm.find(".config-setup-form"); - $formItemContainer.empty(); - - $('.jackettrecaptcha').remove(); - - var hasReacaptcha = false; - var captchaItem = null; - for (var i = 0; i < config.length; i++) { - if (config[i].type === 'recaptcha') { - hasReacaptcha = true; - captchaItem = config[i]; - } - } - - var setupItemTemplate = Handlebars.compile($("#setup-item").html()); - if (hasReacaptcha && !window.jackettIsLocal) { - var setupValueTemplate = Handlebars.compile($("#setup-item-nonlocalrecaptcha").html()); - captchaItem.value_element = setupValueTemplate(captchaItem); - var template = setupItemTemplate(captchaItem); - $formItemContainer.append(template); - } else { - - for (var i = 0; i < config.length; i++) { - var item = config[i]; - var setupValueTemplate = Handlebars.compile($("#setup-item-" + item.type).html()); - item.value_element = setupValueTemplate(item); - var template = setupItemTemplate(item); - $formItemContainer.append(template); - if (item.type === 'recaptcha') { - grecaptcha.render($('.jackettrecaptcha')[0], { - 'sitekey': item.sitekey - }); - } - } - } -} - -function newConfigModal(title, config, caps) { - var configTemplate = Handlebars.compile($("#jackett-config-setup-modal").html()); - var configForm = $(configTemplate({ title: title, caps: caps })); - $("#modals").append(configForm); - populateConfigItems(configForm, config); - return configForm; -} - -function getConfigModalJson(configForm) { - var configJson = []; - configForm.find(".config-setup-form").children().each(function (i, el) { - $el = $(el); - var type = $el.data("type"); - var id = $el.data("id"); - var itemEntry = { id: id }; - switch (type) { - case "hiddendata": - itemEntry.value = $el.find(".setup-item-hiddendata input").val(); - break; - case "inputstring": - itemEntry.value = $el.find(".setup-item-inputstring input").val(); - break; - case "inputbool": - itemEntry.value = $el.find(".setup-item-inputbool input").is(":checked"); - break; - case "recaptcha": - if (window.jackettIsLocal) { - itemEntry.value = $('.g-recaptcha-response').val(); - } else { - itemEntry.cookie = $el.find(".setup-item-recaptcha input").val(); - } - break; - } - configJson.push(itemEntry) - }); - return configJson; -} - -function populateSetupForm(indexerId, name, config, caps) { - var configForm = newConfigModal(name, config, caps); - var $goButton = configForm.find(".setup-indexer-go"); - $goButton.click(function () { - var data = { indexer: indexerId, name: name }; - data.config = getConfigModalJson(configForm); - - var originalBtnText = $goButton.html(); - $goButton.prop('disabled', true); - $goButton.html($('#spinner').html()); - - var jqxhr = $.post("/admin/configure_indexer", JSON.stringify(data), function (data) { - if (data.result == "error") { - if (data.config) { - populateConfigItems(configForm, data.config); - } - doNotify("Configuration failed: " + data.error, "danger", "glyphicon glyphicon-alert"); - } - else { - configForm.modal("hide"); - reloadIndexers(); - doNotify("Successfully configured " + data.name, "success", "glyphicon glyphicon-ok"); - } - }).fail(function () { - doNotify("Request to Jackett server failed", "danger", "glyphicon glyphicon-alert"); - }).always(function () { - $goButton.html(originalBtnText); - $goButton.prop('disabled', false); - }); - }); - - configForm.modal("show"); -} - -function resolveUrl(url) { - var a = document.createElement('a'); - a.href = url; - url = a.href; - return url; -} - -function doNotify(message, type, icon) { - $.notify({ - message: message, - icon: icon - }, { - element: 'body', - type: type, - allow_dismiss: true, - z_index: 9000, - mouse_over: 'pause', - placement: { - from: "bottom", - align: "center" - } - }); -} - -function clearNotifications() { - $('[data-notify="container"]').remove(); -} - - -function bindUIButtons() { - $('body').on('click', '.downloadlink', function (e, b) { - $(e.target).addClass('jackettdownloaded'); - }); - - $('body').on('click', '.jacketdownloadserver', function (event) { - var href = $(event.target).parent().attr('href'); - var jqxhr = $.get(href, function (data) { - if (data.result == "error") { - doNotify("Error: " + data.error, "danger", "glyphicon glyphicon-alert"); - return; - } else { - doNotify("Downloaded sent to the blackhole successfully.", "success", "glyphicon glyphicon-ok"); - } - }).fail(function () { - doNotify("Request to Jackett server failed", "danger", "glyphicon glyphicon-alert"); - }); - event.preventDefault(); - return false; - }); - - $("#jackett-show-releases").click(function () { - var jqxhr = $.get("/admin/GetCache", function (data) { - var releaseTemplate = Handlebars.compile($("#jackett-releases").html()); - var item = { releases: data, Title: 'Releases' }; - var releaseDialog = $(releaseTemplate(item)); - releaseDialog.find('table').DataTable( - { - "pageLength": 20, - "lengthMenu": [[10, 20, 50, -1], [10, 20, 50, "All"]], - "order": [[0, "desc"]], - "columnDefs": [ - { - "targets": 0, - "visible": false, - "searchable": false, - "type": 'date' - }, - { - "targets": 1, - "visible": false, - "searchable": false, - "type": 'date' - }, - { - "targets": 2, - "visible": true, - "searchable": false, - "iDataSort": 0 - }, - { - "targets": 3, - "visible": true, - "searchable": false, - "iDataSort": 1 - } - ], - initComplete: function () { - var count = 0; - this.api().columns().every(function () { - count++; - if (count === 5 || count === 7) { - var column = this; - var select = $('') - .appendTo($(column.footer()).empty()) - .on('change', function () { - var val = $.fn.dataTable.util.escapeRegex( - $(this).val() - ); - - column - .search(val ? '^' + val + '$' : '', true, false) - .draw(); - }); - - column.data().unique().sort().each(function (d, j) { - select.append('') - }); - } - }); - } - }); - $("#modals").append(releaseDialog); - releaseDialog.modal("show"); - - }).fail(function () { - doNotify("Request to Jackett server failed", "danger", "glyphicon glyphicon-alert"); - }); - }); - - $("#jackett-show-search").click(function () { - $('#select-indexer-modal').remove(); - var jqxhr = $.get("/admin/get_indexers", function (data) { - var scope = { - items: data.items - }; - - var indexers = []; - indexers.push({ id: '', name: '-- All --' }); - for (var i = 0; i < data.items.length; i++) { - if (data.items[i].configured === true) { - indexers.push(data.items[i]); - } - } - - var releaseTemplate = Handlebars.compile($("#jackett-search").html()); - var releaseDialog = $(releaseTemplate({ indexers: indexers })); - $("#modals").append(releaseDialog); - releaseDialog.modal("show"); - - var setCategories = function (tracker, items) { - var cats = {}; - for (var i = 0; i < items.length; i++) { - if (items[i].configured === true && (items[i].id === tracker || tracker === '')) { - indexers["'" + items[i].id + "'"] = items[i].name; - for (var prop in items[i].caps) { - cats[prop] = items[i].caps[prop]; - } - } - } - var select = $('#searchCategory'); - select.html(""); - $.each(cats, function (value, key) { - select.append($("") - .attr("value", value).text(key + ' (' + value + ')')); - }); - }; - - setCategories('', data.items); - $('#searchTracker').change(jQuery.proxy(function () { - var trackerId = $('#searchTracker').val(); - setCategories(trackerId, this.items); - }, scope)); - - - $('#jackett-search-perform').click(function () { - if ($('#jackett-search-perform').text().trim() !== 'Search trackers') { - // We are searchin already - return; - } - var queryObj = { - Query: releaseDialog.find('#searchquery').val(), - Category: releaseDialog.find('#searchCategory').val(), - Tracker: releaseDialog.find('#searchTracker').val().replace("'", "").replace("'", ""), - }; - $('#searchResults').empty(); - - $('#jackett-search-perform').html($('#spinner').html()); - var jqxhr = $.post("/admin/search", queryObj, function (data) { - $('#jackett-search-perform').html('Search trackers'); - var resultsTemplate = Handlebars.compile($("#jackett-search-results").html()); - var results = $('#searchResults'); - results.html($(resultsTemplate(data))); - - results.find('table').DataTable( - { - "pageLength": 20, - "lengthMenu": [[10, 20, 50, -1], [10, 20, 50, "All"]], - "order": [[0, "desc"]], - "columnDefs": [ - { - "targets": 0, - "visible": false, - "searchable": false, - "type": 'date' - }, - { - "targets": 1, - "visible": true, - "searchable": false, - "iDataSort": 0 - }, - ], - initComplete: function () { - var count = 0; - this.api().columns().every(function () { - count++; - if (count === 3 || count === 5) { - var column = this; - var select = $('') - .appendTo($(column.footer()).empty()) - .on('change', function () { - var val = $.fn.dataTable.util.escapeRegex( - $(this).val() - ); - - column - .search(val ? '^' + val + '$' : '', true, false) - .draw(); - }); - - column.data().unique().sort().each(function (d, j) { - select.append('') - }); - } - }); - } - }); - - }).fail(function () { - $('#jackett-search-perform').html('Search trackers'); - doNotify("Request to Jackett server failed", "danger", "glyphicon glyphicon-alert"); - }); - }); - - }).fail(function () { - doNotify("Error loading indexers, request to Jackett server failed", "danger", "glyphicon glyphicon-alert"); - }); - }); - - $("#view-jackett-logs").click(function () { - var jqxhr = $.get("/admin/GetLogs", function (data) { - var releaseTemplate = Handlebars.compile($("#jackett-logs").html()); - var item = { logs: data }; - var releaseDialog = $(releaseTemplate(item)); - $("#modals").append(releaseDialog); - releaseDialog.modal("show"); - - }).fail(function () { - doNotify("Request to Jackett server failed", "danger", "glyphicon glyphicon-alert"); - }); - }); - - $("#change-jackett-port").click(function () { - var jackett_port = $("#jackett-port").val(); - var jackett_external = $("#jackett-allowext").is(':checked'); - var jsonObject = { - port: jackett_port, - external: jackett_external, - blackholedir: $("#jackett-savedir").val() - }; - var jqxhr = $.post("/admin/set_config", JSON.stringify(jsonObject), function (data) { - if (data.result == "error") { - doNotify("Error: " + data.error, "danger", "glyphicon glyphicon-alert"); - return; - } else { - doNotify("Redirecting you to complete configuration update..", "success", "glyphicon glyphicon-ok"); - window.setTimeout(function () { - url = window.location.href; - if (data.external) { - window.location.href = url.substr(0, url.lastIndexOf(":") + 1) + data.port; - } else { - window.location.href = 'http://127.0.0.1:' + data.port; - } - }, 3000); - - } - }).fail(function () { - doNotify("Request to Jackett server failed", "danger", "glyphicon glyphicon-alert"); - }); - }); - - $("#change-jackett-password").click(function () { - var password = $("#jackett-adminpwd").val(); - var jsonObject = { password: password }; - - var jqxhr = $.post("/admin/set_admin_password", JSON.stringify(jsonObject), function (data) { - - if (data.result == "error") { - doNotify("Error: " + data.error, "danger", "glyphicon glyphicon-alert"); - return; - } else { - doNotify("Admin password has been set.", "success", "glyphicon glyphicon-ok"); - - window.setTimeout(function () { - window.location = window.location.pathname; - }, 1000); - - } - }).fail(function () { - doNotify("Request to Jackett server failed", "danger", "glyphicon glyphicon-alert"); - }); - }); +$(document).ready(function () { + $.ajaxSetup({ cache: false }); + window.jackettIsLocal = window.location.hostname === 'localhost' || + window.location.hostname === '127.0.0.1'; + + bindUIButtons(); + reloadIndexers(); + loadJackettSettings(); +}); + +function getJackettConfig(callback) { + var jqxhr = $.get("/admin/get_jackett_config", function (data) { + + callback(data); + }).fail(function () { + doNotify("Error loading Jackett settings, request to Jackett server failed", "danger", "glyphicon glyphicon-alert"); + }); +} + +function loadJackettSettings() { + getJackettConfig(function (data) { + $("#api-key-input").val(data.config.api_key); + $("#app-version").html(data.app_version); + $("#jackett-port").val(data.config.port); + $("#jackett-savedir").val(data.config.blackholedir); + $("#jackett-allowext").attr('checked', data.config.external); + var password = data.config.password; + $("#jackett-adminpwd").val(password); + if (password != null && password != '') { + $("#logoutBtn").show(); + } + }); +} + +function reloadIndexers() { + $('#indexers').hide(); + $('#indexers > .indexer').remove(); + $('#unconfigured-indexers').empty(); + var jqxhr = $.get("/admin/get_indexers", function (data) { + displayIndexers(data.items); + }).fail(function () { + doNotify("Error loading indexers, request to Jackett server failed", "danger", "glyphicon glyphicon-alert"); + }); +} + +function displayIndexers(items) { + var indexerTemplate = Handlebars.compile($("#configured-indexer").html()); + var unconfiguredIndexerTemplate = Handlebars.compile($("#unconfigured-indexer").html()); + $('#unconfigured-indexers-template').empty(); + for (var i = 0; i < items.length; i++) { + var item = items[i]; + item.torznab_host = resolveUrl("/torznab/" + item.id); + item.potato_host = resolveUrl("/potato/" + item.id); + if (item.configured) + $('#indexers').append(indexerTemplate(item)); + else + $('#unconfigured-indexers-template').append($(unconfiguredIndexerTemplate(item))); + } + + var addIndexerButton = $($('#add-indexer').html()); + addIndexerButton.appendTo($('#indexers')); + + addIndexerButton.click(function () { + $("#modals").empty(); + var dialog = $($("#select-indexer").html()); + dialog.find('#unconfigured-indexers').html($('#unconfigured-indexers-template').html()); + $("#modals").append(dialog); + dialog.modal("show"); + $('.indexer-setup').each(function (i, btn) { + var $btn = $(btn); + var id = $btn.data("id"); + $btn.click(function () { + $('#select-indexer-modal').modal('hide').on('hidden.bs.modal', function (e) { + displayIndexerSetup(id); + }); + }); + }); + }); + + $('#indexers').fadeIn(); + prepareSetupButtons(); + prepareTestButtons(); + prepareDeleteButtons(); +} + +function prepareDeleteButtons() { + $(".indexer-button-delete").each(function (i, btn) { + var $btn = $(btn); + var id = $btn.data("id"); + $btn.click(function () { + var jqxhr = $.post("/admin/delete_indexer", JSON.stringify({ indexer: id }), function (data) { + if (data.result == "error") { + doNotify("Delete error for " + id + "\n" + data.error, "danger", "glyphicon glyphicon-alert"); + } + else { + doNotify("Deleted " + id, "success", "glyphicon glyphicon-ok"); + } + }).fail(function () { + doNotify("Error deleting indexer, request to Jackett server error", "danger", "glyphicon glyphicon-alert"); + }).always(function () { + reloadIndexers(); + }); + }); + }); +} + +function prepareSetupButtons() { + $('.indexer-setup').each(function (i, btn) { + var $btn = $(btn); + var id = $btn.data("id"); + $btn.click(function () { + displayIndexerSetup(id); + }); + }); +} + +function prepareTestButtons() { + $(".indexer-button-test").each(function (i, btn) { + var $btn = $(btn); + var id = $btn.data("id"); + $btn.click(function () { + doNotify("Test started for " + id, "info", "glyphicon glyphicon-transfer"); + var jqxhr = $.post("/admin/test_indexer", JSON.stringify({ indexer: id }), function (data) { + if (data.result == "error") { + doNotify("Test failed for " + id + ": \n" + data.error, "danger", "glyphicon glyphicon-alert"); + } + else { + doNotify("Test successful for " + id, "success", "glyphicon glyphicon-ok"); + } + }).fail(function () { + doNotify("Error testing indexer, request to Jackett server error", "danger", "glyphicon glyphicon-alert"); + }); + }); + }); +} + +function displayIndexerSetup(id) { + + var jqxhr = $.post("/admin/get_config_form", JSON.stringify({ indexer: id }), function (data) { + if (data.result == "error") { + doNotify("Error: " + data.error, "danger", "glyphicon glyphicon-alert"); + return; + } + + populateSetupForm(id, data.name, data.config, data.caps); + + }).fail(function () { + doNotify("Request to Jackett server failed", "danger", "glyphicon glyphicon-alert"); + }); + + $("#select-indexer-modal").modal("hide"); +} + +function populateConfigItems(configForm, config) { + // Set flag so we show fields named password as a password input + for (var i = 0; i < config.length; i++) { + config[i].ispassword = config[i].id.toLowerCase() === 'password'; + } + var $formItemContainer = configForm.find(".config-setup-form"); + $formItemContainer.empty(); + + $('.jackettrecaptcha').remove(); + + var hasReacaptcha = false; + var captchaItem = null; + for (var i = 0; i < config.length; i++) { + if (config[i].type === 'recaptcha') { + hasReacaptcha = true; + captchaItem = config[i]; + } + } + + var setupItemTemplate = Handlebars.compile($("#setup-item").html()); + if (hasReacaptcha && !window.jackettIsLocal) { + var setupValueTemplate = Handlebars.compile($("#setup-item-nonlocalrecaptcha").html()); + captchaItem.value_element = setupValueTemplate(captchaItem); + var template = setupItemTemplate(captchaItem); + $formItemContainer.append(template); + } else { + + for (var i = 0; i < config.length; i++) { + var item = config[i]; + var setupValueTemplate = Handlebars.compile($("#setup-item-" + item.type).html()); + item.value_element = setupValueTemplate(item); + var template = setupItemTemplate(item); + $formItemContainer.append(template); + if (item.type === 'recaptcha') { + grecaptcha.render($('.jackettrecaptcha')[0], { + 'sitekey': item.sitekey + }); + } + } + } +} + +function newConfigModal(title, config, caps) { + var configTemplate = Handlebars.compile($("#jackett-config-setup-modal").html()); + var configForm = $(configTemplate({ title: title, caps: caps })); + $("#modals").append(configForm); + populateConfigItems(configForm, config); + return configForm; +} + +function getConfigModalJson(configForm) { + var configJson = []; + configForm.find(".config-setup-form").children().each(function (i, el) { + $el = $(el); + var type = $el.data("type"); + var id = $el.data("id"); + var itemEntry = { id: id }; + switch (type) { + case "hiddendata": + itemEntry.value = $el.find(".setup-item-hiddendata input").val(); + break; + case "inputstring": + itemEntry.value = $el.find(".setup-item-inputstring input").val(); + break; + case "inputbool": + itemEntry.value = $el.find(".setup-item-inputbool input").is(":checked"); + break; + case "recaptcha": + if (window.jackettIsLocal) { + itemEntry.value = $('.g-recaptcha-response').val(); + } else { + itemEntry.cookie = $el.find(".setup-item-recaptcha input").val(); + } + break; + } + configJson.push(itemEntry) + }); + return configJson; +} + +function populateSetupForm(indexerId, name, config, caps) { + var configForm = newConfigModal(name, config, caps); + var $goButton = configForm.find(".setup-indexer-go"); + $goButton.click(function () { + var data = { indexer: indexerId, name: name }; + data.config = getConfigModalJson(configForm); + + var originalBtnText = $goButton.html(); + $goButton.prop('disabled', true); + $goButton.html($('#spinner').html()); + + var jqxhr = $.post("/admin/configure_indexer", JSON.stringify(data), function (data) { + if (data.result == "error") { + if (data.config) { + populateConfigItems(configForm, data.config); + } + doNotify("Configuration failed: " + data.error, "danger", "glyphicon glyphicon-alert"); + } + else { + configForm.modal("hide"); + reloadIndexers(); + doNotify("Successfully configured " + data.name, "success", "glyphicon glyphicon-ok"); + } + }).fail(function () { + doNotify("Request to Jackett server failed", "danger", "glyphicon glyphicon-alert"); + }).always(function () { + $goButton.html(originalBtnText); + $goButton.prop('disabled', false); + }); + }); + + configForm.modal("show"); +} + +function resolveUrl(url) { + var a = document.createElement('a'); + a.href = url; + url = a.href; + return url; +} + +function doNotify(message, type, icon) { + $.notify({ + message: message, + icon: icon + }, { + element: 'body', + type: type, + allow_dismiss: true, + z_index: 9000, + mouse_over: 'pause', + placement: { + from: "bottom", + align: "center" + } + }); +} + +function clearNotifications() { + $('[data-notify="container"]').remove(); +} + + +function bindUIButtons() { + $('body').on('click', '.downloadlink', function (e, b) { + $(e.target).addClass('jackettdownloaded'); + }); + + $('body').on('click', '.jacketdownloadserver', function (event) { + var href = $(event.target).parent().attr('href'); + var jqxhr = $.get(href, function (data) { + if (data.result == "error") { + doNotify("Error: " + data.error, "danger", "glyphicon glyphicon-alert"); + return; + } else { + doNotify("Downloaded sent to the blackhole successfully.", "success", "glyphicon glyphicon-ok"); + } + }).fail(function () { + doNotify("Request to Jackett server failed", "danger", "glyphicon glyphicon-alert"); + }); + event.preventDefault(); + return false; + }); + + $("#jackett-show-releases").click(function () { + var jqxhr = $.get("/admin/GetCache", function (data) { + var releaseTemplate = Handlebars.compile($("#jackett-releases").html()); + var item = { releases: data, Title: 'Releases' }; + var releaseDialog = $(releaseTemplate(item)); + releaseDialog.find('table').DataTable( + { + "pageLength": 20, + "lengthMenu": [[10, 20, 50, -1], [10, 20, 50, "All"]], + "order": [[0, "desc"]], + "columnDefs": [ + { + "targets": 0, + "visible": false, + "searchable": false, + "type": 'date' + }, + { + "targets": 1, + "visible": false, + "searchable": false, + "type": 'date' + }, + { + "targets": 2, + "visible": true, + "searchable": false, + "iDataSort": 0 + }, + { + "targets": 3, + "visible": true, + "searchable": false, + "iDataSort": 1 + }, + { + "targets": 6, + "visible": false, + "searchable": false, + "type": 'num' + }, + { + "targets": 7, + "visible": true, + "searchable": false, + "iDataSort": 6 + } + ], + initComplete: function () { + var count = 0; + this.api().columns().every(function () { + count++; + if (count === 5 || count === 7) { + var column = this; + var select = $('') + .appendTo($(column.footer()).empty()) + .on('change', function () { + var val = $.fn.dataTable.util.escapeRegex( + $(this).val() + ); + + column + .search(val ? '^' + val + '$' : '', true, false) + .draw(); + }); + + column.data().unique().sort().each(function (d, j) { + select.append('') + }); + } + }); + } + }); + $("#modals").append(releaseDialog); + releaseDialog.modal("show"); + + }).fail(function () { + doNotify("Request to Jackett server failed", "danger", "glyphicon glyphicon-alert"); + }); + }); + + $("#jackett-show-search").click(function () { + $('#select-indexer-modal').remove(); + var jqxhr = $.get("/admin/get_indexers", function (data) { + var scope = { + items: data.items + }; + + var indexers = []; + indexers.push({ id: '', name: '-- All --' }); + for (var i = 0; i < data.items.length; i++) { + if (data.items[i].configured === true) { + indexers.push(data.items[i]); + } + } + + var releaseTemplate = Handlebars.compile($("#jackett-search").html()); + var releaseDialog = $(releaseTemplate({ indexers: indexers })); + $("#modals").append(releaseDialog); + releaseDialog.modal("show"); + + var setCategories = function (tracker, items) { + var cats = {}; + for (var i = 0; i < items.length; i++) { + if (items[i].configured === true && (items[i].id === tracker || tracker === '')) { + indexers["'" + items[i].id + "'"] = items[i].name; + for (var prop in items[i].caps) { + cats[prop] = items[i].caps[prop]; + } + } + } + var select = $('#searchCategory'); + select.html(""); + $.each(cats, function (value, key) { + select.append($("") + .attr("value", value).text(key + ' (' + value + ')')); + }); + }; + + setCategories('', data.items); + $('#searchTracker').change(jQuery.proxy(function () { + var trackerId = $('#searchTracker').val(); + setCategories(trackerId, this.items); + }, scope)); + + + $('#jackett-search-perform').click(function () { + if ($('#jackett-search-perform').text().trim() !== 'Search trackers') { + // We are searchin already + return; + } + var queryObj = { + Query: releaseDialog.find('#searchquery').val(), + Category: releaseDialog.find('#searchCategory').val(), + Tracker: releaseDialog.find('#searchTracker').val().replace("'", "").replace("'", ""), + }; + $('#searchResults').empty(); + + $('#jackett-search-perform').html($('#spinner').html()); + var jqxhr = $.post("/admin/search", queryObj, function (data) { + $('#jackett-search-perform').html('Search trackers'); + var resultsTemplate = Handlebars.compile($("#jackett-search-results").html()); + var results = $('#searchResults'); + results.html($(resultsTemplate(data))); + + results.find('table').DataTable( + { + "pageLength": 20, + "lengthMenu": [[10, 20, 50, -1], [10, 20, 50, "All"]], + "order": [[0, "desc"]], + "columnDefs": [ + { + "targets": 0, + "visible": false, + "searchable": false, + "type": 'date' + }, + { + "targets": 1, + "visible": true, + "searchable": false, + "iDataSort": 0 + }, + { + "targets": 4, + "visible": false, + "searchable": false, + "type": 'num' + }, + { + "targets": 5, + "visible": true, + "searchable": false, + "iDataSort": 4 + } + ], + initComplete: function () { + var count = 0; + this.api().columns().every(function () { + count++; + if (count === 3 || count === 5) { + var column = this; + var select = $('') + .appendTo($(column.footer()).empty()) + .on('change', function () { + var val = $.fn.dataTable.util.escapeRegex( + $(this).val() + ); + + column + .search(val ? '^' + val + '$' : '', true, false) + .draw(); + }); + + column.data().unique().sort().each(function (d, j) { + select.append('') + }); + } + }); + } + }); + + }).fail(function () { + $('#jackett-search-perform').html('Search trackers'); + doNotify("Request to Jackett server failed", "danger", "glyphicon glyphicon-alert"); + }); + }); + + }).fail(function () { + doNotify("Error loading indexers, request to Jackett server failed", "danger", "glyphicon glyphicon-alert"); + }); + }); + + $("#view-jackett-logs").click(function () { + var jqxhr = $.get("/admin/GetLogs", function (data) { + var releaseTemplate = Handlebars.compile($("#jackett-logs").html()); + var item = { logs: data }; + var releaseDialog = $(releaseTemplate(item)); + $("#modals").append(releaseDialog); + releaseDialog.modal("show"); + + }).fail(function () { + doNotify("Request to Jackett server failed", "danger", "glyphicon glyphicon-alert"); + }); + }); + + $("#change-jackett-port").click(function () { + var jackett_port = $("#jackett-port").val(); + var jackett_external = $("#jackett-allowext").is(':checked'); + var jsonObject = { + port: jackett_port, + external: jackett_external, + blackholedir: $("#jackett-savedir").val() + }; + var jqxhr = $.post("/admin/set_config", JSON.stringify(jsonObject), function (data) { + if (data.result == "error") { + doNotify("Error: " + data.error, "danger", "glyphicon glyphicon-alert"); + return; + } else { + doNotify("Redirecting you to complete configuration update..", "success", "glyphicon glyphicon-ok"); + window.setTimeout(function () { + url = window.location.href; + if (data.external) { + window.location.href = url.substr(0, url.lastIndexOf(":") + 1) + data.port; + } else { + window.location.href = 'http://127.0.0.1:' + data.port; + } + }, 3000); + + } + }).fail(function () { + doNotify("Request to Jackett server failed", "danger", "glyphicon glyphicon-alert"); + }); + }); + + $("#change-jackett-password").click(function () { + var password = $("#jackett-adminpwd").val(); + var jsonObject = { password: password }; + + var jqxhr = $.post("/admin/set_admin_password", JSON.stringify(jsonObject), function (data) { + + if (data.result == "error") { + doNotify("Error: " + data.error, "danger", "glyphicon glyphicon-alert"); + return; + } else { + doNotify("Admin password has been set.", "success", "glyphicon glyphicon-ok"); + + window.setTimeout(function () { + window.location = window.location.pathname; + }, 1000); + + } + }).fail(function () { + doNotify("Request to Jackett server failed", "danger", "glyphicon glyphicon-alert"); + }); + }); } \ No newline at end of file diff --git a/src/Jackett/Content/index.html b/src/Jackett/Content/index.html index f30aef9fb..278ac344a 100644 --- a/src/Jackett/Content/index.html +++ b/src/Jackett/Content/index.html @@ -4,6 +4,7 @@ + @@ -202,6 +203,8 @@ First Seen Tracker Name + Size + Size Category Seeds Leechers @@ -217,6 +220,8 @@ {{jacketTimespan FirstSeen}} {{Tracker}} {{Title}} + {{Size}} + {{jacketSize Size}} {{CategoryDesc}} {{Seeders}} {{Peers}} @@ -296,6 +301,8 @@ Published Tracker Name + Size + Size Category Seeds Leechers @@ -309,6 +316,8 @@ {{jacketTimespan PublishDate}} {{Tracker}} {{Title}} + {{Size}} + {{jacketSize Size}} {{CategoryDesc}} {{Seeders}} {{Peers}} diff --git a/src/Jackett/Content/libs/filesize.min.js b/src/Jackett/Content/libs/filesize.min.js new file mode 100644 index 000000000..bc4cbe92e --- /dev/null +++ b/src/Jackett/Content/libs/filesize.min.js @@ -0,0 +1,6 @@ +/* + 2015 Jason Mulligan + @version 3.1.2 + */ +"use strict"; !function (a) { var b = /b$/, c = { bits: ["B", "kb", "Mb", "Gb", "Tb", "Pb", "Eb", "Zb", "Yb"], bytes: ["B", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"] }, d = function (a) { var d = void 0 === arguments[1] ? {} : arguments[1], e = [], f = !1, g = 0, h = void 0, i = void 0, j = void 0, k = void 0, l = void 0, m = void 0, n = void 0, o = void 0, p = void 0, q = void 0, r = void 0; if (isNaN(a)) throw new Error("Invalid arguments"); return j = d.bits === !0, p = d.unix === !0, i = void 0 !== d.base ? d.base : 2, o = void 0 !== d.round ? d.round : p ? 1 : 2, q = void 0 !== d.spacer ? d.spacer : p ? "" : " ", r = void 0 !== d.suffixes ? d.suffixes : {}, n = void 0 !== d.output ? d.output : "string", h = void 0 !== d.exponent ? d.exponent : -1, m = Number(a), l = 0 > m, k = i > 2 ? 1e3 : 1024, l && (m = -m), 0 === m ? (e[0] = 0, e[1] = p ? "" : "B") : ((-1 === h || isNaN(h)) && (h = Math.floor(Math.log(m) / Math.log(k))), h > 8 && (g = 1e3 * g * (h - 8), h = 8), g = 2 === i ? m / Math.pow(2, 10 * h) : m / Math.pow(1e3, h), j && (g = 8 * g, g > k && (g /= k, h++)), e[0] = Number(g.toFixed(h > 0 ? o : 0)), e[1] = c[j ? "bits" : "bytes"][h], !f && p && (j && b.test(e[1]) && (e[1] = e[1].toLowerCase()), e[1] = e[1].charAt(0), "B" === e[1] ? (e[0] = Math.floor(e[0]), e[1] = "") : j || "k" !== e[1] || (e[1] = "K"))), l && (e[0] = -e[0]), e[1] = r[e[1]] || e[1], "array" === n ? e : "exponent" === n ? h : "object" === n ? { value: e[0], suffix: e[1] } : e.join(q) }; "undefined" != typeof exports ? module.exports = d : "function" == typeof define ? define(function () { return d }) : a.filesize = d }("undefined" != typeof global ? global : window); +//# sourceMappingURL=filesize.min.js.map \ No newline at end of file diff --git a/src/Jackett/Content/libs/handlebarsmoment.js b/src/Jackett/Content/libs/handlebarsmoment.js index 0bf9f5fd1..158822eab 100644 --- a/src/Jackett/Content/libs/handlebarsmoment.js +++ b/src/Jackett/Content/libs/handlebarsmoment.js @@ -1,33 +1,37 @@ - -Handlebars.registerHelper('dateFormat', function (context, block) { - if (window.moment) { - var f = block.hash.format || "MMM DD, YYYY hh:mm:ss A"; - return moment(context).format(f); //had to remove Date(context) - } else { - return context; // moment plugin not available. return data as is. - }; -}); - -Handlebars.registerHelper('jacketTimespan', function (context, block) { - var now = moment(); - var from = moment(context); - var timeSpan = moment.duration(now.diff(from)); - - var minutes = timeSpan.asMinutes(); - if (minutes < 120) { - return Math.round(minutes) + 'm ago'; - } - - var hours = timeSpan.asHours(); - if (hours < 48) { - return Math.round(hours) + 'h ago'; - } - - var days = timeSpan.asDays(); - if (days < 365) { - return Math.round(days) + 'd ago'; - } - - var years = timeSpan.asYears(); - return Math.round(years) + 'y ago'; + +Handlebars.registerHelper('dateFormat', function (context, block) { + if (window.moment) { + var f = block.hash.format || "MMM DD, YYYY hh:mm:ss A"; + return moment(context).format(f); //had to remove Date(context) + } else { + return context; // moment plugin not available. return data as is. + }; +}); + +Handlebars.registerHelper('jacketTimespan', function (context, block) { + var now = moment(); + var from = moment(context); + var timeSpan = moment.duration(now.diff(from)); + + var minutes = timeSpan.asMinutes(); + if (minutes < 120) { + return Math.round(minutes) + 'm ago'; + } + + var hours = timeSpan.asHours(); + if (hours < 48) { + return Math.round(hours) + 'h ago'; + } + + var days = timeSpan.asDays(); + if (days < 365) { + return Math.round(days) + 'd ago'; + } + + var years = timeSpan.asYears(); + return Math.round(years) + 'y ago'; +}); + +Handlebars.registerHelper('jacketSize', function (context, block) { + return filesize(context, { round: 1 }); }); \ No newline at end of file diff --git a/src/Jackett/Controllers/AdminController.cs b/src/Jackett/Controllers/AdminController.cs index 3a091f2ab..05be1b6b6 100644 --- a/src/Jackett/Controllers/AdminController.cs +++ b/src/Jackett/Controllers/AdminController.cs @@ -1,514 +1,515 @@ -using Autofac; -using AutoMapper; -using Jackett.Indexers; -using Jackett.Models; -using Jackett.Services; -using Jackett.Utils; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using NLog; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Net; -using System.Net.Http; -using System.Net.Http.Headers; -using System.Security.Claims; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using System.Web; -using System.Web.Http; -using System.Web.Http.Results; -using System.Web.Security; -using System.Windows.Forms; - -namespace Jackett.Controllers -{ - [RoutePrefix("admin")] - [JackettAuthorized] - [JackettAPINoCache] - public class AdminController : ApiController - { - private IConfigurationService config; - private IIndexerManagerService indexerService; - private IServerService serverService; - private ISecuityService securityService; - private IProcessService processService; - private ICacheService cacheService; - private Logger logger; - private ILogCacheService logCache; - - public AdminController(IConfigurationService config, IIndexerManagerService i, IServerService ss, ISecuityService s, IProcessService p, ICacheService c, Logger l, ILogCacheService lc) - { - this.config = config; - indexerService = i; - serverService = ss; - securityService = s; - processService = p; - cacheService = c; - logger = l; - logCache = lc; - } - - private async Task ReadPostDataJson() - { - var content = await Request.Content.ReadAsStringAsync(); - return JObject.Parse(content); - } - - - private HttpResponseMessage GetFile(string path) - { - var result = new HttpResponseMessage(HttpStatusCode.OK); - var mappedPath = Path.Combine(config.GetContentFolder(), path); - var stream = new FileStream(mappedPath, FileMode.Open); - result.Content = new StreamContent(stream); - result.Content.Headers.ContentType = - new MediaTypeHeaderValue(MimeMapping.GetMimeMapping(mappedPath)); - - return result; - } - - [HttpGet] - [AllowAnonymous] - public RedirectResult Logout() - { - var ctx = Request.GetOwinContext(); - var authManager = ctx.Authentication; - authManager.SignOut("ApplicationCookie"); - return Redirect("/Admin/Dashboard"); - } - - [HttpGet] - [HttpPost] - [AllowAnonymous] - public async Task Dashboard() - { - if (Request.RequestUri.Query != null && Request.RequestUri.Query.Contains("logout")) - { - var file = GetFile("login.html"); - securityService.Logout(file); - return file; - } - - - if (securityService.CheckAuthorised(Request)) - { - return GetFile("index.html"); - - } - else - { - var formData = await Request.Content.ReadAsFormDataAsync(); - - if (formData != null && securityService.HashPassword(formData["password"]) == serverService.Config.AdminPassword) - { - var file = GetFile("index.html"); - securityService.Login(file); - return file; - } - else - { - return GetFile("login.html"); - } - } - } - - [Route("set_admin_password")] - [HttpPost] - public async Task SetAdminPassword() - { - var jsonReply = new JObject(); - try - { - var postData = await ReadPostDataJson(); - var password = (string)postData["password"]; - if (string.IsNullOrEmpty(password)) - { - serverService.Config.AdminPassword = string.Empty; - } - else - { - serverService.Config.AdminPassword = securityService.HashPassword(password); - } - - serverService.SaveConfig(); - jsonReply["result"] = "success"; - } - catch (Exception ex) - { - logger.Error(ex, "Exception in SetAdminPassword"); - jsonReply["result"] = "error"; - jsonReply["error"] = ex.Message; - } - return Json(jsonReply); - } - - [Route("get_config_form")] - [HttpPost] - public async Task GetConfigForm() - { - var jsonReply = new JObject(); - try - { - var postData = await ReadPostDataJson(); - var indexer = indexerService.GetIndexer((string)postData["indexer"]); - var config = await indexer.GetConfigurationForSetup(); - jsonReply["config"] = config.ToJson(null); - jsonReply["caps"] = indexer.TorznabCaps.CapsToJson(); - jsonReply["name"] = indexer.DisplayName; - jsonReply["result"] = "success"; - } - catch (Exception ex) - { - logger.Error(ex, "Exception in GetConfigForm"); - jsonReply["result"] = "error"; - jsonReply["error"] = ex.Message; - } - return Json(jsonReply); - } - - [Route("configure_indexer")] - [HttpPost] - public async Task Configure() - { - var jsonReply = new JObject(); - IIndexer indexer = null; - try - { - var postData = await ReadPostDataJson(); - string indexerString = (string)postData["indexer"]; - indexer = indexerService.GetIndexer((string)postData["indexer"]); - jsonReply["name"] = indexer.DisplayName; - var configurationResult = await indexer.ApplyConfiguration(postData["config"]); - if (configurationResult == IndexerConfigurationStatus.RequiresTesting) - { - await indexerService.TestIndexer((string)postData["indexer"]); - } - else if (configurationResult == IndexerConfigurationStatus.Failed) - { - throw new Exception("Configuration Failed"); - } - jsonReply["result"] = "success"; - } - catch (Exception ex) - { - jsonReply["result"] = "error"; - jsonReply["error"] = ex.Message; - var baseIndexer = indexer as BaseIndexer; - if (null != baseIndexer) - baseIndexer.ResetBaseConfig(); - if (ex is ExceptionWithConfigData) - { - jsonReply["config"] = ((ExceptionWithConfigData)ex).ConfigData.ToJson(null); - } - else - { - logger.Error(ex, "Exception in Configure"); - } - } - return Json(jsonReply); - } - - [Route("get_indexers")] - [HttpGet] - public IHttpActionResult Indexers() - { - var jsonReply = new JObject(); - try - { - jsonReply["result"] = "success"; - JArray items = new JArray(); - - foreach (var indexer in indexerService.GetAllIndexers()) - { - var item = new JObject(); - item["id"] = indexer.ID; - item["name"] = indexer.DisplayName; - item["description"] = indexer.DisplayDescription; - item["configured"] = indexer.IsConfigured; - item["site_link"] = indexer.SiteLink; - item["potatoenabled"] = indexer.TorznabCaps.Categories.Select(c => c.ID).Any(i => PotatoController.MOVIE_CATS.Contains(i)); - - var caps = new JObject(); - foreach (var cap in indexer.TorznabCaps.Categories) - caps[cap.ID.ToString()] = cap.Name; - item["caps"] = caps; - items.Add(item); - } - jsonReply["items"] = items; - } - catch (Exception ex) - { - logger.Error(ex, "Exception in get_indexers"); - jsonReply["result"] = "error"; - jsonReply["error"] = ex.Message; - } - return Json(jsonReply); - } - - [Route("test_indexer")] - [HttpPost] - public async Task Test() - { - JToken jsonReply = new JObject(); - try - { - var postData = await ReadPostDataJson(); - string indexerString = (string)postData["indexer"]; - await indexerService.TestIndexer(indexerString); - jsonReply["name"] = indexerService.GetIndexer(indexerString).DisplayName; - jsonReply["result"] = "success"; - } - catch (Exception ex) - { - logger.Error(ex, "Exception in test_indexer"); - jsonReply["result"] = "error"; - jsonReply["error"] = ex.Message; - } - return Json(jsonReply); - } - - [Route("delete_indexer")] - [HttpPost] - public async Task Delete() - { - var jsonReply = new JObject(); - try - { - var postData = await ReadPostDataJson(); - string indexerString = (string)postData["indexer"]; - indexerService.DeleteIndexer(indexerString); - } - catch (Exception ex) - { - logger.Error(ex, "Exception in delete_indexer"); - jsonReply["result"] = "error"; - jsonReply["error"] = ex.Message; - } - return Json(jsonReply); - } - - [Route("get_jackett_config")] - [HttpGet] - public IHttpActionResult GetConfig() - { - var jsonReply = new JObject(); - try - { - var cfg = new JObject(); - cfg["port"] = serverService.Config.Port; - cfg["external"] = serverService.Config.AllowExternal; - cfg["api_key"] = serverService.Config.APIKey; - cfg["blackholedir"] = serverService.Config.BlackholeDir; - cfg["password"] = string.IsNullOrEmpty(serverService.Config.AdminPassword) ? string.Empty : serverService.Config.AdminPassword.Substring(0, 10); - - jsonReply["config"] = cfg; - jsonReply["app_version"] = config.GetVersion(); - jsonReply["result"] = "success"; - } - catch (Exception ex) - { - logger.Error(ex, "Exception in get_jackett_config"); - jsonReply["result"] = "error"; - jsonReply["error"] = ex.Message; - } - return Json(jsonReply); - } - - [Route("set_config")] - [HttpPost] - public async Task SetConfig() - { - var originalPort = Engine.Server.Config.Port; - var originalAllowExternal = Engine.Server.Config.AllowExternal; - var jsonReply = new JObject(); - try - { - var postData = await ReadPostDataJson(); - int port = (int)postData["port"]; - bool external = (bool)postData["external"]; - string saveDir = (string)postData["blackholedir"]; - - if (port != Engine.Server.Config.Port || external != Engine.Server.Config.AllowExternal) - { - if (ServerUtil.RestrictedPorts.Contains(port)) - { - jsonReply["result"] = "error"; - jsonReply["error"] = "The port you have selected is restricted, try a different one."; - return Json(jsonReply); - } - - // Save port to the config so it can be picked up by the if needed when running as admin below. - Engine.Server.Config.AllowExternal = external; - Engine.Server.Config.Port = port; - Engine.Server.SaveConfig(); - - // On Windows change the url reservations - if (System.Environment.OSVersion.Platform != PlatformID.Unix) - { - if (!ServerUtil.IsUserAdministrator()) - { - try - { - processService.StartProcessAndLog(Application.ExecutablePath, "--ReserveUrls", true); - } - catch - { - Engine.Server.Config.Port = originalPort; - Engine.Server.Config.AllowExternal = originalAllowExternal; - Engine.Server.SaveConfig(); - jsonReply["result"] = "error"; - jsonReply["error"] = "Failed to acquire admin permissions to reserve the new port."; - return Json(jsonReply); - } - } - else - { - serverService.ReserveUrls(true); - } - } - - (new Thread(() => - { - Thread.Sleep(500); - serverService.Stop(); - Engine.BuildContainer(); - Engine.Server.Initalize(); - Engine.Server.Start(); - })).Start(); - } - - - if (saveDir != Engine.Server.Config.BlackholeDir) - { - if (!string.IsNullOrEmpty(saveDir)) - { - if (!Directory.Exists(saveDir)) - { - throw new Exception("Blackhole directory does not exist"); - } - } - - Engine.Server.Config.BlackholeDir = saveDir; - Engine.Server.SaveConfig(); - } - - jsonReply["result"] = "success"; - jsonReply["port"] = port; - jsonReply["external"] = external; - } - catch (Exception ex) - { - logger.Error(ex, "Exception in set_port"); - jsonReply["result"] = "error"; - jsonReply["error"] = ex.Message; - } - return Json(jsonReply); - } - - [Route("GetCache")] - [HttpGet] - public List GetCache() - { - var results = cacheService.GetCachedResults(); - ConfigureCacheResults(results); - return results; - } - - - private void ConfigureCacheResults(List results) - { - var serverUrl = string.Format("{0}://{1}:{2}/", Request.RequestUri.Scheme, Request.RequestUri.Host, Request.RequestUri.Port); - foreach (var result in results) - { - var link = result.Link; - result.Link = serverService.ConvertToProxyLink(link, serverUrl, result.TrackerId); - if (result.Link != null && result.Link.Scheme != "magnet" && !string.IsNullOrWhiteSpace(Engine.Server.Config.BlackholeDir)) - result.BlackholeLink = serverService.ConvertToProxyLink(link, serverUrl, result.TrackerId, "bh", string.Empty); - - } - } - - [Route("GetLogs")] - [HttpGet] - public List GetLogs() - { - return logCache.Logs; - } - - [Route("Search")] - [HttpPost] - public ManualSearchResult Search([FromBody]AdminSearch value) - { - var results = new List(); - var query = new TorznabQuery() - { - SearchTerm = value.Query, - Categories = value.Category == 0 ? new int[0] : new int[1] { value.Category } - }; - - query.ExpandCatsToSubCats(); - - var trackers = indexerService.GetAllIndexers().Where(t => t.IsConfigured).ToList(); - if (!string.IsNullOrWhiteSpace(value.Tracker)) - { - trackers = trackers.Where(t => t.ID == value.Tracker).ToList(); - } - - if (value.Category != 0) - { - trackers = trackers.Where(t => t.TorznabCaps.Categories.Select(c => c.ID).Contains(value.Category)).ToList(); - } - - Parallel.ForEach(trackers.ToList(), indexer => - { - try - { - var searchResults = indexer.PerformQuery(query).Result; - cacheService.CacheRssResults(indexer, searchResults); - searchResults = indexer.FilterResults(query, searchResults); - - lock (results) - { - foreach (var result in searchResults) - { - var item = Mapper.Map(result); - item.Tracker = indexer.DisplayName; - item.TrackerId = indexer.ID; - item.Peers = item.Peers - item.Seeders; // Use peers as leechers - results.Add(item); - } - } - } - catch (Exception e) - { - logger.Error(e, "An error occured during manual search on " + indexer.DisplayName + ": " + e.Message); - } - }); - - ConfigureCacheResults(results); - - if (trackers.Count > 1) - { - results = results.OrderByDescending(d => d.PublishDate).ToList(); - } - - var manualResult = new ManualSearchResult() - { - Results = results, - Indexers = trackers.Select(t => t.DisplayName).ToList() - }; - - - if (manualResult.Indexers.Count == 0) - manualResult.Indexers = new List() { "None" }; - - logger.Info(string.Format("Manual search for \"{0}\" on {1} with {2} results.", query.GetQueryString(), string.Join(", ", manualResult.Indexers), manualResult.Results.Count)); - return manualResult; - } - } -} - +using Autofac; +using AutoMapper; +using Jackett.Indexers; +using Jackett.Models; +using Jackett.Services; +using Jackett.Utils; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using NLog; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Security.Claims; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Web; +using System.Web.Http; +using System.Web.Http.Results; +using System.Web.Security; +using System.Windows.Forms; + +namespace Jackett.Controllers +{ + [RoutePrefix("admin")] + [JackettAuthorized] + [JackettAPINoCache] + public class AdminController : ApiController + { + private IConfigurationService config; + private IIndexerManagerService indexerService; + private IServerService serverService; + private ISecuityService securityService; + private IProcessService processService; + private ICacheService cacheService; + private Logger logger; + private ILogCacheService logCache; + + public AdminController(IConfigurationService config, IIndexerManagerService i, IServerService ss, ISecuityService s, IProcessService p, ICacheService c, Logger l, ILogCacheService lc) + { + this.config = config; + indexerService = i; + serverService = ss; + securityService = s; + processService = p; + cacheService = c; + logger = l; + logCache = lc; + } + + private async Task ReadPostDataJson() + { + var content = await Request.Content.ReadAsStringAsync(); + return JObject.Parse(content); + } + + + private HttpResponseMessage GetFile(string path) + { + var result = new HttpResponseMessage(HttpStatusCode.OK); + var mappedPath = Path.Combine(config.GetContentFolder(), path); + var stream = new FileStream(mappedPath, FileMode.Open); + result.Content = new StreamContent(stream); + result.Content.Headers.ContentType = + new MediaTypeHeaderValue(MimeMapping.GetMimeMapping(mappedPath)); + + return result; + } + + [HttpGet] + [AllowAnonymous] + public RedirectResult Logout() + { + var ctx = Request.GetOwinContext(); + var authManager = ctx.Authentication; + authManager.SignOut("ApplicationCookie"); + return Redirect("/Admin/Dashboard"); + } + + [HttpGet] + [HttpPost] + [AllowAnonymous] + public async Task Dashboard() + { + if (Request.RequestUri.Query != null && Request.RequestUri.Query.Contains("logout")) + { + var file = GetFile("login.html"); + securityService.Logout(file); + return file; + } + + + if (securityService.CheckAuthorised(Request)) + { + return GetFile("index.html"); + + } + else + { + var formData = await Request.Content.ReadAsFormDataAsync(); + + if (formData != null && securityService.HashPassword(formData["password"]) == serverService.Config.AdminPassword) + { + var file = GetFile("index.html"); + securityService.Login(file); + return file; + } + else + { + return GetFile("login.html"); + } + } + } + + [Route("set_admin_password")] + [HttpPost] + public async Task SetAdminPassword() + { + var jsonReply = new JObject(); + try + { + var postData = await ReadPostDataJson(); + var password = (string)postData["password"]; + if (string.IsNullOrEmpty(password)) + { + serverService.Config.AdminPassword = string.Empty; + } + else + { + serverService.Config.AdminPassword = securityService.HashPassword(password); + } + + serverService.SaveConfig(); + jsonReply["result"] = "success"; + } + catch (Exception ex) + { + logger.Error(ex, "Exception in SetAdminPassword"); + jsonReply["result"] = "error"; + jsonReply["error"] = ex.Message; + } + return Json(jsonReply); + } + + [Route("get_config_form")] + [HttpPost] + public async Task GetConfigForm() + { + var jsonReply = new JObject(); + try + { + var postData = await ReadPostDataJson(); + var indexer = indexerService.GetIndexer((string)postData["indexer"]); + var config = await indexer.GetConfigurationForSetup(); + jsonReply["config"] = config.ToJson(null); + jsonReply["caps"] = indexer.TorznabCaps.CapsToJson(); + jsonReply["name"] = indexer.DisplayName; + jsonReply["result"] = "success"; + } + catch (Exception ex) + { + logger.Error(ex, "Exception in GetConfigForm"); + jsonReply["result"] = "error"; + jsonReply["error"] = ex.Message; + } + return Json(jsonReply); + } + + [Route("configure_indexer")] + [HttpPost] + public async Task Configure() + { + var jsonReply = new JObject(); + IIndexer indexer = null; + try + { + var postData = await ReadPostDataJson(); + string indexerString = (string)postData["indexer"]; + indexer = indexerService.GetIndexer((string)postData["indexer"]); + jsonReply["name"] = indexer.DisplayName; + var configurationResult = await indexer.ApplyConfiguration(postData["config"]); + if (configurationResult == IndexerConfigurationStatus.RequiresTesting) + { + await indexerService.TestIndexer((string)postData["indexer"]); + } + else if (configurationResult == IndexerConfigurationStatus.Failed) + { + throw new Exception("Configuration Failed"); + } + jsonReply["result"] = "success"; + } + catch (Exception ex) + { + jsonReply["result"] = "error"; + jsonReply["error"] = ex.Message; + var baseIndexer = indexer as BaseIndexer; + if (null != baseIndexer) + baseIndexer.ResetBaseConfig(); + if (ex is ExceptionWithConfigData) + { + jsonReply["config"] = ((ExceptionWithConfigData)ex).ConfigData.ToJson(null); + } + else + { + logger.Error(ex, "Exception in Configure"); + } + } + return Json(jsonReply); + } + + [Route("get_indexers")] + [HttpGet] + public IHttpActionResult Indexers() + { + var jsonReply = new JObject(); + try + { + jsonReply["result"] = "success"; + JArray items = new JArray(); + + foreach (var indexer in indexerService.GetAllIndexers()) + { + var item = new JObject(); + item["id"] = indexer.ID; + item["name"] = indexer.DisplayName; + item["description"] = indexer.DisplayDescription; + item["configured"] = indexer.IsConfigured; + item["site_link"] = indexer.SiteLink; + item["potatoenabled"] = indexer.TorznabCaps.Categories.Select(c => c.ID).Any(i => PotatoController.MOVIE_CATS.Contains(i)); + + var caps = new JObject(); + foreach (var cap in indexer.TorznabCaps.Categories) + caps[cap.ID.ToString()] = cap.Name; + item["caps"] = caps; + items.Add(item); + } + jsonReply["items"] = items; + } + catch (Exception ex) + { + logger.Error(ex, "Exception in get_indexers"); + jsonReply["result"] = "error"; + jsonReply["error"] = ex.Message; + } + return Json(jsonReply); + } + + [Route("test_indexer")] + [HttpPost] + public async Task Test() + { + JToken jsonReply = new JObject(); + try + { + var postData = await ReadPostDataJson(); + string indexerString = (string)postData["indexer"]; + await indexerService.TestIndexer(indexerString); + jsonReply["name"] = indexerService.GetIndexer(indexerString).DisplayName; + jsonReply["result"] = "success"; + } + catch (Exception ex) + { + logger.Error(ex, "Exception in test_indexer"); + jsonReply["result"] = "error"; + jsonReply["error"] = ex.Message; + } + return Json(jsonReply); + } + + [Route("delete_indexer")] + [HttpPost] + public async Task Delete() + { + var jsonReply = new JObject(); + try + { + var postData = await ReadPostDataJson(); + string indexerString = (string)postData["indexer"]; + indexerService.DeleteIndexer(indexerString); + } + catch (Exception ex) + { + logger.Error(ex, "Exception in delete_indexer"); + jsonReply["result"] = "error"; + jsonReply["error"] = ex.Message; + } + return Json(jsonReply); + } + + [Route("get_jackett_config")] + [HttpGet] + public IHttpActionResult GetConfig() + { + var jsonReply = new JObject(); + try + { + var cfg = new JObject(); + cfg["port"] = serverService.Config.Port; + cfg["external"] = serverService.Config.AllowExternal; + cfg["api_key"] = serverService.Config.APIKey; + cfg["blackholedir"] = serverService.Config.BlackholeDir; + cfg["password"] = string.IsNullOrEmpty(serverService.Config.AdminPassword) ? string.Empty : serverService.Config.AdminPassword.Substring(0, 10); + + jsonReply["config"] = cfg; + jsonReply["app_version"] = config.GetVersion(); + jsonReply["result"] = "success"; + } + catch (Exception ex) + { + logger.Error(ex, "Exception in get_jackett_config"); + jsonReply["result"] = "error"; + jsonReply["error"] = ex.Message; + } + return Json(jsonReply); + } + + [Route("set_config")] + [HttpPost] + public async Task SetConfig() + { + var originalPort = Engine.Server.Config.Port; + var originalAllowExternal = Engine.Server.Config.AllowExternal; + var jsonReply = new JObject(); + try + { + var postData = await ReadPostDataJson(); + int port = (int)postData["port"]; + bool external = (bool)postData["external"]; + string saveDir = (string)postData["blackholedir"]; + + if (port != Engine.Server.Config.Port || external != Engine.Server.Config.AllowExternal) + { + if (ServerUtil.RestrictedPorts.Contains(port)) + { + jsonReply["result"] = "error"; + jsonReply["error"] = "The port you have selected is restricted, try a different one."; + return Json(jsonReply); + } + + // Save port to the config so it can be picked up by the if needed when running as admin below. + Engine.Server.Config.AllowExternal = external; + Engine.Server.Config.Port = port; + Engine.Server.SaveConfig(); + + // On Windows change the url reservations + if (System.Environment.OSVersion.Platform != PlatformID.Unix) + { + if (!ServerUtil.IsUserAdministrator()) + { + try + { + processService.StartProcessAndLog(Application.ExecutablePath, "--ReserveUrls", true); + } + catch + { + Engine.Server.Config.Port = originalPort; + Engine.Server.Config.AllowExternal = originalAllowExternal; + Engine.Server.SaveConfig(); + jsonReply["result"] = "error"; + jsonReply["error"] = "Failed to acquire admin permissions to reserve the new port."; + return Json(jsonReply); + } + } + else + { + serverService.ReserveUrls(true); + } + } + + (new Thread(() => + { + Thread.Sleep(500); + serverService.Stop(); + Engine.BuildContainer(); + Engine.Server.Initalize(); + Engine.Server.Start(); + })).Start(); + } + + + if (saveDir != Engine.Server.Config.BlackholeDir) + { + if (!string.IsNullOrEmpty(saveDir)) + { + if (!Directory.Exists(saveDir)) + { + throw new Exception("Blackhole directory does not exist"); + } + } + + Engine.Server.Config.BlackholeDir = saveDir; + Engine.Server.SaveConfig(); + } + + jsonReply["result"] = "success"; + jsonReply["port"] = port; + jsonReply["external"] = external; + } + catch (Exception ex) + { + logger.Error(ex, "Exception in set_port"); + jsonReply["result"] = "error"; + jsonReply["error"] = ex.Message; + } + return Json(jsonReply); + } + + [Route("GetCache")] + [HttpGet] + public List GetCache() + { + var results = cacheService.GetCachedResults(); + ConfigureCacheResults(results); + return results; + } + + + private void ConfigureCacheResults(List results) + { + var serverUrl = string.Format("{0}://{1}:{2}/", Request.RequestUri.Scheme, Request.RequestUri.Host, Request.RequestUri.Port); + foreach (var result in results) + { + var link = result.Link; + result.Link = serverService.ConvertToProxyLink(link, serverUrl, result.TrackerId); + if (result.Link != null && result.Link.Scheme != "magnet" && !string.IsNullOrWhiteSpace(Engine.Server.Config.BlackholeDir)) + result.BlackholeLink = serverService.ConvertToProxyLink(link, serverUrl, result.TrackerId, "bh", string.Empty); + + } + } + + [Route("GetLogs")] + [HttpGet] + public List GetLogs() + { + return logCache.Logs; + } + + [Route("Search")] + [HttpPost] + public ManualSearchResult Search([FromBody]AdminSearch value) + { + var results = new List(); + var query = new TorznabQuery() + { + SearchTerm = value.Query, + Categories = value.Category == 0 ? new int[0] : new int[1] { value.Category } + }; + + query.ExpandCatsToSubCats(); + + var trackers = indexerService.GetAllIndexers().Where(t => t.IsConfigured).ToList(); + if (!string.IsNullOrWhiteSpace(value.Tracker)) + { + trackers = trackers.Where(t => t.ID == value.Tracker).ToList(); + } + + if (value.Category != 0) + { + trackers = trackers.Where(t => t.TorznabCaps.Categories.Select(c => c.ID).Contains(value.Category)).ToList(); + } + + Parallel.ForEach(trackers.ToList(), indexer => + { + try + { + var searchResults = indexer.PerformQuery(query).Result; + searchResults = indexer.CleanLinks(searchResults); + cacheService.CacheRssResults(indexer, searchResults); + searchResults = indexer.FilterResults(query, searchResults); + + lock (results) + { + foreach (var result in searchResults) + { + var item = Mapper.Map(result); + item.Tracker = indexer.DisplayName; + item.TrackerId = indexer.ID; + item.Peers = item.Peers - item.Seeders; // Use peers as leechers + results.Add(item); + } + } + } + catch (Exception e) + { + logger.Error(e, "An error occured during manual search on " + indexer.DisplayName + ": " + e.Message); + } + }); + + ConfigureCacheResults(results); + + if (trackers.Count > 1) + { + results = results.OrderByDescending(d => d.PublishDate).ToList(); + } + + var manualResult = new ManualSearchResult() + { + Results = results, + Indexers = trackers.Select(t => t.DisplayName).ToList() + }; + + + if (manualResult.Indexers.Count == 0) + manualResult.Indexers = new List() { "None" }; + + logger.Info(string.Format("Manual search for \"{0}\" on {1} with {2} results.", query.GetQueryString(), string.Join(", ", manualResult.Indexers), manualResult.Results.Count)); + return manualResult; + } + } +} + diff --git a/src/Jackett/Indexers/BaseIndexer.cs b/src/Jackett/Indexers/BaseIndexer.cs index 2b7efef21..b155e4ecf 100644 --- a/src/Jackett/Indexers/BaseIndexer.cs +++ b/src/Jackett/Indexers/BaseIndexer.cs @@ -83,6 +83,11 @@ namespace Jackett.Indexers public Uri UncleanLink(Uri link) { + if (link.ToString().StartsWith(downloadUrlBase)) + { + return link; + } + return new Uri(downloadUrlBase + link.ToString(), UriKind.RelativeOrAbsolute); } @@ -223,6 +228,11 @@ namespace Jackett.Indexers public async virtual Task Download(Uri link) { var response = await RequestBytesWithCookiesAndRetry(link.ToString()); + if(response.Status != System.Net.HttpStatusCode.OK && response.Status != System.Net.HttpStatusCode.Continue && response.Status != System.Net.HttpStatusCode.PartialContent) + { + throw new Exception($"Remote server returned {response.Status.ToString()}"); + } + return response.Content; } diff --git a/src/Jackett/Jackett.csproj b/src/Jackett/Jackett.csproj index f01f8b875..a252102ba 100644 --- a/src/Jackett/Jackett.csproj +++ b/src/Jackett/Jackett.csproj @@ -368,6 +368,7 @@ PreserveNewest + PreserveNewest