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,18 +82,63 @@ 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:
handlerTask = HandleConfigForm;
break;
case WebApiMethod.ConfigureIndexer:
handlerTask = HandleConfigureIndexer;
break;
case WebApiMethod.GetIndexers:
handlerTask = HandleGetIndexers;
break;
case WebApiMethod.TestIndexer:
handlerTask = HandleTestIndexer;
break;
case WebApiMethod.DeleteIndexer:
handlerTask = HandleDeleteIndexer;
break;
default:
handlerTask = HandleInvalidApiMethod;
break;
}
JToken jsonReply = await handlerTask(context);
ReplyWithJson(context, jsonReply);
}
async void ReplyWithJson(HttpListenerContext context, JToken json)
{
byte[] jsonBytes = Encoding.UTF8.GetBytes(json.ToString());
await context.Response.OutputStream.WriteAsync(jsonBytes, 0, jsonBytes.Length);
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 try
{ {
var postData = await ReadPostDataJson(context.Request.InputStream); var postData = await ReadPostDataJson(context.Request.InputStream);
@@ -107,8 +154,12 @@ namespace Jackett
jsonReply["result"] = "error"; jsonReply["result"] = "error";
jsonReply["error"] = ex.Message; jsonReply["error"] = ex.Message;
} }
break; return jsonReply;
case WebApiMethod.ConfigureIndexer: }
async Task<JToken> HandleConfigureIndexer(HttpListenerContext context)
{
JToken jsonReply = new JObject();
try try
{ {
var postData = await ReadPostDataJson(context.Request.InputStream); var postData = await ReadPostDataJson(context.Request.InputStream);
@@ -128,12 +179,18 @@ namespace Jackett
jsonReply["config"] = ((ExceptionWithConfigData)ex).ConfigData.ToJson(); jsonReply["config"] = ((ExceptionWithConfigData)ex).ConfigData.ToJson();
} }
} }
break; return jsonReply;
case WebApiMethod.GetIndexers: }
Task<JToken> HandleGetIndexers(HttpListenerContext context)
{
return Task<JToken>.Run(() =>
{
JToken jsonReply = new JObject();
try try
{ {
jsonReply["result"] = "success"; jsonReply["result"] = "success";
jsonReply["api_key"] = ApiKey.Generate(); jsonReply["api_key"] = ApiKey.CurrentKey;
JArray items = new JArray(); JArray items = new JArray();
foreach (var i in indexerManager.Indexers) foreach (var i in indexerManager.Indexers)
{ {
@@ -153,8 +210,13 @@ namespace Jackett
jsonReply["result"] = "error"; jsonReply["result"] = "error";
jsonReply["error"] = ex.Message; jsonReply["error"] = ex.Message;
} }
break; return jsonReply;
case WebApiMethod.TestIndexer: });
}
async Task<JToken> HandleTestIndexer(HttpListenerContext context)
{
JToken jsonReply = new JObject();
try try
{ {
var postData = await ReadPostDataJson(context.Request.InputStream); var postData = await ReadPostDataJson(context.Request.InputStream);
@@ -169,21 +231,24 @@ namespace Jackett
jsonReply["result"] = "error"; jsonReply["result"] = "error";
jsonReply["error"] = ex.Message; jsonReply["error"] = ex.Message;
} }
break; return jsonReply;
default:
jsonReply["result"] = "error";
jsonReply["error"] = "Invalid API method";
break;
} }
ReplyWithJson(context, jsonReply); async Task<JToken> HandleDeleteIndexer(HttpListenerContext context)
}
async void ReplyWithJson(HttpListenerContext context, JToken json)
{ {
byte[] jsonBytes = Encoding.UTF8.GetBytes(json.ToString()); JToken jsonReply = new JObject();
await context.Response.OutputStream.WriteAsync(jsonBytes, 0, jsonBytes.Length); try
context.Response.OutputStream.Close(); {
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() {