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 static string CurrentKey;
const string chars = "abcdefghijklmnopqrstuvwxyz0123456789";
public static string Generate()

View File

@ -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();
}
}
}

View File

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

View File

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

View File

@ -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();

View File

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

View File

@ -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() {