mirror of
https://github.com/SickGear/SickGear.git
synced 2025-01-05 17:43:37 +00:00
Change replace HTTP auth with a login page
Change to improve webserve code Add logout menu item with confirmation Add 404 error page
This commit is contained in:
parent
619aca1965
commit
d0326cda7e
25 changed files with 966 additions and 440 deletions
|
@ -68,6 +68,10 @@
|
|||
* Add cleansing of text used in the processes to a add a show
|
||||
* Add sorting of AniDB available group results
|
||||
* Add error handling and related UI feedback to reflect result of AniDB communications
|
||||
* Change replace HTTP auth with a login page
|
||||
* Change to improve webserve code
|
||||
* Add logout menu item with confirmation
|
||||
* Add 404 error page
|
||||
|
||||
[develop changelog]
|
||||
* Change uT params from unicode to str.format as magnet URLs worked but sending files in POST bodies failed
|
||||
|
|
|
@ -23,6 +23,17 @@ from __future__ import with_statement
|
|||
import os.path
|
||||
import sys
|
||||
|
||||
sickbeardPath = os.path.split(os.path.split(sys.argv[0])[0])[0]
|
||||
sys.path.append(os.path.join(sickbeardPath, 'lib'))
|
||||
sys.path.append(sickbeardPath)
|
||||
|
||||
try:
|
||||
import requests
|
||||
except ImportError:
|
||||
print ('You need to install python requests library')
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
# Try importing Python 2 modules using new names
|
||||
try:
|
||||
import ConfigParser as configparser
|
||||
|
@ -35,77 +46,63 @@ except ImportError:
|
|||
import urllib.request as urllib2
|
||||
from urllib.parse import urlencode
|
||||
|
||||
# workaround for broken urllib2 in python 2.6.5: wrong credentials lead to an infinite recursion
|
||||
if sys.version_info >= (2, 6, 5) and sys.version_info < (2, 6, 6):
|
||||
class HTTPBasicAuthHandler(urllib2.HTTPBasicAuthHandler):
|
||||
def retry_http_basic_auth(self, host, req, realm):
|
||||
# don't retry if auth failed
|
||||
if req.get_header(self.auth_header, None) is not None:
|
||||
return None
|
||||
|
||||
return urllib2.HTTPBasicAuthHandler.retry_http_basic_auth(self, host, req, realm)
|
||||
|
||||
else:
|
||||
HTTPBasicAuthHandler = urllib2.HTTPBasicAuthHandler
|
||||
|
||||
|
||||
def processEpisode(dir_to_process, org_NZB_name=None, status=None):
|
||||
# Default values
|
||||
host = "localhost"
|
||||
port = "8081"
|
||||
username = ""
|
||||
password = ""
|
||||
host = 'localhost'
|
||||
port = '8081'
|
||||
username = ''
|
||||
password = ''
|
||||
ssl = 0
|
||||
web_root = "/"
|
||||
web_root = '/'
|
||||
|
||||
default_url = host + ":" + port + web_root
|
||||
default_url = host + ':' + port + web_root
|
||||
if ssl:
|
||||
default_url = "https://" + default_url
|
||||
default_url = 'https://' + default_url
|
||||
else:
|
||||
default_url = "http://" + default_url
|
||||
default_url = 'http://' + default_url
|
||||
|
||||
# Get values from config_file
|
||||
config = configparser.RawConfigParser()
|
||||
config_filename = os.path.join(os.path.dirname(sys.argv[0]), "autoProcessTV.cfg")
|
||||
config_filename = os.path.join(os.path.dirname(sys.argv[0]), 'autoProcessTV.cfg')
|
||||
|
||||
if not os.path.isfile(config_filename):
|
||||
print ("ERROR: " + config_filename + " doesn\'t exist")
|
||||
print ("copy /rename " + config_filename + ".sample and edit\n")
|
||||
print ("Trying default url: " + default_url + "\n")
|
||||
print ('ERROR: ' + config_filename + " doesn't exist")
|
||||
print ('copy /rename ' + config_filename + '.sample and edit\n')
|
||||
print ('Trying default url: ' + default_url + '\n')
|
||||
|
||||
else:
|
||||
try:
|
||||
print ("Loading config from " + config_filename + "\n")
|
||||
print ('Loading config from ' + config_filename + '\n')
|
||||
|
||||
with open(config_filename, "r") as fp:
|
||||
with open(config_filename, 'r') as fp:
|
||||
config.readfp(fp)
|
||||
|
||||
# Replace default values with config_file values
|
||||
host = config.get("SickBeard", "host")
|
||||
port = config.get("SickBeard", "port")
|
||||
username = config.get("SickBeard", "username")
|
||||
password = config.get("SickBeard", "password")
|
||||
host = config.get('SickBeard', 'host')
|
||||
port = config.get('SickBeard', 'port')
|
||||
username = config.get('SickBeard', 'username')
|
||||
password = config.get('SickBeard', 'password')
|
||||
|
||||
try:
|
||||
ssl = int(config.get("SickBeard", "ssl"))
|
||||
ssl = int(config.get('SickBeard', 'ssl'))
|
||||
|
||||
except (configparser.NoOptionError, ValueError):
|
||||
pass
|
||||
|
||||
try:
|
||||
web_root = config.get("SickBeard", "web_root")
|
||||
if not web_root.startswith("/"):
|
||||
web_root = "/" + web_root
|
||||
web_root = config.get('SickBeard', 'web_root')
|
||||
if not web_root.startswith('/'):
|
||||
web_root = '/' + web_root
|
||||
|
||||
if not web_root.endswith("/"):
|
||||
web_root = web_root + "/"
|
||||
if not web_root.endswith('/'):
|
||||
web_root = web_root + '/'
|
||||
|
||||
except configparser.NoOptionError:
|
||||
pass
|
||||
|
||||
except EnvironmentError:
|
||||
e = sys.exc_info()[1]
|
||||
print ("Could not read configuration file: " + str(e))
|
||||
print ('Could not read configuration file: ' + str(e))
|
||||
# There was a config_file, don't use default values but exit
|
||||
sys.exit(1)
|
||||
|
||||
|
@ -121,34 +118,33 @@ def processEpisode(dir_to_process, org_NZB_name=None, status=None):
|
|||
params['failed'] = status
|
||||
|
||||
if ssl:
|
||||
protocol = "https://"
|
||||
protocol = 'https://'
|
||||
else:
|
||||
protocol = "http://"
|
||||
protocol = 'http://'
|
||||
|
||||
url = protocol + host + ":" + port + web_root + "home/postprocess/processEpisode?" + urlencode(params)
|
||||
url = protocol + host + ':' + port + web_root + 'home/postprocess/processEpisode'
|
||||
login_url = protocol + host + ':' + port + web_root + 'login'
|
||||
|
||||
print ("Opening URL: " + url)
|
||||
print ('Opening URL: ' + url)
|
||||
|
||||
try:
|
||||
password_mgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
|
||||
password_mgr.add_password(None, url, username, password)
|
||||
handler = HTTPBasicAuthHandler(password_mgr)
|
||||
opener = urllib2.build_opener(handler)
|
||||
urllib2.install_opener(opener)
|
||||
|
||||
result = opener.open(url).readlines()
|
||||
|
||||
for line in result:
|
||||
if line:
|
||||
print (line.strip())
|
||||
sess = requests.Session()
|
||||
sess.post(login_url, data={'username': username, 'password': password}, stream=True, verify=False)
|
||||
result = sess.get(url, params=params, stream=True, verify=False)
|
||||
if result.status_code == 401:
|
||||
print 'Verify and use correct username and password in autoProcessTV.cfg'
|
||||
else:
|
||||
for line in result.iter_lines():
|
||||
if line:
|
||||
print (line.strip())
|
||||
|
||||
except IOError:
|
||||
e = sys.exc_info()[1]
|
||||
print ("Unable to open URL: " + str(e))
|
||||
print ('Unable to open URL: ' + str(e))
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print ("This module is supposed to be used as import in other scripts and not run standalone.")
|
||||
print ("Use sabToSickBeard instead.")
|
||||
if __name__ == '__main__':
|
||||
print ('This module is supposed to be used as import in other scripts and not run standalone.')
|
||||
print ('Use sabToSickBeard instead.')
|
||||
sys.exit(1)
|
|
@ -6,20 +6,24 @@ import ConfigParser
|
|||
import logging
|
||||
|
||||
sickbeardPath = os.path.split(os.path.split(sys.argv[0])[0])[0]
|
||||
sys.path.append(os.path.join( sickbeardPath, 'lib'))
|
||||
sys.path.append(os.path.join(sickbeardPath, 'lib'))
|
||||
sys.path.append(sickbeardPath)
|
||||
configFilename = os.path.join(sickbeardPath, "config.ini")
|
||||
configFilename = os.path.join(sickbeardPath, 'config.ini')
|
||||
|
||||
import requests
|
||||
try:
|
||||
import requests
|
||||
except ImportError:
|
||||
print ('You need to install python requests library')
|
||||
sys.exit(1)
|
||||
|
||||
config = ConfigParser.ConfigParser()
|
||||
|
||||
try:
|
||||
fp = open(configFilename, "r")
|
||||
fp = open(configFilename, 'r')
|
||||
config.readfp(fp)
|
||||
fp.close()
|
||||
except IOError, e:
|
||||
print "Could not find/read Sickbeard config.ini: " + str(e)
|
||||
print 'Could not find/read Sickbeard config.ini: ' + str(e)
|
||||
print 'Possibly wrong mediaToSickbeard.py location. Ensure the file is in the autoProcessTV subdir of your Sickbeard installation'
|
||||
time.sleep(3)
|
||||
sys.exit(1)
|
||||
|
@ -28,7 +32,7 @@ scriptlogger = logging.getLogger('mediaToSickbeard')
|
|||
formatter = logging.Formatter('%(asctime)s %(levelname)-8s MEDIATOSICKBEARD :: %(message)s', '%b-%d %H:%M:%S')
|
||||
|
||||
# Get the log dir setting from SB config
|
||||
logdirsetting = config.get("General", "log_dir") if config.get("General", "log_dir") else 'Logs'
|
||||
logdirsetting = config.get('General', 'log_dir') if config.get('General', 'log_dir') else 'Logs'
|
||||
# put the log dir inside the SickBeard dir, unless an absolute path
|
||||
logdir = os.path.normpath(os.path.join(sickbeardPath, logdirsetting))
|
||||
logfile = os.path.join(logdir, 'sickbeard.log')
|
||||
|
@ -49,7 +53,7 @@ def utorrent():
|
|||
# print 'Calling utorrent'
|
||||
if len(sys.argv) < 2:
|
||||
scriptlogger.error('No folder supplied - is this being called from uTorrent?')
|
||||
print "No folder supplied - is this being called from uTorrent?"
|
||||
print 'No folder supplied - is this being called from uTorrent?'
|
||||
time.sleep(3)
|
||||
sys.exit()
|
||||
|
||||
|
@ -69,7 +73,7 @@ def deluge():
|
|||
|
||||
if len(sys.argv) < 4:
|
||||
scriptlogger.error('No folder supplied - is this being called from Deluge?')
|
||||
print "No folder supplied - is this being called from Deluge?"
|
||||
print 'No folder supplied - is this being called from Deluge?'
|
||||
time.sleep(3)
|
||||
sys.exit()
|
||||
|
||||
|
@ -82,7 +86,7 @@ def blackhole():
|
|||
|
||||
if None != os.getenv('TR_TORRENT_DIR'):
|
||||
scriptlogger.debug('Processing script triggered by Transmission')
|
||||
print "Processing script triggered by Transmission"
|
||||
print 'Processing script triggered by Transmission'
|
||||
scriptlogger.debug(u'TR_TORRENT_DIR: ' + os.getenv('TR_TORRENT_DIR'))
|
||||
scriptlogger.debug(u'TR_TORRENT_NAME: ' + os.getenv('TR_TORRENT_NAME'))
|
||||
dirName = os.getenv('TR_TORRENT_DIR')
|
||||
|
@ -90,7 +94,7 @@ def blackhole():
|
|||
else:
|
||||
if len(sys.argv) < 2:
|
||||
scriptlogger.error('No folder supplied - Your client should invoke the script with a Dir and a Relese Name')
|
||||
print "No folder supplied - Your client should invoke the script with a Dir and a Relese Name"
|
||||
print 'No folder supplied - Your client should invoke the script with a Dir and a Release Name'
|
||||
time.sleep(3)
|
||||
sys.exit()
|
||||
|
||||
|
@ -99,50 +103,27 @@ def blackhole():
|
|||
|
||||
return (dirName, nzbName)
|
||||
|
||||
#def sabnzb():
|
||||
# if len(sys.argv) < 2:
|
||||
# scriptlogger.error('No folder supplied - is this being called from SABnzbd?')
|
||||
# print "No folder supplied - is this being called from SABnzbd?"
|
||||
# sys.exit()
|
||||
# elif len(sys.argv) >= 3:
|
||||
# dirName = sys.argv[1]
|
||||
# nzbName = sys.argv[2]
|
||||
# else:
|
||||
# dirName = sys.argv[1]
|
||||
#
|
||||
# return (dirName, nzbName)
|
||||
#
|
||||
#def hella():
|
||||
# if len(sys.argv) < 4:
|
||||
# scriptlogger.error('No folder supplied - is this being called from HellaVCR?')
|
||||
# print "No folder supplied - is this being called from HellaVCR?"
|
||||
# sys.exit()
|
||||
# else:
|
||||
# dirName = sys.argv[3]
|
||||
# nzbName = sys.argv[2]
|
||||
#
|
||||
# return (dirName, nzbName)
|
||||
|
||||
def main():
|
||||
scriptlogger.info(u'Starting external PostProcess script ' + __file__)
|
||||
|
||||
host = config.get("General", "web_host")
|
||||
port = config.get("General", "web_port")
|
||||
username = config.get("General", "web_username")
|
||||
password = config.get("General", "web_password")
|
||||
host = config.get('General', 'web_host')
|
||||
port = config.get('General', 'web_port')
|
||||
username = config.get('General', 'web_username')
|
||||
password = config.get('General', 'web_password')
|
||||
try:
|
||||
ssl = int(config.get("General", "enable_https"))
|
||||
ssl = int(config.get('General', 'enable_https'))
|
||||
except (ConfigParser.NoOptionError, ValueError):
|
||||
ssl = 0
|
||||
|
||||
try:
|
||||
web_root = config.get("General", "web_root")
|
||||
web_root = config.get('General', 'web_root')
|
||||
except ConfigParser.NoOptionError:
|
||||
web_root = ""
|
||||
web_root = ''
|
||||
|
||||
tv_dir = config.get("General", "tv_download_dir")
|
||||
use_torrents = int(config.get("General", "use_torrents"))
|
||||
torrent_method = config.get("General", "torrent_method")
|
||||
tv_dir = config.get('General', 'tv_download_dir')
|
||||
use_torrents = int(config.get('General', 'use_torrents'))
|
||||
torrent_method = config.get('General', 'torrent_method')
|
||||
|
||||
if not use_torrents:
|
||||
scriptlogger.error(u'Enable Use Torrent on Sickbeard to use this Script. Aborting!')
|
||||
|
@ -182,28 +163,31 @@ def main():
|
|||
params['nzbName'] = nzbName
|
||||
|
||||
if ssl:
|
||||
protocol = "https://"
|
||||
protocol = 'https://'
|
||||
else:
|
||||
protocol = "http://"
|
||||
protocol = 'http://'
|
||||
|
||||
if host == '0.0.0.0':
|
||||
host = 'localhost'
|
||||
|
||||
url = protocol + host + ":" + port + web_root + "/home/postprocess/processEpisode"
|
||||
url = protocol + host + ':' + port + web_root + '/home/postprocess/processEpisode'
|
||||
login_url = protocol + host + ':' + port + web_root + '/login'
|
||||
|
||||
scriptlogger.debug("Opening URL: " + url + ' with params=' + str(params))
|
||||
print "Opening URL: " + url + ' with params=' + str(params)
|
||||
scriptlogger.debug('Opening URL: ' + url + ' with params=' + str(params))
|
||||
print 'Opening URL: ' + url + ' with params=' + str(params)
|
||||
|
||||
try:
|
||||
response = requests.get(url, auth=(username, password), params=params, verify=False)
|
||||
sess = requests.Session()
|
||||
sess.post(login_url, data={'username': username, 'password': password}, stream=True, verify=False)
|
||||
response = sess.get(url, auth=(username, password), params=params, verify=False, allow_redirects=False)
|
||||
except Exception, e:
|
||||
scriptlogger.error(u': Unknown exception raised when opening url: ' + str(e))
|
||||
time.sleep(3)
|
||||
sys.exit()
|
||||
|
||||
if response.status_code == 401:
|
||||
scriptlogger.error(u'Invalid Sickbeard Username or Password, check your config')
|
||||
print 'Invalid Sickbeard Username or Password, check your config'
|
||||
scriptlogger.error(u'Verify and use correct username and password in autoProcessTV.cfg')
|
||||
print 'Verify and use correct username and password in autoProcessTV.cfg'
|
||||
time.sleep(3)
|
||||
sys.exit()
|
||||
|
||||
|
|
98
gui/slick/css/dark-login.css
Normal file
98
gui/slick/css/dark-login.css
Normal file
|
@ -0,0 +1,98 @@
|
|||
/* =======================================================================
|
||||
login.tmpl
|
||||
========================================================================== */
|
||||
|
||||
.login-remember span {
|
||||
color: #aaa
|
||||
}
|
||||
|
||||
/* =======================================================================
|
||||
Global
|
||||
========================================================================== */
|
||||
|
||||
.red-text {
|
||||
color: #d33;
|
||||
}
|
||||
|
||||
/* =======================================================================
|
||||
bootstrap Overrides
|
||||
========================================================================== */
|
||||
|
||||
body {
|
||||
color: #fff;
|
||||
background-color: #222;
|
||||
}
|
||||
|
||||
input, textarea, select, .uneditable-input {
|
||||
width: auto;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.btn {
|
||||
color: #fff;
|
||||
text-shadow: 0 1px 1px rgba(0, 0, 0, 0.75);
|
||||
background-color: #2672B6;
|
||||
*background-color: #2672B6;
|
||||
background-image: -ms-linear-gradient(top, #297AB8, #15528F);
|
||||
background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#297AB8), to(#15528F));
|
||||
background-image: -webkit-linear-gradient(top, #297AB8, #15528F);
|
||||
background-image: -o-linear-gradient(top, #297AB8, #15528F);
|
||||
background-image: linear-gradient(top, #297AB8, #15528F);
|
||||
background-image: -moz-linear-gradient(top, #297AB8, #15528F);
|
||||
border: 1px solid #111;
|
||||
border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
|
||||
border-color: #111 #111 #111;
|
||||
border-bottom-color: #111;
|
||||
filter: progid:dximagetransform.microsoft.gradient(startColorstr='#297AB8', endColorstr='#15528F', GradientType=0);
|
||||
filter: progid:dximagetransform.microsoft.gradient(enabled=false);
|
||||
-webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.0), 0 1px 2px rgba(0, 0, 0, 0.05);
|
||||
-moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.0), 0 1px 2px rgba(0, 0, 0, 0.05);
|
||||
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.0), 0 1px 2px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.btn:hover,
|
||||
.btn:active,
|
||||
.btn.active,
|
||||
.btn.disabled,
|
||||
.btn[disabled] {
|
||||
background-color: #2672B6;
|
||||
*background-color: #2672B6;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.btn:active,
|
||||
.btn.active {
|
||||
background-color: #cccccc \9;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
color: #fff;
|
||||
background-color: #2672B6;
|
||||
*background-color: #2672B6;
|
||||
background-position: 0 -150px;
|
||||
-webkit-transition: background-position 0.0s linear;
|
||||
-moz-transition: background-position 0.0s linear;
|
||||
-ms-transition: background-position 0.0s linear;
|
||||
-o-transition: background-position 0.0s linear;
|
||||
transition: background-position 0.0s linear;
|
||||
}
|
||||
|
||||
.btn:focus {
|
||||
outline: thin dotted #333;
|
||||
outline: 5px auto -webkit-focus-ring-color;
|
||||
outline-offset: -2px;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.btn.active,
|
||||
.btn:active {
|
||||
background-color: #2672B6;
|
||||
background-color: #2672B6 \9;
|
||||
background-image: none;
|
||||
color: #fff;
|
||||
outline: 0;
|
||||
-webkit-box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);
|
||||
-moz-box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);
|
||||
box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);
|
||||
}
|
|
@ -298,6 +298,18 @@ inc_top.tmpl
|
|||
background-position: -399px 0;
|
||||
}
|
||||
|
||||
.menu-icon-logout {
|
||||
background-position: -420px 0;
|
||||
}
|
||||
|
||||
.menu-icon-kodi {
|
||||
background-position: -441px 0;
|
||||
}
|
||||
|
||||
.menu-icon-plex {
|
||||
background-position: -462px 0;
|
||||
}
|
||||
|
||||
[class^="submenu-icon-"], [class*=" submenu-icon-"] {
|
||||
background: url("../images/menu/menu-icons-white.png");
|
||||
height: 16px;
|
||||
|
@ -328,6 +340,16 @@ inc_top.tmpl
|
|||
background-position: -378px 0;
|
||||
}
|
||||
|
||||
.submenu-icon-kodi {
|
||||
background-position: -441px 0;
|
||||
}
|
||||
|
||||
.submenu-icon-plex {
|
||||
background-position: -462px -2px;
|
||||
margin-top: 2px;
|
||||
height: 12px;
|
||||
}
|
||||
|
||||
/* =======================================================================
|
||||
inc_bottom.tmpl
|
||||
========================================================================== */
|
||||
|
@ -776,6 +798,14 @@ a.whitelink {
|
|||
|
||||
}
|
||||
|
||||
/* =======================================================================
|
||||
404.tmpl
|
||||
========================================================================== */
|
||||
|
||||
#error-404 path {
|
||||
fill: #fff;
|
||||
}
|
||||
|
||||
/* =======================================================================
|
||||
Global
|
||||
========================================================================== */
|
||||
|
|
85
gui/slick/css/light-login.css
Normal file
85
gui/slick/css/light-login.css
Normal file
|
@ -0,0 +1,85 @@
|
|||
/* =======================================================================
|
||||
login.tmpl
|
||||
========================================================================== */
|
||||
|
||||
.login-remember span {
|
||||
color: #666
|
||||
}
|
||||
|
||||
/* =======================================================================
|
||||
bootstrap Overrides
|
||||
========================================================================== */
|
||||
|
||||
body {
|
||||
color: #000;
|
||||
}
|
||||
|
||||
input, textarea, select, .uneditable-input {
|
||||
width: auto;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.btn {
|
||||
color: #333333;
|
||||
text-shadow: 0 1px 1px rgba(255, 255, 255, 0.75);
|
||||
background-color: #f5f5f5;
|
||||
*background-color: #e6e6e6;
|
||||
background-image: -ms-linear-gradient(top, #ffffff, #e6e6e6);
|
||||
background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), to(#e6e6e6));
|
||||
background-image: -webkit-linear-gradient(top, #ffffff, #e6e6e6);
|
||||
background-image: -o-linear-gradient(top, #ffffff, #e6e6e6);
|
||||
background-image: linear-gradient(top, #ffffff, #e6e6e6);
|
||||
background-image: -moz-linear-gradient(top, #ffffff, #e6e6e6);
|
||||
border: 1px solid #cccccc;
|
||||
border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
|
||||
border-color: #e6e6e6 #e6e6e6 #bfbfbf;
|
||||
border-bottom-color: #b3b3b3;
|
||||
filter: progid:dximagetransform.microsoft.gradient(startColorstr='#ffffff', endColorstr='#e6e6e6', GradientType=0);
|
||||
filter: progid:dximagetransform.microsoft.gradient(enabled=false);
|
||||
-webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
|
||||
-moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
|
||||
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.btn:hover,
|
||||
.btn:active,
|
||||
.btn.active,
|
||||
.btn.disabled,
|
||||
.btn[disabled] {
|
||||
background-color: #e6e6e6;
|
||||
*background-color: #d9d9d9;
|
||||
}
|
||||
|
||||
.btn:active,
|
||||
.btn.active {
|
||||
background-color: #cccccc \9;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
color: #333333;
|
||||
background-color: #e6e6e6;
|
||||
*background-color: #d9d9d9;
|
||||
background-position: 0 -15px;
|
||||
-webkit-transition: background-position 0.1s linear;
|
||||
-moz-transition: background-position 0.1s linear;
|
||||
-ms-transition: background-position 0.1s linear;
|
||||
-o-transition: background-position 0.1s linear;
|
||||
transition: background-position 0.1s linear;
|
||||
}
|
||||
|
||||
.btn:focus {
|
||||
outline: thin dotted #333;
|
||||
outline: 5px auto -webkit-focus-ring-color;
|
||||
outline-offset: -2px;
|
||||
}
|
||||
|
||||
.btn.active,
|
||||
.btn:active {
|
||||
background-color: #e6e6e6;
|
||||
background-color: #d9d9d9 \9;
|
||||
background-image: none;
|
||||
outline: 0;
|
||||
-webkit-box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);
|
||||
-moz-box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);
|
||||
box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);
|
||||
}
|
|
@ -285,6 +285,18 @@ inc_top.tmpl
|
|||
background-position: -399px 0;
|
||||
}
|
||||
|
||||
.menu-icon-logout {
|
||||
background-position: -420px 0;
|
||||
}
|
||||
|
||||
.menu-icon-kodi {
|
||||
background-position: -441px 0;
|
||||
}
|
||||
|
||||
.menu-icon-plex {
|
||||
background-position: -462px 0;
|
||||
}
|
||||
|
||||
[class^="submenu-icon-"], [class*=" submenu-icon-"] {
|
||||
background: url("../images/menu/menu-icons-black.png");
|
||||
height: 16px;
|
||||
|
@ -315,6 +327,16 @@ inc_top.tmpl
|
|||
background-position: -378px 0;
|
||||
}
|
||||
|
||||
.submenu-icon-kodi {
|
||||
background-position: -441px 0;
|
||||
}
|
||||
|
||||
.submenu-icon-plex {
|
||||
background-position: -462px -2px;
|
||||
margin-top: 2px;
|
||||
height: 12px;
|
||||
}
|
||||
|
||||
/* =======================================================================
|
||||
inc_bottom.tmpl
|
||||
========================================================================== */
|
||||
|
@ -738,6 +760,14 @@ a.whitelink {
|
|||
color: #fff;
|
||||
}
|
||||
|
||||
/* =======================================================================
|
||||
404.tmpl
|
||||
========================================================================== */
|
||||
|
||||
#error-404 path {
|
||||
fill: #000;
|
||||
}
|
||||
|
||||
/* =======================================================================
|
||||
Global
|
||||
========================================================================== */
|
||||
|
|
163
gui/slick/css/style-login.css
Normal file
163
gui/slick/css/style-login.css
Normal file
|
@ -0,0 +1,163 @@
|
|||
/* =======================================================================
|
||||
fonts
|
||||
========================================================================== */
|
||||
/* Open Sans */
|
||||
/* Regular */
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
|
||||
src:url('fonts/OpenSans-Regular-webfont.eot');
|
||||
src:url('fonts/OpenSans-Regular-webfont.eot?#iefix') format('embedded-opentype'),
|
||||
url('fonts/OpenSans-Regular-webfont.woff') format('woff'),
|
||||
url('fonts/OpenSans-Regular-webfont.ttf') format('truetype'),
|
||||
url('fonts/OpenSans-Regular-webfont.svg#OpenSansRegular') format('svg');
|
||||
font-weight: normal;
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
/* =======================================================================
|
||||
login.tmpl
|
||||
========================================================================== */
|
||||
|
||||
.login {
|
||||
position : absolute;
|
||||
display: block;
|
||||
height: 275px;
|
||||
width: 300px;
|
||||
margin-top: -137.5px;
|
||||
margin-left: -150px;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
}
|
||||
|
||||
.login-img {
|
||||
height: 128px;
|
||||
width: 250px;
|
||||
background-image: url('../images/sickgear-large.png');
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.login .glyphicon {
|
||||
top: 0
|
||||
}
|
||||
|
||||
.login-error {
|
||||
height: 20px;
|
||||
margin-bottom: 2px;
|
||||
font-size: 13px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.login-remember {
|
||||
font-size: 12px;
|
||||
display: inline-block;
|
||||
padding: 2px 0;
|
||||
line-height: 14px
|
||||
}
|
||||
|
||||
.login-remember input {
|
||||
position: relative;
|
||||
top: 2px
|
||||
}
|
||||
|
||||
.login-remember span {
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.error16 {
|
||||
background: url('../images/error16.png') no-repeat 0 1px;
|
||||
width: 16px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
/* =======================================================================
|
||||
Global
|
||||
========================================================================== */
|
||||
|
||||
.red-text {
|
||||
color: #d33;
|
||||
}
|
||||
|
||||
/* =======================================================================
|
||||
bootstrap Overrides
|
||||
========================================================================== */
|
||||
|
||||
body {
|
||||
padding-top: 60px;
|
||||
overflow-y: scroll;
|
||||
font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
label {
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.btn {
|
||||
display: inline-block;
|
||||
*display: inline;
|
||||
padding: 4px 10px 4px;
|
||||
margin-bottom: 0;
|
||||
*margin-left: .3em;
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
*line-height: 20px;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
cursor: pointer;
|
||||
background-repeat: repeat-x;
|
||||
*border: 0;
|
||||
-webkit-border-radius: 4px;
|
||||
-moz-border-radius: 4px;
|
||||
border-radius: 4px;
|
||||
*zoom: 1;
|
||||
}
|
||||
|
||||
.btn:hover,
|
||||
.btn:active,
|
||||
.btn.active,
|
||||
.btn.disabled,
|
||||
.btn[disabled] {
|
||||
background-color: #e6e6e6;
|
||||
*background-color: #d9d9d9;
|
||||
}
|
||||
|
||||
.btn:active,
|
||||
.btn.active {
|
||||
background-color: #cccccc \9;
|
||||
}
|
||||
|
||||
.btn:first-child {
|
||||
*margin-left: 0;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
color: #333333;
|
||||
text-decoration: none;
|
||||
background-color: #e6e6e6;
|
||||
*background-color: #d9d9d9;
|
||||
background-position: 0 -15px;
|
||||
-webkit-transition: background-position 0.1s linear;
|
||||
-moz-transition: background-position 0.1s linear;
|
||||
-ms-transition: background-position 0.1s linear;
|
||||
-o-transition: background-position 0.1s linear;
|
||||
transition: background-position 0.1s linear;
|
||||
}
|
||||
|
||||
.btn:focus {
|
||||
outline: thin dotted #333;
|
||||
outline: 5px auto -webkit-focus-ring-color;
|
||||
outline-offset: -2px;
|
||||
}
|
||||
|
||||
.btn.active,
|
||||
.btn:active {
|
||||
background-color: #e6e6e6;
|
||||
background-color: #d9d9d9 \9;
|
||||
background-image: none;
|
||||
outline: 0;
|
||||
-webkit-box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);
|
||||
-moz-box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);
|
||||
box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);
|
||||
}
|
|
@ -139,7 +139,6 @@ fonts
|
|||
font-style: normal;
|
||||
}
|
||||
|
||||
|
||||
/* =======================================================================
|
||||
inc_top.tmpl
|
||||
========================================================================== */
|
||||
|
@ -444,6 +443,18 @@ inc_top.tmpl
|
|||
background-position: -399px 0;
|
||||
}
|
||||
|
||||
.menu-icon-logout {
|
||||
background-position: -420px 0;
|
||||
}
|
||||
|
||||
.menu-icon-kodi {
|
||||
background-position: -441px 0;
|
||||
}
|
||||
|
||||
.menu-icon-plex {
|
||||
background-position: -462px 0;
|
||||
}
|
||||
|
||||
[class^="submenu-icon-"], [class*=" submenu-icon-"] {
|
||||
background: url("../images/menu/menu-icons-black.png");
|
||||
height: 16px;
|
||||
|
@ -474,6 +485,14 @@ inc_top.tmpl
|
|||
background-position: -378px 0;
|
||||
}
|
||||
|
||||
.submenu-icon-kodi {
|
||||
background-position: -441px 0;
|
||||
}
|
||||
|
||||
.submenu-icon-plex {
|
||||
background-position: -462px 0;
|
||||
}
|
||||
|
||||
/* =======================================================================
|
||||
inc_bottom.tmpl
|
||||
========================================================================== */
|
||||
|
@ -2206,6 +2225,25 @@ a.whitelink {
|
|||
color: #fff;
|
||||
}
|
||||
|
||||
/* =======================================================================
|
||||
404.tmpl
|
||||
========================================================================== */
|
||||
|
||||
#error-404 {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#error-404 h1 {
|
||||
font-size: 200px;
|
||||
line-height: 200px;
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
#error-404 h2 {
|
||||
text-transform: uppercase;
|
||||
font-size: 50px;
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
/* =======================================================================
|
||||
Global
|
||||
|
|
BIN
gui/slick/images/error16.png
Normal file
BIN
gui/slick/images/error16.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.5 KiB |
Binary file not shown.
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 3.5 KiB |
Binary file not shown.
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 3.6 KiB |
BIN
gui/slick/images/sickgear-large.png
Normal file
BIN
gui/slick/images/sickgear-large.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 24 KiB |
33
gui/slick/interfaces/default/404.tmpl
Normal file
33
gui/slick/interfaces/default/404.tmpl
Normal file
|
@ -0,0 +1,33 @@
|
|||
#import sickbeard
|
||||
|
||||
#set global $title = '404'
|
||||
|
||||
#set global $sbPath = '..'
|
||||
|
||||
#set global $topmenu = '404'
|
||||
#import os.path
|
||||
#include $os.path.join($sickbeard.PROG_DIR, 'gui/slick/interfaces/default/inc_top.tmpl')
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<title>SickGear - BRANCH:[$sickbeard.BRANCH] - $title</title>
|
||||
|
||||
<link rel="stylesheet" type="text/css" href="$sbRoot/css/lib/bootstrap.css?$sbPID"/>
|
||||
<link rel="stylesheet" type="text/css" href="$sbRoot/css/style.css?$sbPID"/>
|
||||
<link rel="stylesheet" type="text/css" href="$sbRoot/css/${sickbeard.THEME_NAME}.css?$sbPID" />
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="error-404">
|
||||
<h1>404</h1>
|
||||
<h2>Page Not Found</h2>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" x="0px" y="0px" width="500px" height="100px" viewBox="0 0 50 10" enable-background="new 0 0 50 10" xml:space="preserve"><g><path d="M2.364 7.785H2.027C1.405 7 1.1 6.1 1.1 5.275c0-0.871 0.31-1.708 0.932-2.514h0.337 C1.843 3.6 1.6 4.4 1.6 5.275C1.582 6.1 1.8 7 2.4 7.785z"/><path d="M5.563 2.569c0 0.854-0.292 1.57-0.874 2.147c-0.577 0.57-1.288 0.855-2.131 0.855V4.931c0.645 0 1.199-0.232 1.662-0.697 c0.463-0.465 0.694-1.02 0.694-1.665H5.563z"/><path d="M8.987 3.372c0.199 0 0.4 0.1 0.5 0.213c0.142 0.1 0.2 0.3 0.2 0.511c0 0.201-0.071 0.372-0.213 0.5 S9.184 4.8 9 4.824c-0.199 0-0.369-0.071-0.511-0.213S8.262 4.3 8.3 4.096c0-0.2 0.071-0.372 0.212-0.513 C8.615 3.4 8.8 3.4 9 3.372z M8.987 4.541c0.121 0 0.224-0.043 0.31-0.131c0.087-0.087 0.13-0.192 0.13-0.314 S9.383 3.9 9.3 3.785C9.21 3.7 9.1 3.7 9 3.657c-0.122 0-0.226 0.042-0.312 0.128C8.59 3.9 8.5 4 8.5 4.1 S8.59 4.3 8.7 4.41C8.763 4.5 8.9 4.5 9 4.541z"/><path d="M10.438 4.787h2.47v2.47h-2.47V4.787z M10.637 4.986v2.072h2.072V4.986H10.637z"/><path d="M14.365 3.372c0.199 0 0.4 0.1 0.5 0.213c0.142 0.1 0.2 0.3 0.2 0.511c0 0.201-0.071 0.372-0.213 0.5 s-0.312 0.213-0.509 0.213c-0.199 0-0.369-0.071-0.511-0.213c-0.143-0.143-0.213-0.313-0.213-0.514c0-0.2 0.07-0.372 0.212-0.513 C13.993 3.4 14.2 3.4 14.4 3.372z M14.365 4.541c0.121 0 0.224-0.043 0.31-0.131c0.086-0.087 0.129-0.192 0.129-0.314 s-0.043-0.226-0.129-0.311c-0.086-0.085-0.189-0.128-0.31-0.128c-0.122 0-0.226 0.042-0.312 0.1 c-0.085 0.085-0.128 0.189-0.128 0.311s0.043 0.2 0.1 0.314C14.141 4.5 14.2 4.5 14.4 4.541z"/><path d="M17.158 5.195c0 0.897-0.303 1.709-0.91 2.438h-0.513V7.612c0.286-0.265 0.519-0.59 0.699-0.971 c0.213-0.455 0.318-0.938 0.318-1.447c0-0.506-0.105-0.985-0.318-1.438c-0.18-0.382-0.413-0.705-0.699-0.97V2.757h0.513 C16.855 3.5 17.2 4.3 17.2 5.195z"/><path d="M23.797 2.569c0 0.854-0.292 1.57-0.874 2.147c-0.577 0.57-1.288 0.855-2.131 0.855V4.931c0.645 0 1.199-0.232 1.662-0.697 c0.463-0.465 0.695-1.02 0.695-1.665H23.797z"/><path d="M31.286 7.646c-0.226-0.278-0.549-0.512-0.971-0.701c-0.47-0.21-0.964-0.315-1.483-0.315s-1.015 0.105-1.485 0.3 c-0.424 0.189-0.748 0.423-0.974 0.701h-0.021V7.188c0.294-0.294 0.679-0.524 1.153-0.69c0.437-0.149 0.879-0.224 1.326-0.224 c0.448 0 0.9 0.1 1.3 0.224c0.473 0.2 0.9 0.4 1.2 0.69v0.458H31.286z"/><path d="M38.286 5.561h-5.357V4.92h2.352V2.569h0.644V4.92h2.362V5.561z"/><path d="M43.638 5.566h-5.352V4.925h5.352V5.566z"/><path d="M49 5.561h-5.357V4.92h2.352V2.569h0.644V4.92H49V5.561z"/></g></svg>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -330,7 +330,7 @@
|
|||
|
||||
<div class="field-pair">
|
||||
<label for="web_username">
|
||||
<span class="component-title">HTTP username</span>
|
||||
<span class="component-title">Username</span>
|
||||
<span class="component-desc">
|
||||
<input type="text" name="web_username" id="web_username" value="$sickbeard.WEB_USERNAME" class="form-control input-sm input300">
|
||||
<p>blank = disable SickGear login</p>
|
||||
|
@ -340,7 +340,7 @@
|
|||
|
||||
<div class="field-pair">
|
||||
<label for="web_password">
|
||||
<span class="component-title">HTTP password</span>
|
||||
<span class="component-title">Password</span>
|
||||
<span class="component-desc">
|
||||
<input type="password" name="web_password" id="web_password" value="$sickbeard.WEB_PASSWORD" class="form-control input-sm input300">
|
||||
<p>blank = no authentication</p>
|
||||
|
|
|
@ -150,7 +150,7 @@
|
|||
<p style="line-height: 1.5; padding: 2px 5px 3px" title="<%= '%s added' % ('TVRage', 'theTVDB')['1' == cur_show['show_id'][:1]] %>">In library</p>
|
||||
#else
|
||||
#set $encoded_show_title = urllib.quote($cur_show['title'].encode("utf-8"))
|
||||
<a href="$sbRoot/home/addTraktShow?indexer_id=${cur_show['show_id']}&showName=${encoded_show_title}" class="btn btn-xs">Add Show</a>
|
||||
<a href="$sbRoot/home/addShows/addTraktShow?indexer_id=${cur_show['show_id']}&showName=${encoded_show_title}" class="btn btn-xs">Add Show</a>
|
||||
#end if
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -91,7 +91,7 @@
|
|||
\$("#SubMenu a[href$='/errorlogs/clearerrors/']").addClass('btn').html('<span class="ui-icon ui-icon-trash pull-left"></span> Clear Errors');
|
||||
\$("#SubMenu a:contains('Re-scan')").addClass('btn').html('<span class="ui-icon ui-icon-refresh pull-left"></span> Re-scan');
|
||||
\$("#SubMenu a:contains('Backlog Overview')").addClass('btn').html('<span class="ui-icon ui-icon-refresh pull-left"></span> Backlog Overview');
|
||||
\$("#SubMenu a[href$='/home/updatePLEX/']").addClass('btn').html('<span class="ui-icon ui-icon-refresh pull-left"></span> Update PLEX');
|
||||
\$("#SubMenu a[href$='/home/updatePLEX/']").addClass('btn').html('<span class="submenu-icon-plex pull-left"></span> Update PLEX');
|
||||
\$("#SubMenu a:contains('Force')").addClass('btn').html('<span class="ui-icon ui-icon-transfer-e-w pull-left"></span> Force Full Update');
|
||||
\$("#SubMenu a:contains('Rename')").addClass('btn').html('<span class="ui-icon ui-icon-tag pull-left"></span> Preview Rename');
|
||||
\$("#SubMenu a[href$='/config/subtitles/']").addClass('btn').html('<span class="ui-icon ui-icon-comment pull-left"></span> Search Subtitles');
|
||||
|
@ -166,7 +166,7 @@
|
|||
<li><a href="$sbRoot/manage/manageSearches/" tabindex="$tab#set $tab += 1#"><i class="menu-icon-manage-searches"></i> Manage Searches</a></li>
|
||||
<li><a href="$sbRoot/manage/episodeStatuses/" tabindex="$tab#set $tab += 1#"><i class="menu-icon-backlog"></i> Episode Status Management</a></li>
|
||||
#if $sickbeard.USE_PLEX and $sickbeard.PLEX_SERVER_HOST != "":
|
||||
<li><a href="$sbRoot/home/updatePLEX/" tabindex="$tab#set $tab += 1#"><i class="menu-icon-backlog-view"></i> Update PLEX</a></li>
|
||||
<li><a href="$sbRoot/home/updatePLEX/" tabindex="$tab#set $tab += 1#"><i class="menu-icon-plex"></i> Update PLEX</a></li>
|
||||
#end if
|
||||
#if $sickbeard.USE_XBMC and $sickbeard.XBMC_HOST != "":
|
||||
<li><a href="$sbRoot/home/updateXBMC/" tabindex="$tab#set $tab += 1#"><i class="menu-icon-xbmc"></i> Update XBMC</a></li>
|
||||
|
@ -211,6 +211,9 @@
|
|||
<a href="#" class="dropdown-toggle" data-toggle="dropdown" data-delay="0" tabindex="$tab#set $tab += 1#"><img src="$sbRoot/images/menu/system18-2.png" class="navbaricon hidden-xs" /><b class="caret hidden-xs"></b><span class="visible-xs">System <b class="caret"></b></span></a>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a href="$sbRoot/manage/manageSearches/forceVersionCheck" tabindex="$tab#set $tab += 1#"><i class="menu-icon-update"></i> Force Version Check</a></li>
|
||||
#if $sickbeard.WEB_USERNAME or $sickbeard.WEB_PASSWORD:
|
||||
<li><a href="$sbRoot/logout" class="confirm logout" tabindex="$tab#set $tab += 1#"><i class="menu-icon-logout"></i> Logout</a></li>
|
||||
#end if
|
||||
<li><a href="$sbRoot/home/restart/?pid=$sbPID" class="confirm restart" tabindex="$tab#set $tab += 1#"><i class="menu-icon-restart"></i> Restart</a></li>
|
||||
<li><a href="$sbRoot/home/shutdown/?pid=$sbPID" class="confirm shutdown" tabindex="$tab#set $tab += 1#"><i class="menu-icon-shutdown"></i> Shutdown</a></li>
|
||||
</ul>
|
||||
|
|
75
gui/slick/interfaces/default/login.tmpl
Normal file
75
gui/slick/interfaces/default/login.tmpl
Normal file
|
@ -0,0 +1,75 @@
|
|||
#import sickbeard
|
||||
|
||||
#set global $title = 'Login'
|
||||
|
||||
#set global $sbPath = '..'
|
||||
|
||||
#set global $topmenu = 'login'
|
||||
#import os.path
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<title>SickGear - BRANCH:[$sickbeard.BRANCH] - $title</title>
|
||||
|
||||
<!--[if lt IE 9]>
|
||||
<script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script>
|
||||
<script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
|
||||
<![endif]-->
|
||||
|
||||
<link rel="shortcut icon" href="$sbRoot/images/ico/favicon.ico">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="$sbRoot/images/ico/apple-touch-icon-180x180.png">
|
||||
<link rel="apple-touch-icon" sizes="152x152" href="$sbRoot/images/ico/apple-touch-icon-152x152.png">
|
||||
<link rel="apple-touch-icon" sizes="144x144" href="$sbRoot/images/ico/apple-touch-icon-144x144.png">
|
||||
<link rel="apple-touch-icon" sizes="120x120" href="$sbRoot/images/ico/apple-touch-icon-120x120.png">
|
||||
<link rel="apple-touch-icon" sizes="114x114" href="$sbRoot/images/ico/apple-touch-icon-114x114.png">
|
||||
<link rel="apple-touch-icon" sizes="76x76" href="$sbRoot/images/ico/apple-touch-icon-76x76.png">
|
||||
<link rel="apple-touch-icon" sizes="72x72" href="$sbRoot/images/ico/apple-touch-icon-72x72.png">
|
||||
<link rel="apple-touch-icon" sizes="60x60" href="$sbRoot/images/ico/apple-touch-icon-60x60.png">
|
||||
<link rel="apple-touch-icon" sizes="57x57" href="$sbRoot/images/ico/apple-touch-icon-57x57.png">
|
||||
<link rel="icon" type="image/png" href="$sbRoot/images/ico/favicon-192x192.png" sizes="192x192">
|
||||
<link rel="icon" type="image/png" href="$sbRoot/images/ico/favicon-160x160.png" sizes="160x160">
|
||||
<link rel="icon" type="image/png" href="$sbRoot/images/ico/favicon-96x96.png" sizes="96x96">
|
||||
<link rel="icon" type="image/png" href="$sbRoot/images/ico/favicon-32x32.png" sizes="32x32">
|
||||
<link rel="icon" type="image/png" href="$sbRoot/images/ico/favicon-16x16.png" sizes="16x16">
|
||||
<meta name="msapplication-TileColor" content="#2b5797">
|
||||
<meta name="msapplication-TileImage" content="$sbRoot/images/ico/mstile-144x144.png">
|
||||
<meta name="msapplication-config" content="$sbRoot/css/browserconfig.xml">
|
||||
|
||||
<link rel="stylesheet" type="text/css" href="$sbRoot/css/lib/bootstrap.css?$sbPID"/>
|
||||
<link rel="stylesheet" type="text/css" href="$sbRoot/css/style-login.css?$sbPID"/>
|
||||
<link rel="stylesheet" type="text/css" href="$sbRoot/css/${sickbeard.THEME_NAME}-login.css?$sbPID" />
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="login">
|
||||
<form action="" method="post">
|
||||
<div class="login-img center-block form-group"></div>
|
||||
|
||||
<div class="login-error">
|
||||
<span class="#if 'authfailed'==$resp then 'showme red-text' else 'hide' #"><i class="error16"> </i> Authentication failed, please retry</span>
|
||||
</div>
|
||||
|
||||
<div class="form-group input-group">
|
||||
<span class="input-group-addon glyphicon glyphicon-user"></span>
|
||||
<input name="username" class="form-control" placeholder="Username" type="text" autofocus required>
|
||||
</div>
|
||||
|
||||
<div class="form-group input-group">
|
||||
<span class="input-group-addon glyphicon glyphicon-lock"></span>
|
||||
<input name="password" class="form-control" placeholder="Password" type="password">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="remember_me" class="login-remember"><input id="remember_me" name="remember_me" type="checkbox" value="1" checked="checked"><span>Remember me</span></label>
|
||||
<input class="btn pull-right" name="submit" type="submit" value="Login">
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -1,4 +1,4 @@
|
|||
var search_status_url = sbRoot + '/getManualSearchStatus';
|
||||
var search_status_url = sbRoot + '/home/getManualSearchStatus';
|
||||
PNotify.prototype.options.maxonscreen = 5;
|
||||
|
||||
$.fn.manualSearches = [];
|
||||
|
|
|
@ -1,5 +1,26 @@
|
|||
$(document).ready(function () {
|
||||
$('a.shutdown').bind("click",function(e) {
|
||||
$('a.logout').bind('click',function(e) {
|
||||
e.preventDefault();
|
||||
var target = $( this ).attr('href');
|
||||
$.confirm({
|
||||
'title' : 'Logout',
|
||||
'message' : 'Are you sure you want to Logout from SickGear ?',
|
||||
'buttons' : {
|
||||
'Yes' : {
|
||||
'class' : 'green',
|
||||
'action': function(){
|
||||
location.href = target;
|
||||
}
|
||||
},
|
||||
'No' : {
|
||||
'class' : 'red',
|
||||
'action': function(){} // Nothing to do in this case. You can as well omit the action property.
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$('a.shutdown').bind('click',function(e) {
|
||||
e.preventDefault();
|
||||
var target = $( this ).attr('href');
|
||||
$.confirm({
|
||||
|
@ -20,7 +41,7 @@ $(document).ready(function () {
|
|||
});
|
||||
});
|
||||
|
||||
$('a.restart').bind("click",function(e) {
|
||||
$('a.restart').bind('click',function(e) {
|
||||
e.preventDefault();
|
||||
var target = $( this ).attr('href');
|
||||
$.confirm({
|
||||
|
@ -41,10 +62,10 @@ $(document).ready(function () {
|
|||
});
|
||||
});
|
||||
|
||||
$('a.remove').bind("click",function(e) {
|
||||
$('a.remove').bind('click',function(e) {
|
||||
e.preventDefault();
|
||||
var target = $( this ).attr('href');
|
||||
var showname = document.getElementById("showtitle").getAttribute('data-showname');
|
||||
var showname = document.getElementById('showtitle').getAttribute('data-showname');
|
||||
$.confirm({
|
||||
'title' : 'Remove Show',
|
||||
'message' : 'Are you sure you want to remove <span class="footerhighlight">' + showname + '</span> from the database ?<br /><br /><input type="checkbox" id="deleteFiles"> <span class="red-text">Check to delete files as well. IRREVERSIBLE</span></input>',
|
||||
|
@ -64,7 +85,7 @@ $(document).ready(function () {
|
|||
});
|
||||
});
|
||||
|
||||
$('a.clearhistory').bind("click",function(e) {
|
||||
$('a.clearhistory').bind('click',function(e) {
|
||||
e.preventDefault();
|
||||
var target = $( this ).attr('href');
|
||||
$.confirm({
|
||||
|
@ -85,7 +106,7 @@ $(document).ready(function () {
|
|||
});
|
||||
});
|
||||
|
||||
$('a.trimhistory').bind("click",function(e) {
|
||||
$('a.trimhistory').bind('click',function(e) {
|
||||
e.preventDefault();
|
||||
var target = $( this ).attr('href');
|
||||
$.confirm({
|
||||
|
|
|
@ -29,6 +29,8 @@ from threading import Lock
|
|||
# apparently py2exe won't build these unless they're imported somewhere
|
||||
import sys
|
||||
import os.path
|
||||
import uuid
|
||||
import base64
|
||||
sys.path.append(os.path.abspath('../lib'))
|
||||
from sickbeard import providers, metadata, config, webserveInit
|
||||
from sickbeard.providers.generic import GenericProvider
|
||||
|
@ -450,6 +452,8 @@ CALENDAR_UNPROTECTED = False
|
|||
TMDB_API_KEY = 'edc5f123313769de83a71e157758030b'
|
||||
TRAKT_API_KEY = 'abd806c54516240c76e4ebc9c5ccf394'
|
||||
|
||||
COOKIE_SECRET = base64.b64encode(uuid.uuid4().bytes + uuid.uuid4().bytes)
|
||||
|
||||
__INITIALIZED__ = False
|
||||
|
||||
def get_backlog_cycle_time():
|
||||
|
@ -499,7 +503,8 @@ def initialize(consoleLogging=True):
|
|||
USE_FAILED_DOWNLOADS, DELETE_FAILED, ANON_REDIRECT, LOCALHOST_IP, TMDB_API_KEY, DEBUG, PROXY_SETTING, PROXY_INDEXERS, \
|
||||
AUTOPOSTPROCESSER_FREQUENCY, DEFAULT_AUTOPOSTPROCESSER_FREQUENCY, MIN_AUTOPOSTPROCESSER_FREQUENCY, \
|
||||
ANIME_DEFAULT, NAMING_ANIME, ANIMESUPPORT, USE_ANIDB, ANIDB_USERNAME, ANIDB_PASSWORD, ANIDB_USE_MYLIST, \
|
||||
ANIME_SPLIT_HOME, SCENE_DEFAULT, BACKLOG_DAYS, ANIME_TREAT_AS_HDTV
|
||||
ANIME_SPLIT_HOME, SCENE_DEFAULT, BACKLOG_DAYS, ANIME_TREAT_AS_HDTV, \
|
||||
COOKIE_SECRET
|
||||
|
||||
if __INITIALIZED__:
|
||||
return False
|
||||
|
|
|
@ -22,7 +22,7 @@ import string
|
|||
from tornado.httputil import HTTPHeaders
|
||||
from tornado.web import RequestHandler
|
||||
from sickbeard import encodingKludge as ek
|
||||
from sickbeard import logger
|
||||
from sickbeard import logger, webserve
|
||||
|
||||
# use the built-in if it's available (python 2.6), if not use the included library
|
||||
try:
|
||||
|
@ -107,7 +107,7 @@ def foldersAtPath(path, includeParent=False, includeFiles=False):
|
|||
return entries
|
||||
|
||||
|
||||
class WebFileBrowser(RequestHandler):
|
||||
class WebFileBrowser(webserve.MainHandler):
|
||||
def index(self, path='', includeFiles=False, *args, **kwargs):
|
||||
self.set_header("Content-Type", "application/json")
|
||||
return json.dumps(foldersAtPath(path, True, bool(int(includeFiles))))
|
||||
|
|
|
@ -39,6 +39,7 @@ from sickbeard.exceptions import ex
|
|||
from sickbeard.common import SNATCHED, SNATCHED_PROPER, DOWNLOADED, SKIPPED, UNAIRED, IGNORED, ARCHIVED, WANTED, UNKNOWN
|
||||
from sickbeard.helpers import remove_article
|
||||
from common import Quality, qualityPresetStrings, statusStrings
|
||||
from sickbeard.webserve import MainHandler
|
||||
|
||||
try:
|
||||
import json
|
||||
|
@ -66,15 +67,27 @@ result_type_map = {RESULT_SUCCESS: "success",
|
|||
}
|
||||
# basically everything except RESULT_SUCCESS / success is bad
|
||||
|
||||
class Api(webserve.MainHandler):
|
||||
|
||||
class Api(webserve.BaseHandler):
|
||||
""" api class that returns json results """
|
||||
version = 4 # use an int since float-point is unpredictible
|
||||
intent = 4
|
||||
|
||||
def index(self, *args, **kwargs):
|
||||
def set_default_headers(self):
|
||||
self.set_header('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0')
|
||||
|
||||
def get(self, route, *args, **kwargs):
|
||||
route = route.strip('/') or 'index'
|
||||
|
||||
kwargs = self.request.arguments
|
||||
for arg, value in kwargs.items():
|
||||
if len(value) == 1:
|
||||
kwargs[arg] = value[0]
|
||||
|
||||
args = args[1:]
|
||||
|
||||
self.apiKey = sickbeard.API_KEY
|
||||
access, accessMsg, args, kwargs = self._grand_access(self.apiKey, args, kwargs)
|
||||
access, accessMsg, args, kwargs = self._grand_access(self.apiKey, route, args, kwargs)
|
||||
|
||||
# set the output callback
|
||||
# default json
|
||||
|
@ -118,10 +131,43 @@ class Api(webserve.MainHandler):
|
|||
outputCallback = outputCallbackDict[outDict['outputType']]
|
||||
else:
|
||||
outputCallback = outputCallbackDict['default']
|
||||
self.finish(outputCallback(outDict))
|
||||
|
||||
return outputCallback(outDict)
|
||||
def _out_as_json(self, dict):
|
||||
self.set_header('Content-Type', 'application/json')
|
||||
try:
|
||||
out = json.dumps(dict, indent=self.intent, sort_keys=True)
|
||||
if 'jsonp' in self.request.query_arguments:
|
||||
out = self.request.arguments['jsonp'] + '(' + out + ');' # wrap with JSONP call if requested
|
||||
|
||||
def builder(self):
|
||||
except Exception, e: # if we fail to generate the output fake an error
|
||||
logger.log(u'API :: ' + traceback.format_exc(), logger.DEBUG)
|
||||
out = '{"result":"' + result_type_map[RESULT_ERROR] + '", "message": "error while composing output: "' + ex(
|
||||
e) + '"}'
|
||||
|
||||
tornado_write_hack_dict = {'unwrap_json': out}
|
||||
return tornado_write_hack_dict
|
||||
|
||||
def _grand_access(self, realKey, apiKey, args, kwargs):
|
||||
""" validate api key and log result """
|
||||
remoteIp = self.request.remote_ip
|
||||
|
||||
if not sickbeard.USE_API:
|
||||
msg = u'API :: ' + remoteIp + ' - SB API Disabled. ACCESS DENIED'
|
||||
return False, msg, args, kwargs
|
||||
elif apiKey == realKey:
|
||||
msg = u'API :: ' + remoteIp + ' - gave correct API KEY. ACCESS GRANTED'
|
||||
return True, msg, args, kwargs
|
||||
elif not apiKey:
|
||||
msg = u'API :: ' + remoteIp + ' - gave NO API KEY. ACCESS DENIED'
|
||||
return False, msg, args, kwargs
|
||||
else:
|
||||
msg = u'API :: ' + remoteIp + ' - gave WRONG API KEY ' + apiKey + '. ACCESS DENIED'
|
||||
return False, msg, args, kwargs
|
||||
|
||||
|
||||
class ApiBuilder(webserve.MainHandler):
|
||||
def index(self):
|
||||
""" expose the api-builder template """
|
||||
t = webserve.PageTemplate(headers=self.request.headers, file="apiBuilder.tmpl")
|
||||
|
||||
|
@ -153,45 +199,6 @@ class Api(webserve.MainHandler):
|
|||
|
||||
return webserve._munge(t)
|
||||
|
||||
def _out_as_json(self, dict):
|
||||
self.set_header("Content-Type", "application/json")
|
||||
try:
|
||||
out = json.dumps(dict, indent=self.intent, sort_keys=True)
|
||||
if 'jsonp' in self.request.query_arguments:
|
||||
out = self.request.arguments['jsonp'] + '(' + out + ');' # wrap with JSONP call if requested
|
||||
|
||||
except Exception, e: # if we fail to generate the output fake an error
|
||||
logger.log(u"API :: " + traceback.format_exc(), logger.DEBUG)
|
||||
out = '{"result":"' + result_type_map[RESULT_ERROR] + '", "message": "error while composing output: "' + ex(
|
||||
e) + '"}'
|
||||
|
||||
tornado_write_hack_dict = {'unwrap_json': out}
|
||||
return tornado_write_hack_dict
|
||||
|
||||
def _grand_access(self, realKey, args, kwargs):
|
||||
""" validate api key and log result """
|
||||
remoteIp = self.request.remote_ip
|
||||
apiKey = kwargs.get("apikey", None)
|
||||
if not apiKey:
|
||||
if args: # if we have keyless vars we assume first one is the api key, always !
|
||||
apiKey = args[0]
|
||||
args = args[1:] # remove the apikey from the args tuple
|
||||
else:
|
||||
del kwargs["apikey"]
|
||||
|
||||
if not sickbeard.USE_API:
|
||||
msg = u"API :: " + remoteIp + " - SB API Disabled. ACCESS DENIED"
|
||||
return False, msg, args, kwargs
|
||||
elif apiKey == realKey:
|
||||
msg = u"API :: " + remoteIp + " - gave correct API KEY. ACCESS GRANTED"
|
||||
return True, msg, args, kwargs
|
||||
elif not apiKey:
|
||||
msg = u"API :: " + remoteIp + " - gave NO API KEY. ACCESS DENIED"
|
||||
return False, msg, args, kwargs
|
||||
else:
|
||||
msg = u"API :: " + remoteIp + " - gave WRONG API KEY " + apiKey + ". ACCESS DENIED"
|
||||
return False, msg, args, kwargs
|
||||
|
||||
|
||||
def call_dispatcher(handler, args, kwargs):
|
||||
""" calls the appropriate CMD class
|
||||
|
@ -2191,7 +2198,7 @@ class CMD_ShowGetPoster(ApiCall):
|
|||
|
||||
def run(self):
|
||||
""" get the poster for a show in sickbeard """
|
||||
return {'outputType': 'image', 'image': self.handler.showPoster(self.indexerid, 'poster')}
|
||||
return {'outputType': 'image', 'image': self.handler.showPoster(self.indexerid, 'poster', True)}
|
||||
|
||||
|
||||
class CMD_ShowGetBanner(ApiCall):
|
||||
|
@ -2209,7 +2216,7 @@ class CMD_ShowGetBanner(ApiCall):
|
|||
|
||||
def run(self):
|
||||
""" get the banner for a show in sickbeard """
|
||||
return {'outputType': 'image', 'image': self.handler.showPoster(self.indexerid, 'banner')}
|
||||
return {'outputType': 'image', 'image': self.handler.showPoster(self.indexerid, 'banner', True)}
|
||||
|
||||
|
||||
class CMD_ShowPause(ApiCall):
|
||||
|
|
|
@ -60,7 +60,6 @@ from sickbeard.scene_numbering import get_scene_numbering, set_scene_numbering,
|
|||
|
||||
from sickbeard.blackandwhitelist import BlackAndWhiteList, short_group_names
|
||||
|
||||
from browser import WebFileBrowser
|
||||
from mimetypes import MimeTypes
|
||||
|
||||
from lib.dateutil import tz
|
||||
|
@ -82,54 +81,8 @@ except ImportError:
|
|||
from lib import adba
|
||||
|
||||
from Cheetah.Template import Template
|
||||
from tornado.web import RequestHandler, HTTPError, asynchronous
|
||||
|
||||
|
||||
def authenticated(handler_class):
|
||||
def wrap_execute(handler_execute):
|
||||
def basicauth(handler, transforms, *args, **kwargs):
|
||||
def _request_basic_auth(handler):
|
||||
handler.set_status(401)
|
||||
handler.set_header('WWW-Authenticate', 'Basic realm="SickGear"')
|
||||
handler._transforms = []
|
||||
handler.finish()
|
||||
return False
|
||||
|
||||
try:
|
||||
if not (sickbeard.WEB_USERNAME and sickbeard.WEB_PASSWORD):
|
||||
return True
|
||||
elif (handler.request.uri.startswith(sickbeard.WEB_ROOT + '/api') and
|
||||
'/api/builder' not in handler.request.uri):
|
||||
return True
|
||||
elif (handler.request.uri.startswith(sickbeard.WEB_ROOT + '/calendar') and
|
||||
sickbeard.CALENDAR_UNPROTECTED):
|
||||
return True
|
||||
|
||||
auth_hdr = handler.request.headers.get('Authorization')
|
||||
|
||||
if auth_hdr == None:
|
||||
return _request_basic_auth(handler)
|
||||
if not auth_hdr.startswith('Basic '):
|
||||
return _request_basic_auth(handler)
|
||||
|
||||
auth_decoded = base64.decodestring(auth_hdr[6:])
|
||||
username, password = auth_decoded.split(':', 2)
|
||||
|
||||
if username != sickbeard.WEB_USERNAME or password != sickbeard.WEB_PASSWORD:
|
||||
return _request_basic_auth(handler)
|
||||
except Exception, e:
|
||||
return _request_basic_auth(handler)
|
||||
return True
|
||||
|
||||
def _execute(self, transforms, *args, **kwargs):
|
||||
if not basicauth(self, transforms, *args, **kwargs):
|
||||
return False
|
||||
return handler_execute(self, transforms, *args, **kwargs)
|
||||
|
||||
return _execute
|
||||
|
||||
handler_class._execute = wrap_execute(handler_class._execute)
|
||||
return handler_class
|
||||
from tornado.web import RequestHandler, HTTPError, asynchronous, authenticated
|
||||
from tornado import gen, escape
|
||||
|
||||
|
||||
class HTTPRedirect(Exception):
|
||||
|
@ -151,8 +104,119 @@ def redirect(url, permanent=False, status=None):
|
|||
raise HTTPRedirect(sickbeard.WEB_ROOT + url, permanent, status)
|
||||
|
||||
|
||||
@authenticated
|
||||
class MainHandler(RequestHandler):
|
||||
class BaseHandler(RequestHandler):
|
||||
def set_default_headers(self):
|
||||
self.set_header('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0')
|
||||
|
||||
def redirect(self, url, permanent=False, status=None):
|
||||
if not url.startswith(sickbeard.WEB_ROOT):
|
||||
url = sickbeard.WEB_ROOT + url
|
||||
|
||||
super(BaseHandler, self).redirect(url, permanent, status)
|
||||
|
||||
def get_current_user(self, *args, **kwargs):
|
||||
if sickbeard.WEB_USERNAME or sickbeard.WEB_PASSWORD:
|
||||
return self.get_secure_cookie('user')
|
||||
else:
|
||||
return True
|
||||
|
||||
def showPoster(self, show=None, which=None, api=None):
|
||||
# Redirect initial poster/banner thumb to default images
|
||||
if which[0:6] == 'poster':
|
||||
default_image_name = 'poster.png'
|
||||
else:
|
||||
default_image_name = 'banner.png'
|
||||
|
||||
static_image_path = os.path.join('/images', default_image_name)
|
||||
if show and sickbeard.helpers.findCertainShow(sickbeard.showList, int(show)):
|
||||
cache_obj = image_cache.ImageCache()
|
||||
|
||||
image_file_name = None
|
||||
if which == 'poster':
|
||||
image_file_name = cache_obj.poster_path(show)
|
||||
if which == 'poster_thumb':
|
||||
image_file_name = cache_obj.poster_thumb_path(show)
|
||||
if which == 'banner':
|
||||
image_file_name = cache_obj.banner_path(show)
|
||||
if which == 'banner_thumb':
|
||||
image_file_name = cache_obj.banner_thumb_path(show)
|
||||
|
||||
if ek.ek(os.path.isfile, image_file_name):
|
||||
static_image_path = image_file_name
|
||||
|
||||
if api:
|
||||
mime_type, encoding = MimeTypes().guess_type(static_image_path)
|
||||
self.set_header('Content-Type', mime_type)
|
||||
with file(static_image_path, 'rb') as img:
|
||||
return img.read()
|
||||
else:
|
||||
static_image_path = os.path.normpath(static_image_path.replace(sickbeard.CACHE_DIR, '/cache'))
|
||||
static_image_path = static_image_path.replace('\\', '/')
|
||||
return redirect(static_image_path)
|
||||
|
||||
|
||||
class LogoutHandler(BaseHandler):
|
||||
def get(self, *args, **kwargs):
|
||||
self.clear_cookie('user')
|
||||
self.redirect('/login/')
|
||||
|
||||
|
||||
class LoginHandler(BaseHandler):
|
||||
def get(self, *args, **kwargs):
|
||||
if self.get_current_user():
|
||||
self.redirect(self.get_argument('next', '/home/'))
|
||||
else:
|
||||
t = PageTemplate(headers=self.request.headers, file='login.tmpl')
|
||||
t.resp = self.get_argument('resp', '')
|
||||
self.set_status(401)
|
||||
self.finish(t.respond())
|
||||
|
||||
def post(self, *args, **kwargs):
|
||||
username = sickbeard.WEB_USERNAME
|
||||
password = sickbeard.WEB_PASSWORD
|
||||
|
||||
if (self.get_argument('username') == username or not username) \
|
||||
and (self.get_argument('password') == password or not password):
|
||||
|
||||
remember_me = int(self.get_argument('remember_me', default=0) or 0)
|
||||
self.set_secure_cookie('user', sickbeard.COOKIE_SECRET, expires_days=30 if remember_me > 0 else None)
|
||||
|
||||
self.redirect(self.get_argument('next', '/home/'))
|
||||
else:
|
||||
next_arg = '&next=' + self.get_argument('next', '/home/')
|
||||
self.redirect('/login?resp=authfailed' + next_arg)
|
||||
|
||||
|
||||
class WebHandler(BaseHandler):
|
||||
def page_not_found(self):
|
||||
t = PageTemplate(headers=self.request.headers, file='404.tmpl')
|
||||
return _munge(t)
|
||||
|
||||
@authenticated
|
||||
@gen.coroutine
|
||||
def get(self, route, *args, **kwargs):
|
||||
route = route.strip('/') or 'index'
|
||||
try:
|
||||
method = getattr(self, route)
|
||||
except:
|
||||
self.finish(self.page_not_found())
|
||||
else:
|
||||
kwargss = self.request.arguments
|
||||
for arg, value in kwargss.items():
|
||||
if len(value) == 1:
|
||||
kwargss[arg] = value[0]
|
||||
try:
|
||||
self.finish(method(**kwargss))
|
||||
except HTTPRedirect, e:
|
||||
self.redirect(e.url, e.permanent, e.status)
|
||||
|
||||
post = get
|
||||
|
||||
|
||||
class MainHandler(WebHandler):
|
||||
def index(self):
|
||||
return redirect('/home/')
|
||||
|
||||
def http_error_401_handler(self):
|
||||
""" Custom handler for 401 error """
|
||||
return r'''<!DOCTYPE html>
|
||||
|
@ -193,102 +257,11 @@ class MainHandler(RequestHandler):
|
|||
</html>""" % (error, error,
|
||||
trace_info, request_info))
|
||||
|
||||
def _dispatch(self):
|
||||
path = self.request.uri.replace(sickbeard.WEB_ROOT, '').split('?')[0]
|
||||
|
||||
method = path.strip('/').split('/')[-1]
|
||||
|
||||
if method == 'robots.txt':
|
||||
method = 'robots_txt'
|
||||
|
||||
if path.startswith('/api') and method != 'builder':
|
||||
apikey = path.strip('/').split('/')[-1]
|
||||
method = path.strip('/').split('/')[0]
|
||||
self.request.arguments.update({'apikey': [apikey]})
|
||||
|
||||
def pred(c):
|
||||
return inspect.isclass(c) and c.__module__ == pred.__module__
|
||||
|
||||
try:
|
||||
klass = [cls[1] for cls in
|
||||
inspect.getmembers(sys.modules[__name__], pred) + [(self.__class__.__name__, self.__class__)] if
|
||||
cls[0].lower() == method.lower() or method in cls[1].__dict__.keys()][0](self.application,
|
||||
self.request)
|
||||
except:
|
||||
klass = None
|
||||
|
||||
if klass and not method.startswith('_'):
|
||||
# Sanitize argument lists:
|
||||
args = self.request.arguments
|
||||
for arg, value in args.items():
|
||||
if len(value) == 1:
|
||||
args[arg] = value[0]
|
||||
|
||||
# Regular method handler for classes
|
||||
func = getattr(klass, method, None)
|
||||
|
||||
# Special index method handler for classes and subclasses:
|
||||
if path.startswith('/api') or path.endswith('/'):
|
||||
if func and getattr(func, 'index', None):
|
||||
func = getattr(func(self.application, self.request), 'index', None)
|
||||
elif not func:
|
||||
func = getattr(klass, 'index', None)
|
||||
|
||||
if callable(func):
|
||||
out = func(**args)
|
||||
self._headers = klass._headers
|
||||
return out
|
||||
|
||||
raise HTTPError(404)
|
||||
|
||||
@asynchronous
|
||||
def get(self, *args, **kwargs):
|
||||
try:
|
||||
self.finish(self._dispatch())
|
||||
except HTTPRedirect, e:
|
||||
self.redirect(e.url, e.permanent, e.status)
|
||||
|
||||
@asynchronous
|
||||
def post(self, *args, **kwargs):
|
||||
try:
|
||||
self.finish(self._dispatch())
|
||||
except HTTPRedirect, e:
|
||||
self.redirect(e.url, e.permanent, e.status)
|
||||
|
||||
def robots_txt(self, *args, **kwargs):
|
||||
""" Keep web crawlers out """
|
||||
self.set_header('Content-Type', 'text/plain')
|
||||
return "User-agent: *\nDisallow: /"
|
||||
|
||||
def showPoster(self, show=None, which=None):
|
||||
# Redirect initial poster/banner thumb to default images
|
||||
if which[0:6] == 'poster':
|
||||
default_image_name = 'poster.png'
|
||||
else:
|
||||
default_image_name = 'banner.png'
|
||||
|
||||
image_path = ek.ek(os.path.join, sickbeard.PROG_DIR, 'gui', 'slick', 'images', default_image_name)
|
||||
if show and sickbeard.helpers.findCertainShow(sickbeard.showList, int(show)):
|
||||
cache_obj = image_cache.ImageCache()
|
||||
|
||||
image_file_name = None
|
||||
if which == 'poster':
|
||||
image_file_name = cache_obj.poster_path(show)
|
||||
if which == 'poster_thumb':
|
||||
image_file_name = cache_obj.poster_thumb_path(show)
|
||||
if which == 'banner':
|
||||
image_file_name = cache_obj.banner_path(show)
|
||||
if which == 'banner_thumb':
|
||||
image_file_name = cache_obj.banner_thumb_path(show)
|
||||
|
||||
if ek.ek(os.path.isfile, image_file_name):
|
||||
image_path = image_file_name
|
||||
|
||||
mime_type, encoding = MimeTypes().guess_type(image_path)
|
||||
self.set_header('Content-Type', mime_type)
|
||||
with file(image_path, 'rb') as img:
|
||||
return img.read()
|
||||
|
||||
def setHomeLayout(self, layout):
|
||||
|
||||
if layout not in ('poster', 'small', 'banner', 'simple'):
|
||||
|
@ -418,7 +391,7 @@ class MainHandler(RequestHandler):
|
|||
|
||||
# add localtime to the dict
|
||||
for index, item in enumerate(sql_results):
|
||||
sql_results[index]['localtime'] = sbdatetime.sbdatetime.convert_to_setting(network_timezones.parse_date_time(item['airdate'],
|
||||
sql_results[index]['localtime'] = sbdatetime.sbdatetime.convert_to_setting(network_timezones.parse_date_time(item['airdate'],
|
||||
item['airs'], item['network']))
|
||||
sql_results[index]['data_show_name'] = value_maybe_article(item['show_name'])
|
||||
sql_results[index]['data_network'] = value_maybe_article(item['network'])
|
||||
|
@ -438,11 +411,26 @@ class MainHandler(RequestHandler):
|
|||
|
||||
return _munge(t)
|
||||
|
||||
# iCalendar (iCal) - Standard RFC 5545 <http://tools.ietf.org/html/rfc5546>
|
||||
# Works with iCloud, Google Calendar and Outlook.
|
||||
def _genericMessage(self, subject, message):
|
||||
t = PageTemplate(headers=self.request.headers, file="genericMessage.tmpl")
|
||||
t.submenu = HomeMenu()
|
||||
t.subject = subject
|
||||
t.message = message
|
||||
return _munge(t)
|
||||
|
||||
|
||||
class CalendarHandler(BaseHandler):
|
||||
def get(self, *args, **kwargs):
|
||||
if sickbeard.CALENDAR_UNPROTECTED or self.get_current_user():
|
||||
self.write(self.calendar())
|
||||
else:
|
||||
self.set_status(401)
|
||||
self.write('User authentication required')
|
||||
|
||||
def calendar(self, *args, **kwargs):
|
||||
""" Provides a subscribeable URL for iCal subscriptions
|
||||
"""
|
||||
""" iCalendar (iCal) - Standard RFC 5545 <http://tools.ietf.org/html/rfc5546>
|
||||
Works with iCloud, Google Calendar and Outlook.
|
||||
Provides a subscribeable URL for iCal subscriptions """
|
||||
|
||||
logger.log(u'Receiving iCal request from %s' % self.request.remote_ip)
|
||||
|
||||
|
@ -490,14 +478,27 @@ class MainHandler(RequestHandler):
|
|||
# Ending the iCal
|
||||
return ical + 'END:VCALENDAR'
|
||||
|
||||
def _genericMessage(self, subject, message):
|
||||
t = PageTemplate(headers=self.request.headers, file="genericMessage.tmpl")
|
||||
t.submenu = HomeMenu()
|
||||
t.subject = subject
|
||||
t.message = message
|
||||
return _munge(t)
|
||||
|
||||
browser = WebFileBrowser
|
||||
class IsAliveHandler(BaseHandler):
|
||||
def get(self, *args, **kwargs):
|
||||
kwargs = self.request.arguments
|
||||
if 'callback' in kwargs and '_' in kwargs:
|
||||
callback, _ = kwargs['callback'][0], kwargs['_']
|
||||
else:
|
||||
return "Error: Unsupported Request. Send jsonp request with 'callback' variable in the query string."
|
||||
|
||||
self.set_header('Cache-Control', 'max-age=0,no-cache,no-store')
|
||||
self.set_header('Content-Type', 'text/javascript')
|
||||
self.set_header('Access-Control-Allow-Origin', '*')
|
||||
self.set_header('Access-Control-Allow-Headers', 'x-requested-with')
|
||||
|
||||
if sickbeard.started:
|
||||
results = callback + '(' + json.dumps(
|
||||
{"msg": str(sickbeard.PID)}) + ');'
|
||||
else:
|
||||
results = callback + '(' + json.dumps({"msg": "nope"}) + ');'
|
||||
|
||||
self.write(results)
|
||||
|
||||
|
||||
class PageTemplate(Template):
|
||||
|
@ -622,7 +623,6 @@ class ManageSearches(MainHandler):
|
|||
|
||||
return _munge(t)
|
||||
|
||||
|
||||
def forceVersionCheck(self, *args, **kwargs):
|
||||
# force a check to see if there is a new version
|
||||
if sickbeard.versionCheckScheduler.action.check_for_new_version(force=True):
|
||||
|
@ -649,7 +649,6 @@ class ManageSearches(MainHandler):
|
|||
|
||||
redirect("/manage/manageSearches/")
|
||||
|
||||
|
||||
def forceFindPropers(self, *args, **kwargs):
|
||||
|
||||
# force it to run the next time it looks
|
||||
|
@ -660,7 +659,6 @@ class ManageSearches(MainHandler):
|
|||
|
||||
redirect("/manage/manageSearches/")
|
||||
|
||||
|
||||
def pauseBacklog(self, paused=None):
|
||||
if paused == "1":
|
||||
sickbeard.searchQueueScheduler.action.pause_backlog() # @UndefinedVariable
|
||||
|
@ -676,7 +674,6 @@ class Manage(MainHandler):
|
|||
t.submenu = ManageMenu()
|
||||
return _munge(t)
|
||||
|
||||
|
||||
def showEpisodeStatuses(self, indexer_id, whichStatus):
|
||||
status_list = [int(whichStatus)]
|
||||
if status_list[0] == SNATCHED:
|
||||
|
@ -699,7 +696,6 @@ class Manage(MainHandler):
|
|||
|
||||
return json.dumps(result)
|
||||
|
||||
|
||||
def episodeStatuses(self, whichStatus=None):
|
||||
|
||||
if whichStatus:
|
||||
|
@ -744,7 +740,6 @@ class Manage(MainHandler):
|
|||
t.sorted_show_ids = sorted_show_ids
|
||||
return _munge(t)
|
||||
|
||||
|
||||
def changeEpisodeStatuses(self, oldStatus, newStatus, *args, **kwargs):
|
||||
|
||||
status_list = [int(oldStatus)]
|
||||
|
@ -783,7 +778,6 @@ class Manage(MainHandler):
|
|||
|
||||
redirect('/manage/episodeStatuses/')
|
||||
|
||||
|
||||
def showSubtitleMissed(self, indexer_id, whichSubs):
|
||||
myDB = db.DBConnection()
|
||||
cur_show_results = myDB.select(
|
||||
|
@ -816,7 +810,6 @@ class Manage(MainHandler):
|
|||
|
||||
return json.dumps(result)
|
||||
|
||||
|
||||
def subtitleMissed(self, whichSubs=None):
|
||||
|
||||
t = PageTemplate(headers=self.request.headers, file="manage_subtitleMissed.tmpl")
|
||||
|
@ -856,7 +849,6 @@ class Manage(MainHandler):
|
|||
t.sorted_show_ids = sorted_show_ids
|
||||
return _munge(t)
|
||||
|
||||
|
||||
def downloadSubtitleMissed(self, *args, **kwargs):
|
||||
|
||||
to_download = {}
|
||||
|
@ -891,7 +883,6 @@ class Manage(MainHandler):
|
|||
|
||||
redirect('/manage/subtitleMissed/')
|
||||
|
||||
|
||||
def backlogShow(self, indexer_id):
|
||||
|
||||
show_obj = helpers.findCertainShow(sickbeard.showList, int(indexer_id))
|
||||
|
@ -901,7 +892,6 @@ class Manage(MainHandler):
|
|||
|
||||
redirect("/manage/backlogOverview/")
|
||||
|
||||
|
||||
def backlogOverview(self, *args, **kwargs):
|
||||
|
||||
t = PageTemplate(headers=self.request.headers, file="manage_backlogOverview.tmpl")
|
||||
|
@ -943,7 +933,6 @@ class Manage(MainHandler):
|
|||
|
||||
return _munge(t)
|
||||
|
||||
|
||||
def massEdit(self, toEdit=None):
|
||||
|
||||
t = PageTemplate(headers=self.request.headers, file="manage_massEdit.tmpl")
|
||||
|
@ -1067,7 +1056,6 @@ class Manage(MainHandler):
|
|||
|
||||
return _munge(t)
|
||||
|
||||
|
||||
def massEditSubmit(self, archive_firstmatch=None, paused=None, anime=None, sports=None, scene=None, flatten_folders=None,
|
||||
quality_preset=False,
|
||||
subtitles=None, air_by_date=None, anyQualities=[], bestQualities=[], toEdit=None, *args,
|
||||
|
@ -1172,7 +1160,6 @@ class Manage(MainHandler):
|
|||
|
||||
redirect("/manage/")
|
||||
|
||||
|
||||
def massUpdate(self, toUpdate=None, toRefresh=None, toRename=None, toDelete=None, toRemove=None, toMetadata=None, toSubtitle=None):
|
||||
|
||||
if toUpdate is not None:
|
||||
|
@ -1291,7 +1278,6 @@ class Manage(MainHandler):
|
|||
|
||||
redirect("/manage/")
|
||||
|
||||
|
||||
def manageTorrents(self, *args, **kwargs):
|
||||
|
||||
t = PageTemplate(headers=self.request.headers, file="manage_torrents.tmpl")
|
||||
|
@ -1317,7 +1303,6 @@ class Manage(MainHandler):
|
|||
|
||||
return _munge(t)
|
||||
|
||||
|
||||
def failedDownloads(self, limit=100, toRemove=None):
|
||||
|
||||
myDB = db.DBConnection('failed.db')
|
||||
|
@ -1452,11 +1437,9 @@ class ConfigGeneral(MainHandler):
|
|||
t.submenu = ConfigMenu
|
||||
return _munge(t)
|
||||
|
||||
|
||||
def saveRootDirs(self, rootDirString=None):
|
||||
sickbeard.ROOT_DIRS = rootDirString
|
||||
|
||||
|
||||
def saveAddShowDefaults(self, defaultStatus, anyQualities, bestQualities, defaultFlattenFolders, subtitles=False,
|
||||
anime=False, scene=False):
|
||||
|
||||
|
@ -1483,7 +1466,6 @@ class ConfigGeneral(MainHandler):
|
|||
|
||||
sickbeard.save_config()
|
||||
|
||||
|
||||
def generateKey(self, *args, **kwargs):
|
||||
""" Return a new randomized API_KEY
|
||||
"""
|
||||
|
@ -1507,7 +1489,6 @@ class ConfigGeneral(MainHandler):
|
|||
logger.log(u"New API generated")
|
||||
return m.hexdigest()
|
||||
|
||||
|
||||
def saveGeneral(self, log_dir=None, web_port=None, web_log=None, encryption_version=None, web_ipv6=None,
|
||||
update_shows_on_start=None, show_update_hour=None, trash_remove_show=None, trash_rotate_logs=None, update_frequency=None, launch_browser=None, web_username=None,
|
||||
use_api=None, api_key=None, indexer_default=None, timezone_display=None, cpu_preset=None,
|
||||
|
@ -1609,7 +1590,6 @@ class ConfigSearch(MainHandler):
|
|||
t.submenu = ConfigMenu
|
||||
return _munge(t)
|
||||
|
||||
|
||||
def saveSearch(self, use_nzbs=None, use_torrents=None, nzb_dir=None, sab_username=None, sab_password=None,
|
||||
sab_apikey=None, sab_category=None, sab_host=None, nzbget_username=None, nzbget_password=None,
|
||||
nzbget_category=None, nzbget_priority=None, nzbget_host=None, nzbget_use_https=None,
|
||||
|
@ -1691,7 +1671,6 @@ class ConfigPostProcessing(MainHandler):
|
|||
t.submenu = ConfigMenu
|
||||
return _munge(t)
|
||||
|
||||
|
||||
def savePostProcessing(self, naming_pattern=None, naming_multi_ep=None,
|
||||
xbmc_data=None, xbmc_12plus_data=None, mediabrowser_data=None, sony_ps3_data=None,
|
||||
wdtv_data=None, tivo_data=None, mede8er_data=None,
|
||||
|
@ -1819,7 +1798,6 @@ class ConfigPostProcessing(MainHandler):
|
|||
|
||||
return result
|
||||
|
||||
|
||||
def isNamingValid(self, pattern=None, multi=None, abd=False, sports=False, anime_type=None):
|
||||
if pattern is None:
|
||||
return "invalid"
|
||||
|
@ -1854,7 +1832,6 @@ class ConfigPostProcessing(MainHandler):
|
|||
else:
|
||||
return "invalid"
|
||||
|
||||
|
||||
def isRarSupported(self, *args, **kwargs):
|
||||
"""
|
||||
Test Packing Support:
|
||||
|
@ -1879,7 +1856,6 @@ class ConfigProviders(MainHandler):
|
|||
t.submenu = ConfigMenu
|
||||
return _munge(t)
|
||||
|
||||
|
||||
def canAddNewznabProvider(self, name):
|
||||
|
||||
if not name:
|
||||
|
@ -1894,7 +1870,6 @@ class ConfigProviders(MainHandler):
|
|||
else:
|
||||
return json.dumps({'success': tempProvider.getID()})
|
||||
|
||||
|
||||
def saveNewznabProvider(self, name, url, key=''):
|
||||
|
||||
if not name or not url:
|
||||
|
@ -1965,7 +1940,6 @@ class ConfigProviders(MainHandler):
|
|||
|
||||
return '1'
|
||||
|
||||
|
||||
def canAddTorrentRssProvider(self, name, url, cookies):
|
||||
|
||||
if not name:
|
||||
|
@ -1985,7 +1959,6 @@ class ConfigProviders(MainHandler):
|
|||
else:
|
||||
return json.dumps({'error': errMsg})
|
||||
|
||||
|
||||
def saveTorrentRssProvider(self, name, url, cookies):
|
||||
|
||||
if not name or not url:
|
||||
|
@ -2005,7 +1978,6 @@ class ConfigProviders(MainHandler):
|
|||
sickbeard.torrentRssProviderList.append(newProvider)
|
||||
return newProvider.getID() + '|' + newProvider.configStr()
|
||||
|
||||
|
||||
def deleteTorrentRssProvider(self, id):
|
||||
|
||||
providerDict = dict(
|
||||
|
@ -2022,7 +1994,6 @@ class ConfigProviders(MainHandler):
|
|||
|
||||
return '1'
|
||||
|
||||
|
||||
def saveProviders(self, newznab_string='', torrentrss_string='', provider_order=None, **kwargs):
|
||||
|
||||
results = []
|
||||
|
@ -2578,7 +2549,6 @@ class ConfigAnime(MainHandler):
|
|||
t.submenu = ConfigMenu
|
||||
return _munge(t)
|
||||
|
||||
|
||||
def saveAnime(self, use_anidb=None, anidb_username=None, anidb_password=None, anidb_use_mylist=None,
|
||||
split_home=None, anime_treat_as_hdtv=None):
|
||||
|
||||
|
@ -2603,7 +2573,6 @@ class ConfigAnime(MainHandler):
|
|||
|
||||
redirect("/config/anime/")
|
||||
|
||||
|
||||
class Config(MainHandler):
|
||||
def index(self, *args, **kwargs):
|
||||
t = PageTemplate(headers=self.request.headers, file="config.tmpl")
|
||||
|
@ -2694,7 +2663,6 @@ class NewHomeAddShows(MainHandler):
|
|||
t.submenu = HomeMenu()
|
||||
return _munge(t)
|
||||
|
||||
|
||||
def getIndexerLanguages(self, *args, **kwargs):
|
||||
result = sickbeard.indexerApi().config['valid_languages']
|
||||
|
||||
|
@ -2706,11 +2674,9 @@ class NewHomeAddShows(MainHandler):
|
|||
|
||||
return json.dumps({'results': result})
|
||||
|
||||
|
||||
def sanitizeFileName(self, name):
|
||||
return helpers.sanitizeFileName(name)
|
||||
|
||||
|
||||
def searchIndexersForShowName(self, search_term, lang="en", indexer=None):
|
||||
if not lang or lang == 'null':
|
||||
lang = "en"
|
||||
|
@ -2742,7 +2708,6 @@ class NewHomeAddShows(MainHandler):
|
|||
lang_id = sickbeard.indexerApi().config['langabbv_to_id'][lang]
|
||||
return json.dumps({'results': final_results, 'langid': lang_id})
|
||||
|
||||
|
||||
def massAddTable(self, rootDir=None):
|
||||
t = PageTemplate(headers=self.request.headers, file="home_massAddTable.tmpl")
|
||||
t.submenu = HomeMenu()
|
||||
|
@ -3110,7 +3075,6 @@ class NewHomeAddShows(MainHandler):
|
|||
|
||||
return (indexer, show_dir, indexer_id, show_name)
|
||||
|
||||
|
||||
def addExistingShows(self, shows_to_add=None, promptForSettings=None):
|
||||
"""
|
||||
Receives a dir list and add them. Adds the ones with given TVDB IDs first, then forwards
|
||||
|
@ -3192,12 +3156,10 @@ class ErrorLogs(MainHandler):
|
|||
|
||||
return _munge(t)
|
||||
|
||||
|
||||
def clearerrors(self, *args, **kwargs):
|
||||
classes.ErrorViewer.clear()
|
||||
redirect("/errorlogs/")
|
||||
|
||||
|
||||
def viewlog(self, minLevel=logger.MESSAGE, maxLines=500):
|
||||
|
||||
t = PageTemplate(headers=self.request.headers, file="viewlogs.tmpl")
|
||||
|
@ -3253,24 +3215,6 @@ class ErrorLogs(MainHandler):
|
|||
|
||||
|
||||
class Home(MainHandler):
|
||||
def is_alive(self, *args, **kwargs):
|
||||
if 'callback' in kwargs and '_' in kwargs:
|
||||
callback, _ = kwargs['callback'], kwargs['_']
|
||||
else:
|
||||
return "Error: Unsupported Request. Send jsonp request with 'callback' variable in the query string."
|
||||
|
||||
self.set_header('Cache-Control', 'max-age=0,no-cache,no-store')
|
||||
self.set_header('Content-Type', 'text/javascript')
|
||||
self.set_header('Access-Control-Allow-Origin', '*')
|
||||
self.set_header('Access-Control-Allow-Headers', 'x-requested-with')
|
||||
|
||||
if sickbeard.started:
|
||||
return callback + '(' + json.dumps(
|
||||
{"msg": str(sickbeard.PID)}) + ');'
|
||||
else:
|
||||
return callback + '(' + json.dumps({"msg": "nope"}) + ');'
|
||||
|
||||
|
||||
def index(self, *args, **kwargs):
|
||||
|
||||
t = PageTemplate(headers=self.request.headers, file="home.tmpl")
|
||||
|
@ -3309,7 +3253,6 @@ class Home(MainHandler):
|
|||
else:
|
||||
return "Unable to connect to host"
|
||||
|
||||
|
||||
def testTorrent(self, torrent_method=None, host=None, username=None, password=None):
|
||||
self.set_header('Cache-Control', 'max-age=0,no-cache,no-store')
|
||||
|
||||
|
@ -3321,7 +3264,6 @@ class Home(MainHandler):
|
|||
|
||||
return accesMsg
|
||||
|
||||
|
||||
def testGrowl(self, host=None, password=None):
|
||||
self.set_header('Cache-Control', 'max-age=0,no-cache,no-store')
|
||||
|
||||
|
@ -3338,7 +3280,6 @@ class Home(MainHandler):
|
|||
else:
|
||||
return "Registration and Testing of growl failed " + urllib.unquote_plus(host) + pw_append
|
||||
|
||||
|
||||
def testProwl(self, prowl_api=None, prowl_priority=0):
|
||||
self.set_header('Cache-Control', 'max-age=0,no-cache,no-store')
|
||||
|
||||
|
@ -3348,7 +3289,6 @@ class Home(MainHandler):
|
|||
else:
|
||||
return "Test prowl notice failed"
|
||||
|
||||
|
||||
def testBoxcar2(self, accesstoken=None, sound=None):
|
||||
self.set_header('Cache-Control', 'max-age=0,no-cache,no-store')
|
||||
|
||||
|
@ -3358,7 +3298,6 @@ class Home(MainHandler):
|
|||
else:
|
||||
return "Error sending Boxcar2 notification"
|
||||
|
||||
|
||||
def testPushover(self, userKey=None, apiKey=None):
|
||||
self.set_header('Cache-Control', 'max-age=0,no-cache,no-store')
|
||||
|
||||
|
@ -3368,13 +3307,11 @@ class Home(MainHandler):
|
|||
else:
|
||||
return "Error sending Pushover notification"
|
||||
|
||||
|
||||
def twitterStep1(self, *args, **kwargs):
|
||||
self.set_header('Cache-Control', 'max-age=0,no-cache,no-store')
|
||||
|
||||
return notifiers.twitter_notifier._get_authorization()
|
||||
|
||||
|
||||
def twitterStep2(self, key):
|
||||
self.set_header('Cache-Control', 'max-age=0,no-cache,no-store')
|
||||
|
||||
|
@ -3385,7 +3322,6 @@ class Home(MainHandler):
|
|||
else:
|
||||
return "Unable to verify key"
|
||||
|
||||
|
||||
def testTwitter(self, *args, **kwargs):
|
||||
self.set_header('Cache-Control', 'max-age=0,no-cache,no-store')
|
||||
|
||||
|
@ -3395,7 +3331,6 @@ class Home(MainHandler):
|
|||
else:
|
||||
return "Error sending tweet"
|
||||
|
||||
|
||||
def testXBMC(self, host=None, username=None, password=None):
|
||||
self.set_header('Cache-Control', 'max-age=0,no-cache,no-store')
|
||||
|
||||
|
@ -3451,7 +3386,6 @@ class Home(MainHandler):
|
|||
else:
|
||||
return notifiers.libnotify.diagnose()
|
||||
|
||||
|
||||
def testNMJ(self, host=None, database=None, mount=None):
|
||||
self.set_header('Cache-Control', 'max-age=0,no-cache,no-store')
|
||||
|
||||
|
@ -3462,7 +3396,6 @@ class Home(MainHandler):
|
|||
else:
|
||||
return "Test failed to start the scan update"
|
||||
|
||||
|
||||
def settingsNMJ(self, host=None):
|
||||
self.set_header('Cache-Control', 'max-age=0,no-cache,no-store')
|
||||
|
||||
|
@ -3474,7 +3407,6 @@ class Home(MainHandler):
|
|||
else:
|
||||
return '{"message": "Failed! Make sure your Popcorn is on and NMJ is running. (see Log & Errors -> Debug for detailed info)", "database": "", "mount": ""}'
|
||||
|
||||
|
||||
def testNMJv2(self, host=None):
|
||||
self.set_header('Cache-Control', 'max-age=0,no-cache,no-store')
|
||||
|
||||
|
@ -3485,7 +3417,6 @@ class Home(MainHandler):
|
|||
else:
|
||||
return "Test notice failed to " + urllib.unquote_plus(host)
|
||||
|
||||
|
||||
def settingsNMJv2(self, host=None, dbloc=None, instance=None):
|
||||
self.set_header('Cache-Control', 'max-age=0,no-cache,no-store')
|
||||
|
||||
|
@ -3498,7 +3429,6 @@ class Home(MainHandler):
|
|||
return '{"message": "Unable to find NMJ Database at location: %(dbloc)s. Is the right location selected and PCH running?", "database": ""}' % {
|
||||
"dbloc": dbloc}
|
||||
|
||||
|
||||
def testTrakt(self, api=None, username=None, password=None):
|
||||
self.set_header('Cache-Control', 'max-age=0,no-cache,no-store')
|
||||
|
||||
|
@ -3508,7 +3438,6 @@ class Home(MainHandler):
|
|||
else:
|
||||
return "Test notice failed to Trakt"
|
||||
|
||||
|
||||
def loadShowNotifyLists(self, *args, **kwargs):
|
||||
self.set_header('Cache-Control', 'max-age=0,no-cache,no-store')
|
||||
|
||||
|
@ -3523,7 +3452,6 @@ class Home(MainHandler):
|
|||
data['_size'] = size
|
||||
return json.dumps(data)
|
||||
|
||||
|
||||
def testEmail(self, host=None, port=None, smtp_from=None, use_tls=None, user=None, pwd=None, to=None):
|
||||
self.set_header('Cache-Control', 'max-age=0,no-cache,no-store')
|
||||
|
||||
|
@ -3533,7 +3461,6 @@ class Home(MainHandler):
|
|||
else:
|
||||
return 'ERROR: %s' % notifiers.email_notifier.last_err
|
||||
|
||||
|
||||
def testNMA(self, nma_api=None, nma_priority=0):
|
||||
self.set_header('Cache-Control', 'max-age=0,no-cache,no-store')
|
||||
|
||||
|
@ -3543,7 +3470,6 @@ class Home(MainHandler):
|
|||
else:
|
||||
return "Test NMA notice failed"
|
||||
|
||||
|
||||
def testPushalot(self, authorizationToken=None):
|
||||
self.set_header('Cache-Control', 'max-age=0,no-cache,no-store')
|
||||
|
||||
|
@ -3553,7 +3479,6 @@ class Home(MainHandler):
|
|||
else:
|
||||
return "Error sending Pushalot notification"
|
||||
|
||||
|
||||
def testPushbullet(self, api=None):
|
||||
self.set_header('Cache-Control', 'max-age=0,no-cache,no-store')
|
||||
|
||||
|
@ -3563,7 +3488,6 @@ class Home(MainHandler):
|
|||
else:
|
||||
return "Error sending Pushbullet notification"
|
||||
|
||||
|
||||
def getPushbulletDevices(self, api=None):
|
||||
self.set_header('Cache-Control', 'max-age=0,no-cache,no-store')
|
||||
|
||||
|
@ -3761,7 +3685,6 @@ class Home(MainHandler):
|
|||
|
||||
return _munge(t)
|
||||
|
||||
|
||||
def plotDetails(self, show, season, episode):
|
||||
myDB = db.DBConnection()
|
||||
result = myDB.select(
|
||||
|
@ -3769,7 +3692,6 @@ class Home(MainHandler):
|
|||
(int(show), int(season), int(episode)))
|
||||
return result[0]['description'] if result else 'Episode not found.'
|
||||
|
||||
|
||||
def sceneExceptions(self, show):
|
||||
exceptionsList = sickbeard.scene_exceptions.get_all_scene_exceptions(show)
|
||||
if not exceptionsList:
|
||||
|
@ -3782,7 +3704,6 @@ class Home(MainHandler):
|
|||
out.append("S" + str(season) + ": " + ", ".join(names))
|
||||
return "<br/>".join(out)
|
||||
|
||||
|
||||
def editShow(self, show=None, location=None, anyQualities=[], bestQualities=[], exceptions_list=[],
|
||||
flatten_folders=None, paused=None, directCall=False, air_by_date=None, sports=None, dvdorder=None,
|
||||
indexerLang=None, subtitles=None, archive_firstmatch=None, rls_ignore_words=None,
|
||||
|
@ -3974,7 +3895,6 @@ class Home(MainHandler):
|
|||
|
||||
redirect("/home/displayShow?show=" + show)
|
||||
|
||||
|
||||
def deleteShow(self, show=None, full=0):
|
||||
|
||||
if show is None:
|
||||
|
@ -4000,7 +3920,6 @@ class Home(MainHandler):
|
|||
'<b>%s</b>' % showObj.name)
|
||||
redirect("/home/")
|
||||
|
||||
|
||||
def refreshShow(self, show=None):
|
||||
|
||||
if show is None:
|
||||
|
@ -4022,7 +3941,6 @@ class Home(MainHandler):
|
|||
|
||||
redirect("/home/displayShow?show=" + str(showObj.indexerid))
|
||||
|
||||
|
||||
def updateShow(self, show=None, force=0):
|
||||
|
||||
if show is None:
|
||||
|
@ -4045,7 +3963,6 @@ class Home(MainHandler):
|
|||
|
||||
redirect("/home/displayShow?show=" + str(showObj.indexerid))
|
||||
|
||||
|
||||
def subtitleShow(self, show=None, force=0):
|
||||
|
||||
if show is None:
|
||||
|
@ -4063,7 +3980,6 @@ class Home(MainHandler):
|
|||
|
||||
redirect("/home/displayShow?show=" + str(showObj.indexerid))
|
||||
|
||||
|
||||
def updateXBMC(self, showName=None):
|
||||
|
||||
# only send update to first host in the list -- workaround for xbmc sql backend users
|
||||
|
@ -4209,7 +4125,6 @@ class Home(MainHandler):
|
|||
else:
|
||||
redirect("/home/displayShow?show=" + show)
|
||||
|
||||
|
||||
def testRename(self, show=None):
|
||||
|
||||
if show is None:
|
||||
|
@ -4255,7 +4170,6 @@ class Home(MainHandler):
|
|||
|
||||
return _munge(t)
|
||||
|
||||
|
||||
def doRename(self, show=None, eps=None):
|
||||
|
||||
if show is None or eps is None:
|
||||
|
@ -4515,7 +4429,6 @@ class Home(MainHandler):
|
|||
|
||||
return json.dumps(result)
|
||||
|
||||
|
||||
def retryEpisode(self, show, season, episode):
|
||||
|
||||
# retrieve the episode object and fail if we can't get one
|
||||
|
|
|
@ -5,11 +5,12 @@ import threading
|
|||
import sys
|
||||
import sickbeard
|
||||
import webserve
|
||||
import browser
|
||||
import webapi
|
||||
|
||||
from sickbeard import logger
|
||||
from sickbeard.helpers import create_https_certificates
|
||||
from tornado.web import Application, StaticFileHandler, RedirectHandler, HTTPError
|
||||
from tornado.web import Application, StaticFileHandler, HTTPError
|
||||
from tornado.httpserver import HTTPServer
|
||||
from tornado.ioloop import IOLoop
|
||||
|
||||
|
@ -41,7 +42,7 @@ class WebServer(threading.Thread):
|
|||
threading.Thread.__init__(self)
|
||||
self.daemon = True
|
||||
self.alive = True
|
||||
self.name = "TORNADO"
|
||||
self.name = 'TORNADO'
|
||||
self.io_loop = io_loop or IOLoop.current()
|
||||
|
||||
self.options = options
|
||||
|
@ -68,12 +69,12 @@ class WebServer(threading.Thread):
|
|||
if not (self.https_cert and os.path.exists(self.https_cert)) or not (
|
||||
self.https_key and os.path.exists(self.https_key)):
|
||||
if not create_https_certificates(self.https_cert, self.https_key):
|
||||
logger.log(u"Unable to create CERT/KEY files, disabling HTTPS")
|
||||
logger.log(u'Unable to create CERT/KEY files, disabling HTTPS')
|
||||
sickbeard.ENABLE_HTTPS = False
|
||||
self.enable_https = False
|
||||
|
||||
if not (os.path.exists(self.https_cert) and os.path.exists(self.https_key)):
|
||||
logger.log(u"Disabled HTTPS because of missing CERT and KEY files", logger.WARNING)
|
||||
logger.log(u'Disabled HTTPS because of missing CERT and KEY files', logger.WARNING)
|
||||
sickbeard.ENABLE_HTTPS = False
|
||||
self.enable_https = False
|
||||
|
||||
|
@ -83,47 +84,87 @@ class WebServer(threading.Thread):
|
|||
autoreload=False,
|
||||
gzip=True,
|
||||
xheaders=sickbeard.HANDLE_REVERSE_PROXY,
|
||||
cookie_secret='61oETzKXQAGaYdkL5gEmGeJJFuYh7EQnp2XdTP1o/Vo='
|
||||
cookie_secret=sickbeard.COOKIE_SECRET,
|
||||
login_url='%s/login/' % self.options['web_root'],
|
||||
)
|
||||
|
||||
# Main Handler
|
||||
self.app.add_handlers(".*$", [
|
||||
(r'%s/api/(.*)(/?)' % self.options['web_root'], webapi.Api),
|
||||
(r'%s/(.*)(/?)' % self.options['web_root'], webserve.MainHandler),
|
||||
(r'(.*)', webserve.MainHandler)
|
||||
self.app.add_handlers('.*$', [
|
||||
(r'%s/api/builder(/?)(.*)' % self.options['web_root'], webapi.ApiBuilder),
|
||||
(r'%s/api(/?.*)' % self.options['web_root'], webapi.Api),
|
||||
(r'%s/config/general(/?.*)' % self.options['web_root'], webserve.ConfigGeneral),
|
||||
(r'%s/config/search(/?.*)' % self.options['web_root'], webserve.ConfigSearch),
|
||||
(r'%s/config/providers(/?.*)' % self.options['web_root'], webserve.ConfigProviders),
|
||||
(r'%s/config/subtitles(/?.*)' % self.options['web_root'], webserve.ConfigSubtitles),
|
||||
(r'%s/config/postProcessing(/?.*)' % self.options['web_root'], webserve.ConfigPostProcessing),
|
||||
(r'%s/config/notifications(/?.*)' % self.options['web_root'], webserve.ConfigNotifications),
|
||||
(r'%s/config/anime(/?.*)' % self.options['web_root'], webserve.ConfigAnime),
|
||||
(r'%s/config(/?.*)' % self.options['web_root'], webserve.Config),
|
||||
(r'%s/errorlogs(/?.*)' % self.options['web_root'], webserve.ErrorLogs),
|
||||
(r'%s/history(/?.*)' % self.options['web_root'], webserve.History),
|
||||
(r'%s/home/is_alive(/?.*)' % self.options['web_root'], webserve.IsAliveHandler),
|
||||
(r'%s/home/addShows(/?.*)' % self.options['web_root'], webserve.NewHomeAddShows),
|
||||
(r'%s/home/postprocess(/?.*)' % self.options['web_root'], webserve.HomePostProcess),
|
||||
(r'%s/home(/?.*)' % self.options['web_root'], webserve.Home),
|
||||
(r'%s/manage/manageSearches(/?.*)' % self.options['web_root'], webserve.ManageSearches),
|
||||
(r'%s/manage/(/?.*)' % self.options['web_root'], webserve.Manage),
|
||||
(r'%s/ui(/?.*)' % self.options['web_root'], webserve.UI),
|
||||
(r'%s/browser(/?.*)' % self.options['web_root'], browser.WebFileBrowser),
|
||||
(r'%s(/?.*)' % self.options['web_root'], webserve.MainHandler),
|
||||
])
|
||||
|
||||
# Static Path Handler
|
||||
self.app.add_handlers(".*$", [
|
||||
(r'%s/(favicon\.ico)' % self.options['web_root'], MultiStaticFileHandler,
|
||||
{'paths': [os.path.join(self.options['data_root'], 'images/ico/favicon.ico')]}),
|
||||
(r'%s/%s/(.*)(/?)' % (self.options['web_root'], 'images'), MultiStaticFileHandler,
|
||||
{'paths': [os.path.join(self.options['data_root'], 'images'),
|
||||
os.path.join(sickbeard.CACHE_DIR, 'images')]}),
|
||||
(r'%s/%s/(.*)(/?)' % (self.options['web_root'], 'css'), MultiStaticFileHandler,
|
||||
{'paths': [os.path.join(self.options['data_root'], 'css')]}),
|
||||
(r'%s/%s/(.*)(/?)' % (self.options['web_root'], 'js'), MultiStaticFileHandler,
|
||||
{'paths': [os.path.join(self.options['data_root'], 'js')]}),
|
||||
# webui login/logout handlers
|
||||
self.app.add_handlers('.*$', [
|
||||
(r'%s/login(/?)' % self.options['web_root'], webserve.LoginHandler),
|
||||
(r'%s/logout(/?)' % self.options['web_root'], webserve.LogoutHandler),
|
||||
])
|
||||
|
||||
# Web calendar handler (Needed because option Unprotected calendar)
|
||||
self.app.add_handlers('.*$', [
|
||||
(r'%s/calendar' % self.options['web_root'], webserve.CalendarHandler),
|
||||
])
|
||||
|
||||
# Static File Handlers
|
||||
self.app.add_handlers('.*$', [
|
||||
# favicon
|
||||
(r'%s/(favicon\.ico)' % self.options['web_root'], StaticFileHandler,
|
||||
{'path': os.path.join(self.options['data_root'], 'images/ico/favicon.ico')}),
|
||||
|
||||
# images
|
||||
(r'%s/images/(.*)' % self.options['web_root'], StaticFileHandler,
|
||||
{'path': os.path.join(self.options['data_root'], 'images')}),
|
||||
|
||||
# cached images
|
||||
(r'%s/cache/images/(.*)' % self.options['web_root'], StaticFileHandler,
|
||||
{'path': os.path.join(sickbeard.CACHE_DIR, 'images')}),
|
||||
|
||||
# css
|
||||
(r'%s/css/(.*)' % self.options['web_root'], StaticFileHandler,
|
||||
{'path': os.path.join(self.options['data_root'], 'css')}),
|
||||
|
||||
# javascript
|
||||
(r'%s/js/(.*)' % self.options['web_root'], StaticFileHandler,
|
||||
{'path': os.path.join(self.options['data_root'], 'js')}),
|
||||
])
|
||||
|
||||
|
||||
def run(self):
|
||||
if self.enable_https:
|
||||
protocol = "https"
|
||||
self.server = HTTPServer(self.app, ssl_options={"certfile": self.https_cert, "keyfile": self.https_key})
|
||||
protocol = 'https'
|
||||
self.server = HTTPServer(self.app, ssl_options={'certfile': self.https_cert, 'keyfile': self.https_key})
|
||||
else:
|
||||
protocol = "http"
|
||||
protocol = 'http'
|
||||
self.server = HTTPServer(self.app)
|
||||
|
||||
logger.log(u"Starting SickGear on " + protocol + "://" + str(self.options['host']) + ":" + str(
|
||||
self.options['port']) + "/")
|
||||
logger.log(u'Starting SickGear on ' + protocol + '://' + str(self.options['host']) + ':' + str(
|
||||
self.options['port']) + '/')
|
||||
|
||||
try:
|
||||
self.server.listen(self.options['port'], self.options['host'])
|
||||
except:
|
||||
etype, evalue, etb = sys.exc_info()
|
||||
logger.log(
|
||||
"Could not start webserver on %s. Excpeption: %s, Error: %s" % (self.options['port'], etype, evalue),
|
||||
'Could not start webserver on %s. Excpeption: %s, Error: %s' % (self.options['port'], etype, evalue),
|
||||
logger.ERROR)
|
||||
return
|
||||
|
||||
|
@ -131,7 +172,7 @@ class WebServer(threading.Thread):
|
|||
self.io_loop.start()
|
||||
self.io_loop.close(True)
|
||||
except (IOError, ValueError):
|
||||
# Ignore errors like "ValueError: I/O operation on closed kqueue fd". These might be thrown during a reload.
|
||||
# Ignore errors like 'ValueError: I/O operation on closed kqueue fd'. These might be thrown during a reload.
|
||||
pass
|
||||
|
||||
def shutDown(self):
|
||||
|
|
Loading…
Reference in a new issue