mirror of https://github.com/Jackett/Jackett
Web UI improvements, delete indexer support, fixed api key
This commit is contained in:
parent
6d31fae4cf
commit
de111c4202
|
@ -9,6 +9,9 @@ namespace Jackett
|
|||
{
|
||||
public class ApiKey
|
||||
{
|
||||
|
||||
public static string CurrentKey;
|
||||
|
||||
const string chars = "abcdefghijklmnopqrstuvwxyz0123456789";
|
||||
|
||||
public static string Generate()
|
||||
|
|
|
@ -11,15 +11,18 @@ namespace Jackett
|
|||
public class IndexerManager
|
||||
{
|
||||
|
||||
static string AppConfigDirectory = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), "Jackett");
|
||||
static string IndexerConfigDirectory = Path.Combine(AppConfigDirectory, "Indexers");
|
||||
static string IndexerConfigDirectory = Path.Combine(Program.AppConfigDirectory, "Indexers");
|
||||
|
||||
public Dictionary<string, IndexerInterface> Indexers { get; private set; }
|
||||
|
||||
public IndexerManager()
|
||||
{
|
||||
Indexers = new Dictionary<string, IndexerInterface>();
|
||||
LoadMissingIndexers();
|
||||
}
|
||||
|
||||
void LoadMissingIndexers()
|
||||
{
|
||||
var implementedIndexerTypes = AppDomain.CurrentDomain.GetAssemblies()
|
||||
.SelectMany(s => s.GetTypes())
|
||||
.Where(p => typeof(IndexerInterface).IsAssignableFrom(p) && !p.IsInterface)
|
||||
|
@ -31,10 +34,13 @@ namespace Jackett
|
|||
}
|
||||
}
|
||||
|
||||
IndexerInterface LoadIndexer(Type indexerType)
|
||||
void LoadIndexer(Type indexerType)
|
||||
{
|
||||
var name = indexerType.Name.Trim().ToLower();
|
||||
|
||||
if (Indexers.ContainsKey(name))
|
||||
return;
|
||||
|
||||
IndexerInterface newIndexer = (IndexerInterface)Activator.CreateInstance(indexerType);
|
||||
newIndexer.OnSaveConfigurationRequested += newIndexer_OnSaveConfigurationRequested;
|
||||
|
||||
|
@ -46,7 +52,6 @@ namespace Jackett
|
|||
}
|
||||
|
||||
Indexers.Add(name, newIndexer);
|
||||
return newIndexer;
|
||||
}
|
||||
|
||||
string GetIndexerConfigFilePath(IndexerInterface indexer)
|
||||
|
@ -71,5 +76,14 @@ namespace Jackett
|
|||
return indexer;
|
||||
}
|
||||
|
||||
public void DeleteIndexer(string name)
|
||||
{
|
||||
var indexer = GetIndexer(name);
|
||||
var configPath = GetIndexerConfigFilePath(indexer);
|
||||
File.Delete(configPath);
|
||||
Indexers.Remove(name);
|
||||
LoadMissingIndexers();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -117,6 +117,9 @@
|
|||
<Content Include="WebContent\congruent_outline.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="WebContent\crissXcross.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="WebContent\handlebars-v3.0.1.js">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
|
@ -9,13 +10,12 @@ namespace Jackett
|
|||
{
|
||||
class Program
|
||||
{
|
||||
public static ManualResetEvent ExitEvent = new ManualResetEvent(false);
|
||||
public static string AppConfigDirectory = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), "Jackett");
|
||||
|
||||
static IndexerManager indexerManager;
|
||||
public static ManualResetEvent ExitEvent = new ManualResetEvent(false);
|
||||
|
||||
static void Main(string[] args)
|
||||
{
|
||||
indexerManager = new IndexerManager();
|
||||
|
||||
var resultPage = new ResultPage(new ChannelInfo
|
||||
{
|
||||
|
|
|
@ -18,6 +18,8 @@ namespace Jackett
|
|||
|
||||
public Server()
|
||||
{
|
||||
LoadApiKey();
|
||||
|
||||
indexerManager = new IndexerManager();
|
||||
webApi = new WebApi(indexerManager);
|
||||
|
||||
|
@ -25,6 +27,18 @@ namespace Jackett
|
|||
listener.Prefixes.Add("http://*:9117/");
|
||||
}
|
||||
|
||||
void LoadApiKey()
|
||||
{
|
||||
var apiKeyFile = Path.Combine(Program.AppConfigDirectory, "api_key.txt");
|
||||
if (File.Exists(apiKeyFile))
|
||||
ApiKey.CurrentKey = File.ReadAllText(apiKeyFile).Trim();
|
||||
else
|
||||
{
|
||||
ApiKey.CurrentKey = ApiKey.Generate();
|
||||
File.WriteAllText(apiKeyFile, ApiKey.CurrentKey);
|
||||
}
|
||||
}
|
||||
|
||||
public async void Start()
|
||||
{
|
||||
listener.Start();
|
||||
|
|
|
@ -21,14 +21,16 @@ namespace Jackett
|
|||
GetConfigForm,
|
||||
ConfigureIndexer,
|
||||
GetIndexers,
|
||||
TestIndexer
|
||||
TestIndexer,
|
||||
DeleteIndexer
|
||||
}
|
||||
static Dictionary<string, WebApiMethod> WebApiMethods = new Dictionary<string, WebApiMethod>
|
||||
{
|
||||
{ "get_config_form", WebApiMethod.GetConfigForm },
|
||||
{ "configure_indexer", WebApiMethod.ConfigureIndexer },
|
||||
{ "get_indexers", WebApiMethod.GetIndexers },
|
||||
{ "test_indexer", WebApiMethod.TestIndexer}
|
||||
{ "test_indexer", WebApiMethod.TestIndexer },
|
||||
{ "delete_indexer", WebApiMethod.DeleteIndexer }
|
||||
};
|
||||
|
||||
IndexerManager indexerManager;
|
||||
|
@ -80,102 +82,39 @@ namespace Jackett
|
|||
return JObject.Parse(postData);
|
||||
}
|
||||
|
||||
delegate Task<JToken> HandlerTask(HttpListenerContext context);
|
||||
|
||||
async void ProcessWebApiRequest(HttpListenerContext context, WebApiMethod method)
|
||||
{
|
||||
var query = HttpUtility.ParseQueryString(context.Request.Url.Query);
|
||||
|
||||
JToken jsonReply = new JObject();
|
||||
|
||||
context.Response.ContentType = "text/json";
|
||||
context.Response.StatusCode = (int)HttpStatusCode.OK;
|
||||
|
||||
HandlerTask handlerTask;
|
||||
|
||||
switch (method)
|
||||
{
|
||||
case WebApiMethod.GetConfigForm:
|
||||
try
|
||||
{
|
||||
var postData = await ReadPostDataJson(context.Request.InputStream);
|
||||
string indexerString = (string)postData["indexer"];
|
||||
var indexer = indexerManager.GetIndexer(indexerString);
|
||||
var config = await indexer.GetConfigurationForSetup();
|
||||
jsonReply["config"] = config.ToJson();
|
||||
jsonReply["name"] = indexer.DisplayName;
|
||||
jsonReply["result"] = "success";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
jsonReply["result"] = "error";
|
||||
jsonReply["error"] = ex.Message;
|
||||
}
|
||||
handlerTask = HandleConfigForm;
|
||||
break;
|
||||
case WebApiMethod.ConfigureIndexer:
|
||||
try
|
||||
{
|
||||
var postData = await ReadPostDataJson(context.Request.InputStream);
|
||||
string indexerString = (string)postData["indexer"];
|
||||
var indexer = indexerManager.GetIndexer(indexerString);
|
||||
jsonReply["name"] = indexer.DisplayName;
|
||||
await indexer.ApplyConfiguration(postData["config"]);
|
||||
await indexer.VerifyConnection();
|
||||
jsonReply["result"] = "success";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
jsonReply["result"] = "error";
|
||||
jsonReply["error"] = ex.Message;
|
||||
if (ex is ExceptionWithConfigData)
|
||||
{
|
||||
jsonReply["config"] = ((ExceptionWithConfigData)ex).ConfigData.ToJson();
|
||||
}
|
||||
}
|
||||
handlerTask = HandleConfigureIndexer;
|
||||
break;
|
||||
case WebApiMethod.GetIndexers:
|
||||
try
|
||||
{
|
||||
jsonReply["result"] = "success";
|
||||
jsonReply["api_key"] = ApiKey.Generate();
|
||||
JArray items = new JArray();
|
||||
foreach (var i in indexerManager.Indexers)
|
||||
{
|
||||
var indexer = i.Value;
|
||||
var item = new JObject();
|
||||
item["id"] = i.Key;
|
||||
item["name"] = indexer.DisplayName;
|
||||
item["description"] = indexer.DisplayDescription;
|
||||
item["configured"] = indexer.IsConfigured;
|
||||
item["site_link"] = indexer.SiteLink;
|
||||
items.Add(item);
|
||||
}
|
||||
jsonReply["items"] = items;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
jsonReply["result"] = "error";
|
||||
jsonReply["error"] = ex.Message;
|
||||
}
|
||||
handlerTask = HandleGetIndexers;
|
||||
break;
|
||||
case WebApiMethod.TestIndexer:
|
||||
try
|
||||
{
|
||||
var postData = await ReadPostDataJson(context.Request.InputStream);
|
||||
string indexerString = (string)postData["indexer"];
|
||||
var indexer = indexerManager.GetIndexer(indexerString);
|
||||
jsonReply["name"] = indexer.DisplayName;
|
||||
await indexer.VerifyConnection();
|
||||
jsonReply["result"] = "success";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
jsonReply["result"] = "error";
|
||||
jsonReply["error"] = ex.Message;
|
||||
}
|
||||
handlerTask = HandleTestIndexer;
|
||||
break;
|
||||
case WebApiMethod.DeleteIndexer:
|
||||
handlerTask = HandleDeleteIndexer;
|
||||
break;
|
||||
default:
|
||||
jsonReply["result"] = "error";
|
||||
jsonReply["error"] = "Invalid API method";
|
||||
handlerTask = HandleInvalidApiMethod;
|
||||
break;
|
||||
}
|
||||
|
||||
JToken jsonReply = await handlerTask(context);
|
||||
ReplyWithJson(context, jsonReply);
|
||||
}
|
||||
|
||||
|
@ -186,5 +125,131 @@ namespace Jackett
|
|||
context.Response.OutputStream.Close();
|
||||
}
|
||||
|
||||
Task<JToken> HandleInvalidApiMethod(HttpListenerContext context)
|
||||
{
|
||||
return Task<JToken>.Run(() =>
|
||||
{
|
||||
JToken jsonReply = new JObject();
|
||||
jsonReply["result"] = "error";
|
||||
jsonReply["error"] = "Invalid API method";
|
||||
return jsonReply;
|
||||
});
|
||||
}
|
||||
|
||||
async Task<JToken> HandleConfigForm(HttpListenerContext context)
|
||||
{
|
||||
JToken jsonReply = new JObject();
|
||||
try
|
||||
{
|
||||
var postData = await ReadPostDataJson(context.Request.InputStream);
|
||||
string indexerString = (string)postData["indexer"];
|
||||
var indexer = indexerManager.GetIndexer(indexerString);
|
||||
var config = await indexer.GetConfigurationForSetup();
|
||||
jsonReply["config"] = config.ToJson();
|
||||
jsonReply["name"] = indexer.DisplayName;
|
||||
jsonReply["result"] = "success";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
jsonReply["result"] = "error";
|
||||
jsonReply["error"] = ex.Message;
|
||||
}
|
||||
return jsonReply;
|
||||
}
|
||||
|
||||
async Task<JToken> HandleConfigureIndexer(HttpListenerContext context)
|
||||
{
|
||||
JToken jsonReply = new JObject();
|
||||
try
|
||||
{
|
||||
var postData = await ReadPostDataJson(context.Request.InputStream);
|
||||
string indexerString = (string)postData["indexer"];
|
||||
var indexer = indexerManager.GetIndexer(indexerString);
|
||||
jsonReply["name"] = indexer.DisplayName;
|
||||
await indexer.ApplyConfiguration(postData["config"]);
|
||||
await indexer.VerifyConnection();
|
||||
jsonReply["result"] = "success";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
jsonReply["result"] = "error";
|
||||
jsonReply["error"] = ex.Message;
|
||||
if (ex is ExceptionWithConfigData)
|
||||
{
|
||||
jsonReply["config"] = ((ExceptionWithConfigData)ex).ConfigData.ToJson();
|
||||
}
|
||||
}
|
||||
return jsonReply;
|
||||
}
|
||||
|
||||
Task<JToken> HandleGetIndexers(HttpListenerContext context)
|
||||
{
|
||||
return Task<JToken>.Run(() =>
|
||||
{
|
||||
JToken jsonReply = new JObject();
|
||||
try
|
||||
{
|
||||
jsonReply["result"] = "success";
|
||||
jsonReply["api_key"] = ApiKey.CurrentKey;
|
||||
JArray items = new JArray();
|
||||
foreach (var i in indexerManager.Indexers)
|
||||
{
|
||||
var indexer = i.Value;
|
||||
var item = new JObject();
|
||||
item["id"] = i.Key;
|
||||
item["name"] = indexer.DisplayName;
|
||||
item["description"] = indexer.DisplayDescription;
|
||||
item["configured"] = indexer.IsConfigured;
|
||||
item["site_link"] = indexer.SiteLink;
|
||||
items.Add(item);
|
||||
}
|
||||
jsonReply["items"] = items;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
jsonReply["result"] = "error";
|
||||
jsonReply["error"] = ex.Message;
|
||||
}
|
||||
return jsonReply;
|
||||
});
|
||||
}
|
||||
|
||||
async Task<JToken> HandleTestIndexer(HttpListenerContext context)
|
||||
{
|
||||
JToken jsonReply = new JObject();
|
||||
try
|
||||
{
|
||||
var postData = await ReadPostDataJson(context.Request.InputStream);
|
||||
string indexerString = (string)postData["indexer"];
|
||||
var indexer = indexerManager.GetIndexer(indexerString);
|
||||
jsonReply["name"] = indexer.DisplayName;
|
||||
await indexer.VerifyConnection();
|
||||
jsonReply["result"] = "success";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
jsonReply["result"] = "error";
|
||||
jsonReply["error"] = ex.Message;
|
||||
}
|
||||
return jsonReply;
|
||||
}
|
||||
|
||||
async Task<JToken> HandleDeleteIndexer(HttpListenerContext context)
|
||||
{
|
||||
JToken jsonReply = new JObject();
|
||||
try
|
||||
{
|
||||
var postData = await ReadPostDataJson(context.Request.InputStream);
|
||||
string indexerString = (string)postData["indexer"];
|
||||
indexerManager.DeleteIndexer(indexerString);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
jsonReply["result"] = "error";
|
||||
jsonReply["error"] = ex.Message;
|
||||
}
|
||||
return jsonReply;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 60 KiB |
|
@ -13,7 +13,7 @@
|
|||
<title>Jackett</title>
|
||||
<style>
|
||||
body {
|
||||
background-image: url("congruent_outline.png");
|
||||
background-image: url("crissXcross.png");
|
||||
background-repeat: repeat;
|
||||
}
|
||||
|
||||
|
@ -100,7 +100,6 @@
|
|||
|
||||
.indexer-add-content > .glyphicon {
|
||||
font-size: 50px;
|
||||
margin-top: 40%;
|
||||
vertical-align: bottom;
|
||||
}
|
||||
|
||||
|
@ -177,12 +176,12 @@
|
|||
<h3>Configured Indexers</h3>
|
||||
<div id="indexers">
|
||||
|
||||
<a class="indexer card" id="add-indexer" href="#" data-toggle="modal" data-target="#select-indexer-modal">
|
||||
<button class="indexer card" id="add-indexer" data-toggle="modal" data-target="#select-indexer-modal">
|
||||
<div class="indexer-add-content">
|
||||
<span class="glyphicon glyphicon glyphicon-plus" aria-hidden="true"></span>
|
||||
<div class="light-text">Add</div>
|
||||
</div>
|
||||
</a>
|
||||
</button>
|
||||
|
||||
</div>
|
||||
|
||||
|
@ -230,10 +229,18 @@
|
|||
<div class="indexer-logo"><img src="logos/{{id}}.png" /></div>
|
||||
<div class="indexer-name"><h3>{{name}}</h3></div>
|
||||
<div class="indexer-buttons">
|
||||
<a class="btn btn-info btn-sm" target="_blank" href="{{site_link}}">Visit <span class="glyphicon glyphicon-new-window" aria-hidden="true"></span></a>
|
||||
<a class="btn btn-warning btn-sm indexer-button-test" href="#" data-id="{{id}}">Test <span class="glyphicon glyphicon-screenshot" aria-hidden="true"></span></a>
|
||||
<a class="btn btn-danger btn-sm" href="#">Delete <span class="glyphicon glyphicon-trash" aria-hidden="true"></span></a>
|
||||
<a class="btn btn-primary btn-sm" href="#">Configure <span class="glyphicon glyphicon-wrench" aria-hidden="true"></span></a>
|
||||
<a class="btn btn-info btn-sm" target="_blank" href="{{site_link}}">
|
||||
Visit <span class="glyphicon glyphicon-new-window" aria-hidden="true"></span>
|
||||
</a>
|
||||
<button class="btn btn-warning btn-sm indexer-button-test" data-id="{{id}}">
|
||||
Test <span class="glyphicon glyphicon-screenshot" aria-hidden="true"></span>
|
||||
</button>
|
||||
<button class="btn btn-danger btn-sm indexer-button-delete" data-id="{{id}}">
|
||||
Delete <span class="glyphicon glyphicon-trash" aria-hidden="true"></span>
|
||||
</button>
|
||||
<button class="btn btn-primary btn-sm" data-id="{{id}}">
|
||||
Configure <span class="glyphicon glyphicon-wrench" aria-hidden="true"></span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="indexer-host">
|
||||
<b>Torznab Host:</b>
|
||||
|
@ -300,6 +307,28 @@
|
|||
$('#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("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() {
|
||||
|
|
Loading…
Reference in New Issue