mirror of
https://github.com/Jackett/Jackett.git
synced 2025-09-17 17:34:09 +02:00
Better reverse proxy support
Added "base path override" config option that makes all links and redirects work with your reverse proxy. Fixed post config update reload to work properly. Make redirects and ajax calls use relative pathing.
This commit is contained in:
@@ -1,15 +1,17 @@
|
||||
$(document).ready(function () {
|
||||
var basePath = "";
|
||||
|
||||
$(document).ready(function () {
|
||||
$.ajaxSetup({ cache: false });
|
||||
window.jackettIsLocal = window.location.hostname === 'localhost' ||
|
||||
window.location.hostname === '127.0.0.1';
|
||||
|
||||
bindUIButtons();
|
||||
reloadIndexers();
|
||||
loadJackettSettings();
|
||||
reloadIndexers();
|
||||
});
|
||||
|
||||
function getJackettConfig(callback) {
|
||||
var jqxhr = $.get("/admin/get_jackett_config", function (data) {
|
||||
var jqxhr = $.get("get_jackett_config", function (data) {
|
||||
|
||||
callback(data);
|
||||
}).fail(function () {
|
||||
@@ -22,6 +24,8 @@ function loadJackettSettings() {
|
||||
$("#api-key-input").val(data.config.api_key);
|
||||
$("#app-version").html(data.app_version);
|
||||
$("#jackett-port").val(data.config.port);
|
||||
$("#jackett-basepathoverride").val(data.config.basepathoverride);
|
||||
basePath = data.config.basepathoverride;
|
||||
$("#jackett-savedir").val(data.config.blackholedir);
|
||||
$("#jackett-allowext").attr('checked', data.config.external);
|
||||
$("#jackett-allowupdate").attr('checked', data.config.updatedisabled);
|
||||
@@ -39,7 +43,7 @@ function reloadIndexers() {
|
||||
$('#indexers').hide();
|
||||
$('#indexers > .indexer').remove();
|
||||
$('#unconfigured-indexers').empty();
|
||||
var jqxhr = $.get("/admin/get_indexers", function (data) {
|
||||
var jqxhr = $.get("get_indexers", function (data) {
|
||||
displayIndexers(data.items);
|
||||
}).fail(function () {
|
||||
doNotify("Error loading indexers, request to Jackett server failed", "danger", "glyphicon glyphicon-alert");
|
||||
@@ -52,8 +56,8 @@ function displayIndexers(items) {
|
||||
$('#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);
|
||||
item.torznab_host = resolveUrl("/" + basePath + "/torznab/" + item.id);
|
||||
item.potato_host = resolveUrl("/" + basePath + "/potato/" + item.id);
|
||||
if (item.configured)
|
||||
$('#indexers').append(indexerTemplate(item));
|
||||
else
|
||||
@@ -92,7 +96,7 @@ function prepareDeleteButtons() {
|
||||
var $btn = $(btn);
|
||||
var id = $btn.data("id");
|
||||
$btn.click(function () {
|
||||
var jqxhr = $.post("/admin/delete_indexer", JSON.stringify({ indexer: id }), function (data) {
|
||||
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");
|
||||
}
|
||||
@@ -125,7 +129,7 @@ function prepareTestButtons() {
|
||||
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) {
|
||||
var jqxhr = $.post("test_indexer", JSON.stringify({ indexer: id }), function (data) {
|
||||
if (data.result == "error") {
|
||||
doNotify("Test failed for " + id + ": \n" + data.error, "danger", "glyphicon glyphicon-alert");
|
||||
}
|
||||
@@ -141,7 +145,7 @@ function prepareTestButtons() {
|
||||
|
||||
function displayIndexerSetup(id, link) {
|
||||
|
||||
var jqxhr = $.post("/admin/get_config_form", JSON.stringify({ indexer: id }), function (data) {
|
||||
var jqxhr = $.post("get_config_form", JSON.stringify({ indexer: id }), function (data) {
|
||||
if (data.result == "error") {
|
||||
doNotify("Error: " + data.error, "danger", "glyphicon glyphicon-alert");
|
||||
return;
|
||||
@@ -247,7 +251,7 @@ function populateSetupForm(indexerId, name, config, caps, link) {
|
||||
$goButton.prop('disabled', true);
|
||||
$goButton.html($('#spinner').html());
|
||||
|
||||
var jqxhr = $.post("/admin/configure_indexer", JSON.stringify(data), function (data) {
|
||||
var jqxhr = $.post("configure_indexer", JSON.stringify(data), function (data) {
|
||||
if (data.result == "error") {
|
||||
if (data.config) {
|
||||
populateConfigItems(configForm, data.config);
|
||||
@@ -321,7 +325,7 @@ function bindUIButtons() {
|
||||
});
|
||||
|
||||
$("#jackett-show-releases").click(function () {
|
||||
var jqxhr = $.get("/admin/GetCache", function (data) {
|
||||
var jqxhr = $.get("GetCache", function (data) {
|
||||
var releaseTemplate = Handlebars.compile($("#jackett-releases").html());
|
||||
var item = { releases: data, Title: 'Releases' };
|
||||
var releaseDialog = $(releaseTemplate(item));
|
||||
@@ -403,7 +407,7 @@ function bindUIButtons() {
|
||||
|
||||
$("#jackett-show-search").click(function () {
|
||||
$('#select-indexer-modal').remove();
|
||||
var jqxhr = $.get("/admin/get_indexers", function (data) {
|
||||
var jqxhr = $.get("get_indexers", function (data) {
|
||||
var scope = {
|
||||
items: data.items
|
||||
};
|
||||
@@ -459,7 +463,7 @@ function bindUIButtons() {
|
||||
$('#searchResults').empty();
|
||||
|
||||
$('#jackett-search-perform').html($('#spinner').html());
|
||||
var jqxhr = $.post("/admin/search", queryObj, function (data) {
|
||||
var jqxhr = $.post("search", queryObj, function (data) {
|
||||
$('#jackett-search-perform').html('Search trackers');
|
||||
var resultsTemplate = Handlebars.compile($("#jackett-search-results").html());
|
||||
var results = $('#searchResults');
|
||||
@@ -534,7 +538,7 @@ function bindUIButtons() {
|
||||
});
|
||||
|
||||
$("#view-jackett-logs").click(function () {
|
||||
var jqxhr = $.get("/admin/GetLogs", function (data) {
|
||||
var jqxhr = $.get("GetLogs", function (data) {
|
||||
var releaseTemplate = Handlebars.compile($("#jackett-logs").html());
|
||||
var item = { logs: data };
|
||||
var releaseDialog = $(releaseTemplate(item));
|
||||
@@ -548,6 +552,7 @@ function bindUIButtons() {
|
||||
|
||||
$("#change-jackett-port").click(function () {
|
||||
var jackett_port = $("#jackett-port").val();
|
||||
var jackett_basepathoverride = $("#jackett-basepathoverride").val();
|
||||
var jackett_external = $("#jackett-allowext").is(':checked');
|
||||
var jackett_update = $("#jackett-allowupdate").is(':checked');
|
||||
var jackett_prerelease = $("#jackett-prerelease").is(':checked');
|
||||
@@ -558,21 +563,17 @@ function bindUIButtons() {
|
||||
updatedisabled: jackett_update,
|
||||
prerelease: jackett_prerelease,
|
||||
blackholedir: $("#jackett-savedir").val(),
|
||||
logging: jackett_logging
|
||||
logging: jackett_logging,
|
||||
basepathoverride: jackett_basepathoverride
|
||||
};
|
||||
var jqxhr = $.post("/admin/set_config", JSON.stringify(jsonObject), function (data) {
|
||||
var jqxhr = $.post("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;
|
||||
}
|
||||
window.location.reload(true);
|
||||
}, 3000);
|
||||
|
||||
}
|
||||
@@ -582,7 +583,7 @@ function bindUIButtons() {
|
||||
});
|
||||
|
||||
$("#trigger-updater").click(function () {
|
||||
var jqxhr = $.get("/admin/trigger_update", function (data) {
|
||||
var jqxhr = $.get("trigger_update", function (data) {
|
||||
if (data.result == "error") {
|
||||
doNotify("Error: " + data.error, "danger", "glyphicon glyphicon-alert");
|
||||
return;
|
||||
@@ -598,7 +599,7 @@ function bindUIButtons() {
|
||||
var password = $("#jackett-adminpwd").val();
|
||||
var jsonObject = { password: password };
|
||||
|
||||
var jqxhr = $.post("/admin/set_admin_password", JSON.stringify(jsonObject), function (data) {
|
||||
var jqxhr = $.post("set_admin_password", JSON.stringify(jsonObject), function (data) {
|
||||
|
||||
if (data.result == "error") {
|
||||
doNotify("Error: " + data.error, "danger", "glyphicon glyphicon-alert");
|
||||
|
@@ -4,27 +4,27 @@
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel='shortcut icon' type='image/x-icon' href='/favicon.ico' />
|
||||
<script src="../../libs/filesize.min.js"></script>
|
||||
<script src="../../libs/jquery.min.js"></script>
|
||||
<script src="../../libs/jquery.dataTables.min.js"></script>
|
||||
<script src="../../libs/handlebars.min.js"></script>
|
||||
<script src="../../libs/moment.min.js"></script>
|
||||
<script src="../../libs/handlebarsmoment.js"></script>
|
||||
<script src="../../bootstrap/bootstrap.min.js"></script>
|
||||
<script src="../../libs/bootstrap-notify.js"></script>
|
||||
<script src="../libs/filesize.min.js"></script>
|
||||
<script src="../libs/jquery.min.js"></script>
|
||||
<script src="../libs/jquery.dataTables.min.js"></script>
|
||||
<script src="../libs/handlebars.min.js"></script>
|
||||
<script src="../libs/moment.min.js"></script>
|
||||
<script src="../libs/handlebarsmoment.js"></script>
|
||||
<script src="../bootstrap/bootstrap.min.js"></script>
|
||||
<script src="../libs/bootstrap-notify.js"></script>
|
||||
<script src="https://www.google.com/recaptcha/api.js?onload=onloadCallback&render=explicit" async defer></script>
|
||||
|
||||
<link href="../../bootstrap/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="../../animate.css" rel="stylesheet">
|
||||
<link href="../../custom.css" rel="stylesheet">
|
||||
<link href="../../css/jquery.dataTables.css" rel="stylesheet">
|
||||
<link rel="stylesheet" href="../../css/font-awesome.min.css">
|
||||
<link href="../bootstrap/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="../animate.css" rel="stylesheet">
|
||||
<link href="../custom.css" rel="stylesheet">
|
||||
<link href="../css/jquery.dataTables.css" rel="stylesheet">
|
||||
<link rel="stylesheet" href="../css/font-awesome.min.css">
|
||||
<title>Jackett</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="page">
|
||||
|
||||
<img id="logo" src="../../jacket_medium.png" alt="Logo" /><span id="header-title">Jackett</span>
|
||||
<img id="logo" src="../jacket_medium.png" alt="Logo" /><span id="header-title">Jackett</span>
|
||||
|
||||
<div class="pull-right jackett-apikey">
|
||||
<span class="input-header">API Key: </span>
|
||||
@@ -84,10 +84,13 @@
|
||||
Logout
|
||||
</a>
|
||||
</div>
|
||||
<div class="input-area">
|
||||
<span class="input-header">Base Path Override: </span>
|
||||
<input id="jackett-basepathoverride" class="form-control input-right" type="text" value="" placeholder="jackett/">
|
||||
</div>
|
||||
<div class="input-area">
|
||||
<span class="input-header">Server port: </span>
|
||||
<input id="jackett-port" class="form-control input-right" type="text" value="" placeholder="9117">
|
||||
|
||||
</div>
|
||||
<div class="input-area">
|
||||
<span class="input-header">Manual download blackhole directory: </span>
|
||||
@@ -172,7 +175,7 @@
|
||||
<div class="indexer-logo">
|
||||
<!-- Make section browser searchable -->
|
||||
<span class="hidden-name">{{name}}</span>
|
||||
<img alt="{{name}}" title="{{name}}" src="../../logos/{{id}}.png" />
|
||||
<img alt="{{name}}" title="{{name}}" src="../logos/{{id}}.png" />
|
||||
</div>
|
||||
<div class="indexer-buttons">
|
||||
<button class="btn btn-primary btn-sm indexer-setup" data-id="{{id}}" data-link="{{site_link}}">
|
||||
@@ -206,7 +209,7 @@
|
||||
<div class="indexer-logo indexer-setup" data-id="{{id}}" data-link="{{site_link}}">
|
||||
<!-- Make section browser searchable -->
|
||||
<span class="hidden-name">{{name}}</span>
|
||||
<img alt="{{name}}" title="{{name}}" src="../../logos/{{id}}.png" />
|
||||
<img alt="{{name}}" title="{{name}}" src="../logos/{{id}}.png" />
|
||||
|
||||
</div>
|
||||
</div>
|
||||
@@ -478,6 +481,6 @@
|
||||
<span class="spinner glyphicon glyphicon-refresh"></span>
|
||||
</script>
|
||||
|
||||
<script src="../../custom.js"></script>
|
||||
<script src="../custom.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
@@ -6,28 +6,28 @@
|
||||
|
||||
<link rel='shortcut icon' type='image/x-icon' href='/favicon.ico' />
|
||||
|
||||
<script src="../../libs/jquery.min.js"></script>
|
||||
<script src="../../libs/jquery.dataTables.min.js"></script>
|
||||
<script src="../../libs/handlebars.min.js"></script>
|
||||
<script src="../../libs/moment.min.js"></script>
|
||||
<script src="../../libs/handlebarsmoment.js"></script>
|
||||
<script src="../../bootstrap/bootstrap.min.js"></script>
|
||||
<script src="../../libs/bootstrap-notify.js"></script>
|
||||
<script src="../libs/jquery.min.js"></script>
|
||||
<script src="../libs/jquery.dataTables.min.js"></script>
|
||||
<script src="../libs/handlebars.min.js"></script>
|
||||
<script src="../libs/moment.min.js"></script>
|
||||
<script src="../libs/handlebarsmoment.js"></script>
|
||||
<script src="../bootstrap/bootstrap.min.js"></script>
|
||||
<script src="../libs/bootstrap-notify.js"></script>
|
||||
|
||||
<link href="../../bootstrap/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="../../animate.css" rel="stylesheet">
|
||||
<link href="../../custom.css" rel="stylesheet">
|
||||
<link href="../bootstrap/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="../animate.css" rel="stylesheet">
|
||||
<link href="../custom.css" rel="stylesheet">
|
||||
|
||||
<title>Jackett</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="page">
|
||||
|
||||
<img id="logo" src="../../jacket_medium.png" /><span id="header-title">Jackett</span>
|
||||
<img id="logo" src="../jacket_medium.png" /><span id="header-title">Jackett</span>
|
||||
|
||||
<hr />
|
||||
<h1>Login</h1>
|
||||
<form action="../Admin/Dashboard" method="post">
|
||||
<form action="Dashboard" method="post">
|
||||
<div class="input-area">
|
||||
<span class="input-header">Admin password</span>
|
||||
<input id="password" name="password" class="form-control input-right" type="password">
|
||||
|
@@ -80,7 +80,7 @@ namespace Jackett.Controllers
|
||||
var ctx = Request.GetOwinContext();
|
||||
var authManager = ctx.Authentication;
|
||||
authManager.SignOut("ApplicationCookie");
|
||||
return Redirect("/Admin/Dashboard");
|
||||
return Redirect("Admin/Dashboard");
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
@@ -318,6 +318,7 @@ namespace Jackett.Controllers
|
||||
cfg["prerelease"] = serverService.Config.UpdatePrerelease;
|
||||
cfg["password"] = string.IsNullOrEmpty(serverService.Config.AdminPassword) ? string.Empty : serverService.Config.AdminPassword.Substring(0, 10);
|
||||
cfg["logging"] = Startup.TracingEnabled;
|
||||
cfg["basepathoverride"] = serverService.Config.BasePathOverride;
|
||||
|
||||
|
||||
jsonReply["config"] = cfg;
|
||||
@@ -349,9 +350,12 @@ namespace Jackett.Controllers
|
||||
bool updateDisabled = (bool)postData["updatedisabled"];
|
||||
bool preRelease = (bool)postData["prerelease"];
|
||||
bool logging = (bool)postData["logging"];
|
||||
string basePathOverride = (string)postData["basepathoverride"];
|
||||
|
||||
Engine.Server.Config.UpdateDisabled = updateDisabled;
|
||||
Engine.Server.Config.UpdatePrerelease = preRelease;
|
||||
Engine.Server.Config.BasePathOverride = basePathOverride;
|
||||
Startup.BasePath = Engine.Server.BasePath();
|
||||
Engine.Server.SaveConfig();
|
||||
|
||||
Engine.SetLogLevel(logging ? LogLevel.Debug : LogLevel.Info);
|
||||
@@ -446,7 +450,7 @@ namespace Jackett.Controllers
|
||||
|
||||
private void ConfigureCacheResults(List<TrackerCacheResult> results)
|
||||
{
|
||||
var serverUrl = string.Format("{0}://{1}:{2}/", Request.RequestUri.Scheme, Request.RequestUri.Host, Request.RequestUri.Port);
|
||||
var serverUrl = string.Format("{0}://{1}:{2}{3}", Request.RequestUri.Scheme, Request.RequestUri.Host, Request.RequestUri.Port, serverService.BasePath());
|
||||
foreach (var result in results)
|
||||
{
|
||||
var link = result.Link;
|
||||
|
@@ -117,7 +117,7 @@ namespace Jackett.Controllers
|
||||
}
|
||||
|
||||
releases = indexer.FilterResults(torznabQuery, releases);
|
||||
var serverUrl = string.Format("{0}://{1}:{2}/", Request.RequestUri.Scheme, Request.RequestUri.Host, Request.RequestUri.Port);
|
||||
var serverUrl = string.Format("{0}://{1}:{2}{3}", Request.RequestUri.Scheme, Request.RequestUri.Host, Request.RequestUri.Port, serverService.BasePath());
|
||||
var potatoResponse = new TorrentPotatoResponse();
|
||||
|
||||
releases = TorznabUtil.FilterResultsToTitle(releases, torznabQuery.SanitizedSearchTerm, year);
|
||||
|
@@ -100,7 +100,7 @@ namespace Jackett.Controllers
|
||||
|
||||
logger.Info(logBuilder.ToString());
|
||||
|
||||
var serverUrl = string.Format("{0}://{1}:{2}/", Request.RequestUri.Scheme, Request.RequestUri.Host, Request.RequestUri.Port);
|
||||
var serverUrl = string.Format("{0}://{1}:{2}{3}", Request.RequestUri.Scheme, Request.RequestUri.Host, Request.RequestUri.Port, serverService.BasePath());
|
||||
var resultPage = new ResultPage(new ChannelInfo
|
||||
{
|
||||
Title = indexer.DisplayName,
|
||||
|
@@ -23,6 +23,7 @@ namespace Jackett.Models.Config
|
||||
public string BlackholeDir { get; set; }
|
||||
public bool UpdateDisabled { get; set; }
|
||||
public bool UpdatePrerelease { get; set; }
|
||||
public string BasePathOverride { get; set; }
|
||||
|
||||
public string[] GetListenAddresses(bool? external = null)
|
||||
{
|
||||
|
@@ -19,6 +19,7 @@ using System.Net;
|
||||
using System.Net.NetworkInformation;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web;
|
||||
|
||||
@@ -32,7 +33,8 @@ namespace Jackett.Services
|
||||
void ReserveUrls(bool doInstall = true);
|
||||
ServerConfig Config { get; }
|
||||
void SaveConfig();
|
||||
Uri ConvertToProxyLink(Uri link, string serverUrl, string indexerId, string action = "dl", string file = "t.torrent");
|
||||
Uri ConvertToProxyLink(Uri link, string serverUrl, string indexerId, string action = "dl", string file = "t.torrent");
|
||||
string BasePath();
|
||||
}
|
||||
|
||||
public class ServerService : IServerService
|
||||
@@ -77,6 +79,23 @@ namespace Jackett.Services
|
||||
return new Uri(proxyLink);
|
||||
}
|
||||
|
||||
public string BasePath()
|
||||
{
|
||||
if (config.BasePathOverride == null || config.BasePathOverride == "") {
|
||||
return "/";
|
||||
}
|
||||
var path = config.BasePathOverride;
|
||||
if (!path.EndsWith("/"))
|
||||
{
|
||||
path = path + "/";
|
||||
}
|
||||
if (!path.StartsWith("/"))
|
||||
{
|
||||
path = "/" + path;
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
private void LoadConfig()
|
||||
{
|
||||
// Load config
|
||||
@@ -139,6 +158,7 @@ namespace Jackett.Services
|
||||
logger.Info("Starting web server at " + config.GetListenAddresses()[0]);
|
||||
var startOptions = new StartOptions();
|
||||
config.GetListenAddresses().ToList().ForEach(u => startOptions.Urls.Add(u));
|
||||
Startup.BasePath = BasePath();
|
||||
_server = WebApp.Start<Startup>(startOptions);
|
||||
logger.Debug("Web server started");
|
||||
updater.StartUpdateChecker();
|
||||
|
@@ -58,6 +58,12 @@ namespace Jackett
|
||||
set;
|
||||
}
|
||||
|
||||
public static string BasePath
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public void Configuration(IAppBuilder appBuilder)
|
||||
{
|
||||
// Configure Web API for self-host.
|
||||
|
@@ -1,4 +1,5 @@
|
||||
using Microsoft.Owin;
|
||||
using Jackett.Services;
|
||||
using Microsoft.Owin;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
@@ -11,7 +12,7 @@ namespace Jackett.Utils
|
||||
{
|
||||
public WebApiRootRedirectMiddleware(OwinMiddleware next)
|
||||
: base(next)
|
||||
{
|
||||
{
|
||||
}
|
||||
|
||||
public async override Task Invoke(IOwinContext context)
|
||||
@@ -21,7 +22,9 @@ namespace Jackett.Utils
|
||||
{
|
||||
// 301 is the status code of permanent redirect
|
||||
context.Response.StatusCode = 301;
|
||||
context.Response.Headers.Set("Location", "/Admin/Dashboard");
|
||||
var redir = Startup.BasePath + "Admin/Dashboard";
|
||||
Engine.Logger.Info("redirecting to " + redir);
|
||||
context.Response.Headers.Set("Location", redir);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
Reference in New Issue
Block a user