Web UI improvements, delete indexer support, fixed api key

This commit is contained in:
zone117x 2015-04-15 21:05:34 -06:00
parent 6d31fae4cf
commit de111c4202
8 changed files with 221 additions and 93 deletions

View File

@ -9,6 +9,9 @@ namespace Jackett
{ {
public class ApiKey public class ApiKey
{ {
public static string CurrentKey;
const string chars = "abcdefghijklmnopqrstuvwxyz0123456789"; const string chars = "abcdefghijklmnopqrstuvwxyz0123456789";
public static string Generate() public static string Generate()

View File

@ -11,15 +11,18 @@ namespace Jackett
public class IndexerManager public class IndexerManager
{ {
static string AppConfigDirectory = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), "Jackett"); static string IndexerConfigDirectory = Path.Combine(Program.AppConfigDirectory, "Indexers");
static string IndexerConfigDirectory = Path.Combine(AppConfigDirectory, "Indexers");
public Dictionary<string, IndexerInterface> Indexers { get; private set; } public Dictionary<string, IndexerInterface> Indexers { get; private set; }
public IndexerManager() public IndexerManager()
{ {
Indexers = new Dictionary<string, IndexerInterface>(); Indexers = new Dictionary<string, IndexerInterface>();
LoadMissingIndexers();
}
void LoadMissingIndexers()
{
var implementedIndexerTypes = AppDomain.CurrentDomain.GetAssemblies() var implementedIndexerTypes = AppDomain.CurrentDomain.GetAssemblies()
.SelectMany(s => s.GetTypes()) .SelectMany(s => s.GetTypes())
.Where(p => typeof(IndexerInterface).IsAssignableFrom(p) && !p.IsInterface) .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(); var name = indexerType.Name.Trim().ToLower();
if (Indexers.ContainsKey(name))
return;
IndexerInterface newIndexer = (IndexerInterface)Activator.CreateInstance(indexerType); IndexerInterface newIndexer = (IndexerInterface)Activator.CreateInstance(indexerType);
newIndexer.OnSaveConfigurationRequested += newIndexer_OnSaveConfigurationRequested; newIndexer.OnSaveConfigurationRequested += newIndexer_OnSaveConfigurationRequested;
@ -46,7 +52,6 @@ namespace Jackett
} }
Indexers.Add(name, newIndexer); Indexers.Add(name, newIndexer);
return newIndexer;
} }
string GetIndexerConfigFilePath(IndexerInterface indexer) string GetIndexerConfigFilePath(IndexerInterface indexer)
@ -71,5 +76,14 @@ namespace Jackett
return indexer; return indexer;
} }
public void DeleteIndexer(string name)
{
var indexer = GetIndexer(name);
var configPath = GetIndexerConfigFilePath(indexer);
File.Delete(configPath);
Indexers.Remove(name);
LoadMissingIndexers();
}
} }
} }

View File

@ -117,6 +117,9 @@
<Content Include="WebContent\congruent_outline.png"> <Content Include="WebContent\congruent_outline.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>
<Content Include="WebContent\crissXcross.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="WebContent\handlebars-v3.0.1.js"> <Content Include="WebContent\handlebars-v3.0.1.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>

View File

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
@ -9,13 +10,12 @@ namespace Jackett
{ {
class Program 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) static void Main(string[] args)
{ {
indexerManager = new IndexerManager();
var resultPage = new ResultPage(new ChannelInfo var resultPage = new ResultPage(new ChannelInfo
{ {

View File

@ -18,6 +18,8 @@ namespace Jackett
public Server() public Server()
{ {
LoadApiKey();
indexerManager = new IndexerManager(); indexerManager = new IndexerManager();
webApi = new WebApi(indexerManager); webApi = new WebApi(indexerManager);
@ -25,6 +27,18 @@ namespace Jackett
listener.Prefixes.Add("http://*:9117/"); 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() public async void Start()
{ {
listener.Start(); listener.Start();

View File

@ -21,14 +21,16 @@ namespace Jackett
GetConfigForm, GetConfigForm,
ConfigureIndexer, ConfigureIndexer,
GetIndexers, GetIndexers,
TestIndexer TestIndexer,
DeleteIndexer
} }
static Dictionary<string, WebApiMethod> WebApiMethods = new Dictionary<string, WebApiMethod> static Dictionary<string, WebApiMethod> WebApiMethods = new Dictionary<string, WebApiMethod>
{ {
{ "get_config_form", WebApiMethod.GetConfigForm }, { "get_config_form", WebApiMethod.GetConfigForm },
{ "configure_indexer", WebApiMethod.ConfigureIndexer }, { "configure_indexer", WebApiMethod.ConfigureIndexer },
{ "get_indexers", WebApiMethod.GetIndexers }, { "get_indexers", WebApiMethod.GetIndexers },
{ "test_indexer", WebApiMethod.TestIndexer} { "test_indexer", WebApiMethod.TestIndexer },
{ "delete_indexer", WebApiMethod.DeleteIndexer }
}; };
IndexerManager indexerManager; IndexerManager indexerManager;
@ -80,102 +82,39 @@ namespace Jackett
return JObject.Parse(postData); return JObject.Parse(postData);
} }
delegate Task<JToken> HandlerTask(HttpListenerContext context);
async void ProcessWebApiRequest(HttpListenerContext context, WebApiMethod method) async void ProcessWebApiRequest(HttpListenerContext context, WebApiMethod method)
{ {
var query = HttpUtility.ParseQueryString(context.Request.Url.Query); var query = HttpUtility.ParseQueryString(context.Request.Url.Query);
JToken jsonReply = new JObject();
context.Response.ContentType = "text/json"; context.Response.ContentType = "text/json";
context.Response.StatusCode = (int)HttpStatusCode.OK; context.Response.StatusCode = (int)HttpStatusCode.OK;
HandlerTask handlerTask;
switch (method) switch (method)
{ {
case WebApiMethod.GetConfigForm: case WebApiMethod.GetConfigForm:
try handlerTask = HandleConfigForm;
{
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;
}
break; break;
case WebApiMethod.ConfigureIndexer: case WebApiMethod.ConfigureIndexer:
try handlerTask = HandleConfigureIndexer;
{
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();
}
}
break; break;
case WebApiMethod.GetIndexers: case WebApiMethod.GetIndexers:
try handlerTask = HandleGetIndexers;
{
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;
}
break; break;
case WebApiMethod.TestIndexer: case WebApiMethod.TestIndexer:
try handlerTask = HandleTestIndexer;
{ break;
var postData = await ReadPostDataJson(context.Request.InputStream); case WebApiMethod.DeleteIndexer:
string indexerString = (string)postData["indexer"]; handlerTask = HandleDeleteIndexer;
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;
}
break; break;
default: default:
jsonReply["result"] = "error"; handlerTask = HandleInvalidApiMethod;
jsonReply["error"] = "Invalid API method";
break; break;
} }
JToken jsonReply = await handlerTask(context);
ReplyWithJson(context, jsonReply); ReplyWithJson(context, jsonReply);
} }
@ -186,5 +125,131 @@ namespace Jackett
context.Response.OutputStream.Close(); 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

View File

@ -13,7 +13,7 @@
<title>Jackett</title> <title>Jackett</title>
<style> <style>
body { body {
background-image: url("congruent_outline.png"); background-image: url("crissXcross.png");
background-repeat: repeat; background-repeat: repeat;
} }
@ -100,7 +100,6 @@
.indexer-add-content > .glyphicon { .indexer-add-content > .glyphicon {
font-size: 50px; font-size: 50px;
margin-top: 40%;
vertical-align: bottom; vertical-align: bottom;
} }
@ -177,12 +176,12 @@
<h3>Configured Indexers</h3> <h3>Configured Indexers</h3>
<div id="indexers"> <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"> <div class="indexer-add-content">
<span class="glyphicon glyphicon glyphicon-plus" aria-hidden="true"></span> <span class="glyphicon glyphicon glyphicon-plus" aria-hidden="true"></span>
<div class="light-text">Add</div> <div class="light-text">Add</div>
</div> </div>
</a> </button>
</div> </div>
@ -230,10 +229,18 @@
<div class="indexer-logo"><img src="logos/{{id}}.png" /></div> <div class="indexer-logo"><img src="logos/{{id}}.png" /></div>
<div class="indexer-name"><h3>{{name}}</h3></div> <div class="indexer-name"><h3>{{name}}</h3></div>
<div class="indexer-buttons"> <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-info btn-sm" target="_blank" href="{{site_link}}">
<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> Visit <span class="glyphicon glyphicon-new-window" aria-hidden="true"></span>
<a class="btn btn-danger btn-sm" href="#">Delete <span class="glyphicon glyphicon-trash" aria-hidden="true"></span></a> </a>
<a class="btn btn-primary btn-sm" href="#">Configure <span class="glyphicon glyphicon-wrench" 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>
<div class="indexer-host"> <div class="indexer-host">
<b>Torznab Host:</b> <b>Torznab Host:</b>
@ -300,6 +307,28 @@
$('#indexers').fadeIn(); $('#indexers').fadeIn();
prepareSetupButtons(); prepareSetupButtons();
prepareTestButtons(); 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() { function prepareSetupButtons() {