Compare commits
129 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b7e2bd9684 | ||
|
|
f0a243e3d3 | ||
|
|
6e108c9ef2 | ||
|
|
89edcc1924 | ||
|
|
8a6aca47a1 | ||
|
|
d03e5780b8 | ||
|
|
209d8f9b40 | ||
|
|
c257b1be3d | ||
|
|
2c48c8de2e | ||
|
|
a767ef6aed | ||
|
|
ad61d1dd03 | ||
|
|
33c3d187a0 | ||
|
|
4eb486d4e2 | ||
|
|
bfb6c167a4 | ||
|
|
44abf3bdf6 | ||
|
|
c950572592 | ||
|
|
3999cb13fd | ||
|
|
af65075f0c | ||
|
|
de2a2b465b | ||
|
|
cd7a77f02d | ||
|
|
f4a5394b63 | ||
|
|
3fb6a8dedb | ||
|
|
50c8f84eba | ||
|
|
2c7ecdee92 | ||
|
|
72390a793a | ||
|
|
04ad4e5d3e | ||
|
|
5ef9c6a433 | ||
|
|
e6baffc839 | ||
|
|
e361eb25a5 | ||
|
|
9b420e91c9 | ||
|
|
3a4bf971b2 | ||
|
|
1128691c5d | ||
|
|
15043aef3f | ||
|
|
2a3b4afa03 | ||
|
|
00a98efa81 | ||
|
|
f013dd7f0d | ||
|
|
7b91b1c769 | ||
|
|
5583cce322 | ||
|
|
b995c5f992 | ||
|
|
214ac4a53d | ||
|
|
c0f2f59fc1 | ||
|
|
b90a847a6f | ||
|
|
a58bb385f5 | ||
|
|
9754baeb1c | ||
|
|
ffcd154966 | ||
|
|
97cfe9488c | ||
|
|
374b6f616a | ||
|
|
e2f51595b6 | ||
|
|
04091a16aa | ||
|
|
9d9d2fd9a2 | ||
|
|
5746115331 | ||
|
|
42f1a4926c | ||
|
|
7d87fd461b | ||
|
|
1ba9976979 | ||
|
|
659c199043 | ||
|
|
81a3f53226 | ||
|
|
1cbff28f67 | ||
|
|
8e15acbf30 | ||
|
|
e07be60db6 | ||
|
|
539c9662ff | ||
|
|
b396014f8d | ||
|
|
1db32415b6 | ||
|
|
b24629db6b | ||
|
|
9b5cdcf8fb | ||
|
|
4831415d14 | ||
|
|
a4c51f0b20 | ||
|
|
ec3ba1fb93 | ||
|
|
61966f7036 | ||
|
|
4f69e81841 | ||
|
|
d0d90581df | ||
|
|
8ea5c27633 | ||
|
|
517500fdf3 | ||
|
|
c4c1c9b6ab | ||
|
|
2388889ede | ||
|
|
55cfe878d7 | ||
|
|
a2daaee468 | ||
|
|
2c360e395e | ||
|
|
399cfee594 | ||
|
|
be646ae6ab | ||
|
|
b470253d9f | ||
|
|
b83c493492 | ||
|
|
991277bb01 | ||
|
|
5626013b81 | ||
|
|
2810d37758 | ||
|
|
c2f08f01e0 | ||
|
|
17ff087e06 | ||
|
|
77de565b7c | ||
|
|
54d238aa4d | ||
|
|
379d09f8cc | ||
|
|
00de72b127 | ||
|
|
f9c84fa7dd | ||
|
|
c8e46691bb | ||
|
|
df1bb636e5 | ||
|
|
ff886fad0d | ||
|
|
6dbee7a413 | ||
|
|
3f8fcd7172 | ||
|
|
d94f7388e6 | ||
|
|
ad8b49fea8 | ||
|
|
ce00270c12 | ||
|
|
8c501f8f58 | ||
|
|
ce313ebc65 | ||
|
|
887ad881a2 | ||
|
|
ce40827552 | ||
|
|
2777d89482 | ||
|
|
727b300a0e | ||
|
|
652b021a8e | ||
|
|
fdf33acfbb | ||
|
|
b001bc9b6f | ||
|
|
8802cb1d8c | ||
|
|
e19a2fbae7 | ||
|
|
53e38f98f9 | ||
|
|
e783e227f6 | ||
|
|
f3dfbe4181 | ||
|
|
bcd8ca8bc4 | ||
|
|
816d6a63cd | ||
|
|
88d3f25700 | ||
|
|
80f69b11db | ||
|
|
81a11f20c8 | ||
|
|
9e2a839953 | ||
|
|
3cefcde270 | ||
|
|
87a1eacfe7 | ||
|
|
7cbc1a8419 | ||
|
|
7b5570eb0b | ||
|
|
1a43a4dcf0 | ||
|
|
2c2a6592c7 | ||
|
|
f31de6ee4e | ||
|
|
8fcd1f6b6c | ||
|
|
d7f3a473d7 | ||
|
|
ab2eb0c94e |
2
.gitignore
vendored
@@ -1,5 +1,5 @@
|
||||
#Compiled python
|
||||
*.py[cod]
|
||||
*.py[co]
|
||||
|
||||
# Working folders for Win build
|
||||
build/
|
||||
|
||||
27
ABOUT.txt
@@ -1,5 +1,5 @@
|
||||
*******************************************
|
||||
*** This is SABnzbd 1.1.x ***
|
||||
*** This is SABnzbd 1.0.2 ***
|
||||
*******************************************
|
||||
SABnzbd is an open-source cross-platform binary newsreader.
|
||||
It simplifies the process of downloading from Usenet dramatically,
|
||||
@@ -10,22 +10,33 @@ SABnzbd also has a fully customizable user interface,
|
||||
and offers a complete API for third-party applications to hook into.
|
||||
|
||||
There is an extensive Wiki on the use of SABnzbd.
|
||||
https://sabnzbd.org/wiki/
|
||||
http://wiki.sabnzbd.org/
|
||||
|
||||
IMPORTANT INFORMATION about release 1.0.0:
|
||||
https://sabnzbd.org/wiki/introducing-1-0
|
||||
http://wiki.sabnzbd.org/introducing-1-0-0
|
||||
|
||||
|
||||
Please also read the file "ISSUES.txt"
|
||||
|
||||
The organization of the download queue is different from 0.7.x (and older).
|
||||
1.0.0 will not finish downloading an existing queue.
|
||||
Also, your sabnzbd.ini file will be upgraded, making it
|
||||
incompatible with older releases.
|
||||
|
||||
*******************************************
|
||||
*** Upgrading from 0.7.x and below ***
|
||||
*** Upgrading from 0.7.x or 0.6.x ***
|
||||
*******************************************
|
||||
Empty your current queue
|
||||
Stop SABnzbd.
|
||||
Install new version
|
||||
Start SABnzbd.
|
||||
|
||||
|
||||
*******************************************
|
||||
*** Upgrading from 0.5.x ***
|
||||
*******************************************
|
||||
Stop SABnzbd.
|
||||
Uninstall current version, keeping the data.
|
||||
Install new version
|
||||
Start SABnzbd.
|
||||
|
||||
The organization of the download queue is different from 0.7.x (and older).
|
||||
1.0.0 will not finish downloading an existing queue.
|
||||
Also, your sabnzbd.ini file will be upgraded, making it
|
||||
incompatible with older releases.
|
||||
|
||||
19
INSTALL.txt
@@ -1,4 +1,4 @@
|
||||
SABnzbd 1.1.0
|
||||
SABnzbd 1.0.2
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
0) LICENSE
|
||||
@@ -55,14 +55,17 @@ You need to have Python installed plus some non-standard Python modules
|
||||
and a few tools.
|
||||
|
||||
Unix/Linux/OSX
|
||||
Python-2.7 http://www.python.org (2.7.9+ recommended)
|
||||
Python-2.6 or 2.7 http://www.python.org
|
||||
|
||||
OSX Mavericks or newer
|
||||
OSX Leopard/SnowLeopard
|
||||
Python 2.6 http://www.activestate.com
|
||||
|
||||
OSX Lion/MountainLion
|
||||
Apple Python 2.7 Included in OSX (default)
|
||||
|
||||
Windows
|
||||
Python-2.7.latest http://www.python.com
|
||||
Most versions will work: 2.7.10+ recommended
|
||||
Most versions will work: 2.7.10 recommended
|
||||
PyWin32 use "pip install pypiwin32"
|
||||
|
||||
Essential modules
|
||||
@@ -71,7 +74,7 @@ Essential modules
|
||||
unrar >= 5.00+ http://www.rarlab.com/rar_add.htm
|
||||
|
||||
Optional modules
|
||||
unzip >= 6.00 http://www.info-zip.org/
|
||||
unzip >= 5.52 http://www.info-zip.org/
|
||||
7zip >= 9.20 http://www.7zip.org/
|
||||
yenc module >= 0.3 http://sabnzbd.sourceforge.net/yenc-0.3.tar.gz
|
||||
http://sabnzbd.sourceforge.net/yenc-0.3-w32fixed.zip (Win32-only)
|
||||
@@ -89,7 +92,7 @@ Optional modules Unix/Linux/OSX
|
||||
If not, you cannot use the NotifyOSD feature.
|
||||
|
||||
Embedded modules (preferably use the included version)
|
||||
CherryPy-6.0.2 with patches http://www.cherrypy.org
|
||||
CherryPy-3.8.0 with patches http://www.cherrypy.org
|
||||
|
||||
|
||||
Unpack the ZIP-file containing the SABnzbd sources to any folder of your liking.
|
||||
@@ -126,7 +129,7 @@ may help you solve problems easier.
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
Visit the WIKI site:
|
||||
https://sabnzbd.org/wiki/
|
||||
http://wiki.sabnzbd.org/
|
||||
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
@@ -135,4 +138,4 @@ Visit the WIKI site:
|
||||
|
||||
Several parts of SABnzbd were built by other people, illustrating the
|
||||
wonderful world of Free Open Source Software.
|
||||
See the licenses folder of the main program and of the skin folders.
|
||||
See the licenses folder of the main program and of the skin folders.
|
||||
|
||||
@@ -24,13 +24,13 @@
|
||||
For these the server blocking method is not very favourable.
|
||||
There is an INI-only option that will limit blocks to 1 minute.
|
||||
no_penalties = 1
|
||||
See: https://sabnzbd.org/wiki/configuration/1.0/special
|
||||
See: http://wiki.sabnzbd.org/configure-special-1-0
|
||||
|
||||
- Some third-party utilties try to probe SABnzbd API in such a way that you will
|
||||
often see warnings about unauthenticated access.
|
||||
If you are sure these probes are harmless, you can suppress the warnings by
|
||||
setting the option "api_warnings" to 0.
|
||||
See: https://sabnzbd.org/wiki/configuration/1.0/special
|
||||
See: http://wiki.sabnzbd.org/configure-special-1-0
|
||||
|
||||
- On OSX you may encounter downloaded files with foreign characters.
|
||||
The par2 repair may fail when the files were created on a Windows system.
|
||||
@@ -41,7 +41,7 @@
|
||||
You will see this only when downloaded files contain accented characters.
|
||||
You need to fix it yourself by running the convmv utility (available for most Linux platforms).
|
||||
Possible the file system override setting 'fsys_type' might be solve things:
|
||||
See: https://sabnzbd.org/wiki/configuration/1.0/special
|
||||
See: http://wiki.sabnzbd.org/configure-special-1-0
|
||||
|
||||
- The "Watched Folder" sometimes fails to delete the NZB files it has
|
||||
processed. This happens when other software still accesses these files.
|
||||
@@ -81,4 +81,4 @@
|
||||
- Squeeze Linux
|
||||
There is a "special" option that will allow you to select an alternative library.
|
||||
use_pickle = 1
|
||||
See: https://sabnzbd.org/wiki/configuration/1.0/special
|
||||
See: http://wiki.sabnzbd.org/configure-special-1-0
|
||||
|
||||
4
PKG-INFO
@@ -1,7 +1,7 @@
|
||||
Metadata-Version: 1.0
|
||||
Name: SABnzbd
|
||||
Version: 1.1.0
|
||||
Summary: SABnzbd-1.1.0
|
||||
Version: 1.0.2
|
||||
Summary: SABnzbd-1.0.2
|
||||
Home-page: http://sabnzbd.org
|
||||
Author: The SABnzbd Team
|
||||
Author-email: team@sabnzbd.org
|
||||
|
||||
20
README.md
@@ -55,24 +55,8 @@ If you want multi-language support, run:
|
||||
python tools/make_mo.py
|
||||
```
|
||||
|
||||
Our many other command line options are explained in depth [here](https://sabnzbd.org/wiki/advanced/command-line-parameters).
|
||||
Our many other command line options are explained in depth [here](http://wiki.sabnzbd.org/command-line-parameters).
|
||||
|
||||
## About Our Repo
|
||||
|
||||
The workflow we use, is a simplified form of "GitFlow".
|
||||
Basically:
|
||||
- "master" contains only stable releases (which have been merged to "master")
|
||||
- "develop" is the target for integration
|
||||
- "1.0.x" is a release and maintenance branch for 1.0.x: 1.0.0 -> 1.0.1 -> 1.0.2
|
||||
- "1.1.x" is a release and maintenance branch for 1.1.x: 1.1.0 -> 1.1.1 -> 1.1.2
|
||||
- "feature/my_feature" is a temporary feature branch
|
||||
- "hotfix/my_hotfix is an optional temporary branch for bugfix(es)
|
||||
|
||||
Condtions:
|
||||
- Merging of a stable release into "master" will be simple: the release branch is always right.
|
||||
- "master" is not merged back to "develop"
|
||||
- "develop" is not re-based on "master".
|
||||
- Release branches branch from "develop" only.
|
||||
- Bugfixes created specifically for a release branch are done there (because they are specific, they're not cherry-picked to "develop").
|
||||
- Bugfixes done on "develop" may be cherry-picked to a release branch.
|
||||
- We will not release a 1.0.2 if a 1.1.0 has already been released.
|
||||
We're going to be attempting to follow the [gitflow model](http://nvie.com/posts/a-successful-git-branching-model/), so you can consider "master" to be whatever our present stable release build is (presently 0.6.x) and "develop" to be whatever our next build will be (presently 0.7.x). Once we transition from unstable to stable dev builds we'll create release branches, and encourage you to follow along and help us test.
|
||||
|
||||
88
README.mkd
@@ -1,50 +1,52 @@
|
||||
Release Notes - SABnzbd 1.1.0
|
||||
Release Notes - SABnzbd 1.0.2
|
||||
===============================
|
||||
|
||||
## What's new in 1.1.0
|
||||
- Login via HTML-form instead of basic authentication
|
||||
- Glitter now offers Compact and Tabbed layouts (similar to old Classic skin)
|
||||
- Notification scripts for custom notification services
|
||||
- Enable upload of multiple NZBs from the UI
|
||||
- Performance improvements of download process
|
||||
- Added delayed download feature (for when your Usenet is behind the indexers)
|
||||
- Repair blocks are added prospectively during download when needed (reducing extra par2 verification runs)
|
||||
- Prevent action of password managers on Server page
|
||||
- Faster initial connections to servers with multiple IP-addresses (HappyEyeBalls)
|
||||
- Support of more Rating headers (for indexers that support it)
|
||||
- Glitter shows progress notifications for larger operations
|
||||
- Log files are now anonymized automatically, for easier posting on public websites
|
||||
- Added censored INI file to log download
|
||||
- Can now download zipped NZB files from indexer
|
||||
## Bugfixes in 1.0.2
|
||||
- Fix hangups at 100% when QuickCheck is off and "all-pars" is on
|
||||
- Fix handling of "too many connections" for some Usenet servers
|
||||
|
||||
|
||||
## Changes:
|
||||
- New SABnzbd logo
|
||||
- Job password is now a separate field and not included in the job-title anymore
|
||||
- Move some seldom used options to Special
|
||||
- Now requires Python 2.7 (2.6 no longer supported)
|
||||
- If filename occurs twice in NZB, only larger file is added
|
||||
- Display days when ETA is above 24h
|
||||
- API information now uses day/hour/min/sec notation when ETA above 24h
|
||||
- Bump self-signed certificate to sha256 (only for newly created)
|
||||
## What's new in 1.0.1
|
||||
- Prevent creating orphan items in "incomplete" when deleting downloading jobs.
|
||||
- Forced item with missing articles caused overflow into paused jobs
|
||||
- Do QuickCheck even on files that would be removed by the Cleanup-list (problematic for RAR files).
|
||||
- Fix "Download all par2 files" behavior
|
||||
- Treat ambiguous numeric values as number of minutes for custom pause time.
|
||||
- Accept MIME records that have only LF line endings (error in some third-party utilities)
|
||||
- Fix PushOver support.
|
||||
- Fix breaking Glitter bug with large script_log
|
||||
- Fix issues with deleting jobs via the API
|
||||
- Fix issue where Sonarr could not read using the History-API
|
||||
- Increase default cache to 450M
|
||||
- The pre-queue script can now return an accept value of 2, meaning immediate failure. (Useful for Sonarr.)
|
||||
- Add start script for portable Windows installations
|
||||
|
||||
## What's new in 1.0.0
|
||||
- Full Unicode support with Chinese and Russian translations
|
||||
- New default UI: Glitter
|
||||
- Server priorities instead of primary/backup ==> REVIEW YOUR SERVER SETTINGS!
|
||||
- Newsserver IPv6 load balancing aka Happy Eyeballs / RFC 6555
|
||||
- Duplicate detection for series
|
||||
- More filters in RSS
|
||||
- 7zip support
|
||||
- Option to save repair time by downloading all par2 files
|
||||
- Support for long paths in Windows (above 260)
|
||||
- Improved security for external access
|
||||
- Lots of small improvements and bug fixes
|
||||
- Redesign of notifications classes
|
||||
- More notification services supported
|
||||
- Diagnostic dashboard tab for "Status" page
|
||||
- Bonjour/ZeroConfig support
|
||||
|
||||
## Bug fixes
|
||||
- Fix errors when saving job files to disk
|
||||
- Prevent "lock" errors in the History database
|
||||
- Restore support for multi-volume 7zip files
|
||||
- Restore scanning for passwords after NZB name edit
|
||||
- Fix portable.cmd
|
||||
- Fix for stalling download at 99% (when using prospective downloading)
|
||||
- Fix problem with deleting files in NZB details view
|
||||
- Prevent old shutdown-page from stopping a new SABnzbd instance
|
||||
- Prevent watched folder scan crash on invalid file names
|
||||
- Fix XSS vulnerability on OSX and Unix**
|
||||
|
||||
|
||||
** The XSS vulnerability was discovered by Han Sahin from Securify BV.
|
||||
Thank you, Han.
|
||||
|
||||
## Remarks
|
||||
- SABnzbd's webserver now doesn't listen to IPv6 addresses by default.
|
||||
- Use Config->Special->ipv6_hosting if you want this enabled.
|
||||
- "localhost" will be replaced with "127.0.0.1", check any browser bookmark and third-party tool
|
||||
- Classic skin has been removed
|
||||
- Support extra parameters for par2 on other platforms than Windows
|
||||
- Option to verify HTTPS connections (default off)
|
||||
- Auto-negotiates best Usenet ssl protocol (override possible)
|
||||
- When upgrading from 0.7.x, a backup server will get priority 1
|
||||
|
||||
|
||||
## About
|
||||
@@ -57,8 +59,8 @@ Thank you, Han.
|
||||
(c) Copyright 2007-2016 by "The SABnzbd-team" \<team@sabnzbd.org\>
|
||||
|
||||
|
||||
### IMPORTANT INFORMATION about release 1.0.0+
|
||||
<https://sabnzbd.org/wiki/introducing-1-0>
|
||||
### IMPORTANT INFORMATION about release 1.0.0
|
||||
<http://wiki.sabnzbd.org/introducing-1-0-0>
|
||||
|
||||
### Known problems and solutions
|
||||
- Read the file "ISSUES.txt"
|
||||
|
||||
79
SABnzbd.py
@@ -16,8 +16,8 @@
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
import sys
|
||||
if sys.version_info[:2] < (2, 7) or sys.version_info[:2] >= (3, 0):
|
||||
print "Sorry, requires Python 2.7."
|
||||
if sys.version_info[:2] < (2, 6) or sys.version_info[:2] >= (3, 0):
|
||||
print "Sorry, requires Python 2.6 or 2.7."
|
||||
sys.exit(1)
|
||||
|
||||
# Make sure UTF-8 is default 8bit encoding
|
||||
@@ -27,7 +27,7 @@ try:
|
||||
sys.setdefaultencoding('utf-8')
|
||||
except:
|
||||
print 'Sorry, you MUST add the SABnzbd folder to the PYTHONPATH environment variable'
|
||||
print 'or find another way to force Python to use UTF-8 for text encoding.'
|
||||
print 'or find another way to force Python to use UTF-8 for string encoding.'
|
||||
sys.exit(1)
|
||||
|
||||
import logging
|
||||
@@ -52,8 +52,8 @@ except:
|
||||
sys.exit(1)
|
||||
|
||||
import cherrypy
|
||||
if [int(n) for n in cherrypy.__version__.split('.')] < [6, 0, 2]:
|
||||
print 'Sorry, requires Python module Cherrypy 6.0.2+ (use the included version)'
|
||||
if [int(n) for n in cherrypy.__version__.split('.')] < [3, 8, 0]:
|
||||
print 'Sorry, requires Python module Cherrypy 3.8.0+ (use the included version)'
|
||||
sys.exit(1)
|
||||
|
||||
from cherrypy import _cpserver
|
||||
@@ -98,13 +98,26 @@ import sabnzbd.config as config
|
||||
import sabnzbd.cfg
|
||||
import sabnzbd.downloader
|
||||
from sabnzbd.encoding import unicoder, deunicode
|
||||
import sabnzbd.notifier as notifier
|
||||
import sabnzbd.growler as growler
|
||||
import sabnzbd.zconfig
|
||||
|
||||
from threading import Thread
|
||||
|
||||
LOG_FLAG = False # Global for this module, signaling loglevel change
|
||||
|
||||
_first_log = True
|
||||
|
||||
|
||||
def FORCELOG(txt):
|
||||
global _first_log
|
||||
if _first_log:
|
||||
os.remove('d:/temp/debug.txt')
|
||||
_first_log = False
|
||||
ff = open('d:/temp/debug.txt', 'a+')
|
||||
ff.write(txt)
|
||||
ff.write('\n')
|
||||
ff.close()
|
||||
|
||||
|
||||
try:
|
||||
import win32api
|
||||
@@ -491,12 +504,14 @@ def print_modules():
|
||||
if sabnzbd.newsunpack.ZIP_COMMAND:
|
||||
logging.info("unzip binary... found (%s)", sabnzbd.newsunpack.ZIP_COMMAND)
|
||||
else:
|
||||
logging.info(T('unzip binary... NOT found!'))
|
||||
if sabnzbd.cfg.enable_unzip():
|
||||
logging.warning(T('unzip binary... NOT found!'))
|
||||
|
||||
if sabnzbd.newsunpack.SEVEN_COMMAND:
|
||||
logging.info("7za binary... found (%s)", sabnzbd.newsunpack.SEVEN_COMMAND)
|
||||
else:
|
||||
logging.info(T('7za binary... NOT found!'))
|
||||
if sabnzbd.cfg.enable_7zip():
|
||||
logging.info(T('7za binary... NOT found!'))
|
||||
|
||||
if not sabnzbd.WIN32:
|
||||
if sabnzbd.newsunpack.NICE_COMMAND:
|
||||
@@ -676,7 +691,7 @@ def get_webhost(cherryhost, cherryport, https_port):
|
||||
|
||||
if cherryport == https_port and sabnzbd.cfg.enable_https():
|
||||
sabnzbd.cfg.enable_https.set(False)
|
||||
# Should have a translated message, but that's not available yet
|
||||
# TODO: Should have a translated message, but that's not available yet
|
||||
logging.error(T('HTTP and HTTPS ports cannot be the same'))
|
||||
|
||||
return cherryhost, cherryport, browserhost, https_port
|
||||
@@ -928,7 +943,7 @@ def main():
|
||||
autobrowser = bool(int(arg))
|
||||
except:
|
||||
autobrowser = True
|
||||
elif opt in ('--autorestarted', ):
|
||||
elif opt in ('--autorestarted'):
|
||||
autorestarted = True
|
||||
elif opt in ('-c', '--clean'):
|
||||
clean_up = True
|
||||
@@ -988,7 +1003,7 @@ def main():
|
||||
osx_console = True
|
||||
elif opt in ('--ipv6_hosting',):
|
||||
ipv6_hosting = arg
|
||||
|
||||
|
||||
sabnzbd.MY_FULLNAME = os.path.normpath(os.path.abspath(sabnzbd.MY_FULLNAME))
|
||||
sabnzbd.MY_NAME = os.path.basename(sabnzbd.MY_FULLNAME)
|
||||
sabnzbd.DIR_PROG = os.path.dirname(sabnzbd.MY_FULLNAME)
|
||||
@@ -1110,15 +1125,14 @@ def main():
|
||||
else:
|
||||
if not url:
|
||||
url = 'https://%s:%s/sabnzbd/api?' % (browserhost, port)
|
||||
if not sabnzbd.cfg.fixed_ports():
|
||||
if new_instance or not check_for_sabnzbd(url, upload_nzbs, autobrowser):
|
||||
newport = find_free_port(browserhost, port)
|
||||
if newport > 0:
|
||||
sabnzbd.cfg.https_port.set(newport)
|
||||
if https_port:
|
||||
https_port = newport
|
||||
else:
|
||||
http_port = newport
|
||||
if new_instance or not check_for_sabnzbd(url, upload_nzbs, autobrowser):
|
||||
newport = find_free_port(browserhost, port)
|
||||
if newport > 0:
|
||||
sabnzbd.cfg.https_port.set(newport)
|
||||
if https_port:
|
||||
https_port = newport
|
||||
else:
|
||||
http_port = newport
|
||||
except:
|
||||
Bail_Out(browserhost, cherryport, '49')
|
||||
|
||||
@@ -1131,12 +1145,11 @@ def main():
|
||||
else:
|
||||
if not url:
|
||||
url = 'http://%s:%s/sabnzbd/api?' % (browserhost, cherryport)
|
||||
if not sabnzbd.cfg.fixed_ports():
|
||||
if new_instance or not check_for_sabnzbd(url, upload_nzbs, autobrowser):
|
||||
port = find_free_port(browserhost, cherryport)
|
||||
if port > 0:
|
||||
sabnzbd.cfg.cherryport.set(port)
|
||||
cherryport = port
|
||||
if new_instance or not check_for_sabnzbd(url, upload_nzbs, autobrowser):
|
||||
port = find_free_port(browserhost, cherryport)
|
||||
if port > 0:
|
||||
sabnzbd.cfg.cherryport.set(port)
|
||||
cherryport = port
|
||||
except:
|
||||
Bail_Out(browserhost, cherryport, '49')
|
||||
|
||||
@@ -1330,6 +1343,8 @@ def main():
|
||||
sabnzbd.WEB_COLOR2 = CheckColor(sabnzbd.cfg.web_color2(), web_dir2)
|
||||
sabnzbd.cfg.web_color2.set(sabnzbd.WEB_COLOR2)
|
||||
|
||||
logging.debug('Unwanted extensions are ... %s', sabnzbd.cfg.unwanted_extensions())
|
||||
|
||||
if fork and not sabnzbd.WIN32:
|
||||
daemonize()
|
||||
|
||||
@@ -1546,7 +1561,7 @@ def main():
|
||||
if sabnzbd.FOUNDATION:
|
||||
import sabnzbd.osxmenu
|
||||
sabnzbd.osxmenu.notify("SAB_Launched", None)
|
||||
notifier.send_notification('SABnzbd%s' % notifier.hostname(),
|
||||
growler.send_notification('SABnzbd%s' % growler.hostname(),
|
||||
T('SABnzbd %s started') % sabnzbd.__version__, 'startup')
|
||||
# Now's the time to check for a new version
|
||||
check_latest_version()
|
||||
@@ -1595,11 +1610,11 @@ def main():
|
||||
if sabnzbd.LAST_WARNING:
|
||||
msg = sabnzbd.LAST_WARNING
|
||||
sabnzbd.LAST_WARNING = None
|
||||
sabnzbd.notifier.send_notification(T('Warning'), msg, 'warning')
|
||||
sabnzbd.growler.send_notification(T('Warning'), msg, 'warning')
|
||||
if sabnzbd.LAST_ERROR:
|
||||
msg = sabnzbd.LAST_ERROR
|
||||
sabnzbd.LAST_ERROR = None
|
||||
sabnzbd.notifier.send_notification(T('Error'), msg, 'error')
|
||||
sabnzbd.growler.send_notification(T('Error'), msg, 'error')
|
||||
|
||||
if sabnzbd.WIN_SERVICE:
|
||||
rc = win32event.WaitForMultipleObjects((sabnzbd.WIN_SERVICE.hWaitStop,
|
||||
@@ -1607,7 +1622,7 @@ def main():
|
||||
if rc == win32event.WAIT_OBJECT_0:
|
||||
if mail:
|
||||
mail.send('stop')
|
||||
sabnzbd.save_state()
|
||||
sabnzbd.save_state(flag=True)
|
||||
logging.info('Leaving SABnzbd')
|
||||
sabnzbd.SABSTOP = True
|
||||
return
|
||||
@@ -1669,7 +1684,7 @@ def main():
|
||||
sys.argv = re_argv
|
||||
os.chdir(org_dir)
|
||||
if sabnzbd.DARWIN:
|
||||
# When executing from sources on osx, after a restart, process is detached from console
|
||||
# TODO: when executing from sources on osx, after a restart, process is detached from console
|
||||
# If OSX frozen restart of app instead of embedded python
|
||||
if getattr(sys, 'frozen', None) == 'macosx_app':
|
||||
# [[NSProcessInfo processInfo] processIdentifier]]
|
||||
@@ -1716,7 +1731,7 @@ def main():
|
||||
# Failing AppHelper libary!
|
||||
os._exit(0)
|
||||
else:
|
||||
notifier.send_notification('SABnzbd', T('SABnzbd shutdown finished'), 'startup')
|
||||
growler.send_notification('SABnzbd', T('SABnzbd shutdown finished'), 'startup')
|
||||
os._exit(0)
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Copyright (c) 2004-2016, CherryPy Team (team@cherrypy.org)
|
||||
Copyright (c) 2004-2015, CherryPy Team (team@cherrypy.org)
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
|
||||
76
cherrypy/Patch-for-CP-3.8.0.diff
Normal file
@@ -0,0 +1,76 @@
|
||||
From 0f6da83f5acff3fc9c4eda2d3111849ef1429711 Mon Sep 17 00:00:00 2001
|
||||
From: shypike <shypike@sabnzbd.org>
|
||||
Date: Thu, 23 Jul 2015 18:16:27 +0200
|
||||
Subject: [PATCH] Patch CherryPy to support 301 redirection.
|
||||
|
||||
Needed to support the broken Bonjour/ZeroConfig protocol that
|
||||
only allows an HTTP address to set, even for a HTTPS-only server.
|
||||
---
|
||||
cherrypy/wsgiserver/wsgiserver2.py | 23 ++++++++++++++++++-----
|
||||
1 file changed, 18 insertions(+), 5 deletions(-)
|
||||
|
||||
diff --git a/cherrypy/wsgiserver/wsgiserver2.py b/cherrypy/wsgiserver/wsgiserver2.py
|
||||
index c0896d3..9367f7b 100644
|
||||
--- a/cherrypy/wsgiserver/wsgiserver2.py
|
||||
+++ b/cherrypy/wsgiserver/wsgiserver2.py
|
||||
@@ -75,7 +75,7 @@ __all__ = ['HTTPRequest', 'HTTPConnection', 'HTTPServer',
|
||||
'WorkerThread', 'ThreadPool', 'SSLAdapter',
|
||||
'CherryPyWSGIServer',
|
||||
'Gateway', 'WSGIGateway', 'WSGIGateway_10', 'WSGIGateway_u0',
|
||||
- 'WSGIPathInfoDispatcher', 'get_ssl_adapter_class']
|
||||
+ 'WSGIPathInfoDispatcher', 'get_ssl_adapter_class', 'redirect_url']
|
||||
|
||||
import os
|
||||
try:
|
||||
@@ -97,6 +97,7 @@ except ImportError:
|
||||
import StringIO
|
||||
DEFAULT_BUFFER_SIZE = -1
|
||||
|
||||
+REDIRECT_URL = None # Application can write its HTTP-->HTTPS redirection URL here
|
||||
|
||||
class FauxSocket(object):
|
||||
|
||||
@@ -167,6 +168,12 @@ quoted_slash = re.compile(ntob("(?i)%2F"))
|
||||
|
||||
import errno
|
||||
|
||||
+def redirect_url(url=None):
|
||||
+ global REDIRECT_URL
|
||||
+ if url and '%s' in url:
|
||||
+ REDIRECT_URL = url
|
||||
+ return REDIRECT_URL
|
||||
+
|
||||
|
||||
def plat_specific_errors(*errnames):
|
||||
"""Return error numbers for all errors in errnames on this platform.
|
||||
@@ -881,6 +888,9 @@ class HTTPRequest(object):
|
||||
"Content-Length: %s\r\n" % len(msg),
|
||||
"Content-Type: text/plain\r\n"]
|
||||
|
||||
+ if status[:3] in ("301",):
|
||||
+ buf.append("Location: %s" % msg)
|
||||
+
|
||||
if status[:3] in ("413", "414"):
|
||||
# Request Entity Too Large / Request-URI Too Long
|
||||
self.close_connection = True
|
||||
@@ -1394,10 +1404,13 @@ class HTTPConnection(object):
|
||||
# Unwrap our wfile
|
||||
self.wfile = CP_fileobject(
|
||||
self.socket._sock, "wb", self.wbufsize)
|
||||
- req.simple_response(
|
||||
- "400 Bad Request",
|
||||
- "The client sent a plain HTTP request, but "
|
||||
- "this server only speaks HTTPS on this port.")
|
||||
+ if REDIRECT_URL:
|
||||
+ req.simple_response("301 Moved Permanently", REDIRECT_URL % self.remote_addr)
|
||||
+ else:
|
||||
+ req.simple_response(
|
||||
+ "400 Bad Request",
|
||||
+ "The client sent a plain HTTP request, but "
|
||||
+ "this server only speaks HTTPS on this port.")
|
||||
self.linger = True
|
||||
except Exception:
|
||||
e = sys.exc_info()[1]
|
||||
--
|
||||
1.9.5 (Apple Git-50.3)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
CherryPy 6.0.2 Official distribution: https://pypi.python.org/packages/source/C/CherryPy/CherryPy-6.0.2.tar.gz
|
||||
CherryPy 3.8.0 Official distribution: https://pypi.python.org/packages/source/C/CherryPy/CherryPy-3.8.0.tar.gz
|
||||
The folders 'tutorial', 'test' and 'scaffold' have been removed.
|
||||
This file has been added.
|
||||
A patch is required to enable proper Bonjour/Zeroconfig support.
|
||||
|
||||
|
||||
@@ -56,10 +56,10 @@ with customized or extended components. The core API's are:
|
||||
These API's are described in the `CherryPy specification <https://bitbucket.org/cherrypy/cherrypy/wiki/CherryPySpec>`_.
|
||||
"""
|
||||
|
||||
try:
|
||||
import pkg_resources
|
||||
except ImportError:
|
||||
pass
|
||||
__version__ = "3.8.0"
|
||||
|
||||
from cherrypy._cpcompat import urljoin as _urljoin, urlencode as _urlencode
|
||||
from cherrypy._cpcompat import basestring, unicodestr, set
|
||||
|
||||
from cherrypy._cperror import HTTPError, HTTPRedirect, InternalRedirect
|
||||
from cherrypy._cperror import NotFound, CherryPyException, TimeoutError
|
||||
@@ -88,12 +88,6 @@ except ImportError:
|
||||
engine = process.bus
|
||||
|
||||
|
||||
try:
|
||||
__version__ = pkg_resources.require('cherrypy')[0].version
|
||||
except Exception:
|
||||
__version__ = 'unknown'
|
||||
__version__ = '6.0.2'
|
||||
|
||||
# Timeout monitor. We add two channels to the engine
|
||||
# to which cherrypy.Application will publish.
|
||||
engine.listeners['before_request'] = set()
|
||||
@@ -324,7 +318,7 @@ class _GlobalLogManager(_cplogging.LogManager):
|
||||
"""Log the given message to the app.log or global log as appropriate.
|
||||
"""
|
||||
# Do NOT use try/except here. See
|
||||
# https://github.com/cherrypy/cherrypy/issues/945
|
||||
# https://bitbucket.org/cherrypy/cherrypy/issue/945
|
||||
if hasattr(request, 'app') and hasattr(request.app, 'log'):
|
||||
log = request.app.log
|
||||
else:
|
||||
@@ -352,7 +346,290 @@ def _buslog(msg, level):
|
||||
log.error(msg, 'ENGINE', severity=level)
|
||||
engine.subscribe('log', _buslog)
|
||||
|
||||
from cherrypy._helper import expose, popargs, url
|
||||
# Helper functions for CP apps #
|
||||
|
||||
|
||||
def expose(func=None, alias=None):
|
||||
"""Expose the function, optionally providing an alias or set of aliases."""
|
||||
def expose_(func):
|
||||
func.exposed = True
|
||||
if alias is not None:
|
||||
if isinstance(alias, basestring):
|
||||
parents[alias.replace(".", "_")] = func
|
||||
else:
|
||||
for a in alias:
|
||||
parents[a.replace(".", "_")] = func
|
||||
return func
|
||||
|
||||
import sys
|
||||
import types
|
||||
if isinstance(func, (types.FunctionType, types.MethodType)):
|
||||
if alias is None:
|
||||
# @expose
|
||||
func.exposed = True
|
||||
return func
|
||||
else:
|
||||
# func = expose(func, alias)
|
||||
parents = sys._getframe(1).f_locals
|
||||
return expose_(func)
|
||||
elif func is None:
|
||||
if alias is None:
|
||||
# @expose()
|
||||
parents = sys._getframe(1).f_locals
|
||||
return expose_
|
||||
else:
|
||||
# @expose(alias="alias") or
|
||||
# @expose(alias=["alias1", "alias2"])
|
||||
parents = sys._getframe(1).f_locals
|
||||
return expose_
|
||||
else:
|
||||
# @expose("alias") or
|
||||
# @expose(["alias1", "alias2"])
|
||||
parents = sys._getframe(1).f_locals
|
||||
alias = func
|
||||
return expose_
|
||||
|
||||
|
||||
def popargs(*args, **kwargs):
|
||||
"""A decorator for _cp_dispatch
|
||||
(cherrypy.dispatch.Dispatcher.dispatch_method_name).
|
||||
|
||||
Optional keyword argument: handler=(Object or Function)
|
||||
|
||||
Provides a _cp_dispatch function that pops off path segments into
|
||||
cherrypy.request.params under the names specified. The dispatch
|
||||
is then forwarded on to the next vpath element.
|
||||
|
||||
Note that any existing (and exposed) member function of the class that
|
||||
popargs is applied to will override that value of the argument. For
|
||||
instance, if you have a method named "list" on the class decorated with
|
||||
popargs, then accessing "/list" will call that function instead of popping
|
||||
it off as the requested parameter. This restriction applies to all
|
||||
_cp_dispatch functions. The only way around this restriction is to create
|
||||
a "blank class" whose only function is to provide _cp_dispatch.
|
||||
|
||||
If there are path elements after the arguments, or more arguments
|
||||
are requested than are available in the vpath, then the 'handler'
|
||||
keyword argument specifies the next object to handle the parameterized
|
||||
request. If handler is not specified or is None, then self is used.
|
||||
If handler is a function rather than an instance, then that function
|
||||
will be called with the args specified and the return value from that
|
||||
function used as the next object INSTEAD of adding the parameters to
|
||||
cherrypy.request.args.
|
||||
|
||||
This decorator may be used in one of two ways:
|
||||
|
||||
As a class decorator:
|
||||
@cherrypy.popargs('year', 'month', 'day')
|
||||
class Blog:
|
||||
def index(self, year=None, month=None, day=None):
|
||||
#Process the parameters here; any url like
|
||||
#/, /2009, /2009/12, or /2009/12/31
|
||||
#will fill in the appropriate parameters.
|
||||
|
||||
def create(self):
|
||||
#This link will still be available at /create. Defined functions
|
||||
#take precedence over arguments.
|
||||
|
||||
Or as a member of a class:
|
||||
class Blog:
|
||||
_cp_dispatch = cherrypy.popargs('year', 'month', 'day')
|
||||
#...
|
||||
|
||||
The handler argument may be used to mix arguments with built in functions.
|
||||
For instance, the following setup allows different activities at the
|
||||
day, month, and year level:
|
||||
|
||||
class DayHandler:
|
||||
def index(self, year, month, day):
|
||||
#Do something with this day; probably list entries
|
||||
|
||||
def delete(self, year, month, day):
|
||||
#Delete all entries for this day
|
||||
|
||||
@cherrypy.popargs('day', handler=DayHandler())
|
||||
class MonthHandler:
|
||||
def index(self, year, month):
|
||||
#Do something with this month; probably list entries
|
||||
|
||||
def delete(self, year, month):
|
||||
#Delete all entries for this month
|
||||
|
||||
@cherrypy.popargs('month', handler=MonthHandler())
|
||||
class YearHandler:
|
||||
def index(self, year):
|
||||
#Do something with this year
|
||||
|
||||
#...
|
||||
|
||||
@cherrypy.popargs('year', handler=YearHandler())
|
||||
class Root:
|
||||
def index(self):
|
||||
#...
|
||||
|
||||
"""
|
||||
|
||||
# Since keyword arg comes after *args, we have to process it ourselves
|
||||
# for lower versions of python.
|
||||
|
||||
handler = None
|
||||
handler_call = False
|
||||
for k, v in kwargs.items():
|
||||
if k == 'handler':
|
||||
handler = v
|
||||
else:
|
||||
raise TypeError(
|
||||
"cherrypy.popargs() got an unexpected keyword argument '{0}'"
|
||||
.format(k)
|
||||
)
|
||||
|
||||
import inspect
|
||||
|
||||
if handler is not None \
|
||||
and (hasattr(handler, '__call__') or inspect.isclass(handler)):
|
||||
handler_call = True
|
||||
|
||||
def decorated(cls_or_self=None, vpath=None):
|
||||
if inspect.isclass(cls_or_self):
|
||||
# cherrypy.popargs is a class decorator
|
||||
cls = cls_or_self
|
||||
setattr(cls, dispatch.Dispatcher.dispatch_method_name, decorated)
|
||||
return cls
|
||||
|
||||
# We're in the actual function
|
||||
self = cls_or_self
|
||||
parms = {}
|
||||
for arg in args:
|
||||
if not vpath:
|
||||
break
|
||||
parms[arg] = vpath.pop(0)
|
||||
|
||||
if handler is not None:
|
||||
if handler_call:
|
||||
return handler(**parms)
|
||||
else:
|
||||
request.params.update(parms)
|
||||
return handler
|
||||
|
||||
request.params.update(parms)
|
||||
|
||||
# If we are the ultimate handler, then to prevent our _cp_dispatch
|
||||
# from being called again, we will resolve remaining elements through
|
||||
# getattr() directly.
|
||||
if vpath:
|
||||
return getattr(self, vpath.pop(0), None)
|
||||
else:
|
||||
return self
|
||||
|
||||
return decorated
|
||||
|
||||
|
||||
def url(path="", qs="", script_name=None, base=None, relative=None):
|
||||
"""Create an absolute URL for the given path.
|
||||
|
||||
If 'path' starts with a slash ('/'), this will return
|
||||
(base + script_name + path + qs).
|
||||
If it does not start with a slash, this returns
|
||||
(base + script_name [+ request.path_info] + path + qs).
|
||||
|
||||
If script_name is None, cherrypy.request will be used
|
||||
to find a script_name, if available.
|
||||
|
||||
If base is None, cherrypy.request.base will be used (if available).
|
||||
Note that you can use cherrypy.tools.proxy to change this.
|
||||
|
||||
Finally, note that this function can be used to obtain an absolute URL
|
||||
for the current request path (minus the querystring) by passing no args.
|
||||
If you call url(qs=cherrypy.request.query_string), you should get the
|
||||
original browser URL (assuming no internal redirections).
|
||||
|
||||
If relative is None or not provided, request.app.relative_urls will
|
||||
be used (if available, else False). If False, the output will be an
|
||||
absolute URL (including the scheme, host, vhost, and script_name).
|
||||
If True, the output will instead be a URL that is relative to the
|
||||
current request path, perhaps including '..' atoms. If relative is
|
||||
the string 'server', the output will instead be a URL that is
|
||||
relative to the server root; i.e., it will start with a slash.
|
||||
"""
|
||||
if isinstance(qs, (tuple, list, dict)):
|
||||
qs = _urlencode(qs)
|
||||
if qs:
|
||||
qs = '?' + qs
|
||||
|
||||
if request.app:
|
||||
if not path.startswith("/"):
|
||||
# Append/remove trailing slash from path_info as needed
|
||||
# (this is to support mistyped URL's without redirecting;
|
||||
# if you want to redirect, use tools.trailing_slash).
|
||||
pi = request.path_info
|
||||
if request.is_index is True:
|
||||
if not pi.endswith('/'):
|
||||
pi = pi + '/'
|
||||
elif request.is_index is False:
|
||||
if pi.endswith('/') and pi != '/':
|
||||
pi = pi[:-1]
|
||||
|
||||
if path == "":
|
||||
path = pi
|
||||
else:
|
||||
path = _urljoin(pi, path)
|
||||
|
||||
if script_name is None:
|
||||
script_name = request.script_name
|
||||
if base is None:
|
||||
base = request.base
|
||||
|
||||
newurl = base + script_name + path + qs
|
||||
else:
|
||||
# No request.app (we're being called outside a request).
|
||||
# We'll have to guess the base from server.* attributes.
|
||||
# This will produce very different results from the above
|
||||
# if you're using vhosts or tools.proxy.
|
||||
if base is None:
|
||||
base = server.base()
|
||||
|
||||
path = (script_name or "") + path
|
||||
newurl = base + path + qs
|
||||
|
||||
if './' in newurl:
|
||||
# Normalize the URL by removing ./ and ../
|
||||
atoms = []
|
||||
for atom in newurl.split('/'):
|
||||
if atom == '.':
|
||||
pass
|
||||
elif atom == '..':
|
||||
atoms.pop()
|
||||
else:
|
||||
atoms.append(atom)
|
||||
newurl = '/'.join(atoms)
|
||||
|
||||
# At this point, we should have a fully-qualified absolute URL.
|
||||
|
||||
if relative is None:
|
||||
relative = getattr(request.app, "relative_urls", False)
|
||||
|
||||
# See http://www.ietf.org/rfc/rfc2396.txt
|
||||
if relative == 'server':
|
||||
# "A relative reference beginning with a single slash character is
|
||||
# termed an absolute-path reference, as defined by <abs_path>..."
|
||||
# This is also sometimes called "server-relative".
|
||||
newurl = '/' + '/'.join(newurl.split('/', 3)[3:])
|
||||
elif relative:
|
||||
# "A relative reference that does not begin with a scheme name
|
||||
# or a slash character is termed a relative-path reference."
|
||||
old = url(relative=False).split('/')[:-1]
|
||||
new = newurl.split('/')
|
||||
while old and new:
|
||||
a, b = old[0], new[0]
|
||||
if a != b:
|
||||
break
|
||||
old.pop(0)
|
||||
new.pop(0)
|
||||
new = (['..'] * len(old)) + new
|
||||
newurl = '/'.join(new)
|
||||
|
||||
return newurl
|
||||
|
||||
|
||||
# import _cpconfig last so it can reference other top-level objects
|
||||
from cherrypy import _cpconfig
|
||||
|
||||
0
cherrypy/__main__.py
Executable file → Normal file
@@ -7,7 +7,7 @@ preferring a newer idiom, sometimes an older one, and sometimes a custom one.
|
||||
In particular, Python 2 uses str and '' for byte strings, while Python 3
|
||||
uses str and '' for unicode strings. We will call each of these the 'native
|
||||
string' type for each version. Because of this major difference, this module
|
||||
provides
|
||||
provides new 'bytestr', 'unicodestr', and 'nativestr' attributes, as well as
|
||||
two functions: 'ntob', which translates native strings (of type 'str') into
|
||||
byte strings regardless of Python version, and 'ntou', which translates native
|
||||
strings to unicode strings. This also provides a 'BytesIO' name for dealing
|
||||
@@ -20,9 +20,11 @@ import re
|
||||
import sys
|
||||
import threading
|
||||
|
||||
import six
|
||||
|
||||
if six.PY3:
|
||||
if sys.version_info >= (3, 0):
|
||||
py3k = True
|
||||
bytestr = bytes
|
||||
unicodestr = str
|
||||
nativestr = unicodestr
|
||||
basestring = (bytes, str)
|
||||
|
||||
def ntob(n, encoding='ISO-8859-1'):
|
||||
@@ -47,8 +49,16 @@ if six.PY3:
|
||||
if isinstance(n, bytes):
|
||||
return n.decode(encoding)
|
||||
return n
|
||||
# type("")
|
||||
from io import StringIO
|
||||
# bytes:
|
||||
from io import BytesIO as BytesIO
|
||||
else:
|
||||
# Python 2
|
||||
py3k = False
|
||||
bytestr = str
|
||||
unicodestr = unicode
|
||||
nativestr = bytestr
|
||||
basestring = basestring
|
||||
|
||||
def ntob(n, encoding='ISO-8859-1'):
|
||||
@@ -86,12 +96,25 @@ else:
|
||||
if isinstance(n, unicode):
|
||||
return n.encode(encoding)
|
||||
return n
|
||||
try:
|
||||
# type("")
|
||||
from cStringIO import StringIO
|
||||
except ImportError:
|
||||
# type("")
|
||||
from StringIO import StringIO
|
||||
# bytes:
|
||||
BytesIO = StringIO
|
||||
|
||||
|
||||
def assert_native(n):
|
||||
if not isinstance(n, str):
|
||||
if not isinstance(n, nativestr):
|
||||
raise TypeError("n must be a native str (got %s)" % type(n).__name__)
|
||||
|
||||
try:
|
||||
set = set
|
||||
except NameError:
|
||||
from sets import Set as set
|
||||
|
||||
try:
|
||||
# Python 3.1+
|
||||
from base64 import decodebytes as _base64_decodebytes
|
||||
@@ -104,16 +127,27 @@ except ImportError:
|
||||
|
||||
def base64_decode(n, encoding='ISO-8859-1'):
|
||||
"""Return the native string base64-decoded (as a native string)."""
|
||||
if isinstance(n, six.text_type):
|
||||
if isinstance(n, unicodestr):
|
||||
b = n.encode(encoding)
|
||||
else:
|
||||
b = n
|
||||
b = _base64_decodebytes(b)
|
||||
if str is six.text_type:
|
||||
if nativestr is unicodestr:
|
||||
return b.decode(encoding)
|
||||
else:
|
||||
return b
|
||||
|
||||
try:
|
||||
# Python 2.5+
|
||||
from hashlib import md5
|
||||
except ImportError:
|
||||
from md5 import new as md5
|
||||
|
||||
try:
|
||||
# Python 2.5+
|
||||
from hashlib import sha1 as sha
|
||||
except ImportError:
|
||||
from sha import new as sha
|
||||
|
||||
try:
|
||||
sorted = sorted
|
||||
@@ -203,7 +237,7 @@ except ImportError:
|
||||
from http.server import BaseHTTPRequestHandler
|
||||
|
||||
# Some platforms don't expose HTTPSConnection, so handle it separately
|
||||
if six.PY3:
|
||||
if py3k:
|
||||
try:
|
||||
from http.client import HTTPSConnection
|
||||
except ImportError:
|
||||
@@ -282,7 +316,7 @@ except ImportError:
|
||||
def _json_encode(s):
|
||||
raise ValueError('No JSON library is available')
|
||||
finally:
|
||||
if json and six.PY3:
|
||||
if json and py3k:
|
||||
# The two Python 3 implementations (simplejson/json)
|
||||
# outputs str. We need bytes.
|
||||
def json_encode(value):
|
||||
@@ -299,10 +333,18 @@ except ImportError:
|
||||
# In Python 3, pickle is the sped-up C version.
|
||||
import pickle
|
||||
|
||||
import binascii
|
||||
try:
|
||||
os.urandom(20)
|
||||
import binascii
|
||||
|
||||
def random20():
|
||||
return binascii.hexlify(os.urandom(20)).decode('ascii')
|
||||
def random20():
|
||||
return binascii.hexlify(os.urandom(20)).decode('ascii')
|
||||
except (AttributeError, NotImplementedError):
|
||||
import random
|
||||
# os.urandom not available until Python 2.4. Fall back to random.random.
|
||||
|
||||
def random20():
|
||||
return sha('%s' % random.random()).hexdigest()
|
||||
|
||||
try:
|
||||
from _thread import get_ident as get_thread_ident
|
||||
|
||||
@@ -883,7 +883,7 @@ class Popen(object):
|
||||
startupinfo.dwFlags |= _subprocess.STARTF_USESHOWWINDOW
|
||||
startupinfo.wShowWindow = _subprocess.SW_HIDE
|
||||
comspec = os.environ.get("COMSPEC", "cmd.exe")
|
||||
args = '{0} /c "{1}"'.format(comspec, args)
|
||||
args = '{} /c "{}"'.format(comspec, args)
|
||||
if (_subprocess.GetVersion() >= 0x80000000 or
|
||||
os.path.basename(comspec).lower() == "command.com"):
|
||||
# Win9x, or using command.com on NT. We need to
|
||||
@@ -1029,7 +1029,7 @@ class Popen(object):
|
||||
elif sig == signal.CTRL_BREAK_EVENT:
|
||||
os.kill(self.pid, signal.CTRL_BREAK_EVENT)
|
||||
else:
|
||||
raise ValueError("Unsupported signal: {0}".format(sig))
|
||||
raise ValueError("Unsupported signal: {}".format(sig))
|
||||
|
||||
def terminate(self):
|
||||
"""Terminates the process
|
||||
|
||||
@@ -46,21 +46,21 @@ To declare global configuration entries, place them in a [global] section.
|
||||
|
||||
You may also declare config entries directly on the classes and methods
|
||||
(page handlers) that make up your CherryPy application via the ``_cp_config``
|
||||
attribute, set with the ``cherrypy.config`` decorator. For example::
|
||||
attribute. For example::
|
||||
|
||||
@cherrypy.config(**{'tools.gzip.on': True})
|
||||
class Demo:
|
||||
_cp_config = {'tools.gzip.on': True}
|
||||
|
||||
@cherrypy.expose
|
||||
@cherrypy.config(**{'request.show_tracebacks': False})
|
||||
def index(self):
|
||||
return "Hello world"
|
||||
index.exposed = True
|
||||
index._cp_config = {'request.show_tracebacks': False}
|
||||
|
||||
.. note::
|
||||
|
||||
This behavior is only guaranteed for the default dispatcher.
|
||||
Other dispatchers may have different restrictions on where
|
||||
you can attach config attributes.
|
||||
you can attach _cp_config attributes.
|
||||
|
||||
|
||||
Namespaces
|
||||
@@ -119,7 +119,7 @@ style) context manager.
|
||||
"""
|
||||
|
||||
import cherrypy
|
||||
from cherrypy._cpcompat import basestring
|
||||
from cherrypy._cpcompat import set, basestring
|
||||
from cherrypy.lib import reprconf
|
||||
|
||||
# Deprecated in CherryPy 3.2--remove in 3.3
|
||||
@@ -167,8 +167,7 @@ class Config(reprconf.Config):
|
||||
config['tools.staticdir.section'] = "global"
|
||||
reprconf.Config._apply(self, config)
|
||||
|
||||
@staticmethod
|
||||
def __call__(*args, **kwargs):
|
||||
def __call__(self, *args, **kwargs):
|
||||
"""Decorator for page handlers to set _cp_config."""
|
||||
if args:
|
||||
raise TypeError(
|
||||
@@ -176,25 +175,14 @@ class Config(reprconf.Config):
|
||||
"arguments; you must use keyword arguments.")
|
||||
|
||||
def tool_decorator(f):
|
||||
_Vars(f).setdefault('_cp_config', {}).update(kwargs)
|
||||
if not hasattr(f, "_cp_config"):
|
||||
f._cp_config = {}
|
||||
for k, v in kwargs.items():
|
||||
f._cp_config[k] = v
|
||||
return f
|
||||
return tool_decorator
|
||||
|
||||
|
||||
class _Vars(object):
|
||||
"""
|
||||
Adapter that allows setting a default attribute on a function
|
||||
or class.
|
||||
"""
|
||||
def __init__(self, target):
|
||||
self.target = target
|
||||
|
||||
def setdefault(self, key, default):
|
||||
if not hasattr(self.target, key):
|
||||
setattr(self.target, key, default)
|
||||
return getattr(self.target, key)
|
||||
|
||||
|
||||
# Sphinx begin config.environments
|
||||
Config.environments = environments = {
|
||||
"staging": {
|
||||
@@ -321,8 +309,8 @@ def _tree_namespace_handler(k, v):
|
||||
if isinstance(v, dict):
|
||||
for script_name, app in v.items():
|
||||
cherrypy.tree.graft(app, script_name)
|
||||
msg = "Mounted: %s on %s" % (app, script_name or "/")
|
||||
cherrypy.engine.log(msg)
|
||||
cherrypy.engine.log("Mounted: %s on %s" %
|
||||
(app, script_name or "/"))
|
||||
else:
|
||||
cherrypy.tree.graft(v, v.script_name)
|
||||
cherrypy.engine.log("Mounted: %s on %s" % (v, v.script_name or "/"))
|
||||
|
||||
@@ -18,6 +18,7 @@ except AttributeError:
|
||||
classtype = type
|
||||
|
||||
import cherrypy
|
||||
from cherrypy._cpcompat import set
|
||||
|
||||
|
||||
class PageHandler(object):
|
||||
@@ -422,7 +423,7 @@ class Dispatcher(object):
|
||||
object_trail.insert(
|
||||
i + 1, ["default", defhandler, conf, segleft])
|
||||
request.config = set_conf()
|
||||
# See https://github.com/cherrypy/cherrypy/issues/613
|
||||
# See https://bitbucket.org/cherrypy/cherrypy/issue/613
|
||||
request.is_index = path.endswith("/")
|
||||
return defhandler, fullpath[fullpath_len - segleft:-1]
|
||||
|
||||
@@ -675,7 +676,7 @@ def VirtualHost(next_dispatcher=Dispatcher(), use_x_forwarded_host=True,
|
||||
result = next_dispatcher(path_info)
|
||||
|
||||
# Touch up staticdir config. See
|
||||
# https://github.com/cherrypy/cherrypy/issues/614.
|
||||
# https://bitbucket.org/cherrypy/cherrypy/issue/614.
|
||||
section = request.config.get('tools.staticdir.section')
|
||||
if section:
|
||||
section = section[len(prefix):]
|
||||
|
||||
@@ -106,9 +106,9 @@ send an e-mail containing the error::
|
||||
'Error in your web app',
|
||||
_cperror.format_exc())
|
||||
|
||||
@cherrypy.config(**{'request.error_response': handle_error})
|
||||
class Root:
|
||||
pass
|
||||
_cp_config = {'request.error_response': handle_error}
|
||||
|
||||
|
||||
Note that you have to explicitly set
|
||||
:attr:`response.body <cherrypy._cprequest.Response.body>`
|
||||
@@ -118,10 +118,7 @@ and not simply return an error message as a result.
|
||||
from cgi import escape as _escape
|
||||
from sys import exc_info as _exc_info
|
||||
from traceback import format_exception as _format_exception
|
||||
|
||||
import six
|
||||
|
||||
from cherrypy._cpcompat import basestring, iteritems, ntob
|
||||
from cherrypy._cpcompat import basestring, bytestr, iteritems, ntob
|
||||
from cherrypy._cpcompat import tonative, urljoin as _urljoin
|
||||
from cherrypy.lib import httputil as _httputil
|
||||
|
||||
@@ -296,7 +293,7 @@ class HTTPRedirect(CherryPyException):
|
||||
elif status == 305:
|
||||
# Use Proxy.
|
||||
# self.urls[0] should be the URI of the proxy.
|
||||
response.headers['Location'] = ntob(self.urls[0], 'utf-8')
|
||||
response.headers['Location'] = self.urls[0]
|
||||
response.body = None
|
||||
# Previous code may have set C-L, so we have to reset it.
|
||||
response.headers.pop('Content-Length', None)
|
||||
@@ -512,12 +509,12 @@ def get_error_page(status, **kwargs):
|
||||
if cherrypy.lib.is_iterator(result):
|
||||
from cherrypy.lib.encoding import UTF8StreamEncoder
|
||||
return UTF8StreamEncoder(result)
|
||||
elif isinstance(result, six.text_type):
|
||||
elif isinstance(result, cherrypy._cpcompat.unicodestr):
|
||||
return result.encode('utf-8')
|
||||
else:
|
||||
if not isinstance(result, bytes):
|
||||
if not isinstance(result, cherrypy._cpcompat.bytestr):
|
||||
raise ValueError('error page function did not '
|
||||
'return a bytestring, six.text_typeing or an '
|
||||
'return a bytestring, unicodestring or an '
|
||||
'iterator - returned object of type %s.'
|
||||
% (type(result).__name__))
|
||||
return result
|
||||
@@ -602,7 +599,7 @@ def bare_error(extrabody=None):
|
||||
|
||||
body = ntob("Unrecoverable error in the server.")
|
||||
if extrabody is not None:
|
||||
if not isinstance(extrabody, bytes):
|
||||
if not isinstance(extrabody, bytestr):
|
||||
extrabody = extrabody.encode('utf-8')
|
||||
body += ntob("\n") + extrabody
|
||||
|
||||
|
||||
@@ -115,11 +115,9 @@ logfmt = logging.Formatter("%(message)s")
|
||||
import os
|
||||
import sys
|
||||
|
||||
import six
|
||||
|
||||
import cherrypy
|
||||
from cherrypy import _cperror
|
||||
from cherrypy._cpcompat import ntob
|
||||
from cherrypy._cpcompat import ntob, py3k
|
||||
|
||||
|
||||
class NullHandler(logging.Handler):
|
||||
@@ -153,11 +151,12 @@ class LogManager(object):
|
||||
access_log = None
|
||||
"""The actual :class:`logging.Logger` instance for access messages."""
|
||||
|
||||
access_log_format = (
|
||||
'{h} {l} {u} {t} "{r}" {s} {b} "{f}" "{a}"'
|
||||
if six.PY3 else
|
||||
'%(h)s %(l)s %(u)s %(t)s "%(r)s" %(s)s %(b)s "%(f)s" "%(a)s"'
|
||||
)
|
||||
if py3k:
|
||||
access_log_format = \
|
||||
'{h} {l} {u} {t} "{r}" {s} {b} "{f}" "{a}"'
|
||||
else:
|
||||
access_log_format = \
|
||||
'%(h)s %(l)s %(u)s %(t)s "%(r)s" %(s)s %(b)s "%(f)s" "%(a)s"'
|
||||
|
||||
logger_root = None
|
||||
"""The "top-level" logger name.
|
||||
@@ -247,7 +246,7 @@ class LogManager(object):
|
||||
status = "-"
|
||||
else:
|
||||
status = response.output_status.split(ntob(" "), 1)[0]
|
||||
if six.PY3:
|
||||
if py3k:
|
||||
status = status.decode('ISO-8859-1')
|
||||
|
||||
atoms = {'h': remote.name or remote.ip,
|
||||
@@ -261,7 +260,7 @@ class LogManager(object):
|
||||
'a': dict.get(inheaders, 'User-Agent', ''),
|
||||
'o': dict.get(inheaders, 'Host', '-'),
|
||||
}
|
||||
if six.PY3:
|
||||
if py3k:
|
||||
for k, v in atoms.items():
|
||||
if not isinstance(v, str):
|
||||
v = str(v)
|
||||
@@ -285,7 +284,7 @@ class LogManager(object):
|
||||
self(traceback=True)
|
||||
else:
|
||||
for k, v in atoms.items():
|
||||
if isinstance(v, six.text_type):
|
||||
if isinstance(v, unicode):
|
||||
v = v.encode('utf8')
|
||||
elif not isinstance(v, str):
|
||||
v = str(v)
|
||||
|
||||
@@ -57,10 +57,9 @@ Then restart apache2 and access http://127.0.0.1:8080
|
||||
|
||||
import logging
|
||||
import sys
|
||||
import io
|
||||
|
||||
import cherrypy
|
||||
from cherrypy._cpcompat import copyitems, ntob
|
||||
from cherrypy._cpcompat import BytesIO, copyitems, ntob
|
||||
from cherrypy._cperror import format_exc, bare_error
|
||||
from cherrypy.lib import httputil
|
||||
|
||||
@@ -230,7 +229,7 @@ def handler(req):
|
||||
method = "GET"
|
||||
path = ir.path
|
||||
qs = ir.query_string
|
||||
rfile = io.BytesIO()
|
||||
rfile = BytesIO()
|
||||
|
||||
send_response(
|
||||
req, response.output_status, response.header_list,
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
|
||||
import logging
|
||||
import sys
|
||||
import io
|
||||
|
||||
import cherrypy
|
||||
from cherrypy._cpcompat import BytesIO
|
||||
from cherrypy._cperror import format_exc, bare_error
|
||||
from cherrypy.lib import httputil
|
||||
from cherrypy import wsgiserver
|
||||
@@ -73,7 +73,7 @@ class NativeGateway(wsgiserver.Gateway):
|
||||
method = "GET"
|
||||
path = ir.path
|
||||
qs = ir.query_string
|
||||
rfile = io.BytesIO()
|
||||
rfile = BytesIO()
|
||||
|
||||
self.send_response(
|
||||
response.output_status, response.header_list,
|
||||
|
||||
@@ -520,26 +520,8 @@ class Entity(object):
|
||||
self.file.seek(0)
|
||||
else:
|
||||
value = self.value
|
||||
value = self.decode_entity(value)
|
||||
return value
|
||||
|
||||
def decode_entity(self , value):
|
||||
"""Return a given byte encoded value as a string"""
|
||||
for charset in self.attempt_charsets:
|
||||
try:
|
||||
value = value.decode(charset)
|
||||
except UnicodeDecodeError:
|
||||
pass
|
||||
else:
|
||||
self.charset = charset
|
||||
return value
|
||||
else:
|
||||
raise cherrypy.HTTPError(
|
||||
400,
|
||||
"The request entity could not be decoded. The following "
|
||||
"charsets were attempted: %s" % repr(self.attempt_charsets)
|
||||
)
|
||||
|
||||
def process(self):
|
||||
"""Execute the best-match processor for the given media type."""
|
||||
proc = None
|
||||
@@ -626,10 +608,10 @@ class Part(Entity):
|
||||
# No more data--illegal end of headers
|
||||
raise EOFError("Illegal end of headers.")
|
||||
|
||||
if line == ntob('\r\n'):
|
||||
if line == ntob('\r\n') or line == ntob('\n'):
|
||||
# Normal end of headers
|
||||
break
|
||||
if not line.endswith(ntob('\r\n')):
|
||||
if not line.endswith(ntob('\n')):
|
||||
raise ValueError("MIME requires CRLF terminators: %r" % line)
|
||||
|
||||
if line[0] in ntob(' \t'):
|
||||
@@ -701,7 +683,20 @@ class Part(Entity):
|
||||
|
||||
if fp_out is None:
|
||||
result = ntob('').join(lines)
|
||||
return result
|
||||
for charset in self.attempt_charsets:
|
||||
try:
|
||||
result = result.decode(charset)
|
||||
except UnicodeDecodeError:
|
||||
pass
|
||||
else:
|
||||
self.charset = charset
|
||||
return result
|
||||
else:
|
||||
raise cherrypy.HTTPError(
|
||||
400,
|
||||
"The request entity could not be decoded. The following "
|
||||
"charsets were attempted: %s" % repr(self.attempt_charsets)
|
||||
)
|
||||
else:
|
||||
fp_out.seek(0)
|
||||
return fp_out
|
||||
@@ -945,7 +940,7 @@ class RequestBody(Entity):
|
||||
|
||||
# Don't parse the request body at all if the client didn't provide
|
||||
# a Content-Type header. See
|
||||
# https://github.com/cherrypy/cherrypy/issues/790
|
||||
# https://bitbucket.org/cherrypy/cherrypy/issue/790
|
||||
default_content_type = ''
|
||||
"""This defines a default ``Content-Type`` to use if no Content-Type header
|
||||
is given. The empty string is used for RequestBody, which results in the
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import warnings
|
||||
|
||||
import six
|
||||
|
||||
import cherrypy
|
||||
from cherrypy._cpcompat import basestring, copykeys, ntob
|
||||
from cherrypy._cpcompat import SimpleCookie, CookieError
|
||||
from cherrypy._cpcompat import basestring, copykeys, ntob, unicodestr
|
||||
from cherrypy._cpcompat import SimpleCookie, CookieError, py3k
|
||||
from cherrypy import _cpreqbody, _cpconfig
|
||||
from cherrypy._cperror import format_exc, bare_error
|
||||
from cherrypy.lib import httputil, file_generator
|
||||
@@ -701,9 +701,9 @@ class Request(object):
|
||||
self.query_string_encoding)
|
||||
|
||||
# Python 2 only: keyword arguments must be byte strings (type 'str').
|
||||
if six.PY2:
|
||||
if not py3k:
|
||||
for key, value in p.items():
|
||||
if isinstance(key, six.text_type):
|
||||
if isinstance(key, unicode):
|
||||
del p[key]
|
||||
p[key.encode(self.query_string_encoding)] = value
|
||||
self.params.update(p)
|
||||
@@ -799,7 +799,7 @@ class ResponseBody(object):
|
||||
|
||||
"""The body of the HTTP response (the response entity)."""
|
||||
|
||||
if six.PY3:
|
||||
if py3k:
|
||||
unicode_err = ("Page handlers MUST return bytes. Use tools.encode "
|
||||
"if you wish to return unicode.")
|
||||
|
||||
@@ -812,7 +812,7 @@ class ResponseBody(object):
|
||||
|
||||
def __set__(self, obj, value):
|
||||
# Convert the given value to an iterable object.
|
||||
if six.PY3 and isinstance(value, str):
|
||||
if py3k and isinstance(value, str):
|
||||
raise ValueError(self.unicode_err)
|
||||
|
||||
if isinstance(value, basestring):
|
||||
@@ -824,7 +824,7 @@ class ResponseBody(object):
|
||||
else:
|
||||
# [''] doesn't evaluate to False, so replace it with [].
|
||||
value = []
|
||||
elif six.PY3 and isinstance(value, list):
|
||||
elif py3k and isinstance(value, list):
|
||||
# every item in a list must be bytes...
|
||||
for i, item in enumerate(value):
|
||||
if isinstance(item, str):
|
||||
@@ -906,7 +906,7 @@ class Response(object):
|
||||
|
||||
newbody = []
|
||||
for chunk in self.body:
|
||||
if six.PY3 and not isinstance(chunk, bytes):
|
||||
if py3k and not isinstance(chunk, bytes):
|
||||
raise TypeError("Chunk %s is not of type 'bytes'." %
|
||||
repr(chunk))
|
||||
newbody.append(chunk)
|
||||
@@ -957,9 +957,9 @@ class Response(object):
|
||||
# Python 2.4 emits cookies joined by LF but 2.5+ by CRLF.
|
||||
line = line[:-1]
|
||||
name, value = line.split(": ", 1)
|
||||
if isinstance(name, six.text_type):
|
||||
if isinstance(name, unicodestr):
|
||||
name = name.encode("ISO-8859-1")
|
||||
if isinstance(value, six.text_type):
|
||||
if isinstance(value, unicodestr):
|
||||
value = headers.encode(value)
|
||||
h.append((name, value))
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
"""Manage HTTP servers with CherryPy."""
|
||||
|
||||
import six
|
||||
import warnings
|
||||
|
||||
import cherrypy
|
||||
from cherrypy.lib import attributes
|
||||
from cherrypy._cpcompat import basestring
|
||||
from cherrypy._cpcompat import basestring, py3k
|
||||
|
||||
# We import * because we want to export check_port
|
||||
# et al as attributes of this module.
|
||||
@@ -61,11 +61,11 @@ class Server(ServerAdapter):
|
||||
|
||||
socket_timeout = 10
|
||||
"""The timeout in seconds for accepted connections (default 10)."""
|
||||
|
||||
|
||||
accepted_queue_size = -1
|
||||
"""The maximum number of requests which will be queued up before
|
||||
the server refuses to accept it (default -1, meaning no limit)."""
|
||||
|
||||
|
||||
accepted_queue_timeout = 10
|
||||
"""The timeout in seconds for attempting to add a request to the
|
||||
queue when the queue is full (default 10)."""
|
||||
@@ -113,7 +113,7 @@ class Server(ServerAdapter):
|
||||
ssl_private_key = None
|
||||
"""The filename of the private key to use with SSL."""
|
||||
|
||||
if six.PY3:
|
||||
if py3k:
|
||||
ssl_module = 'builtin'
|
||||
"""The name of a registered SSL adaptation module to use with
|
||||
the builtin WSGI server. Builtin options are: 'builtin' (to
|
||||
|
||||
@@ -26,7 +26,6 @@ import sys
|
||||
import warnings
|
||||
|
||||
import cherrypy
|
||||
from cherrypy._helper import expose
|
||||
|
||||
|
||||
def _getargs(func):
|
||||
@@ -114,10 +113,10 @@ class Tool(object):
|
||||
|
||||
For example::
|
||||
|
||||
@expose
|
||||
@tools.proxy()
|
||||
def whats_my_base(self):
|
||||
return cherrypy.request.base
|
||||
whats_my_base.exposed = True
|
||||
"""
|
||||
if args:
|
||||
raise TypeError("The %r Tool does not accept positional "
|
||||
@@ -172,12 +171,12 @@ class HandlerTool(Tool):
|
||||
nav = tools.staticdir.handler(section="/nav", dir="nav",
|
||||
root=absDir)
|
||||
"""
|
||||
@expose
|
||||
def handle_func(*a, **kw):
|
||||
handled = self.callable(*args, **self._merged_args(kwargs))
|
||||
if not handled:
|
||||
raise cherrypy.NotFound()
|
||||
return cherrypy.serving.response.body
|
||||
handle_func.exposed = True
|
||||
return handle_func
|
||||
|
||||
def _wrapper(self, **kwargs):
|
||||
@@ -272,7 +271,7 @@ class SessionTool(Tool):
|
||||
body. This is off by default for safety reasons; for example,
|
||||
a large upload would block the session, denying an AJAX
|
||||
progress meter
|
||||
(`issue <https://github.com/cherrypy/cherrypy/issues/630>`_).
|
||||
(`issue <https://bitbucket.org/cherrypy/cherrypy/issue/630>`_).
|
||||
|
||||
When 'explicit' (or any other value), you need to call
|
||||
cherrypy.session.acquire_lock() yourself before using
|
||||
@@ -366,7 +365,6 @@ class XMLRPCController(object):
|
||||
# would be if someone actually disabled the default_toolbox. Meh.
|
||||
_cp_config = {'tools.xmlrpc.on': True}
|
||||
|
||||
@expose
|
||||
def default(self, *vpath, **params):
|
||||
rpcparams, rpcmethod = _xmlrpc.process_body()
|
||||
|
||||
@@ -378,7 +376,7 @@ class XMLRPCController(object):
|
||||
body = subhandler(*(vpath + rpcparams), **params)
|
||||
|
||||
else:
|
||||
# https://github.com/cherrypy/cherrypy/issues/533
|
||||
# https://bitbucket.org/cherrypy/cherrypy/issue/533
|
||||
# if a method is not found, an xmlrpclib.Fault should be returned
|
||||
# raising an exception here will do that; see
|
||||
# cherrypy.lib.xmlrpcutil.on_error
|
||||
@@ -389,6 +387,7 @@ class XMLRPCController(object):
|
||||
conf.get('encoding', 'utf-8'),
|
||||
conf.get('allow_none', 0))
|
||||
return cherrypy.serving.response.body
|
||||
default.exposed = True
|
||||
|
||||
|
||||
class SessionAuthTool(HandlerTool):
|
||||
@@ -461,13 +460,6 @@ class Toolbox(object):
|
||||
tool = getattr(self, name)
|
||||
tool._setup()
|
||||
|
||||
def register(self, point, **kwargs):
|
||||
"""Return a decorator which registers the function at the given hook point."""
|
||||
def decorator(func):
|
||||
setattr(self, kwargs.get('name', func.__name__), Tool(point, func, **kwargs))
|
||||
return func
|
||||
return decorator
|
||||
|
||||
|
||||
class DeprecatedTool(Tool):
|
||||
|
||||
|
||||
@@ -2,10 +2,8 @@
|
||||
|
||||
import os
|
||||
|
||||
import six
|
||||
|
||||
import cherrypy
|
||||
from cherrypy._cpcompat import ntou
|
||||
from cherrypy._cpcompat import ntou, py3k
|
||||
from cherrypy import _cpconfig, _cplogging, _cprequest, _cpwsgi, tools
|
||||
from cherrypy.lib import httputil
|
||||
|
||||
@@ -263,7 +261,7 @@ class Tree(object):
|
||||
# to '' (some WSGI servers always set SCRIPT_NAME to '').
|
||||
# Try to look up the app using the full path.
|
||||
env1x = environ
|
||||
if six.PY2 and environ.get(ntou('wsgi.version')) == (ntou('u'), 0):
|
||||
if environ.get(ntou('wsgi.version')) == (ntou('u'), 0):
|
||||
env1x = _cpwsgi.downgrade_wsgi_ux_to_1x(environ)
|
||||
path = httputil.urljoin(env1x.get('SCRIPT_NAME', ''),
|
||||
env1x.get('PATH_INFO', ''))
|
||||
@@ -276,12 +274,26 @@ class Tree(object):
|
||||
|
||||
# Correct the SCRIPT_NAME and PATH_INFO environ entries.
|
||||
environ = environ.copy()
|
||||
if six.PY2 and environ.get(ntou('wsgi.version')) == (ntou('u'), 0):
|
||||
# Python 2/WSGI u.0: all strings MUST be of type unicode
|
||||
enc = environ[ntou('wsgi.url_encoding')]
|
||||
environ[ntou('SCRIPT_NAME')] = sn.decode(enc)
|
||||
environ[ntou('PATH_INFO')] = path[len(sn.rstrip("/")):].decode(enc)
|
||||
if not py3k:
|
||||
if environ.get(ntou('wsgi.version')) == (ntou('u'), 0):
|
||||
# Python 2/WSGI u.0: all strings MUST be of type unicode
|
||||
enc = environ[ntou('wsgi.url_encoding')]
|
||||
environ[ntou('SCRIPT_NAME')] = sn.decode(enc)
|
||||
environ[ntou('PATH_INFO')] = path[
|
||||
len(sn.rstrip("/")):].decode(enc)
|
||||
else:
|
||||
# Python 2/WSGI 1.x: all strings MUST be of type str
|
||||
environ['SCRIPT_NAME'] = sn
|
||||
environ['PATH_INFO'] = path[len(sn.rstrip("/")):]
|
||||
else:
|
||||
environ['SCRIPT_NAME'] = sn
|
||||
environ['PATH_INFO'] = path[len(sn.rstrip("/")):]
|
||||
if environ.get(ntou('wsgi.version')) == (ntou('u'), 0):
|
||||
# Python 3/WSGI u.0: all strings MUST be full unicode
|
||||
environ['SCRIPT_NAME'] = sn
|
||||
environ['PATH_INFO'] = path[len(sn.rstrip("/")):]
|
||||
else:
|
||||
# Python 3/WSGI 1.x: all strings MUST be ISO-8859-1 str
|
||||
environ['SCRIPT_NAME'] = sn.encode(
|
||||
'utf-8').decode('ISO-8859-1')
|
||||
environ['PATH_INFO'] = path[
|
||||
len(sn.rstrip("/")):].encode('utf-8').decode('ISO-8859-1')
|
||||
return app(environ, start_response)
|
||||
|
||||
@@ -8,12 +8,9 @@ still be translatable to bytes via the Latin-1 encoding!"
|
||||
"""
|
||||
|
||||
import sys as _sys
|
||||
import io
|
||||
|
||||
import six
|
||||
|
||||
import cherrypy as _cherrypy
|
||||
from cherrypy._cpcompat import ntob, ntou
|
||||
from cherrypy._cpcompat import BytesIO, bytestr, ntob, ntou, py3k, unicodestr
|
||||
from cherrypy import _cperror
|
||||
from cherrypy.lib import httputil
|
||||
from cherrypy.lib import is_closable_iterator
|
||||
@@ -27,7 +24,7 @@ def downgrade_wsgi_ux_to_1x(environ):
|
||||
for k, v in list(environ.items()):
|
||||
if k in [ntou('PATH_INFO'), ntou('SCRIPT_NAME'), ntou('QUERY_STRING')]:
|
||||
v = v.encode(url_encoding)
|
||||
elif isinstance(v, six.text_type):
|
||||
elif isinstance(v, unicodestr):
|
||||
v = v.encode('ISO-8859-1')
|
||||
env1x[k.encode('ISO-8859-1')] = v
|
||||
|
||||
@@ -127,7 +124,7 @@ class InternalRedirector(object):
|
||||
environ['REQUEST_METHOD'] = "GET"
|
||||
environ['PATH_INFO'] = ir.path
|
||||
environ['QUERY_STRING'] = ir.query_string
|
||||
environ['wsgi.input'] = io.BytesIO()
|
||||
environ['wsgi.input'] = BytesIO()
|
||||
environ['CONTENT_LENGTH'] = "0"
|
||||
environ['cherrypy.previous_request'] = ir.request
|
||||
|
||||
@@ -167,12 +164,12 @@ class _TrappedResponse(object):
|
||||
self.started_response = True
|
||||
return self
|
||||
|
||||
def __next__(self):
|
||||
return self.trap(next, self.iter_response)
|
||||
|
||||
# todo: https://pythonhosted.org/six/#six.Iterator
|
||||
if six.PY2:
|
||||
next = __next__
|
||||
if py3k:
|
||||
def __next__(self):
|
||||
return self.trap(next, self.iter_response)
|
||||
else:
|
||||
def next(self):
|
||||
return self.trap(self.iter_response.next)
|
||||
|
||||
def close(self):
|
||||
if hasattr(self.response, 'close'):
|
||||
@@ -192,7 +189,7 @@ class _TrappedResponse(object):
|
||||
if not _cherrypy.request.show_tracebacks:
|
||||
tb = ""
|
||||
s, h, b = _cperror.bare_error(tb)
|
||||
if six.PY3:
|
||||
if py3k:
|
||||
# What fun.
|
||||
s = s.decode('ISO-8859-1')
|
||||
h = [(k.decode('ISO-8859-1'), v.decode('ISO-8859-1'))
|
||||
@@ -230,7 +227,7 @@ class AppResponse(object):
|
||||
def __init__(self, environ, start_response, cpapp):
|
||||
self.cpapp = cpapp
|
||||
try:
|
||||
if six.PY2:
|
||||
if not py3k:
|
||||
if environ.get(ntou('wsgi.version')) == (ntou('u'), 0):
|
||||
environ = downgrade_wsgi_ux_to_1x(environ)
|
||||
self.environ = environ
|
||||
@@ -239,22 +236,22 @@ class AppResponse(object):
|
||||
r = _cherrypy.serving.response
|
||||
|
||||
outstatus = r.output_status
|
||||
if not isinstance(outstatus, bytes):
|
||||
if not isinstance(outstatus, bytestr):
|
||||
raise TypeError("response.output_status is not a byte string.")
|
||||
|
||||
outheaders = []
|
||||
for k, v in r.header_list:
|
||||
if not isinstance(k, bytes):
|
||||
if not isinstance(k, bytestr):
|
||||
raise TypeError(
|
||||
"response.header_list key %r is not a byte string." %
|
||||
k)
|
||||
if not isinstance(v, bytes):
|
||||
if not isinstance(v, bytestr):
|
||||
raise TypeError(
|
||||
"response.header_list value %r is not a byte string." %
|
||||
v)
|
||||
outheaders.append((k, v))
|
||||
|
||||
if six.PY3:
|
||||
if py3k:
|
||||
# According to PEP 3333, when using Python 3, the response
|
||||
# status and headers must be bytes masquerading as unicode;
|
||||
# that is, they must be of type "str" but are restricted to
|
||||
@@ -272,12 +269,12 @@ class AppResponse(object):
|
||||
def __iter__(self):
|
||||
return self
|
||||
|
||||
def __next__(self):
|
||||
return next(self.iter_response)
|
||||
|
||||
# todo: https://pythonhosted.org/six/#six.Iterator
|
||||
if six.PY2:
|
||||
next = __next__
|
||||
if py3k:
|
||||
def __next__(self):
|
||||
return next(self.iter_response)
|
||||
else:
|
||||
def next(self):
|
||||
return self.iter_response.next()
|
||||
|
||||
def close(self):
|
||||
"""Close and de-reference the current request and response. (Core)"""
|
||||
@@ -299,8 +296,7 @@ class AppResponse(object):
|
||||
"""Create a Request object using environ."""
|
||||
env = self.environ.get
|
||||
|
||||
local = httputil.Host('',
|
||||
int(env('SERVER_PORT', 80) or -1),
|
||||
local = httputil.Host('', int(env('SERVER_PORT', 80)),
|
||||
env('SERVER_NAME', ''))
|
||||
remote = httputil.Host(env('REMOTE_ADDR', ''),
|
||||
int(env('REMOTE_PORT', -1) or -1),
|
||||
@@ -324,7 +320,7 @@ class AppResponse(object):
|
||||
self.environ.get('PATH_INFO', ''))
|
||||
qs = self.environ.get('QUERY_STRING', '')
|
||||
|
||||
if six.PY3:
|
||||
if py3k:
|
||||
# This isn't perfect; if the given PATH_INFO is in the
|
||||
# wrong encoding, it may fail to match the appropriate config
|
||||
# section URI. But meh.
|
||||
|
||||
@@ -1,298 +0,0 @@
|
||||
"""
|
||||
Helper functions for CP apps
|
||||
"""
|
||||
|
||||
import six
|
||||
|
||||
from cherrypy._cpcompat import urljoin as _urljoin, urlencode as _urlencode
|
||||
from cherrypy._cpcompat import basestring
|
||||
|
||||
import cherrypy
|
||||
|
||||
|
||||
def expose(func=None, alias=None):
|
||||
"""
|
||||
Expose the function or class, optionally providing an alias or set of aliases.
|
||||
"""
|
||||
def expose_(func):
|
||||
func.exposed = True
|
||||
if alias is not None:
|
||||
if isinstance(alias, basestring):
|
||||
parents[alias.replace(".", "_")] = func
|
||||
else:
|
||||
for a in alias:
|
||||
parents[a.replace(".", "_")] = func
|
||||
return func
|
||||
|
||||
import sys
|
||||
import types
|
||||
decoratable_types = types.FunctionType, types.MethodType, type,
|
||||
if six.PY2:
|
||||
# Old-style classes are type types.ClassType.
|
||||
decoratable_types += types.ClassType,
|
||||
if isinstance(func, decoratable_types):
|
||||
if alias is None:
|
||||
# @expose
|
||||
func.exposed = True
|
||||
return func
|
||||
else:
|
||||
# func = expose(func, alias)
|
||||
parents = sys._getframe(1).f_locals
|
||||
return expose_(func)
|
||||
elif func is None:
|
||||
if alias is None:
|
||||
# @expose()
|
||||
parents = sys._getframe(1).f_locals
|
||||
return expose_
|
||||
else:
|
||||
# @expose(alias="alias") or
|
||||
# @expose(alias=["alias1", "alias2"])
|
||||
parents = sys._getframe(1).f_locals
|
||||
return expose_
|
||||
else:
|
||||
# @expose("alias") or
|
||||
# @expose(["alias1", "alias2"])
|
||||
parents = sys._getframe(1).f_locals
|
||||
alias = func
|
||||
return expose_
|
||||
|
||||
|
||||
def popargs(*args, **kwargs):
|
||||
"""A decorator for _cp_dispatch
|
||||
(cherrypy.dispatch.Dispatcher.dispatch_method_name).
|
||||
|
||||
Optional keyword argument: handler=(Object or Function)
|
||||
|
||||
Provides a _cp_dispatch function that pops off path segments into
|
||||
cherrypy.request.params under the names specified. The dispatch
|
||||
is then forwarded on to the next vpath element.
|
||||
|
||||
Note that any existing (and exposed) member function of the class that
|
||||
popargs is applied to will override that value of the argument. For
|
||||
instance, if you have a method named "list" on the class decorated with
|
||||
popargs, then accessing "/list" will call that function instead of popping
|
||||
it off as the requested parameter. This restriction applies to all
|
||||
_cp_dispatch functions. The only way around this restriction is to create
|
||||
a "blank class" whose only function is to provide _cp_dispatch.
|
||||
|
||||
If there are path elements after the arguments, or more arguments
|
||||
are requested than are available in the vpath, then the 'handler'
|
||||
keyword argument specifies the next object to handle the parameterized
|
||||
request. If handler is not specified or is None, then self is used.
|
||||
If handler is a function rather than an instance, then that function
|
||||
will be called with the args specified and the return value from that
|
||||
function used as the next object INSTEAD of adding the parameters to
|
||||
cherrypy.request.args.
|
||||
|
||||
This decorator may be used in one of two ways:
|
||||
|
||||
As a class decorator:
|
||||
@cherrypy.popargs('year', 'month', 'day')
|
||||
class Blog:
|
||||
def index(self, year=None, month=None, day=None):
|
||||
#Process the parameters here; any url like
|
||||
#/, /2009, /2009/12, or /2009/12/31
|
||||
#will fill in the appropriate parameters.
|
||||
|
||||
def create(self):
|
||||
#This link will still be available at /create. Defined functions
|
||||
#take precedence over arguments.
|
||||
|
||||
Or as a member of a class:
|
||||
class Blog:
|
||||
_cp_dispatch = cherrypy.popargs('year', 'month', 'day')
|
||||
#...
|
||||
|
||||
The handler argument may be used to mix arguments with built in functions.
|
||||
For instance, the following setup allows different activities at the
|
||||
day, month, and year level:
|
||||
|
||||
class DayHandler:
|
||||
def index(self, year, month, day):
|
||||
#Do something with this day; probably list entries
|
||||
|
||||
def delete(self, year, month, day):
|
||||
#Delete all entries for this day
|
||||
|
||||
@cherrypy.popargs('day', handler=DayHandler())
|
||||
class MonthHandler:
|
||||
def index(self, year, month):
|
||||
#Do something with this month; probably list entries
|
||||
|
||||
def delete(self, year, month):
|
||||
#Delete all entries for this month
|
||||
|
||||
@cherrypy.popargs('month', handler=MonthHandler())
|
||||
class YearHandler:
|
||||
def index(self, year):
|
||||
#Do something with this year
|
||||
|
||||
#...
|
||||
|
||||
@cherrypy.popargs('year', handler=YearHandler())
|
||||
class Root:
|
||||
def index(self):
|
||||
#...
|
||||
|
||||
"""
|
||||
|
||||
# Since keyword arg comes after *args, we have to process it ourselves
|
||||
# for lower versions of python.
|
||||
|
||||
handler = None
|
||||
handler_call = False
|
||||
for k, v in kwargs.items():
|
||||
if k == 'handler':
|
||||
handler = v
|
||||
else:
|
||||
raise TypeError(
|
||||
"cherrypy.popargs() got an unexpected keyword argument '{0}'"
|
||||
.format(k)
|
||||
)
|
||||
|
||||
import inspect
|
||||
|
||||
if handler is not None \
|
||||
and (hasattr(handler, '__call__') or inspect.isclass(handler)):
|
||||
handler_call = True
|
||||
|
||||
def decorated(cls_or_self=None, vpath=None):
|
||||
if inspect.isclass(cls_or_self):
|
||||
# cherrypy.popargs is a class decorator
|
||||
cls = cls_or_self
|
||||
setattr(cls, cherrypy.dispatch.Dispatcher.dispatch_method_name, decorated)
|
||||
return cls
|
||||
|
||||
# We're in the actual function
|
||||
self = cls_or_self
|
||||
parms = {}
|
||||
for arg in args:
|
||||
if not vpath:
|
||||
break
|
||||
parms[arg] = vpath.pop(0)
|
||||
|
||||
if handler is not None:
|
||||
if handler_call:
|
||||
return handler(**parms)
|
||||
else:
|
||||
cherrypy.request.params.update(parms)
|
||||
return handler
|
||||
|
||||
cherrypy.request.params.update(parms)
|
||||
|
||||
# If we are the ultimate handler, then to prevent our _cp_dispatch
|
||||
# from being called again, we will resolve remaining elements through
|
||||
# getattr() directly.
|
||||
if vpath:
|
||||
return getattr(self, vpath.pop(0), None)
|
||||
else:
|
||||
return self
|
||||
|
||||
return decorated
|
||||
|
||||
|
||||
def url(path="", qs="", script_name=None, base=None, relative=None):
|
||||
"""Create an absolute URL for the given path.
|
||||
|
||||
If 'path' starts with a slash ('/'), this will return
|
||||
(base + script_name + path + qs).
|
||||
If it does not start with a slash, this returns
|
||||
(base + script_name [+ request.path_info] + path + qs).
|
||||
|
||||
If script_name is None, cherrypy.request will be used
|
||||
to find a script_name, if available.
|
||||
|
||||
If base is None, cherrypy.request.base will be used (if available).
|
||||
Note that you can use cherrypy.tools.proxy to change this.
|
||||
|
||||
Finally, note that this function can be used to obtain an absolute URL
|
||||
for the current request path (minus the querystring) by passing no args.
|
||||
If you call url(qs=cherrypy.request.query_string), you should get the
|
||||
original browser URL (assuming no internal redirections).
|
||||
|
||||
If relative is None or not provided, request.app.relative_urls will
|
||||
be used (if available, else False). If False, the output will be an
|
||||
absolute URL (including the scheme, host, vhost, and script_name).
|
||||
If True, the output will instead be a URL that is relative to the
|
||||
current request path, perhaps including '..' atoms. If relative is
|
||||
the string 'server', the output will instead be a URL that is
|
||||
relative to the server root; i.e., it will start with a slash.
|
||||
"""
|
||||
if isinstance(qs, (tuple, list, dict)):
|
||||
qs = _urlencode(qs)
|
||||
if qs:
|
||||
qs = '?' + qs
|
||||
|
||||
if cherrypy.request.app:
|
||||
if not path.startswith("/"):
|
||||
# Append/remove trailing slash from path_info as needed
|
||||
# (this is to support mistyped URL's without redirecting;
|
||||
# if you want to redirect, use tools.trailing_slash).
|
||||
pi = cherrypy.request.path_info
|
||||
if cherrypy.request.is_index is True:
|
||||
if not pi.endswith('/'):
|
||||
pi = pi + '/'
|
||||
elif cherrypy.request.is_index is False:
|
||||
if pi.endswith('/') and pi != '/':
|
||||
pi = pi[:-1]
|
||||
|
||||
if path == "":
|
||||
path = pi
|
||||
else:
|
||||
path = _urljoin(pi, path)
|
||||
|
||||
if script_name is None:
|
||||
script_name = cherrypy.request.script_name
|
||||
if base is None:
|
||||
base = cherrypy.request.base
|
||||
|
||||
newurl = base + script_name + path + qs
|
||||
else:
|
||||
# No request.app (we're being called outside a request).
|
||||
# We'll have to guess the base from server.* attributes.
|
||||
# This will produce very different results from the above
|
||||
# if you're using vhosts or tools.proxy.
|
||||
if base is None:
|
||||
base = cherrypy.server.base()
|
||||
|
||||
path = (script_name or "") + path
|
||||
newurl = base + path + qs
|
||||
|
||||
if './' in newurl:
|
||||
# Normalize the URL by removing ./ and ../
|
||||
atoms = []
|
||||
for atom in newurl.split('/'):
|
||||
if atom == '.':
|
||||
pass
|
||||
elif atom == '..':
|
||||
atoms.pop()
|
||||
else:
|
||||
atoms.append(atom)
|
||||
newurl = '/'.join(atoms)
|
||||
|
||||
# At this point, we should have a fully-qualified absolute URL.
|
||||
|
||||
if relative is None:
|
||||
relative = getattr(cherrypy.request.app, "relative_urls", False)
|
||||
|
||||
# See http://www.ietf.org/rfc/rfc2396.txt
|
||||
if relative == 'server':
|
||||
# "A relative reference beginning with a single slash character is
|
||||
# termed an absolute-path reference, as defined by <abs_path>..."
|
||||
# This is also sometimes called "server-relative".
|
||||
newurl = '/' + '/'.join(newurl.split('/', 3)[3:])
|
||||
elif relative:
|
||||
# "A relative reference that does not begin with a scheme name
|
||||
# or a slash character is termed a relative-path reference."
|
||||
old = url(relative=False).split('/')[:-1]
|
||||
new = newurl.split('/')
|
||||
while old and new:
|
||||
a, b = old[0], new[0]
|
||||
if a != b:
|
||||
break
|
||||
old.pop(0)
|
||||
new.pop(0)
|
||||
new = (['..'] * len(old)) + new
|
||||
newurl = '/'.join(new)
|
||||
|
||||
return newurl
|
||||
0
cherrypy/cherryd
Executable file → Normal file
15
cherrypy/daemon.py
Executable file → Normal file
@@ -53,12 +53,15 @@ def start(configfiles=None, daemonize=False, environment=None,
|
||||
cherrypy.server.unsubscribe()
|
||||
|
||||
addr = cherrypy.server.bind_addr
|
||||
cls = (
|
||||
servers.FlupFCGIServer if fastcgi else
|
||||
servers.FlupSCGIServer if scgi else
|
||||
servers.FlupCGIServer
|
||||
)
|
||||
f = cls(application=cherrypy.tree, bindAddress=addr)
|
||||
if fastcgi:
|
||||
f = servers.FlupFCGIServer(application=cherrypy.tree,
|
||||
bindAddress=addr)
|
||||
elif scgi:
|
||||
f = servers.FlupSCGIServer(application=cherrypy.tree,
|
||||
bindAddress=addr)
|
||||
else:
|
||||
f = servers.FlupCGIServer(application=cherrypy.tree,
|
||||
bindAddress=addr)
|
||||
s = servers.ServerAdapter(engine, httpserver=f, bind_addr=addr)
|
||||
s.subscribe()
|
||||
|
||||
|
||||
@@ -23,11 +23,10 @@ __date__ = 'April 2009'
|
||||
|
||||
|
||||
import time
|
||||
from hashlib import md5
|
||||
from cherrypy._cpcompat import parse_http_list, parse_keqv_list
|
||||
|
||||
import cherrypy
|
||||
from cherrypy._cpcompat import ntob
|
||||
from cherrypy._cpcompat import md5, ntob
|
||||
md5_hex = lambda s: md5(ntob(s)).hexdigest()
|
||||
|
||||
qop_auth = 'auth'
|
||||
|
||||
@@ -353,7 +353,7 @@ def get(invalid_methods=("POST", "PUT", "DELETE"), debug=False, **kwargs):
|
||||
return False
|
||||
|
||||
# Copy the response headers. See
|
||||
# https://github.com/cherrypy/cherrypy/issues/721.
|
||||
# https://bitbucket.org/cherrypy/cherrypy/issue/721.
|
||||
response.headers = rh = httputil.HeaderMap()
|
||||
for k in h:
|
||||
dict.__setitem__(rh, k, dict.__getitem__(h, k))
|
||||
|
||||
@@ -23,14 +23,11 @@ it will call ``serve()`` for you.
|
||||
import re
|
||||
import sys
|
||||
import cgi
|
||||
from cherrypy._cpcompat import quote_plus
|
||||
import os
|
||||
import os.path
|
||||
localFile = os.path.join(os.path.dirname(__file__), "coverage.cache")
|
||||
|
||||
from cherrypy._cpcompat import quote_plus
|
||||
|
||||
import cherrypy
|
||||
|
||||
the_coverage = None
|
||||
try:
|
||||
from coverage import coverage
|
||||
@@ -293,11 +290,10 @@ class CoverStats(object):
|
||||
root = os.path.dirname(cherrypy.__file__)
|
||||
self.root = root
|
||||
|
||||
@cherrypy.expose
|
||||
def index(self):
|
||||
return TEMPLATE_FRAMESET % self.root.lower()
|
||||
index.exposed = True
|
||||
|
||||
@cherrypy.expose
|
||||
def menu(self, base="/", pct="50", showpct="",
|
||||
exclude=r'python\d\.\d|test|tut\d|tutorial'):
|
||||
|
||||
@@ -332,6 +328,7 @@ class CoverStats(object):
|
||||
|
||||
yield "</div>"
|
||||
yield "</body></html>"
|
||||
menu.exposed = True
|
||||
|
||||
def annotated_file(self, filename, statements, excluded, missing):
|
||||
source = open(filename, 'r')
|
||||
@@ -355,7 +352,6 @@ class CoverStats(object):
|
||||
buffer = []
|
||||
yield template % (lineno, cgi.escape(line))
|
||||
|
||||
@cherrypy.expose
|
||||
def report(self, name):
|
||||
filename, statements, excluded, missing, _ = self.coverage.analysis2(
|
||||
name)
|
||||
@@ -370,6 +366,7 @@ class CoverStats(object):
|
||||
yield '</table>'
|
||||
yield '</body>'
|
||||
yield '</html>'
|
||||
report.exposed = True
|
||||
|
||||
|
||||
def serve(path=localFile, port=8080, root=None):
|
||||
|
||||
@@ -210,7 +210,6 @@ def extrapolate_statistics(scope):
|
||||
|
||||
# -------------------- CherryPy Applications Statistics --------------------- #
|
||||
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
|
||||
@@ -295,11 +294,6 @@ class ByteCountWrapper(object):
|
||||
average_uriset_time = lambda s: s['Count'] and (s['Sum'] / s['Count']) or 0
|
||||
|
||||
|
||||
def _get_threading_ident():
|
||||
if sys.version_info >= (3, 3):
|
||||
return threading.get_ident()
|
||||
return threading._get_ident()
|
||||
|
||||
class StatsTool(cherrypy.Tool):
|
||||
|
||||
"""Record various information about the current request."""
|
||||
@@ -328,7 +322,7 @@ class StatsTool(cherrypy.Tool):
|
||||
|
||||
appstats['Current Requests'] += 1
|
||||
appstats['Total Requests'] += 1
|
||||
appstats['Requests'][_get_threading_ident()] = {
|
||||
appstats['Requests'][threading._get_ident()] = {
|
||||
'Bytes Read': None,
|
||||
'Bytes Written': None,
|
||||
# Use a lambda so the ip gets updated by tools.proxy later
|
||||
@@ -345,7 +339,7 @@ class StatsTool(cherrypy.Tool):
|
||||
debug=False, **kwargs):
|
||||
"""Record the end of a request."""
|
||||
resp = cherrypy.serving.response
|
||||
w = appstats['Requests'][_get_threading_ident()]
|
||||
w = appstats['Requests'][threading._get_ident()]
|
||||
|
||||
r = cherrypy.request.rfile.bytes_read
|
||||
w['Bytes Read'] = r
|
||||
@@ -475,7 +469,6 @@ class StatsPage(object):
|
||||
},
|
||||
}
|
||||
|
||||
@cherrypy.expose
|
||||
def index(self):
|
||||
# Transform the raw data into pretty output for HTML
|
||||
yield """
|
||||
@@ -579,6 +572,7 @@ table.stats2 th {
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
index.exposed = True
|
||||
|
||||
def get_namespaces(self):
|
||||
"""Yield (title, scalars, collections) for each namespace."""
|
||||
@@ -611,13 +605,7 @@ table.stats2 th {
|
||||
"""Return ([headers], [rows]) for the given collection."""
|
||||
# E.g., the 'Requests' dict.
|
||||
headers = []
|
||||
try:
|
||||
# python2
|
||||
vals = v.itervalues()
|
||||
except AttributeError:
|
||||
# python3
|
||||
vals = v.values()
|
||||
for record in vals:
|
||||
for record in v.itervalues():
|
||||
for k3 in record:
|
||||
format = formatting.get(k3, missing)
|
||||
if format is None:
|
||||
@@ -678,22 +666,22 @@ table.stats2 th {
|
||||
return headers, subrows
|
||||
|
||||
if json is not None:
|
||||
@cherrypy.expose
|
||||
def data(self):
|
||||
s = extrapolate_statistics(logging.statistics)
|
||||
cherrypy.response.headers['Content-Type'] = 'application/json'
|
||||
return json.dumps(s, sort_keys=True, indent=4)
|
||||
data.exposed = True
|
||||
|
||||
@cherrypy.expose
|
||||
def pause(self, namespace):
|
||||
logging.statistics.get(namespace, {})['Enabled'] = False
|
||||
raise cherrypy.HTTPRedirect('./')
|
||||
pause.exposed = True
|
||||
pause.cp_config = {'tools.allow.on': True,
|
||||
'tools.allow.methods': ['POST']}
|
||||
|
||||
@cherrypy.expose
|
||||
def resume(self, namespace):
|
||||
logging.statistics.get(namespace, {})['Enabled'] = True
|
||||
raise cherrypy.HTTPRedirect('./')
|
||||
resume.exposed = True
|
||||
resume.cp_config = {'tools.allow.on': True,
|
||||
'tools.allow.methods': ['POST']}
|
||||
|
||||
@@ -2,12 +2,9 @@
|
||||
|
||||
import logging
|
||||
import re
|
||||
from hashlib import md5
|
||||
|
||||
import six
|
||||
|
||||
import cherrypy
|
||||
from cherrypy._cpcompat import basestring
|
||||
from cherrypy._cpcompat import basestring, md5, set, unicodestr
|
||||
from cherrypy.lib import httputil as _httputil
|
||||
from cherrypy.lib import is_iterator
|
||||
|
||||
@@ -195,10 +192,11 @@ def proxy(base=None, local='X-Forwarded-Host', remote='X-Forwarded-For',
|
||||
if lbase is not None:
|
||||
base = lbase.split(',')[0]
|
||||
if not base:
|
||||
base = request.headers.get('Host', '127.0.0.1')
|
||||
port = request.local.port
|
||||
if port != 80:
|
||||
base += ':%s' % port
|
||||
if port == 80:
|
||||
base = '127.0.0.1'
|
||||
else:
|
||||
base = '127.0.0.1:%s' % port
|
||||
|
||||
if base.find("://") == -1:
|
||||
# add http:// or https:// if needed
|
||||
@@ -306,7 +304,7 @@ class SessionAuth(object):
|
||||
|
||||
def login_screen(self, from_page='..', username='', error_msg='',
|
||||
**kwargs):
|
||||
return (six.text_type("""<html><body>
|
||||
return (unicodestr("""<html><body>
|
||||
Message: %(error_msg)s
|
||||
<form method="post" action="do_login">
|
||||
Login: <input type="text" name="username" value="%(username)s" size="10" />
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
import struct
|
||||
import time
|
||||
import io
|
||||
|
||||
import six
|
||||
|
||||
import cherrypy
|
||||
from cherrypy._cpcompat import basestring, ntob
|
||||
from cherrypy._cpcompat import basestring, BytesIO, ntob, set, unicodestr
|
||||
from cherrypy.lib import file_generator
|
||||
from cherrypy.lib import is_closable_iterator
|
||||
from cherrypy.lib import set_vary_header
|
||||
@@ -49,7 +46,7 @@ class UTF8StreamEncoder:
|
||||
|
||||
def __next__(self):
|
||||
res = next(self._iterator)
|
||||
if isinstance(res, six.text_type):
|
||||
if isinstance(res, unicodestr):
|
||||
res = res.encode('utf-8')
|
||||
return res
|
||||
|
||||
@@ -98,7 +95,7 @@ class ResponseEncoder:
|
||||
|
||||
def encoder(body):
|
||||
for chunk in body:
|
||||
if isinstance(chunk, six.text_type):
|
||||
if isinstance(chunk, unicodestr):
|
||||
chunk = chunk.encode(encoding, self.errors)
|
||||
yield chunk
|
||||
self.body = encoder(self.body)
|
||||
@@ -111,7 +108,7 @@ class ResponseEncoder:
|
||||
self.attempted_charsets.add(encoding)
|
||||
body = []
|
||||
for chunk in self.body:
|
||||
if isinstance(chunk, six.text_type):
|
||||
if isinstance(chunk, unicodestr):
|
||||
try:
|
||||
chunk = chunk.encode(encoding, self.errors)
|
||||
except (LookupError, UnicodeError):
|
||||
@@ -304,7 +301,7 @@ def compress(body, compress_level):
|
||||
def decompress(body):
|
||||
import gzip
|
||||
|
||||
zbuf = io.BytesIO()
|
||||
zbuf = BytesIO()
|
||||
zbuf.write(body)
|
||||
zbuf.seek(0)
|
||||
zfile = gzip.GzipFile(mode='rb', fileobj=zbuf)
|
||||
|
||||
@@ -143,11 +143,10 @@ class GCRoot(object):
|
||||
"Should be 1 in this request thread only."),
|
||||
]
|
||||
|
||||
@cherrypy.expose
|
||||
def index(self):
|
||||
return "Hello, world!"
|
||||
index.exposed = True
|
||||
|
||||
@cherrypy.expose
|
||||
def stats(self):
|
||||
output = ["Statistics:"]
|
||||
|
||||
@@ -215,3 +214,4 @@ class GCRoot(object):
|
||||
output.extend(t.format(tree))
|
||||
|
||||
return "\n".join(output)
|
||||
stats.exposed = True
|
||||
|
||||
@@ -62,9 +62,7 @@ __all__ = ("digestAuth", "basicAuth", "doAuth", "checkResponse",
|
||||
|
||||
##########################################################################
|
||||
import time
|
||||
from hashlib import md5
|
||||
|
||||
from cherrypy._cpcompat import base64_decode, ntob
|
||||
from cherrypy._cpcompat import base64_decode, ntob, md5
|
||||
from cherrypy._cpcompat import parse_http_list, parse_keqv_list
|
||||
|
||||
MD5 = "MD5"
|
||||
|
||||
@@ -8,15 +8,12 @@ to a public caning.
|
||||
"""
|
||||
|
||||
from binascii import b2a_base64
|
||||
|
||||
import six
|
||||
|
||||
from cherrypy._cpcompat import BaseHTTPRequestHandler, HTTPDate, ntob, ntou
|
||||
from cherrypy._cpcompat import basestring, iteritems
|
||||
from cherrypy._cpcompat import reversed, sorted, unquote_qs
|
||||
from cherrypy._cpcompat import basestring, bytestr, iteritems, nativestr
|
||||
from cherrypy._cpcompat import reversed, sorted, unicodestr, unquote_qs
|
||||
response_codes = BaseHTTPRequestHandler.responses.copy()
|
||||
|
||||
# From https://github.com/cherrypy/cherrypy/issues/361
|
||||
# From https://bitbucket.org/cherrypy/cherrypy/issue/361
|
||||
response_codes[500] = ('Internal Server Error',
|
||||
'The server encountered an unexpected condition '
|
||||
'which prevented it from fulfilling the request.')
|
||||
@@ -26,7 +23,7 @@ response_codes[503] = ('Service Unavailable',
|
||||
'maintenance of the server.')
|
||||
|
||||
import re
|
||||
from cgi import parse_header
|
||||
import urllib
|
||||
|
||||
|
||||
def urljoin(*atoms):
|
||||
@@ -146,7 +143,40 @@ class HeaderElement(object):
|
||||
|
||||
def parse(elementstr):
|
||||
"""Transform 'token;key=val' to ('token', {'key': 'val'})."""
|
||||
initial_value, params = parse_header(elementstr)
|
||||
# Split the element into a value and parameters. The 'value' may
|
||||
# be of the form, "token=token", but we don't split that here.
|
||||
atoms = [x.strip() for x in elementstr.split(";") if x.strip()]
|
||||
|
||||
# Clumsy fix for lack of proper handling of constructions like:
|
||||
# form-data; name="name"; filename="one;word.nzb"
|
||||
# A proper parser should be used, but this patch will at least allow
|
||||
# having semicolons in a file name.
|
||||
if 'filename' in elementstr:
|
||||
xatoms = []
|
||||
append_next = False
|
||||
for atom in atoms:
|
||||
if append_next:
|
||||
append_next = False
|
||||
atom = xatoms.pop(-1) + ';' + atom
|
||||
if '"' in atom and not atom.endswith('"'):
|
||||
append_next = True
|
||||
xatoms.append(atom)
|
||||
atoms = xatoms
|
||||
# End of patch
|
||||
|
||||
if not atoms:
|
||||
initial_value = ''
|
||||
else:
|
||||
initial_value = atoms.pop(0).strip()
|
||||
params = {}
|
||||
for atom in atoms:
|
||||
atom = [x.strip() for x in atom.split("=", 1) if x.strip()]
|
||||
key = atom.pop(0)
|
||||
if atom:
|
||||
val = atom[0]
|
||||
else:
|
||||
val = ""
|
||||
params[key] = val
|
||||
return initial_value, params
|
||||
parse = staticmethod(parse)
|
||||
|
||||
@@ -408,7 +438,7 @@ class CaseInsensitiveDict(dict):
|
||||
# A CRLF is allowed in the definition of TEXT only as part of a header
|
||||
# field continuation. It is expected that the folding LWS will be
|
||||
# replaced with a single SP before interpretation of the TEXT value."
|
||||
if str == bytes:
|
||||
if nativestr == bytestr:
|
||||
header_translate_table = ''.join([chr(i) for i in xrange(256)])
|
||||
header_translate_deletechars = ''.join(
|
||||
[chr(i) for i in xrange(32)]) + chr(127)
|
||||
@@ -457,13 +487,13 @@ class HeaderMap(CaseInsensitiveDict):
|
||||
transmitting on the wire for HTTP.
|
||||
"""
|
||||
for k, v in header_items:
|
||||
if isinstance(k, six.text_type):
|
||||
if isinstance(k, unicodestr):
|
||||
k = cls.encode(k)
|
||||
|
||||
if not isinstance(v, basestring):
|
||||
v = str(v)
|
||||
|
||||
if isinstance(v, six.text_type):
|
||||
if isinstance(v, unicodestr):
|
||||
v = cls.encode(v)
|
||||
|
||||
# See header_translate_* constants above.
|
||||
|
||||
@@ -8,11 +8,11 @@ You can profile any of your pages as follows::
|
||||
from cherrypy.lib import profiler
|
||||
|
||||
class Root:
|
||||
p = profiler.Profiler("/path/to/profile/dir")
|
||||
p = profile.Profiler("/path/to/profile/dir")
|
||||
|
||||
@cherrypy.expose
|
||||
def index(self):
|
||||
self.p.run(self._index)
|
||||
index.exposed = True
|
||||
|
||||
def _index(self):
|
||||
return "Hello, world!"
|
||||
@@ -33,10 +33,6 @@ module from the command line, it will call ``serve()`` for you.
|
||||
|
||||
"""
|
||||
|
||||
import io
|
||||
|
||||
import cherrypy
|
||||
|
||||
|
||||
def new_func_strip_path(func_name):
|
||||
"""Make profiler output more readable by adding `__init__` modules' parents
|
||||
@@ -59,6 +55,8 @@ import os.path
|
||||
import sys
|
||||
import warnings
|
||||
|
||||
from cherrypy._cpcompat import StringIO
|
||||
|
||||
_count = 0
|
||||
|
||||
|
||||
@@ -90,7 +88,7 @@ class Profiler(object):
|
||||
def stats(self, filename, sortby='cumulative'):
|
||||
""":rtype stats(index): output of print_stats() for the given profile.
|
||||
"""
|
||||
sio = io.StringIO()
|
||||
sio = StringIO()
|
||||
if sys.version_info >= (2, 5):
|
||||
s = pstats.Stats(os.path.join(self.path, filename), stream=sio)
|
||||
s.strip_dirs()
|
||||
@@ -112,7 +110,6 @@ class Profiler(object):
|
||||
sio.close()
|
||||
return response
|
||||
|
||||
@cherrypy.expose
|
||||
def index(self):
|
||||
return """<html>
|
||||
<head><title>CherryPy profile data</title></head>
|
||||
@@ -122,8 +119,8 @@ class Profiler(object):
|
||||
</frameset>
|
||||
</html>
|
||||
"""
|
||||
index.exposed = True
|
||||
|
||||
@cherrypy.expose
|
||||
def menu(self):
|
||||
yield "<h2>Profiling runs</h2>"
|
||||
yield "<p>Click on one of the runs below to see profiling data.</p>"
|
||||
@@ -132,12 +129,13 @@ class Profiler(object):
|
||||
for i in runs:
|
||||
yield "<a href='report?filename=%s' target='main'>%s</a><br />" % (
|
||||
i, i)
|
||||
menu.exposed = True
|
||||
|
||||
@cherrypy.expose
|
||||
def report(self, filename):
|
||||
import cherrypy
|
||||
cherrypy.response.headers['Content-Type'] = 'text/plain'
|
||||
return self.stats(filename)
|
||||
report.exposed = True
|
||||
|
||||
|
||||
class ProfileAggregator(Profiler):
|
||||
|
||||
@@ -281,14 +281,13 @@ class _Builder2:
|
||||
# Everything else becomes args
|
||||
else :
|
||||
args.append(self.build(child))
|
||||
|
||||
return callee(*args, **kwargs)
|
||||
|
||||
def build_Keyword(self, o):
|
||||
key, value_obj = o.getChildren()
|
||||
value = self.build(value_obj)
|
||||
kw_dict = {key: value}
|
||||
return kw_dict
|
||||
return kw_dict
|
||||
|
||||
def build_List(self, o):
|
||||
return map(self.build, o.getChildren())
|
||||
@@ -378,39 +377,7 @@ class _Builder3:
|
||||
def build_Index(self, o):
|
||||
return self.build(o.value)
|
||||
|
||||
def _build_call35(self, o):
|
||||
"""
|
||||
Workaround for python 3.5 _ast.Call signature, docs found here
|
||||
https://greentreesnakes.readthedocs.org/en/latest/nodes.html
|
||||
"""
|
||||
import ast
|
||||
callee = self.build(o.func)
|
||||
args = []
|
||||
if o.args is not None:
|
||||
for a in o.args:
|
||||
if isinstance(a, ast.Starred):
|
||||
args.append(self.build(a.value))
|
||||
else:
|
||||
args.append(self.build(a))
|
||||
kwargs = {}
|
||||
for kw in o.keywords:
|
||||
if kw.arg is None: # double asterix `**`
|
||||
rst = self.build(kw.value)
|
||||
if not isinstance(rst, dict):
|
||||
raise TypeError("Invalid argument for call."
|
||||
"Must be a mapping object.")
|
||||
# give preference to the keys set directly from arg=value
|
||||
for k, v in rst.items():
|
||||
if k not in kwargs:
|
||||
kwargs[k] = v
|
||||
else: # defined on the call as: arg=value
|
||||
kwargs[kw.arg] = self.build(kw.value)
|
||||
return callee(*args, **kwargs)
|
||||
|
||||
def build_Call(self, o):
|
||||
if sys.version_info >= (3, 5):
|
||||
return self._build_call35(o)
|
||||
|
||||
callee = self.build(o.func)
|
||||
|
||||
if o.args is None:
|
||||
@@ -421,16 +388,13 @@ class _Builder3:
|
||||
if o.starargs is None:
|
||||
starargs = ()
|
||||
else:
|
||||
starargs = tuple(self.build(o.starargs))
|
||||
starargs = self.build(o.starargs)
|
||||
|
||||
if o.kwargs is None:
|
||||
kwargs = {}
|
||||
else:
|
||||
kwargs = self.build(o.kwargs)
|
||||
if o.keywords is not None: # direct a=b keywords
|
||||
for kw in o.keywords:
|
||||
# preference because is a direct keyword against **kwargs
|
||||
kwargs[kw.arg] = self.build(kw.value)
|
||||
|
||||
return callee(*(args + starargs), **kwargs)
|
||||
|
||||
def build_List(self, o):
|
||||
|
||||
@@ -94,11 +94,10 @@ import datetime
|
||||
import os
|
||||
import time
|
||||
import threading
|
||||
|
||||
import six
|
||||
import types
|
||||
|
||||
import cherrypy
|
||||
from cherrypy._cpcompat import copyitems, pickle, random20
|
||||
from cherrypy._cpcompat import copyitems, pickle, random20, unicodestr
|
||||
from cherrypy.lib import httputil
|
||||
from cherrypy.lib import lockfile
|
||||
from cherrypy.lib import locking
|
||||
@@ -183,7 +182,7 @@ class Session(object):
|
||||
cherrypy.log('Expired or malicious session %r; '
|
||||
'making a new one' % id, 'TOOLS.SESSIONS')
|
||||
# Expired or malicious session. Make a new one.
|
||||
# See https://github.com/cherrypy/cherrypy/issues/709.
|
||||
# See https://bitbucket.org/cherrypy/cherrypy/issue/709.
|
||||
self.id = None
|
||||
self.missing = True
|
||||
self._regenerate()
|
||||
@@ -592,6 +591,93 @@ class FileSession(Session):
|
||||
and not fname.endswith(self.LOCK_SUFFIX))])
|
||||
|
||||
|
||||
class PostgresqlSession(Session):
|
||||
|
||||
""" Implementation of the PostgreSQL backend for sessions. It assumes
|
||||
a table like this::
|
||||
|
||||
create table session (
|
||||
id varchar(40),
|
||||
data text,
|
||||
expiration_time timestamp
|
||||
)
|
||||
|
||||
You must provide your own get_db function.
|
||||
"""
|
||||
|
||||
pickle_protocol = pickle.HIGHEST_PROTOCOL
|
||||
|
||||
def __init__(self, id=None, **kwargs):
|
||||
Session.__init__(self, id, **kwargs)
|
||||
self.cursor = self.db.cursor()
|
||||
|
||||
def setup(cls, **kwargs):
|
||||
"""Set up the storage system for Postgres-based sessions.
|
||||
|
||||
This should only be called once per process; this will be done
|
||||
automatically when using sessions.init (as the built-in Tool does).
|
||||
"""
|
||||
for k, v in kwargs.items():
|
||||
setattr(cls, k, v)
|
||||
|
||||
self.db = self.get_db()
|
||||
setup = classmethod(setup)
|
||||
|
||||
def __del__(self):
|
||||
if self.cursor:
|
||||
self.cursor.close()
|
||||
self.db.commit()
|
||||
|
||||
def _exists(self):
|
||||
# Select session data from table
|
||||
self.cursor.execute('select data, expiration_time from session '
|
||||
'where id=%s', (self.id,))
|
||||
rows = self.cursor.fetchall()
|
||||
return bool(rows)
|
||||
|
||||
def _load(self):
|
||||
# Select session data from table
|
||||
self.cursor.execute('select data, expiration_time from session '
|
||||
'where id=%s', (self.id,))
|
||||
rows = self.cursor.fetchall()
|
||||
if not rows:
|
||||
return None
|
||||
|
||||
pickled_data, expiration_time = rows[0]
|
||||
data = pickle.loads(pickled_data)
|
||||
return data, expiration_time
|
||||
|
||||
def _save(self, expiration_time):
|
||||
pickled_data = pickle.dumps(self._data, self.pickle_protocol)
|
||||
self.cursor.execute('update session set data = %s, '
|
||||
'expiration_time = %s where id = %s',
|
||||
(pickled_data, expiration_time, self.id))
|
||||
|
||||
def _delete(self):
|
||||
self.cursor.execute('delete from session where id=%s', (self.id,))
|
||||
|
||||
def acquire_lock(self):
|
||||
"""Acquire an exclusive lock on the currently-loaded session data."""
|
||||
# We use the "for update" clause to lock the row
|
||||
self.locked = True
|
||||
self.cursor.execute('select id from session where id=%s for update',
|
||||
(self.id,))
|
||||
if self.debug:
|
||||
cherrypy.log('Lock acquired.', 'TOOLS.SESSIONS')
|
||||
|
||||
def release_lock(self):
|
||||
"""Release the lock on the currently-loaded session data."""
|
||||
# We just close the cursor and that will remove the lock
|
||||
# introduced by the "for update" clause
|
||||
self.cursor.close()
|
||||
self.locked = False
|
||||
|
||||
def clean_up(self):
|
||||
"""Clean up expired sessions."""
|
||||
self.cursor.execute('delete from session where expiration_time < %s',
|
||||
(self.now(),))
|
||||
|
||||
|
||||
class MemcachedSession(Session):
|
||||
|
||||
# The most popular memcached client for Python isn't thread-safe.
|
||||
@@ -622,7 +708,7 @@ class MemcachedSession(Session):
|
||||
def _set_id(self, value):
|
||||
# This encode() call is where we differ from the superclass.
|
||||
# Memcache keys MUST be byte strings, not unicode.
|
||||
if isinstance(value, six.text_type):
|
||||
if isinstance(value, unicodestr):
|
||||
value = value.encode('utf-8')
|
||||
|
||||
self._id = value
|
||||
@@ -721,7 +807,7 @@ def init(storage_type='ram', path=None, path_header=None, name='session_id',
|
||||
"""Initialize session object (using cookies).
|
||||
|
||||
storage_type
|
||||
One of 'ram', 'file', memcached'. This will be
|
||||
One of 'ram', 'file', 'postgresql', 'memcached'. This will be
|
||||
used to look up the corresponding class in cherrypy.lib.sessions
|
||||
globals. For example, 'file' will use the FileSession class.
|
||||
|
||||
|
||||
@@ -49,10 +49,7 @@ def serve_file(path, content_type=None, disposition=None, name=None,
|
||||
|
||||
try:
|
||||
st = os.stat(path)
|
||||
except (OSError, TypeError, ValueError):
|
||||
# OSError when file fails to stat
|
||||
# TypeError on Python 2 when there's a null byte
|
||||
# ValueError on Python 3 when there's a null byte
|
||||
except OSError:
|
||||
if debug:
|
||||
cherrypy.log('os.stat(%r) failed' % path, 'TOOLS.STATIC')
|
||||
raise cherrypy.NotFound()
|
||||
|
||||
@@ -8,7 +8,7 @@ import time
|
||||
import threading
|
||||
|
||||
from cherrypy._cpcompat import basestring, get_daemon, get_thread_ident
|
||||
from cherrypy._cpcompat import ntob, Timer, SetDaemonProperty
|
||||
from cherrypy._cpcompat import ntob, set, Timer, SetDaemonProperty
|
||||
|
||||
# _module__file__base is used by Autoreload to make
|
||||
# absolute any filenames retrieved from sys.modules which are not
|
||||
@@ -109,35 +109,12 @@ class SignalHandler(object):
|
||||
self.handlers['SIGINT'] = self._jython_SIGINT_handler
|
||||
|
||||
self._previous_handlers = {}
|
||||
# used to determine is the process is a daemon in `self._is_daemonized`
|
||||
self._original_pid = os.getpid()
|
||||
|
||||
|
||||
def _jython_SIGINT_handler(self, signum=None, frame=None):
|
||||
# See http://bugs.jython.org/issue1313
|
||||
self.bus.log('Keyboard Interrupt: shutting down bus')
|
||||
self.bus.exit()
|
||||
|
||||
def _is_daemonized(self):
|
||||
"""Return boolean indicating if the current process is
|
||||
running as a daemon.
|
||||
|
||||
The criteria to determine the `daemon` condition is to verify
|
||||
if the current pid is not the same as the one that got used on
|
||||
the initial construction of the plugin *and* the stdin is not
|
||||
connected to a terminal.
|
||||
|
||||
The sole validation of the tty is not enough when the plugin
|
||||
is executing inside other process like in a CI tool
|
||||
(Buildbot, Jenkins).
|
||||
"""
|
||||
if (self._original_pid != os.getpid() and
|
||||
not os.isatty(sys.stdin.fileno())):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
def subscribe(self):
|
||||
"""Subscribe self.handlers to signals."""
|
||||
for sig, func in self.handlers.items():
|
||||
@@ -203,13 +180,13 @@ class SignalHandler(object):
|
||||
|
||||
def handle_SIGHUP(self):
|
||||
"""Restart if daemonized, else exit."""
|
||||
if self._is_daemonized():
|
||||
self.bus.log("SIGHUP caught while daemonized. Restarting.")
|
||||
self.bus.restart()
|
||||
else:
|
||||
if os.isatty(sys.stdin.fileno()):
|
||||
# not daemonized (may be foreground or background)
|
||||
self.bus.log("SIGHUP caught but not daemonized. Exiting.")
|
||||
self.bus.exit()
|
||||
else:
|
||||
self.bus.log("SIGHUP caught while daemonized. Restarting.")
|
||||
self.bus.restart()
|
||||
|
||||
|
||||
try:
|
||||
@@ -223,7 +200,7 @@ class DropPrivileges(SimplePlugin):
|
||||
|
||||
"""Drop privileges. uid/gid arguments not available on Windows.
|
||||
|
||||
Special thanks to `Gavin Baker <http://antonym.org/2005/12/dropping-privileges-in-python.html>`_
|
||||
Special thanks to `Gavin Baker <http://antonym.org/2005/12/dropping-privileges-in-python.html>`_
|
||||
"""
|
||||
|
||||
def __init__(self, bus, umask=None, uid=None, gid=None):
|
||||
|
||||
@@ -59,9 +59,9 @@ hello.py::
|
||||
|
||||
class HelloWorld:
|
||||
\"""Sample request handler class.\"""
|
||||
@cherrypy.expose
|
||||
def index(self):
|
||||
return "Hello world!"
|
||||
index.exposed = True
|
||||
|
||||
cherrypy.tree.mount(HelloWorld())
|
||||
# CherryPy autoreload must be disabled for the flup server to work
|
||||
@@ -183,7 +183,8 @@ class ServerAdapter(object):
|
||||
if not self.httpserver:
|
||||
return ''
|
||||
host, port = self.bind_addr
|
||||
if getattr(self.httpserver, 'ssl_adapter', None):
|
||||
if getattr(self.httpserver, 'ssl_certificate', None) or \
|
||||
getattr(self.httpserver, 'ssl_adapter', None):
|
||||
scheme = "https"
|
||||
if port != 443:
|
||||
host += ":%s" % port
|
||||
|
||||
@@ -67,7 +67,8 @@ import threading
|
||||
import time
|
||||
import traceback as _traceback
|
||||
import warnings
|
||||
import operator
|
||||
|
||||
from cherrypy._cpcompat import set
|
||||
|
||||
# Here I save the value of os.getcwd(), which, if I am imported early enough,
|
||||
# will be the directory from which the startup script was run. This is needed
|
||||
@@ -86,7 +87,7 @@ class ChannelFailures(Exception):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
# Don't use 'super' here; Exceptions are old-style in Py2.4
|
||||
# See https://github.com/cherrypy/cherrypy/issues/959
|
||||
# See https://bitbucket.org/cherrypy/cherrypy/issue/959
|
||||
Exception.__init__(self, *args, **kwargs)
|
||||
self._exceptions = list()
|
||||
|
||||
@@ -161,11 +162,9 @@ class Bus(object):
|
||||
def __init__(self):
|
||||
self.execv = False
|
||||
self.state = states.STOPPED
|
||||
channels = 'start', 'stop', 'exit', 'graceful', 'log', 'main'
|
||||
self.listeners = dict(
|
||||
(channel, set())
|
||||
for channel in channels
|
||||
)
|
||||
[(channel, set()) for channel
|
||||
in ('start', 'stop', 'exit', 'graceful', 'log', 'main')])
|
||||
self._priorities = {}
|
||||
|
||||
def subscribe(self, channel, callback, priority=None):
|
||||
@@ -193,11 +192,14 @@ class Bus(object):
|
||||
exc = ChannelFailures()
|
||||
output = []
|
||||
|
||||
raw_items = (
|
||||
(self._priorities[(channel, listener)], listener)
|
||||
for listener in self.listeners[channel]
|
||||
)
|
||||
items = sorted(raw_items, key=operator.itemgetter(0))
|
||||
items = [(self._priorities[(channel, listener)], listener)
|
||||
for listener in self.listeners[channel]]
|
||||
try:
|
||||
items.sort(key=lambda item: item[0])
|
||||
except TypeError:
|
||||
# Python 2.3 had no 'key' arg, but that doesn't matter
|
||||
# since it could sort dissimilar types just fine.
|
||||
items.sort()
|
||||
for priority, listener in items:
|
||||
try:
|
||||
output.append(listener(*args, **kwargs))
|
||||
@@ -317,10 +319,10 @@ class Bus(object):
|
||||
raise
|
||||
|
||||
# Waiting for ALL child threads to finish is necessary on OS X.
|
||||
# See https://github.com/cherrypy/cherrypy/issues/581.
|
||||
# See https://bitbucket.org/cherrypy/cherrypy/issue/581.
|
||||
# It's also good to let them all shut down before allowing
|
||||
# the main thread to call atexit handlers.
|
||||
# See https://github.com/cherrypy/cherrypy/issues/751.
|
||||
# See https://bitbucket.org/cherrypy/cherrypy/issue/751.
|
||||
self.log("Waiting for child threads to terminate...")
|
||||
for t in threading.enumerate():
|
||||
# Validate the we're not trying to join the MainThread
|
||||
@@ -440,7 +442,13 @@ class Bus(object):
|
||||
def log(self, msg="", level=20, traceback=False):
|
||||
"""Log the given message. Append the last traceback if requested."""
|
||||
if traceback:
|
||||
msg += "\n" + "".join(_traceback.format_exception(*sys.exc_info()))
|
||||
# Work-around for bug in Python's traceback implementation
|
||||
# which crashes when the error message contains %1, %2 etc.
|
||||
errors = sys.exc_info()
|
||||
if '%' in errors[1].message:
|
||||
errors[1].message = errors[1].message.replace('%', '#')
|
||||
errors[1].args = [item.replace('%', '#') for item in errors[1].args]
|
||||
msg += "\n" + "".join(_traceback.format_exception(*errors))
|
||||
self.publish('log', msg, level)
|
||||
|
||||
bus = Bus()
|
||||
|
||||
@@ -33,14 +33,6 @@ class BuiltinSSLAdapter(wsgiserver.SSLAdapter):
|
||||
|
||||
private_key = None
|
||||
"""The filename of the server's private key file."""
|
||||
|
||||
certificate_chain = None
|
||||
"""The filename of the certificate chain file."""
|
||||
|
||||
"""The ssl.SSLContext that will be used to wrap sockets where available
|
||||
(on Python > 2.7.9 / 3.3)
|
||||
"""
|
||||
context = None
|
||||
|
||||
def __init__(self, certificate, private_key, certificate_chain=None):
|
||||
if ssl is None:
|
||||
@@ -48,12 +40,6 @@ class BuiltinSSLAdapter(wsgiserver.SSLAdapter):
|
||||
self.certificate = certificate
|
||||
self.private_key = private_key
|
||||
self.certificate_chain = certificate_chain
|
||||
if hasattr(ssl, 'create_default_context'):
|
||||
self.context = ssl.create_default_context(
|
||||
purpose=ssl.Purpose.CLIENT_AUTH,
|
||||
cafile=certificate_chain
|
||||
)
|
||||
self.context.load_cert_chain(certificate, private_key)
|
||||
|
||||
def bind(self, sock):
|
||||
"""Wrap and return the given socket."""
|
||||
@@ -62,15 +48,10 @@ class BuiltinSSLAdapter(wsgiserver.SSLAdapter):
|
||||
def wrap(self, sock):
|
||||
"""Wrap and return the given socket, plus WSGI environ entries."""
|
||||
try:
|
||||
if self.context is not None:
|
||||
s = self.context.wrap_socket(sock,do_handshake_on_connect=True,
|
||||
server_side=True)
|
||||
else:
|
||||
s = ssl.wrap_socket(sock, do_handshake_on_connect=True,
|
||||
server_side=True, certfile=self.certificate,
|
||||
keyfile=self.private_key,
|
||||
ssl_version=ssl.PROTOCOL_SSLv23,
|
||||
ca_certs=self.certificate_chain)
|
||||
s = ssl.wrap_socket(sock, do_handshake_on_connect=True,
|
||||
server_side=True, certfile=self.certificate,
|
||||
keyfile=self.private_key,
|
||||
ssl_version=ssl.PROTOCOL_SSLv23)
|
||||
except ssl.SSLError:
|
||||
e = sys.exc_info()[1]
|
||||
if e.errno == ssl.SSL_ERROR_EOF:
|
||||
|
||||
@@ -68,7 +68,7 @@ class SSL_fileobject(wsgiserver.CP_fileobject):
|
||||
time.sleep(self.ssl_retry)
|
||||
except SSL.WantWriteError:
|
||||
time.sleep(self.ssl_retry)
|
||||
except SSL.SysCallError as e:
|
||||
except SSL.SysCallError, e:
|
||||
if is_reader and e.args == (-1, 'Unexpected EOF'):
|
||||
return ""
|
||||
|
||||
@@ -76,7 +76,7 @@ class SSL_fileobject(wsgiserver.CP_fileobject):
|
||||
if is_reader and errnum in wsgiserver.socket_errors_to_ignore:
|
||||
return ""
|
||||
raise socket.error(errnum)
|
||||
except SSL.Error as e:
|
||||
except SSL.Error, e:
|
||||
if is_reader and e.args == (-1, 'Unexpected EOF'):
|
||||
return ""
|
||||
|
||||
|
||||
@@ -75,8 +75,8 @@ __all__ = ['HTTPRequest', 'HTTPConnection', 'HTTPServer',
|
||||
'WorkerThread', 'ThreadPool', 'SSLAdapter',
|
||||
'CherryPyWSGIServer',
|
||||
'Gateway', 'WSGIGateway', 'WSGIGateway_10', 'WSGIGateway_u0',
|
||||
'WSGIPathInfoDispatcher', 'get_ssl_adapter_class',
|
||||
'socket_errors_to_ignore', 'redirect_url']
|
||||
'socket_errors_to_ignore',
|
||||
'WSGIPathInfoDispatcher', 'get_ssl_adapter_class', 'redirect_url']
|
||||
|
||||
import os
|
||||
try:
|
||||
@@ -84,47 +84,22 @@ try:
|
||||
except:
|
||||
import Queue as queue
|
||||
import re
|
||||
import email.utils
|
||||
import rfc822
|
||||
import socket
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
import traceback as traceback_
|
||||
import operator
|
||||
from urllib import unquote
|
||||
from urlparse import urlparse
|
||||
import warnings
|
||||
import errno
|
||||
import logging
|
||||
try:
|
||||
# prefer slower Python-based io module
|
||||
import _pyio as io
|
||||
except ImportError:
|
||||
# Python 2.6
|
||||
import io
|
||||
|
||||
try:
|
||||
import pkg_resources
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
if 'win' in sys.platform and hasattr(socket, "AF_INET6"):
|
||||
if not hasattr(socket, 'IPPROTO_IPV6'):
|
||||
socket.IPPROTO_IPV6 = 41
|
||||
if not hasattr(socket, 'IPV6_V6ONLY'):
|
||||
socket.IPV6_V6ONLY = 27
|
||||
|
||||
|
||||
DEFAULT_BUFFER_SIZE = io.DEFAULT_BUFFER_SIZE
|
||||
try:
|
||||
import cStringIO as StringIO
|
||||
except ImportError:
|
||||
import StringIO
|
||||
DEFAULT_BUFFER_SIZE = -1
|
||||
|
||||
REDIRECT_URL = None # Application can write its HTTP-->HTTPS redirection URL here
|
||||
|
||||
try:
|
||||
cp_version = pkg_resources.require('cherrypy')[0].version
|
||||
except Exception:
|
||||
cp_version = 'unknown'
|
||||
|
||||
|
||||
class FauxSocket(object):
|
||||
|
||||
"""Faux socket with the minimal interface required by pypy"""
|
||||
@@ -136,8 +111,26 @@ _fileobject_uses_str_type = isinstance(
|
||||
socket._fileobject(FauxSocket())._rbuf, basestring)
|
||||
del FauxSocket # this class is not longer required for anything.
|
||||
|
||||
import threading
|
||||
import time
|
||||
import traceback
|
||||
|
||||
|
||||
def format_exc(limit=None):
|
||||
"""Like print_exc() but return a string. Backport for Python 2.3."""
|
||||
try:
|
||||
etype, value, tb = sys.exc_info()
|
||||
return ''.join(traceback.format_exception(etype, value, tb, limit))
|
||||
finally:
|
||||
etype = value = tb = None
|
||||
|
||||
import operator
|
||||
|
||||
from urllib import unquote
|
||||
import warnings
|
||||
|
||||
if sys.version_info >= (3, 0):
|
||||
bytestr = bytes
|
||||
unicodestr = str
|
||||
basestring = (bytes, str)
|
||||
|
||||
@@ -148,6 +141,7 @@ if sys.version_info >= (3, 0):
|
||||
# In Python 3, the native string type is unicode
|
||||
return n.encode(encoding)
|
||||
else:
|
||||
bytestr = str
|
||||
unicodestr = unicode
|
||||
basestring = basestring
|
||||
|
||||
@@ -173,6 +167,7 @@ ASTERISK = ntob('*')
|
||||
FORWARD_SLASH = ntob('/')
|
||||
quoted_slash = re.compile(ntob("(?i)%2F"))
|
||||
|
||||
import errno
|
||||
|
||||
def redirect_url(url=None):
|
||||
global REDIRECT_URL
|
||||
@@ -208,8 +203,6 @@ socket_errors_to_ignore = plat_specific_errors(
|
||||
)
|
||||
socket_errors_to_ignore.append("timed out")
|
||||
socket_errors_to_ignore.append("The read operation timed out")
|
||||
if sys.platform == 'darwin':
|
||||
socket_errors_to_ignore.append(plat_specific_errors("EPROTOTYPE"))
|
||||
|
||||
socket_errors_nonblocking = plat_specific_errors(
|
||||
'EAGAIN', 'EWOULDBLOCK', 'WSAEWOULDBLOCK')
|
||||
@@ -225,6 +218,7 @@ comma_separated_headers = [
|
||||
]
|
||||
|
||||
|
||||
import logging
|
||||
if not hasattr(logging, 'statistics'):
|
||||
logging.statistics = {}
|
||||
|
||||
@@ -316,7 +310,7 @@ class SizeCheckWrapper(object):
|
||||
self.bytes_read += len(data)
|
||||
self._check_length()
|
||||
res.append(data)
|
||||
# See https://github.com/cherrypy/cherrypy/issues/421
|
||||
# See https://bitbucket.org/cherrypy/cherrypy/issue/421
|
||||
if len(data) < 256 or data[-1:] == LF:
|
||||
return EMPTY.join(res)
|
||||
|
||||
@@ -688,10 +682,6 @@ class HTTPRequest(object):
|
||||
|
||||
# uri may be an abs_path (including "http://host.domain.tld");
|
||||
scheme, authority, path = self.parse_request_uri(uri)
|
||||
if path is None:
|
||||
self.simple_response("400 Bad Request",
|
||||
"Invalid path in Request-URI.")
|
||||
return False
|
||||
if path and NUMBER_SIGN in path:
|
||||
self.simple_response("400 Bad Request",
|
||||
"Illegal #fragment in Request-URI.")
|
||||
@@ -704,20 +694,21 @@ class HTTPRequest(object):
|
||||
if path and QUESTION_MARK in path:
|
||||
path, qs = path.split(QUESTION_MARK, 1)
|
||||
|
||||
# Unquote the path+params (e.g. "/this%20path" -> "/this path").
|
||||
# http://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html#sec5.1.2
|
||||
#
|
||||
# But note that "...a URI must be separated into its components
|
||||
# before the escaped characters within those components can be
|
||||
# safely decoded." http://www.ietf.org/rfc/rfc2396.txt, sec 2.4.2
|
||||
# Therefore, "/this%2Fpath" becomes "/this%2Fpath", not "/this/path".
|
||||
try:
|
||||
atoms = [unquote(x) for x in quoted_slash.split(path)]
|
||||
except ValueError:
|
||||
ex = sys.exc_info()[1]
|
||||
self.simple_response("400 Bad Request", ex.args[0])
|
||||
return False
|
||||
path = "%2F".join(atoms)
|
||||
if path is not None:
|
||||
# Unquote the path+params (e.g. "/this%20path" -> "/this path").
|
||||
# http://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html#sec5.1.2
|
||||
#
|
||||
# But note that "...a URI must be separated into its components
|
||||
# before the escaped characters within those components can be
|
||||
# safely decoded." http://www.ietf.org/rfc/rfc2396.txt, sec 2.4.2
|
||||
# Therefore, "/this%2Fpath" becomes "/this%2Fpath", not "/this/path".
|
||||
try:
|
||||
atoms = [unquote(x) for x in quoted_slash.split(path)]
|
||||
except ValueError:
|
||||
ex = sys.exc_info()[1]
|
||||
self.simple_response("400 Bad Request", ex.args[0])
|
||||
return False
|
||||
path = "%2F".join(atoms)
|
||||
self.path = path
|
||||
|
||||
# Note that, like wsgiref and most other HTTP servers,
|
||||
@@ -816,7 +807,7 @@ class HTTPRequest(object):
|
||||
if self.inheaders.get("Expect", "") == "100-continue":
|
||||
# Don't use simple_response here, because it emits headers
|
||||
# we don't want. See
|
||||
# https://github.com/cherrypy/cherrypy/issues/951
|
||||
# https://bitbucket.org/cherrypy/cherrypy/issue/951
|
||||
msg = self.server.protocol + " 100 Continue\r\n\r\n"
|
||||
try:
|
||||
self.conn.wfile.sendall(msg)
|
||||
@@ -849,12 +840,19 @@ class HTTPRequest(object):
|
||||
if uri == ASTERISK:
|
||||
return None, None, uri
|
||||
|
||||
scheme, authority, path, params, query, fragment = urlparse(uri)
|
||||
if scheme and QUESTION_MARK not in scheme:
|
||||
i = uri.find('://')
|
||||
if i > 0 and QUESTION_MARK not in uri[:i]:
|
||||
# An absoluteURI.
|
||||
# If there's a scheme (and it must be http or https), then:
|
||||
# http_URL = "http:" "//" host [ ":" port ] [ abs_path [ "?" query
|
||||
# ]]
|
||||
scheme, remainder = uri[:i].lower(), uri[i + 3:]
|
||||
try:
|
||||
authority, path = remainder.split(FORWARD_SLASH, 1)
|
||||
except ValueError:
|
||||
authority = remainder.split(FORWARD_SLASH, 1)
|
||||
path = ''
|
||||
path = FORWARD_SLASH + path
|
||||
return scheme, authority, path
|
||||
|
||||
if uri.startswith(FORWARD_SLASH):
|
||||
@@ -894,7 +892,10 @@ class HTTPRequest(object):
|
||||
buf = [self.server.protocol + SPACE +
|
||||
status + CRLF,
|
||||
"Content-Length: %s\r\n" % len(msg),
|
||||
"Content-Type: text/html\r\n" if status[:3] == '301' else "Content-Type: text/plain\r\n"]
|
||||
"Content-Type: text/plain\r\n"]
|
||||
|
||||
if status[:3] in ("301",):
|
||||
buf.append("Location: %s" % msg)
|
||||
|
||||
if status[:3] in ("413", "414"):
|
||||
# Request Entity Too Large / Request-URI Too Long
|
||||
@@ -985,7 +986,7 @@ class HTTPRequest(object):
|
||||
self.rfile.read(remaining)
|
||||
|
||||
if "date" not in hkeys:
|
||||
self.outheaders.append(("Date", email.utils.formatdate()))
|
||||
self.outheaders.append(("Date", rfc822.formatdate()))
|
||||
|
||||
if "server" not in hkeys:
|
||||
self.outheaders.append(("Server", self.server.server_name))
|
||||
@@ -1066,7 +1067,7 @@ class CP_fileobject(socket._fileobject):
|
||||
if size < 0:
|
||||
# Read until EOF
|
||||
# reset _rbuf. we consume it via buf.
|
||||
self._rbuf = io.BytesIO()
|
||||
self._rbuf = StringIO.StringIO()
|
||||
while True:
|
||||
data = self.recv(rbufsize)
|
||||
if not data:
|
||||
@@ -1081,12 +1082,12 @@ class CP_fileobject(socket._fileobject):
|
||||
# return.
|
||||
buf.seek(0)
|
||||
rv = buf.read(size)
|
||||
self._rbuf = io.BytesIO()
|
||||
self._rbuf = StringIO.StringIO()
|
||||
self._rbuf.write(buf.read())
|
||||
return rv
|
||||
|
||||
# reset _rbuf. we consume it via buf.
|
||||
self._rbuf = io.BytesIO()
|
||||
self._rbuf = StringIO.StringIO()
|
||||
while True:
|
||||
left = size - buf_len
|
||||
# recv() will malloc the amount of memory given as its
|
||||
@@ -1124,7 +1125,7 @@ class CP_fileobject(socket._fileobject):
|
||||
buf.seek(0)
|
||||
bline = buf.readline(size)
|
||||
if bline.endswith('\n') or len(bline) == size:
|
||||
self._rbuf = io.BytesIO()
|
||||
self._rbuf = StringIO.StringIO()
|
||||
self._rbuf.write(buf.read())
|
||||
return bline
|
||||
del bline
|
||||
@@ -1135,7 +1136,7 @@ class CP_fileobject(socket._fileobject):
|
||||
buf.seek(0)
|
||||
buffers = [buf.read()]
|
||||
# reset _rbuf. we consume it via buf.
|
||||
self._rbuf = io.BytesIO()
|
||||
self._rbuf = StringIO.StringIO()
|
||||
data = None
|
||||
recv = self.recv
|
||||
while data != "\n":
|
||||
@@ -1147,7 +1148,7 @@ class CP_fileobject(socket._fileobject):
|
||||
|
||||
buf.seek(0, 2) # seek end
|
||||
# reset _rbuf. we consume it via buf.
|
||||
self._rbuf = io.BytesIO()
|
||||
self._rbuf = StringIO.StringIO()
|
||||
while True:
|
||||
data = self.recv(self._rbufsize)
|
||||
if not data:
|
||||
@@ -1169,11 +1170,11 @@ class CP_fileobject(socket._fileobject):
|
||||
if buf_len >= size:
|
||||
buf.seek(0)
|
||||
rv = buf.read(size)
|
||||
self._rbuf = io.BytesIO()
|
||||
self._rbuf = StringIO.StringIO()
|
||||
self._rbuf.write(buf.read())
|
||||
return rv
|
||||
# reset _rbuf. we consume it via buf.
|
||||
self._rbuf = io.BytesIO()
|
||||
self._rbuf = StringIO.StringIO()
|
||||
while True:
|
||||
data = self.recv(self._rbufsize)
|
||||
if not data:
|
||||
@@ -1379,7 +1380,7 @@ class HTTPConnection(object):
|
||||
# Don't error if we're between requests; only error
|
||||
# if 1) no request has been started at all, or 2) we're
|
||||
# in the middle of a request.
|
||||
# See https://github.com/cherrypy/cherrypy/issues/853
|
||||
# See https://bitbucket.org/cherrypy/cherrypy/issue/853
|
||||
if (not request_seen) or (req and req.started_request):
|
||||
# Don't bother writing the 408 if the response
|
||||
# has already started being written.
|
||||
@@ -1410,10 +1411,7 @@ class HTTPConnection(object):
|
||||
self.wfile = CP_fileobject(
|
||||
self.socket._sock, "wb", self.wbufsize)
|
||||
if REDIRECT_URL:
|
||||
msg = '<!DOCTYPE html><html><head>' \
|
||||
'<meta http-equiv="refresh" content="0; url=%s">' \
|
||||
'</head><body></body></html>' % (REDIRECT_URL % self.remote_addr)
|
||||
req.simple_response("301 Moved Permanently", msg)
|
||||
req.simple_response("301 Moved Permanently", REDIRECT_URL % self.remote_addr)
|
||||
else:
|
||||
req.simple_response(
|
||||
"400 Bad Request",
|
||||
@@ -1678,7 +1676,7 @@ class ThreadPool(object):
|
||||
except (AssertionError,
|
||||
# Ignore repeated Ctrl-C.
|
||||
# See
|
||||
# https://github.com/cherrypy/cherrypy/issues/691.
|
||||
# https://bitbucket.org/cherrypy/cherrypy/issue/691.
|
||||
KeyboardInterrupt):
|
||||
pass
|
||||
|
||||
@@ -1778,7 +1776,7 @@ class HTTPServer(object):
|
||||
timeout = 10
|
||||
"""The timeout in seconds for accepted connections (default 10)."""
|
||||
|
||||
version = "CherryPy/" + cp_version
|
||||
version = "CherryPy/3.8.0"
|
||||
"""A version string for the HTTPServer."""
|
||||
|
||||
software = None
|
||||
@@ -1905,6 +1903,25 @@ class HTTPServer(object):
|
||||
if self.software is None:
|
||||
self.software = "%s Server" % self.version
|
||||
|
||||
# SSL backward compatibility
|
||||
if (self.ssl_adapter is None and
|
||||
getattr(self, 'ssl_certificate', None) and
|
||||
getattr(self, 'ssl_private_key', None)):
|
||||
warnings.warn(
|
||||
"SSL attributes are deprecated in CherryPy 3.2, and will "
|
||||
"be removed in CherryPy 3.3. Use an ssl_adapter attribute "
|
||||
"instead.",
|
||||
DeprecationWarning
|
||||
)
|
||||
try:
|
||||
from cherrypy.wsgiserver.ssl_pyopenssl import pyOpenSSLAdapter
|
||||
except ImportError:
|
||||
pass
|
||||
else:
|
||||
self.ssl_adapter = pyOpenSSLAdapter(
|
||||
self.ssl_certificate, self.ssl_private_key,
|
||||
getattr(self, 'ssl_certificate_chain', None))
|
||||
|
||||
# Select the appropriate socket
|
||||
if isinstance(self.bind_addr, basestring):
|
||||
# AF_UNIX socket
|
||||
@@ -1917,7 +1934,7 @@ class HTTPServer(object):
|
||||
|
||||
# So everyone can access the socket...
|
||||
try:
|
||||
os.chmod(self.bind_addr, 0o777)
|
||||
os.chmod(self.bind_addr, 511) # 0777
|
||||
except:
|
||||
pass
|
||||
|
||||
@@ -1986,7 +2003,7 @@ class HTTPServer(object):
|
||||
sys.stderr.write(msg + '\n')
|
||||
sys.stderr.flush()
|
||||
if traceback:
|
||||
tblines = traceback_.format_exc()
|
||||
tblines = format_exc()
|
||||
sys.stderr.write(tblines)
|
||||
sys.stderr.flush()
|
||||
|
||||
@@ -2003,7 +2020,7 @@ class HTTPServer(object):
|
||||
|
||||
# If listening on the IPV6 any address ('::' = IN6ADDR_ANY),
|
||||
# activate dual-stack. See
|
||||
# https://github.com/cherrypy/cherrypy/issues/871.
|
||||
# https://bitbucket.org/cherrypy/cherrypy/issue/871.
|
||||
if (hasattr(socket, 'AF_INET6') and family == socket.AF_INET6
|
||||
and self.bind_addr[0] in ('::', '::0', '::0.0.0.0')):
|
||||
try:
|
||||
@@ -2097,15 +2114,15 @@ class HTTPServer(object):
|
||||
# the call, and I *think* I'm reading it right that Python
|
||||
# will then go ahead and poll for and handle the signal
|
||||
# elsewhere. See
|
||||
# https://github.com/cherrypy/cherrypy/issues/707.
|
||||
# https://bitbucket.org/cherrypy/cherrypy/issue/707.
|
||||
return
|
||||
if x.args[0] in socket_errors_nonblocking:
|
||||
# Just try again. See
|
||||
# https://github.com/cherrypy/cherrypy/issues/479.
|
||||
# https://bitbucket.org/cherrypy/cherrypy/issue/479.
|
||||
return
|
||||
if x.args[0] in socket_errors_to_ignore:
|
||||
# Our socket was closed.
|
||||
# See https://github.com/cherrypy/cherrypy/issues/686.
|
||||
# See https://bitbucket.org/cherrypy/cherrypy/issue/686.
|
||||
return
|
||||
raise
|
||||
|
||||
@@ -2138,7 +2155,7 @@ class HTTPServer(object):
|
||||
if x.args[0] not in socket_errors_to_ignore:
|
||||
# Changed to use error code and not message
|
||||
# See
|
||||
# https://github.com/cherrypy/cherrypy/issues/860.
|
||||
# https://bitbucket.org/cherrypy/cherrypy/issue/860.
|
||||
raise
|
||||
else:
|
||||
# Note that we're explicitly NOT using AI_PASSIVE,
|
||||
@@ -2188,7 +2205,7 @@ ssl_adapters = {
|
||||
}
|
||||
|
||||
|
||||
def get_ssl_adapter_class(name='builtin'):
|
||||
def get_ssl_adapter_class(name='pyopenssl'):
|
||||
"""Return an SSL adapter class for the given name."""
|
||||
adapter = ssl_adapters[name.lower()]
|
||||
if isinstance(adapter, basestring):
|
||||
|
||||
@@ -75,8 +75,7 @@ __all__ = ['HTTPRequest', 'HTTPConnection', 'HTTPServer',
|
||||
'WorkerThread', 'ThreadPool', 'SSLAdapter',
|
||||
'CherryPyWSGIServer',
|
||||
'Gateway', 'WSGIGateway', 'WSGIGateway_10', 'WSGIGateway_u0',
|
||||
'WSGIPathInfoDispatcher', 'get_ssl_adapter_class',
|
||||
'socket_errors_to_ignore']
|
||||
'WSGIPathInfoDispatcher', 'get_ssl_adapter_class']
|
||||
|
||||
import os
|
||||
try:
|
||||
@@ -87,42 +86,23 @@ import re
|
||||
import email.utils
|
||||
import socket
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
import traceback as traceback_
|
||||
import errno
|
||||
import logging
|
||||
from urllib.parse import urlparse
|
||||
|
||||
try:
|
||||
# prefer slower Python-based io module
|
||||
import _pyio as io
|
||||
except ImportError:
|
||||
# Python 2.6
|
||||
import io
|
||||
|
||||
try:
|
||||
import pkg_resources
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
if 'win' in sys.platform and hasattr(socket, "AF_INET6"):
|
||||
if not hasattr(socket, 'IPPROTO_IPV6'):
|
||||
socket.IPPROTO_IPV6 = 41
|
||||
if not hasattr(socket, 'IPV6_V6ONLY'):
|
||||
socket.IPV6_V6ONLY = 27
|
||||
|
||||
|
||||
if sys.version_info < (3, 1):
|
||||
import io
|
||||
else:
|
||||
import _pyio as io
|
||||
DEFAULT_BUFFER_SIZE = io.DEFAULT_BUFFER_SIZE
|
||||
|
||||
|
||||
try:
|
||||
cp_version = pkg_resources.require('cherrypy')[0].version
|
||||
except Exception:
|
||||
cp_version = 'unknown'
|
||||
|
||||
import threading
|
||||
import time
|
||||
from traceback import format_exc
|
||||
|
||||
if sys.version_info >= (3, 0):
|
||||
bytestr = bytes
|
||||
unicodestr = str
|
||||
basestring = (bytes, str)
|
||||
|
||||
@@ -133,6 +113,7 @@ if sys.version_info >= (3, 0):
|
||||
# In Python 3, the native string type is unicode
|
||||
return n.encode(encoding)
|
||||
else:
|
||||
bytestr = str
|
||||
unicodestr = unicode
|
||||
basestring = basestring
|
||||
|
||||
@@ -158,6 +139,8 @@ ASTERISK = ntob('*')
|
||||
FORWARD_SLASH = ntob('/')
|
||||
quoted_slash = re.compile(ntob("(?i)%2F"))
|
||||
|
||||
import errno
|
||||
|
||||
|
||||
def plat_specific_errors(*errnames):
|
||||
"""Return error numbers for all errors in errnames on this platform.
|
||||
@@ -186,8 +169,6 @@ socket_errors_to_ignore = plat_specific_errors(
|
||||
)
|
||||
socket_errors_to_ignore.append("timed out")
|
||||
socket_errors_to_ignore.append("The read operation timed out")
|
||||
if sys.platform == 'darwin':
|
||||
socket_errors_to_ignore.append(plat_specific_errors("EPROTOTYPE"))
|
||||
|
||||
socket_errors_nonblocking = plat_specific_errors(
|
||||
'EAGAIN', 'EWOULDBLOCK', 'WSAEWOULDBLOCK')
|
||||
@@ -203,6 +184,7 @@ comma_separated_headers = [
|
||||
]
|
||||
|
||||
|
||||
import logging
|
||||
if not hasattr(logging, 'statistics'):
|
||||
logging.statistics = {}
|
||||
|
||||
@@ -294,7 +276,7 @@ class SizeCheckWrapper(object):
|
||||
self.bytes_read += len(data)
|
||||
self._check_length()
|
||||
res.append(data)
|
||||
# See https://github.com/cherrypy/cherrypy/issues/421
|
||||
# See https://bitbucket.org/cherrypy/cherrypy/issue/421
|
||||
if len(data) < 256 or data[-1:] == LF:
|
||||
return EMPTY.join(res)
|
||||
|
||||
@@ -668,10 +650,6 @@ class HTTPRequest(object):
|
||||
|
||||
# uri may be an abs_path (including "http://host.domain.tld");
|
||||
scheme, authority, path = self.parse_request_uri(uri)
|
||||
if path is None:
|
||||
self.simple_response("400 Bad Request",
|
||||
"Invalid path in Request-URI.")
|
||||
return False
|
||||
if NUMBER_SIGN in path:
|
||||
self.simple_response("400 Bad Request",
|
||||
"Illegal #fragment in Request-URI.")
|
||||
@@ -797,7 +775,7 @@ class HTTPRequest(object):
|
||||
if self.inheaders.get(b"Expect", b"") == b"100-continue":
|
||||
# Don't use simple_response here, because it emits headers
|
||||
# we don't want. See
|
||||
# https://github.com/cherrypy/cherrypy/issues/951
|
||||
# https://bitbucket.org/cherrypy/cherrypy/issue/951
|
||||
msg = self.server.protocol.encode(
|
||||
'ascii') + b" 100 Continue\r\n\r\n"
|
||||
try:
|
||||
@@ -831,13 +809,14 @@ class HTTPRequest(object):
|
||||
if uri == ASTERISK:
|
||||
return None, None, uri
|
||||
|
||||
scheme, authority, path, params, query, fragment = urlparse(uri)
|
||||
if scheme and QUESTION_MARK not in scheme:
|
||||
scheme, sep, remainder = uri.partition(b'://')
|
||||
if sep and QUESTION_MARK not in scheme:
|
||||
# An absoluteURI.
|
||||
# If there's a scheme (and it must be http or https), then:
|
||||
# http_URL = "http:" "//" host [ ":" port ] [ abs_path [ "?" query
|
||||
# ]]
|
||||
return scheme, authority, path
|
||||
authority, path_a, path_b = remainder.partition(FORWARD_SLASH)
|
||||
return scheme.lower(), authority, path_a + path_b
|
||||
|
||||
if uri.startswith(FORWARD_SLASH):
|
||||
# An abs_path.
|
||||
@@ -1100,7 +1079,7 @@ class HTTPConnection(object):
|
||||
# Don't error if we're between requests; only error
|
||||
# if 1) no request has been started at all, or 2) we're
|
||||
# in the middle of a request.
|
||||
# See https://github.com/cherrypy/cherrypy/issues/853
|
||||
# See https://bitbucket.org/cherrypy/cherrypy/issue/853
|
||||
if (not request_seen) or (req and req.started_request):
|
||||
# Don't bother writing the 408 if the response
|
||||
# has already started being written.
|
||||
@@ -1389,7 +1368,7 @@ class ThreadPool(object):
|
||||
except (AssertionError,
|
||||
# Ignore repeated Ctrl-C.
|
||||
# See
|
||||
# https://github.com/cherrypy/cherrypy/issues/691.
|
||||
# https://bitbucket.org/cherrypy/cherrypy/issue/691.
|
||||
KeyboardInterrupt):
|
||||
pass
|
||||
|
||||
@@ -1489,7 +1468,7 @@ class HTTPServer(object):
|
||||
timeout = 10
|
||||
"""The timeout in seconds for accepted connections (default 10)."""
|
||||
|
||||
version = "CherryPy/" + cp_version
|
||||
version = "CherryPy/3.8.0"
|
||||
"""A version string for the HTTPServer."""
|
||||
|
||||
software = None
|
||||
@@ -1629,7 +1608,7 @@ class HTTPServer(object):
|
||||
|
||||
# So everyone can access the socket...
|
||||
try:
|
||||
os.chmod(self.bind_addr, 0o777)
|
||||
os.chmod(self.bind_addr, 511) # 0777
|
||||
except:
|
||||
pass
|
||||
|
||||
@@ -1697,7 +1676,7 @@ class HTTPServer(object):
|
||||
sys.stderr.write(msg + '\n')
|
||||
sys.stderr.flush()
|
||||
if traceback:
|
||||
tblines = traceback_.format_exc()
|
||||
tblines = format_exc()
|
||||
sys.stderr.write(tblines)
|
||||
sys.stderr.flush()
|
||||
|
||||
@@ -1714,7 +1693,7 @@ class HTTPServer(object):
|
||||
|
||||
# If listening on the IPV6 any address ('::' = IN6ADDR_ANY),
|
||||
# activate dual-stack. See
|
||||
# https://github.com/cherrypy/cherrypy/issues/871.
|
||||
# https://bitbucket.org/cherrypy/cherrypy/issue/871.
|
||||
if (hasattr(socket, 'AF_INET6') and family == socket.AF_INET6
|
||||
and self.bind_addr[0] in ('::', '::0', '::0.0.0.0')):
|
||||
try:
|
||||
@@ -1808,15 +1787,15 @@ class HTTPServer(object):
|
||||
# the call, and I *think* I'm reading it right that Python
|
||||
# will then go ahead and poll for and handle the signal
|
||||
# elsewhere. See
|
||||
# https://github.com/cherrypy/cherrypy/issues/707.
|
||||
# https://bitbucket.org/cherrypy/cherrypy/issue/707.
|
||||
return
|
||||
if x.args[0] in socket_errors_nonblocking:
|
||||
# Just try again. See
|
||||
# https://github.com/cherrypy/cherrypy/issues/479.
|
||||
# https://bitbucket.org/cherrypy/cherrypy/issue/479.
|
||||
return
|
||||
if x.args[0] in socket_errors_to_ignore:
|
||||
# Our socket was closed.
|
||||
# See https://github.com/cherrypy/cherrypy/issues/686.
|
||||
# See https://bitbucket.org/cherrypy/cherrypy/issue/686.
|
||||
return
|
||||
raise
|
||||
|
||||
@@ -1849,7 +1828,7 @@ class HTTPServer(object):
|
||||
if x.args[0] not in socket_errors_to_ignore:
|
||||
# Changed to use error code and not message
|
||||
# See
|
||||
# https://github.com/cherrypy/cherrypy/issues/860.
|
||||
# https://bitbucket.org/cherrypy/cherrypy/issue/860.
|
||||
raise
|
||||
else:
|
||||
# Note that we're explicitly NOT using AI_PASSIVE,
|
||||
@@ -1895,7 +1874,6 @@ class Gateway(object):
|
||||
# of such classes (in which case they will be lazily loaded).
|
||||
ssl_adapters = {
|
||||
'builtin': 'cherrypy.wsgiserver.ssl_builtin.BuiltinSSLAdapter',
|
||||
'pyopenssl': 'cherrypy.wsgiserver.ssl_pyopenssl.pyOpenSSLAdapter',
|
||||
}
|
||||
|
||||
|
||||
|
||||
BIN
icons/nzb.ico
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 38 KiB |
BIN
icons/sabnzbd16.ico
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 5.3 KiB |
|
Before Width: | Height: | Size: 5.3 KiB |
|
Before Width: | Height: | Size: 5.3 KiB |
BIN
icons/sabnzbd16green.ico
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
icons/sabnzbd16paused.ico
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
@@ -2,7 +2,7 @@
|
||||
uniConfig for SABnzbd 0.7.x
|
||||
zoggy@sabnzbd.org
|
||||
|
||||
Changed by Safihre for 1.0.x
|
||||
Changed by Safihre for 0.8.x
|
||||
|
||||
========================================================
|
||||
LIBRARIES USED
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal">×</button>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title"></h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
|
||||
@@ -28,12 +28,11 @@
|
||||
<link rel="apple-touch-icon" sizes="76x76" href="${root}staticcfg/ico/apple-touch-icon-76x76-precomposed.png" />
|
||||
<link rel="apple-touch-icon" sizes="120x120" href="${root}staticcfg/ico/apple-touch-icon-120x120-precomposed.png" />
|
||||
<link rel="apple-touch-icon" sizes="152x152" href="${root}staticcfg/ico/apple-touch-icon-152x152-precomposed.png" />
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="${root}staticcfg/ico/apple-touch-icon-180x180-precomposed.png" />
|
||||
<link rel="apple-touch-icon" sizes="192x192" href="${root}staticcfg/ico/android-192x192.png" />
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="${root}staticcfg/ico/apple-touch-icon-180x180-precomposed.png" />
|
||||
|
||||
<link rel="stylesheet" type="text/css" href="${root}staticcfg/bootstrap/css/bootstrap.min.css" />
|
||||
<link rel="stylesheet" type="text/css" href="${root}staticcfg/css/style.css?p=$pid" />
|
||||
<link rel="shortcut icon" href="${root}staticcfg/ico/favicon.ico?v=1.1.0" />
|
||||
<link rel="shortcut icon" href="${root}staticcfg/ico/favicon.ico" />
|
||||
|
||||
<script type="text/javascript">
|
||||
// Keeping track of the form state
|
||||
@@ -53,7 +52,7 @@
|
||||
configTranslate.failed = "$T('smpl-failed')";
|
||||
configTranslate.explainRestart = "$T('explain-Restart')";
|
||||
configTranslate.wizzardRestart = "$T('wizard-restarting')";
|
||||
configTranslate.needRestart = "$T('restartRequired') $T('explain-needNewLogin')".replace(/\<br(\s*\/|)\>/g, '\n');
|
||||
configTranslate.needRestart = "$T('restartRequired')";
|
||||
configTranslate.buttonRestart = "$T('button-restart')";
|
||||
configTranslate.confirmLeave = "$T('confirmWithoutSavingPrompt')";
|
||||
</script>
|
||||
@@ -96,10 +95,12 @@
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
</button>
|
||||
|
||||
<a class="navbar-logo navbar-logo-small" href="${root}" title="$T('Home')">
|
||||
#include $webdir + "/staticcfg/images/logo-small.svg"#
|
||||
</a>
|
||||
<!--#if $active_lang in ('pl', 'es', 'ru', 'sr', 'fi') #-->
|
||||
<a class="navbar-brand navbar-brand-small" href="${root}" title="$T('Home')"><img src="${root}staticcfg/images/logo-small.png" width="47" height="45" id="logo" alt="$T('Home')" /></a>
|
||||
<a class="navbar-brand navbar-brand-mobile" href="${root}" title="$T('Home')"><img src="${root}staticcfg/images/logo.png" width="120" height="45" id="logo" alt="$T('Home')" /></a>
|
||||
<!--#else#-->
|
||||
<a class="navbar-brand" href="${root}" title="$T('Home')"><img src="${root}staticcfg/images/logo.png" width="120" height="45" id="logo" alt="$T('Home')" /></a>
|
||||
<!--#end if#-->
|
||||
</div>
|
||||
<div id="navbar" class="navbar-collapse collapse">
|
||||
<ul class="nav navbar-nav">
|
||||
|
||||
@@ -1,134 +1,117 @@
|
||||
<!--#set global $pane="Config"#-->
|
||||
<!--#set global $help_uri="configuration/1.1/configure"#-->
|
||||
<!--#set global $help_uri="configure-1-0"#-->
|
||||
<!--#include $webdir + "/_inc_header_uc.tmpl"#-->
|
||||
|
||||
<!--#from sabnzbd.newswrapper import HAVE_SSL#-->
|
||||
<!--#from locale import getpreferredencoding#-->
|
||||
<!--#import sabnzbd.utils.sslinfo#-->
|
||||
<!--#from sabnzbd.decoder import HAVE_YENC#-->
|
||||
<div class="colmask">
|
||||
<div class="section padTable">
|
||||
<table class="table table-striped">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th scope="row">$T('version'): </th>
|
||||
<td>$version [$build]</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">$T('uptime'): </th>
|
||||
<td>$uptime</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">$T('confgFile'): </th>
|
||||
<td>$configfn</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">$T('cache').capitalize(): </th>
|
||||
<td><!--#set $msg=$T('ft-buffer@2')%($cache_art, $cache_size)#-->$msg</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">$T('parameters'): </th>
|
||||
<td>$cmdline</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">$T('pythonVersion'): </th>
|
||||
<td>$sys.version[:120] [$getpreferredencoding()]</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">OpenSSL:</th>
|
||||
<td>
|
||||
<!--#if HAVE_SSL#-->
|
||||
<!--#set $sslversions = ', '.join(sabnzbd.utils.sslinfo.ssl_protocols_labels())#-->
|
||||
$sabnzbd.utils.sslinfo.ssl_version() [$sslversions]
|
||||
<!--#else#-->
|
||||
<span class="label label-warning">$T('notAvailable')</span>
|
||||
<a href="$helpuri$help_uri#no_ssl" target="_blank"><span class="glyphicon glyphicon-question-sign"></span></a>
|
||||
<!--#end if#-->
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">pyOpenSSL:</th>
|
||||
<td>
|
||||
<!--#if HAVE_SSL#-->
|
||||
$sabnzbd.utils.sslinfo.pyopenssl_version()
|
||||
<!--#else#-->
|
||||
<span class="label label-warning">$T('notAvailable')</span>
|
||||
<a href="$helpuri$help_uri#no_ssl" target="_blank"><span class="glyphicon glyphicon-question-sign"></span></a>
|
||||
<!--#end if#-->
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">yEnc:</th>
|
||||
<td>
|
||||
<!--#if HAVE_YENC#-->
|
||||
<span class="glyphicon glyphicon-ok"></span>
|
||||
<!--#else#-->
|
||||
<span class="label label-warning">$T('notAvailable')</span>
|
||||
<a href="$helpuri$help_uri#no_yenc" target="_blank"><span class="glyphicon glyphicon-question-sign"></span></a>
|
||||
<!--#end if#-->
|
||||
</td>
|
||||
</tr>
|
||||
<!--#if not $have_unzip #-->
|
||||
<tr>
|
||||
<th scope="row">$T('opt-enable_unzip'):</th>
|
||||
<td>
|
||||
<span class="label label-warning">$T('notAvailable')</span>
|
||||
<a href="${helpuri}installation/install-off-modules#toc8" target="_blank"><span class="glyphicon glyphicon-question-sign"></span></a>
|
||||
</td>
|
||||
</tr>
|
||||
<!--#end if#-->
|
||||
<!--#if not $have_7zip #-->
|
||||
<tr>
|
||||
<th scope="row">$T('opt-enable_7zip'):</th>
|
||||
<td>
|
||||
<span class="label label-warning">$T('notAvailable')</span>
|
||||
<a href="${helpuri}installation/install-off-modules#toc8" target="_blank"><span class="glyphicon glyphicon-question-sign"></span></a>
|
||||
</td>
|
||||
</tr>
|
||||
<!--#end if#-->
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="colmask">
|
||||
<div class="section padTable">
|
||||
<table class="table table-striped">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th scope="row">$T('homePage') </th>
|
||||
<td><a href="http://sabnzbd.org/" target="_blank">http://sabnzbd.org/</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">$T('menu-wiki') </th>
|
||||
<td><a href="https://sabnzbd.org/wiki/" target="_blank">https://sabnzbd.org/wiki/</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">$T('menu-forums') </th>
|
||||
<td><a href="http://forums.sabnzbd.org/" target="_blank">http://forums.sabnzbd.org/</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">$T('source') </th>
|
||||
<td><a href="https://github.com/sabnzbd/sabnzbd" target="_blank">https://github.com/sabnzbd/sabnzbd</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">$T('menu-irc') </th>
|
||||
<td><a href="irc://irc.synirc.net/#sabnzbd"><i>#sabnzbd</i> on <i>irc.synirc.net</i></a> $T('or') (<a href="http://sabnzbd.org/live-chat/" target="_blank">webchat</a>)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">$T('menu-issues') </th>
|
||||
<td><a href="https://sabnzbd.org/wiki/introduction/known-issues" target="_blank">https://sabnzbd.org/wiki/introduction/known-issues</a></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<!--#from sabnzbd.newsunpack import PAR2_COMMAND, PAR2C_COMMAND, RAR_COMMAND, ZIP_COMMAND, SEVEN_COMMAND, NICE_COMMAND, IONICE_COMMAND#-->
|
||||
|
||||
<div class="colmask">
|
||||
<div class="padding alt">
|
||||
<h5 class="copyright">Copyright © 2008-2016 The SABnzbd Team <<a href="mailto:team@sabnzbd.org">team@sabnzbd.org</a>></h5>
|
||||
<p class="copyright"><small>$T('yourRights')</small></p>
|
||||
<div class="colmask">
|
||||
<div class="section padTable">
|
||||
<table class="table table-striped">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th scope="row">$T('version'): </th>
|
||||
<td>$version [$build]</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">$T('uptime'): </th>
|
||||
<td>$uptime</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">$T('confgFile'): </th>
|
||||
<td>$configfn</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">$T('cache').capitalize(): </th>
|
||||
<td><!--#set $msg=$T('ft-buffer@2')%($cache_art, $cache_size)#-->$msg</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">$T('parameters'): </th>
|
||||
<td>$cmdline</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">$T('pythonVersion'): </th>
|
||||
<td>$sys.version[:120]</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">OpenSSL:</th>
|
||||
<td>
|
||||
<!--#if HAVE_SSL#-->
|
||||
<!--#set $sslversions = ', '.join(sabnzbd.utils.sslinfo.ssl_protocols())#-->
|
||||
$sabnzbd.utils.sslinfo.ssl_version() <em>[$sslversions]</em>
|
||||
<!--#else#-->
|
||||
<span class="label label-warning">$T('notAvailable')</span>
|
||||
<a href="$helpuri$help_uri#no_ssl" target="_blank"><span class="glyphicon glyphicon-question-sign"></span></a>
|
||||
<!--#end if#-->
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">pyOpenSSL:</th>
|
||||
<td>
|
||||
<!--#if HAVE_SSL#-->
|
||||
$sabnzbd.utils.sslinfo.pyopenssl_version()
|
||||
<!--#else#-->
|
||||
<span class="label label-warning">$T('notAvailable')</span>
|
||||
<a href="$helpuri$help_uri#no_ssl" target="_blank"><span class="glyphicon glyphicon-question-sign"></span></a>
|
||||
<!--#end if#-->
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">yEnc:</th>
|
||||
<td>
|
||||
<!--#if HAVE_YENC#-->
|
||||
<span class="glyphicon glyphicon-ok"></span>
|
||||
<!--#else#-->
|
||||
<span class="label label-warning">$T('notAvailable')</span>
|
||||
<a href="$helpuri$help_uri#no_yenc" target="_blank"><span class="glyphicon glyphicon-question-sign"></span></a>
|
||||
<!--#end if#-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="colmask">
|
||||
<div class="section padTable">
|
||||
<table class="table table-striped">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th scope="row">$T('homePage') </th>
|
||||
<td><a href="http://sabnzbd.org/" target="_blank">http://sabnzbd.org/</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">$T('menu-wiki') </th>
|
||||
<td><a href="http://wiki.sabnzbd.org/faq" target="_blank">http://wiki.sabnzbd.org/faq</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">$T('menu-forums') </th>
|
||||
<td><a href="http://forums.sabnzbd.org/" target="_blank">http://forums.sabnzbd.org/</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">$T('source') </th>
|
||||
<td><a href="https://github.com/sabnzbd/sabnzbd" target="_blank">https://github.com/sabnzbd/sabnzbd</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">$T('menu-irc') </th>
|
||||
<td><a href="irc://irc.synirc.net/#sabnzbd"><i>#sabnzbd</i> on <i>irc.synirc.net</i></a> $T('or') (<a href="http://sabnzbd.org/live-chat/" target="_blank">webchat</a>)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">$T('menu-issues') </th>
|
||||
<td><a href="http://wiki.sabnzbd.org/issues-1-0-0" target="_blank">http://wiki.sabnzbd.org/issues-1-0-0</a></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="colmask">
|
||||
<div class="padding alt">
|
||||
<h5 class="copyright">Copyright © 2008-2016 The SABnzbd Team <<a href="mailto:team@sabnzbd.org">team@sabnzbd.org</a>></h5>
|
||||
<p class="copyright"><small>$T('yourRights')</small></p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!--#include $webdir + "/_inc_footer_uc.tmpl"#-->
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!--#set global $pane="Categories"#-->
|
||||
<!--#set global $help_uri="configuration/1.1/categories"#-->
|
||||
<!--#set global $help_uri="configure-categories-1-0"#-->
|
||||
<!--#include $webdir + "/_inc_header_uc.tmpl"#-->
|
||||
<div class="colmask">
|
||||
<div class="section">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!--#set global $pane="Folders"#-->
|
||||
<!--#set global $help_uri="configuration/1.1/folders"#-->
|
||||
<!--#set global $help_uri="configure-folders-1-0"#-->
|
||||
<!--#include $webdir + "/_inc_header_uc.tmpl"#-->
|
||||
|
||||
<div class="colmask">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!--#set global $pane="General"#-->
|
||||
<!--#set global $help_uri="configuration/1.1/general"#-->
|
||||
<!--#set global $help_uri="configure-general-1-0"#-->
|
||||
<!--#include $webdir + "/_inc_header_uc.tmpl"#-->
|
||||
|
||||
<div class="colmask">
|
||||
@@ -20,25 +20,35 @@
|
||||
</div>
|
||||
<div class="field-pair">
|
||||
<label class="config" for="port">$T('opt-port')</label>
|
||||
<input type="number" name="port" id="port" value="$port" size="8" data-original="$port" />
|
||||
<input type="number" name="port" id="port" value="$port" size="8" />
|
||||
<span class="desc">$T('explain-port')</span>
|
||||
</div>
|
||||
<div class="field-pair">
|
||||
<label class="config" for="username">$T('opt-web_username')</label>
|
||||
<input type="text" name="username" id="username" value="$username" />
|
||||
<span class="desc">$T('explain-web_username')</span>
|
||||
</div>
|
||||
<div class="field-pair">
|
||||
<label class="config" for="password">$T('opt-web_password')</label>
|
||||
<input type="text" name="password" id="password" value="$password" />
|
||||
<span class="desc">$T('explain-web_password')</span>
|
||||
</div>
|
||||
<div class="field-pair">
|
||||
<label class="config" for="web_dir">$T('opt-web_dir')</label>
|
||||
<select name="web_dir" id="web_dir">
|
||||
<!--#for $webline in $web_list#-->
|
||||
<!--#if $webline.lower() == $web_dir.lower()#-->
|
||||
<option value="$webline" selected="selected">$webline</option>
|
||||
<!--#else#-->
|
||||
<option value="$webline">$webline</option>
|
||||
<!--#end if#-->
|
||||
<!--#end for#-->
|
||||
<select name="web_dir" id="web_dir">
|
||||
<!--#for $webline in $web_list#-->
|
||||
<!--#if $webline.lower() == $web_dir.lower()#-->
|
||||
<option value="$webline" selected="selected">$webline</option>
|
||||
<!--#else#-->
|
||||
<option value="$webline">$webline</option>
|
||||
<!--#end if#-->
|
||||
<!--#end for#-->
|
||||
</select>
|
||||
<span class="desc">$T('explain-web_dir') <a href="$caller_url1">$caller_url1</a></span>
|
||||
</div>
|
||||
<div class="field-pair">
|
||||
<label class="config" for="web_dir2">$T('opt-web_dir2')</label>
|
||||
<select name="web_dir2" id="web_dir2">
|
||||
<select name="web_dir2" id="web_dir2">
|
||||
<option value="None" selected="selected">$T("None")</option>
|
||||
<!--#for $webline in $web_list#-->
|
||||
<!--#if $webline.lower() == $web_dir2.lower()#-->
|
||||
@@ -52,31 +62,22 @@
|
||||
</div>
|
||||
<div class="field-pair">
|
||||
<label class="config" for="language">$T('opt-language')</label>
|
||||
<select name="language" id="language" class="select">
|
||||
<!--#for $webline in $lang_list#-->
|
||||
<!--#if $webline[0].lower() == $language.lower()#-->
|
||||
<option value="$webline[0]" selected="selected">$webline[1]</option>
|
||||
<!--#else#-->
|
||||
<option value="$webline[0]">$webline[1]</option>
|
||||
<!--#end if#-->
|
||||
<!--#end for#-->
|
||||
</select>
|
||||
<select name="language" id="language" class="select">
|
||||
<!--#for $webline in $lang_list#-->
|
||||
<!--#if $webline[0].lower() == $language.lower()#-->
|
||||
<option value="$webline[0]" selected="selected">$webline[1]</option>
|
||||
<!--#else#-->
|
||||
<option value="$webline[0]">$webline[1]</option>
|
||||
<!--#end if#-->
|
||||
<!--#end for#-->
|
||||
</select>
|
||||
<span class="desc">$T('explain-language')</span>
|
||||
<div class="alert alert-info alert-translate">
|
||||
$T('explain-ask-language') <a href="https://sabnzbd.org/wiki/translate" target="_blank" class="alert-link">https://sabnzbd.org/wiki/translate</a>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Tricks to avoid browser auto-fill, fixed on-submit with javascript -->
|
||||
<div class="field-pair">
|
||||
<label class="config" for="${pid}_wu">$T('opt-web_username')</label>
|
||||
<input type="text" name="${pid}_wu" id="${pid}_wu" value="$username" data-hide="username" />
|
||||
<span class="desc">$T('explain-web_username')</span>
|
||||
</div>
|
||||
<div class="field-pair">
|
||||
<label class="config" for="${pid}_wp">$T('opt-web_password')</label>
|
||||
<input type="text" name="${pid}_wp" id="${pid}_wp" value="$password" data-hide="password" />
|
||||
<span class="desc">$T('explain-web_password')</span>
|
||||
</div>
|
||||
<label class="config" for="local_ranges">$T('opt-local_ranges')</label>
|
||||
<input type="text" name="local_ranges" id="local_ranges" value="$local_ranges" />
|
||||
<span class="desc">$T('explain-local_ranges')</span>
|
||||
</div>
|
||||
<div class="field-pair">
|
||||
<label class="config" for="inet_exposure">$T('opt-inet_exposure')</label>
|
||||
<select name="inet_exposure" id="inet_exposure" class="select">
|
||||
@@ -85,15 +86,9 @@
|
||||
<option value="2" <!--#if $inet_exposure == 2 then 'selected="selected"' else ""#-->>$T('inet-api')</option>
|
||||
<option value="3" <!--#if $inet_exposure == 3 then 'selected="selected"' else ""#-->>$T('inet-fullapi')</option>
|
||||
<option value="4" <!--#if $inet_exposure == 4 then 'selected="selected"' else ""#-->>$T('inet-ui')</option>
|
||||
<option value="5" <!--#if $inet_exposure == 5 then 'selected="selected"' else ""#-->>$T('inet-ui') - $T('inet-external_login')</option>
|
||||
</select>
|
||||
<span class="desc">$T('explain-inet_exposure')</span>
|
||||
</div>
|
||||
<div class="field-pair">
|
||||
<label class="config" for="local_ranges">$T('opt-local_ranges')</label>
|
||||
<input type="text" name="local_ranges" id="local_ranges" value="$local_ranges" />
|
||||
<span class="desc">$T('explain-local_ranges')</span>
|
||||
</div>
|
||||
<div class="field-pair">
|
||||
<label class="config" for="disable_api_key">$T('opt-disableApikey')</label>
|
||||
<input type="checkbox" name="disable_api_key" id="disable_api_key" value="1" <!--#if int($disable_api_key) > 0 then 'checked="checked"' else ""#--> />
|
||||
@@ -101,16 +96,16 @@
|
||||
</div>
|
||||
<div class="field-pair">
|
||||
<label class="config" for="apikey">$T('opt-apikey')</label>
|
||||
<input type="text" id="apikey" class="fileBrowserField" value="$session" readonly />
|
||||
<input type="text" id="apikey" class="fileBrowserField" value="$session" />
|
||||
<button class="btn btn-default show_qrcode" title="$T('explain-qr-code')" rel="$session" ><span class="glyphicon glyphicon-qrcode"></span></button>
|
||||
<button class="btn btn-default generate_key" id="generate_new_apikey" title="$T('button-apikey')"><span class="glyphicon glyphicon-repeat"></span></button>
|
||||
<button class="btn btn-default" id="generate_new_apikey" title="$T('button-apikey')"><span class="glyphicon glyphicon-repeat"></span></button>
|
||||
<span class="desc">$T('explain-apikey')</span>
|
||||
</div>
|
||||
<div class="field-pair">
|
||||
<label class="config" for="nzbkey">$T('opt-nzbkey')</label>
|
||||
<input type="text" id="nzbkey" class="fileBrowserField" value="$nzb_key" readonly />
|
||||
<input type="text" id="nzbkey" class="fileBrowserField" value="$nzb_key" />
|
||||
<button class="btn btn-default show_qrcode" title="$T('explain-qr-code')" rel="$nzb_key" ><span class="glyphicon glyphicon-qrcode"></span></button>
|
||||
<button class="btn btn-default generate_key" id="generate_new_nzbkey" title="$T('button-apikey')"><span class="glyphicon glyphicon-repeat"></span></button>
|
||||
<button class="btn btn-default" id="generate_new_nzbkey" title="$T('button-apikey')"><span class="glyphicon glyphicon-repeat"></span></button>
|
||||
<span class="desc">$T('explain-nzbkey')</span>
|
||||
</div>
|
||||
<div class="field-pair">
|
||||
@@ -137,7 +132,7 @@
|
||||
</div>
|
||||
<div class="field-pair">
|
||||
<label class="config" for="https_port">$T('opt-https_port')</label>
|
||||
<input type="number" name="https_port" id="https_port" value="$https_port" size="8" data-original="$https_port" />
|
||||
<input type="number" name="https_port" id="https_port" value="$https_port" size="8" />
|
||||
<span class="desc">$T('explain-https_port')</span>
|
||||
</div>
|
||||
<div class="field-pair">
|
||||
@@ -181,7 +176,7 @@
|
||||
<div class="field-pair">
|
||||
<label class="config" for="cache_limit">$T('opt-cache_limitstr')</label>
|
||||
<input type="text" name="cache_limit" id="cache_limit" value="$cache_limit" class="smaller_input" />
|
||||
<span class="desc">$T('explain-cache_limitstr').replace("64M", "256M").replace("128M", "512M")</span>
|
||||
<span class="desc">$T('explain-cache_limitstr')</span>
|
||||
</div>
|
||||
<div class="field-pair">
|
||||
<button class="btn btn-default saveButton"><span class="glyphicon glyphicon-ok"></span> $T('button-saveChanges')</button>
|
||||
@@ -204,19 +199,7 @@
|
||||
|
||||
<script type="text/javascript">
|
||||
\$(document).ready(function(){
|
||||
// Show the message about translating when it's non-English
|
||||
function hideOrShowTranslate() {
|
||||
if(\$('#language').val() == 'en') {
|
||||
\$('.alert-translate').hide()
|
||||
} else {
|
||||
\$('.alert-translate').show()
|
||||
}
|
||||
}
|
||||
\$('#language').on('change', hideOrShowTranslate)
|
||||
hideOrShowTranslate()
|
||||
|
||||
\$('#apikey, #nzbkey').click(function () { \$(this).select() });
|
||||
|
||||
\$('#generate_new_apikey').click(function () {
|
||||
if (confirm("$T('Plush-confirm')")) {
|
||||
$.ajax({
|
||||
@@ -235,7 +218,7 @@
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: "../../tapi",
|
||||
data: { mode:'config', name:'set_nzbkey', apikey: \$('#apikey').val() },
|
||||
data: {mode:'config', name:'set_nzbkey', apikey: \$('#apikey').val()},
|
||||
success: function(msg){
|
||||
\$('#nzbkey').val(msg);
|
||||
document.location = document.location;
|
||||
@@ -245,13 +228,17 @@
|
||||
});
|
||||
|
||||
\$('.show_qrcode').click(function (e) {
|
||||
// Make QR code
|
||||
var qrcode = \$('<img />', {
|
||||
src: 'https://chart.googleapis.com/chart?chs=300x300&cht=qr&chl=' + \$(this).attr('rel'),
|
||||
alt: 'loading...',
|
||||
width: 300,
|
||||
height: 300
|
||||
});
|
||||
|
||||
// Show in modal
|
||||
\$('#modal_qr .modal-dialog').width(330)
|
||||
\$('#modal_qr .modal-body').html('').qrcode({
|
||||
"size": 280,
|
||||
"color": "#3a3",
|
||||
"text": \$(this).attr('rel')
|
||||
});
|
||||
\$('#modal_qr .modal-body').html(qrcode)
|
||||
\$('#modal_qr').modal('show');
|
||||
|
||||
// No save on this button click
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!--#set global $pane="Email"#-->
|
||||
<!--#set global $help_uri="configuration/1.1/notifications"#-->
|
||||
<!--#set global $help_uri="configure-notifications-1-0"#-->
|
||||
<!--#include $webdir + "/_inc_header_uc.tmpl"#-->
|
||||
|
||||
<div class="colmask">
|
||||
@@ -63,9 +63,8 @@
|
||||
<div class="field-pair">
|
||||
<button class="btn btn-default saveButton"><span class="glyphicon glyphicon-ok"></span> $T('button-saveChanges')</button>
|
||||
<button class="btn btn-default" type="button" id="test_email" rel="$T('askTestEmail')"><span class="glyphicon glyphicon-envelope"></span> $T('link-testEmail')</button>
|
||||
</div>
|
||||
<div class="field-pair result-box">
|
||||
<div class="alert"></div>
|
||||
<span id="testmail-result" class="icon path darkred"> </span>
|
||||
<span id="config_err_msg" class="icon path darkred"> </span>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div><!-- /col1 -->
|
||||
@@ -111,10 +110,6 @@
|
||||
<label class="config wide" for="ncenter_prio_disk_full">$T($notify_texts['disk_full'])</label>
|
||||
<input type="checkbox" name="ncenter_prio_disk_full" id="ncenter_prio_disk_full" value="1" <!--#if int($ncenter_prio_disk_full) > 0 then 'checked="checked"' else ""#--> />
|
||||
</div>
|
||||
<div class="field-pair">
|
||||
<label class="config wide" for="ncenter_prio_new_login">$T($notify_texts['new_login'])</label>
|
||||
<input type="checkbox" name="ncenter_prio_new_login" id="ncenter_prio_new_login" value="1" <!--#if int($ncenter_prio_new_login) > 0 then 'checked="checked"' else ""#--> />
|
||||
</div>
|
||||
<div class="field-pair">
|
||||
<label class="config wide" for="ncenter_prio_warning">$T($notify_texts['warning'])</label>
|
||||
<input type="checkbox" name="ncenter_prio_warning" id="ncenter_prio_warning" value="1" <!--#if int($ncenter_prio_warning) > 0 then 'checked="checked"' else ""#--> />
|
||||
@@ -128,12 +123,10 @@
|
||||
<input type="checkbox" name="ncenter_prio_other" id="ncenter_prio_other" value="1" <!--#if int($ncenter_prio_other) > 0 then 'checked="checked"' else ""#--> />
|
||||
</div>
|
||||
<div class="field-pair">
|
||||
<button class="btn btn-default saveButton"><span class="glyphicon glyphicon-ok"></span> $T('button-saveChanges')</button>
|
||||
<button class="btn btn-default" type="button" id="test_notif"><span class="glyphicon glyphicon-comment"></span> $T('testNotify')</button>
|
||||
<button class="btn btn-default saveButton"><span class="glyphicon glyphicon-ok"></span> $T('button-saveChanges')</button>
|
||||
<button class="btn btn-default" type="button" id="test_notification"><span class="glyphicon glyphicon-comment"></span> $T('testNotify')</button>
|
||||
<span id="testnotice-result" class="icon path darkred"> </span>
|
||||
</div>
|
||||
<div class="field-pair result-box">
|
||||
<div class="alert"></div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div><!-- /col1 -->
|
||||
</div><!-- /section -->
|
||||
@@ -179,10 +172,6 @@
|
||||
<label class="config wide" for="acenter_prio_disk_full">$T($notify_texts['disk_full'])</label>
|
||||
<input type="checkbox" name="acenter_prio_disk_full" id="acenter_prio_disk_full" value="1" <!--#if int($acenter_prio_disk_full) > 0 then 'checked="checked"' else ""#--> />
|
||||
</div>
|
||||
<div class="field-pair">
|
||||
<label class="config wide" for="acenter_prio_new_login">$T($notify_texts['new_login'])</label>
|
||||
<input type="checkbox" name="acenter_prio_new_login" id="acenter_prio_new_login" value="1" <!--#if int($acenter_prio_new_login) > 0 then 'checked="checked"' else ""#--> />
|
||||
</div>
|
||||
<div class="field-pair">
|
||||
<label class="config wide" for="acenter_prio_warning">$T($notify_texts['warning'])</label>
|
||||
<input type="checkbox" name="acenter_prio_warning" id="acenter_prio_warning" value="1" <!--#if int($acenter_prio_warning) > 0 then 'checked="checked"' else ""#--> />
|
||||
@@ -196,12 +185,10 @@
|
||||
<input type="checkbox" name="acenter_prio_other" id="acenter_prio_other" value="1" <!--#if int($acenter_prio_other) > 0 then 'checked="checked"' else ""#--> />
|
||||
</div>
|
||||
<div class="field-pair">
|
||||
<button class="btn btn-default saveButton"><span class="glyphicon glyphicon-ok"></span> $T('button-saveChanges')</button>
|
||||
<button class="btn btn-default" type="button" id="test_windows"><span class="glyphicon glyphicon-comment"></span> $T('testNotify')</button>
|
||||
<button class="btn btn-default saveButton"><span class="glyphicon glyphicon-ok"></span> $T('button-saveChanges')</button>
|
||||
<button class="btn btn-default" type="button" id="test_windows_notification"><span class="glyphicon glyphicon-comment"></span> $T('testNotify')</button>
|
||||
<span id="testnotice-result" class="icon path darkred"> </span>
|
||||
</div>
|
||||
<div class="field-pair result-box">
|
||||
<div class="alert"></div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div><!-- /col1 -->
|
||||
</div><!-- /section -->
|
||||
@@ -247,10 +234,6 @@
|
||||
<label class="config wide" for="ntfosd_prio_disk_full">$T($notify_texts['disk_full'])</label>
|
||||
<input type="checkbox" name="ntfosd_prio_disk_full" id="ntfosd_prio_disk_full" value="1" <!--#if int($ntfosd_prio_disk_full) > 0 then 'checked="checked"' else ""#--> />
|
||||
</div>
|
||||
<div class="field-pair">
|
||||
<label class="config wide" for="ntfosd_prio_new_login">$T($notify_texts['new_login'])</label>
|
||||
<input type="checkbox" name="ntfosd_prio_new_login" id="ntfosd_prio_new_login" value="1" <!--#if int($ntfosd_prio_new_login) > 0 then 'checked="checked"' else ""#--> />
|
||||
</div>
|
||||
<div class="field-pair">
|
||||
<label class="config wide" for="ntfosd_prio_warning">$T($notify_texts['warning'])</label>
|
||||
<input type="checkbox" name="ntfosd_prio_warning" id="ntfosd_prio_warning" value="1" <!--#if int($ntfosd_prio_warning) > 0 then 'checked="checked"' else ""#--> />
|
||||
@@ -264,12 +247,10 @@
|
||||
<input type="checkbox" name="ntfosd_prio_other" id="ntfosd_prio_other" value="1" <!--#if int($ntfosd_prio_other) > 0 then 'checked="checked"' else ""#--> />
|
||||
</div>
|
||||
<div class="field-pair">
|
||||
<button class="btn btn-default saveButton"><span class="glyphicon glyphicon-ok"></span> $T('button-saveChanges')</button>
|
||||
<button class="btn btn-default" type="button" id="test_osd"><span class="glyphicon glyphicon-comment"></span> $T('testNotify')</button>
|
||||
<button class="btn btn-default saveButton"><span class="glyphicon glyphicon-ok"></span> $T('button-saveChanges')</button>
|
||||
<button class="btn btn-default" type="button" id="test_osd"><span class="glyphicon glyphicon-comment"></span> $T('testNotify')</button>
|
||||
<span id="testosd-result" class="icon path darkred"> </span>
|
||||
</div>
|
||||
<div class="field-pair result-box">
|
||||
<div class="alert"></div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div><!-- /col1 -->
|
||||
</div><!-- /section -->
|
||||
@@ -324,10 +305,6 @@
|
||||
<label class="config wide" for="growl_prio_disk_full">$T($notify_texts['disk_full'])</label>
|
||||
<input type="checkbox" name="growl_prio_disk_full" id="growl_prio_disk_full" value="1" <!--#if int($growl_prio_disk_full) > 0 then 'checked="checked"' else ""#--> />
|
||||
</div>
|
||||
<div class="field-pair">
|
||||
<label class="config wide" for="growl_prio_new_login">$T($notify_texts['new_login'])</label>
|
||||
<input type="checkbox" name="growl_prio_new_login" id="growl_prio_new_login" value="1" <!--#if int($growl_prio_new_login) > 0 then 'checked="checked"' else ""#--> />
|
||||
</div>
|
||||
<div class="field-pair">
|
||||
<label class="config wide" for="growl_prio_warning">$T($notify_texts['warning'])</label>
|
||||
<input type="checkbox" name="growl_prio_warning" id="growl_prio_warning" value="1" <!--#if int($growl_prio_warning) > 0 then 'checked="checked"' else ""#--> />
|
||||
@@ -341,12 +318,10 @@
|
||||
<input type="checkbox" name="growl_prio_other" id="growl_prio_other" value="1" <!--#if int($growl_prio_other) > 0 then 'checked="checked"' else ""#--> />
|
||||
</div>
|
||||
<div class="field-pair">
|
||||
<button class="btn btn-default saveButton"><span class="glyphicon glyphicon-ok"></span> $T('button-saveChanges')</button>
|
||||
<button class="btn btn-default" type="button" id="test_growl"><span class="glyphicon glyphicon-comment"></span> $T('testNotify')</button>
|
||||
</div>
|
||||
<div class="field-pair result-box">
|
||||
<div class="alert"></div>
|
||||
</div>
|
||||
<button class="btn btn-default saveButton"><span class="glyphicon glyphicon-ok"></span> $T('button-saveChanges')</button>
|
||||
<button class="btn btn-default" type="button" id="test_growl"><span class="glyphicon glyphicon-comment"></span> $T('testNotify')</button>
|
||||
<span id="testgrowl-result" class="icon path darkred"> </span>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div><!-- /col1 -->
|
||||
</div><!-- /section -->
|
||||
@@ -444,18 +419,7 @@
|
||||
<option value="1" <!--#if $prowl_prio_disk_full == 1 then 'selected="selected"' else ""#--> >$T('prowl-high')</option>
|
||||
<option value="2" <!--#if $prowl_prio_disk_full == 2 then 'selected="selected"' else ""#--> >$T('prowl-emergency')</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="field-pair">
|
||||
<label class="config" for="prowl_prio_new_login">$T($notify_texts['new_login'])</label>
|
||||
<select name="prowl_prio_new_login" id="prowl_prio_new_login">
|
||||
<option value="-3" <!--#if $prowl_prio_new_login == -3 then 'selected="selected"' else ""#--> >$T('prowl-off')</option>
|
||||
<option value="-2" <!--#if $prowl_prio_new_login == -2 then 'selected="selected"' else ""#--> >$T('prowl-very-low')</option>
|
||||
<option value="-1" <!--#if $prowl_prio_new_login == -1 then 'selected="selected"' else ""#--> >$T('prowl-moderate')</option>
|
||||
<option value="0" <!--#if $prowl_prio_new_login == 0 then 'selected="selected"' else ""#--> >$T('prowl-normal')</option>
|
||||
<option value="1" <!--#if $prowl_prio_new_login == 1 then 'selected="selected"' else ""#--> >$T('prowl-high')</option>
|
||||
<option value="2" <!--#if $prowl_prio_new_login == 2 then 'selected="selected"' else ""#--> >$T('prowl-emergency')</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field-pair">
|
||||
<label class="config" for="prowl_prio_warning">$T($notify_texts['warning'])</label>
|
||||
<select name="prowl_prio_warning" id="prowl_prio_warning">
|
||||
@@ -488,13 +452,11 @@
|
||||
<option value="1" <!--#if $prowl_prio_other == 1 then 'selected="selected"' else ""#--> >$T('prowl-high')</option>
|
||||
<option value="2" <!--#if $prowl_prio_other == 2 then 'selected="selected"' else ""#--> >$T('prowl-emergency')</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="field-pair">
|
||||
</div>
|
||||
<div class="field-pair">
|
||||
<button class="btn btn-default saveButton"><span class="glyphicon glyphicon-ok"></span> $T('button-saveChanges')</button>
|
||||
<button class="btn btn-default" type="button" id="test_prowl"><span class="glyphicon glyphicon-comment"></span> $T('testNotify')</button>
|
||||
</div>
|
||||
<div class="field-pair result-box">
|
||||
<div class="alert"></div>
|
||||
<span id="testprowl-result" class="icon path darkred"> </span>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div><!-- /col1 -->
|
||||
@@ -604,18 +566,7 @@
|
||||
<option value="1" <!--#if $pushover_prio_disk_full == 1 then 'selected="selected"' else ""#--> >$T('pushover-high')</option>
|
||||
<option value="2" <!--#if $pushover_prio_disk_full == 2 then 'selected="selected"' else ""#--> >$T('prowl-emergency')</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="field-pair">
|
||||
<label class="config" for="pushover_prio_new_login">$T($notify_texts['new_login'])</label>
|
||||
<select name="pushover_prio_new_login" id="pushover_prio_new_login">
|
||||
<option value="-3" <!--#if $pushover_prio_new_login == -3 then 'selected="selected"' else ""#--> >$T('pushover-off')</option>
|
||||
<option value="-2" <!--#if $pushover_prio_new_login == -2 then 'selected="selected"' else ""#--> >$T('prowl-very-low')</option>
|
||||
<option value="-1" <!--#if $pushover_prio_new_login == -1 then 'selected="selected"' else ""#--> >$T('pushover-low')</option>
|
||||
<option value="0" <!--#if $pushover_prio_new_login == 0 then 'selected="selected"' else ""#--> >$T('prowl-normal')</option>
|
||||
<option value="1" <!--#if $pushover_prio_new_login == 1 then 'selected="selected"' else ""#--> >$T('pushover-high')</option>
|
||||
<option value="2" <!--#if $pushover_prio_new_login == 2 then 'selected="selected"' else ""#--> >$T('prowl-emergency')</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field-pair">
|
||||
<label class="config" for="pushover_prio_warning">$T($notify_texts['warning'])</label>
|
||||
<select name="pushover_prio_warning" id="pushover_prio_warning">
|
||||
@@ -648,14 +599,12 @@
|
||||
<option value="1" <!--#if $pushover_prio_other == 1 then 'selected="selected"' else ""#--> >$T('pushover-high')</option>
|
||||
<option value="2" <!--#if $pushover_prio_other == 2 then 'selected="selected"' else ""#--> >$T('prowl-emergency')</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field-pair">
|
||||
<div class="field-pair">
|
||||
<button class="btn btn-default saveButton"><span class="glyphicon glyphicon-ok"></span> $T('button-saveChanges')</button>
|
||||
<button class="btn btn-default" type="button" id="test_pushover"><span class="glyphicon glyphicon-comment"></span> $T('testNotify')</button>
|
||||
</div>
|
||||
<div class="field-pair result-box">
|
||||
<div class="alert"></div>
|
||||
<span id="testpushover-result" class="icon path darkred"> </span>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div><!-- /col1 -->
|
||||
@@ -713,10 +662,6 @@
|
||||
<label class="config wide" for="pushbullet_prio_disk_full">$T($notify_texts['disk_full'])</label>
|
||||
<input type="checkbox" name="pushbullet_prio_disk_full" id="pushbullet_prio_disk_full" value="1" <!--#if int($pushbullet_prio_disk_full) > 0 then 'checked="checked"' else ""#--> />
|
||||
</div>
|
||||
<div class="field-pair">
|
||||
<label class="config wide" for="pushbullet_prio_new_login">$T($notify_texts['new_login'])</label>
|
||||
<input type="checkbox" name="pushbullet_prio_new_login" id="pushbullet_prio_new_login" value="1" <!--#if int($pushbullet_prio_new_login) > 0 then 'checked="checked"' else ""#--> />
|
||||
</div>
|
||||
<div class="field-pair">
|
||||
<label class="config wide" for="pushbullet_prio_warning">$T($notify_texts['warning'])</label>
|
||||
<input type="checkbox" name="pushbullet_prio_warning" id="pushbullet_prio_warning" value="1" <!--#if int($pushbullet_prio_warning) > 0 then 'checked="checked"' else ""#--> />
|
||||
@@ -732,94 +677,11 @@
|
||||
<div class="field-pair">
|
||||
<button class="btn btn-default saveButton"><span class="glyphicon glyphicon-ok"></span> $T('button-saveChanges')</button>
|
||||
<button class="btn btn-default" type="button" id="test_pushbullet"><span class="glyphicon glyphicon-comment"></span> $T('testNotify')</button>
|
||||
</div>
|
||||
<div class="field-pair result-box">
|
||||
<div class="alert"></div>
|
||||
</div>
|
||||
<span id="testpushbullet-result" class="icon path darkred"> </span>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div><!-- /col1 -->
|
||||
</div><!-- /section -->
|
||||
<div class="section" id="nscript">
|
||||
<div class="col2">
|
||||
<h3>$T('section-NScript')</h3>
|
||||
<table>
|
||||
<tr>
|
||||
<td><input type="checkbox" name="nscript_enable" id="nscript_enable" value="1" <!--#if int($nscript_enable) > 0 then 'checked="checked"' else ""#--> /></td>
|
||||
<td><label for="nscript_enable"> $T('opt-nscript_enable')</label></td>
|
||||
</tr>
|
||||
</table>
|
||||
<em>$T('explain-nscript_enable')</em>
|
||||
</div><!-- /col2 -->
|
||||
<div class="col1" <!--#if int($nscript_enable) > 0 then '' else 'style="display:none"'#-->>
|
||||
<fieldset>
|
||||
<div class="field-pair">
|
||||
<label class="config" for="nscript_script">$T('opt-nscript_script')</label>
|
||||
<select name="nscript_script">
|
||||
<!--#for $sc in $script_list#-->
|
||||
<option value="$sc" <!--#if $nscript_script.lower()==$sc.lower() then 'selected="selected"' else ""#-->>$Tspec($sc)</option>
|
||||
<!--#end for#-->
|
||||
</select>
|
||||
<span class="desc">$T('explain-nscript_script')</span>
|
||||
</div>
|
||||
<div class="field-pair">
|
||||
<label class="config" for="nscript_parameters">$T('opt-nscript_parameters')</label>
|
||||
<input type="text" name="nscript_parameters" id="nscript_parameters" value="$nscript_parameters" />
|
||||
<span class="desc">$T('Optional') - $T('explain-nscript_parameters')</span>
|
||||
</div>
|
||||
<div class="field-pair">
|
||||
<label class="config wide" for="nscript_prio_startup">$T($notify_texts['startup']).replace('/', ' / ')</label>
|
||||
<input type="checkbox" name="nscript_prio_startup" id="nscript_prio_startup" value="1" <!--#if int($nscript_prio_startup) > 0 then 'checked="checked"' else ""#--> />
|
||||
</div>
|
||||
<div class="field-pair">
|
||||
<label class="config wide" for="nscript_prio_download">$T($notify_texts['download']) / $T('link-pause') / $T('link-resume')</label>
|
||||
<input type="checkbox" name="nscript_prio_download" id="nscript_prio_download" value="1" <!--#if int($nscript_prio_download) > 0 then 'checked="checked"' else ""#--> />
|
||||
</div>
|
||||
<div class="field-pair">
|
||||
<label class="config wide" for="nscript_prio_pp">$T($notify_texts['pp'])</label>
|
||||
<input type="checkbox" name="nscript_prio_pp" id="nscript_prio_pp" value="1" <!--#if int($nscript_prio_pp) > 0 then 'checked="checked"' else ""#--> />
|
||||
</div>
|
||||
<div class="field-pair">
|
||||
<label class="config wide" for="nscript_prio_complete">$T($notify_texts['complete'])</label>
|
||||
<input type="checkbox" name="nscript_prio_complete" id="nscript_prio_complete" value="1" <!--#if int($nscript_prio_complete) > 0 then 'checked="checked"' else ""#--> />
|
||||
</div>
|
||||
<div class="field-pair">
|
||||
<label class="config wide" for="nscript_prio_failed">$T($notify_texts['failed'])</label>
|
||||
<input type="checkbox" name="nscript_prio_failed" id="nscript_prio_failed" value="1" <!--#if int($nscript_prio_failed) > 0 then 'checked="checked"' else ""#--> />
|
||||
</div>
|
||||
<div class="field-pair">
|
||||
<label class="config wide" for="nscript_prio_queue_done">$T($notify_texts['queue_done'])</label>
|
||||
<input type="checkbox" name="nscript_prio_queue_done" id="nscript_prio_queue_done" value="1" <!--#if int($nscript_prio_queue_done) > 0 then 'checked="checked"' else ""#--> />
|
||||
</div>
|
||||
<div class="field-pair">
|
||||
<label class="config wide" for="nscript_prio_disk_full">$T($notify_texts['disk_full'])</label>
|
||||
<input type="checkbox" name="nscript_prio_disk_full" id="nscript_prio_disk_full" value="1" <!--#if int($nscript_prio_disk_full) > 0 then 'checked="checked"' else ""#--> />
|
||||
</div>
|
||||
<div class="field-pair">
|
||||
<label class="config wide" for="nscript_prio_new_login">$T($notify_texts['new_login'])</label>
|
||||
<input type="checkbox" name="nscript_prio_new_login" id="nscript_prio_new_login" value="1" <!--#if int($nscript_prio_new_login) > 0 then 'checked="checked"' else ""#--> />
|
||||
</div>
|
||||
<div class="field-pair">
|
||||
<label class="config wide" for="nscript_prio_warning">$T($notify_texts['warning'])</label>
|
||||
<input type="checkbox" name="nscript_prio_warning" id="nscript_prio_warning" value="1" <!--#if int($nscript_prio_warning) > 0 then 'checked="checked"' else ""#--> />
|
||||
</div>
|
||||
<div class="field-pair">
|
||||
<label class="config wide" for="nscript_prio_error">$T($notify_texts['error'])</label>
|
||||
<input type="checkbox" name="nscript_prio_error" id="nscript_prio_error" value="1" <!--#if int($nscript_prio_error) > 0 then 'checked="checked"' else ""#--> />
|
||||
</div>
|
||||
<div class="field-pair">
|
||||
<label class="config wide" for="nscript_prio_other">$T($notify_texts['other'])</label>
|
||||
<input type="checkbox" name="nscript_prio_other" id="nscript_prio_other" value="1" <!--#if int($nscript_prio_other) > 0 then 'checked="checked"' else ""#--> />
|
||||
</div>
|
||||
<div class="field-pair">
|
||||
<button class="btn btn-default saveButton"><span class="glyphicon glyphicon-ok"></span> $T('button-saveChanges')</button>
|
||||
<button class="btn btn-default" type="button" id="test_nscript"><span class="glyphicon glyphicon-comment"></span> $T('testNotify')</button>
|
||||
</div>
|
||||
<div class="field-pair result-box">
|
||||
<div class="alert"></div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div><!-- /col1 -->
|
||||
</div><!-- /section -->
|
||||
</form>
|
||||
</div><!-- /colmask -->
|
||||
|
||||
@@ -841,44 +703,194 @@
|
||||
/**
|
||||
Testing functions
|
||||
**/
|
||||
function testNotification(buttonObj) {
|
||||
// Confirm?
|
||||
if(\$(buttonObj).attr('rel')) {
|
||||
if(!confirm(\$(buttonObj).attr('rel'))) return false;
|
||||
\$('#test_email').click(function () {
|
||||
if (confirm(\$('#test_email').attr('rel'))) {
|
||||
var data = { mode: 'test_email', apikey: '$session', output: 'json' };
|
||||
\$("#email").extractFormDataTo(data);
|
||||
\$.ajax({
|
||||
type: "GET",
|
||||
url: "../../tapi",
|
||||
data: data,
|
||||
beforeSend: function () {
|
||||
\$('#test_email').attr("disabled", "disabled");
|
||||
\$('#testmail-result').removeClass("success failure").addClass("loading").html('$T('post-Verifying')');
|
||||
},
|
||||
complete: function () {
|
||||
\$('#test_email').removeAttr("disabled");
|
||||
\$('#testmail-result').removeClass("loading");
|
||||
},
|
||||
success: function (data) {
|
||||
if (data.status == true) {
|
||||
\$('#testmail-result').addClass("success").html('$T('smpl-emailsent')');
|
||||
} else {
|
||||
\$('#testmail-result').addClass("failure").html(data.error);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
// Disable button and get the data
|
||||
\$(buttonObj).attr("disabled", "disabled")
|
||||
\$(buttonObj).find('span').toggleClass('glyphicon-comment glyphicon-refresh spin-glyphicon')
|
||||
var data = { mode: buttonObj.id, apikey: '$session', output: 'json' };
|
||||
\$(buttonObj).parents('.section').extractFormDataTo(data);
|
||||
|
||||
// Clear up the box
|
||||
resultBox = \$(buttonObj).parents('.section').find('.result-box .alert');
|
||||
|
||||
// Get the request
|
||||
});
|
||||
\$('#test_notification').click(function () {
|
||||
\$.ajax({
|
||||
type: "GET",
|
||||
url: "../../tapi",
|
||||
data: data
|
||||
}).then(function(data) {
|
||||
// Remove disabled and make the box
|
||||
\$(buttonObj).removeAttr("disabled")
|
||||
\$(buttonObj).find('span').toggleClass('glyphicon-comment glyphicon-refresh spin-glyphicon')
|
||||
resultBox.removeClass('alert-success alert-danger').show()
|
||||
if(data.status) {
|
||||
resultBox.addClass('alert-success')
|
||||
resultBox.text('$T('smpl-notesent')')
|
||||
resultBox.prepend('<span class="glyphicon glyphicon-ok-sign"></span> ')
|
||||
} else {
|
||||
resultBox.addClass('alert-danger')
|
||||
resultBox.text(data.error)
|
||||
resultBox.prepend('<span class="glyphicon glyphicon-exclamation-sign"></span> ')
|
||||
data: {mode: 'test_notif', apikey: '$session', output: 'json' },
|
||||
beforeSend: function () {
|
||||
\$('#test_notification').attr("disabled", "disabled");
|
||||
\$('#testnotice-result').removeClass("success failure").addClass("loading").html('$T('post-Verifying')');
|
||||
},
|
||||
complete: function () {
|
||||
\$('#test_notification').removeAttr("disabled");
|
||||
\$('#testnotice-result').removeClass("loading");
|
||||
},
|
||||
success: function (data) {
|
||||
if (data.status == true) {
|
||||
\$('#testnotice-result').addClass("success").html('$T('smpl-notesent')');
|
||||
} else {
|
||||
\$('#testnotice-result').addClass("failure").html(data.error);
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
\$('#test_email, #test_notif, #test_windows, #test_pushbullet, #test_pushover, #test_prowl, #test_growl, #test_osd, #test_nscript').click(function () {
|
||||
testNotification(this)
|
||||
})
|
||||
});
|
||||
});
|
||||
\$('#test_windows_notification').click(function () {
|
||||
\$.ajax({
|
||||
type: "GET",
|
||||
url: "../../tapi",
|
||||
data: {mode: 'test_windows', apikey: '$session', output: 'json' },
|
||||
beforeSend: function () {
|
||||
\$('#test_notification').attr("disabled", "disabled");
|
||||
\$('#testnotice-result').removeClass("success failure").addClass("loading").html('$T('post-Verifying')');
|
||||
},
|
||||
complete: function () {
|
||||
\$('#test_notification').removeAttr("disabled");
|
||||
\$('#testnotice-result').removeClass("loading");
|
||||
},
|
||||
success: function (data) {
|
||||
if (data.status == true) {
|
||||
\$('#testnotice-result').addClass("success").html('$T('smpl-notesent')');
|
||||
} else {
|
||||
\$('#testnotice-result').addClass("failure").html(data.error);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
\$('#test_pushbullet').click(function () {
|
||||
var data = { mode: 'test_pushbullet', apikey: '$session', output: 'json' };
|
||||
\$("#pushbullet").extractFormDataTo(data);
|
||||
\$.ajax({
|
||||
type: "GET",
|
||||
url: "../../tapi",
|
||||
data: data,
|
||||
beforeSend: function () {
|
||||
\$('#test_pushbullet').attr("disabled", "disabled");
|
||||
\$('#testpushbullet-result').removeClass("success failure").addClass("loading").html('$T('post-Verifying')');
|
||||
},
|
||||
complete: function () {
|
||||
\$('#test_pushbullet').removeAttr("disabled");
|
||||
\$('#testpushbullet-result').removeClass("loading");
|
||||
},
|
||||
success: function (data) {
|
||||
if (data.status == true) {
|
||||
\$('#testpushbullet-result').addClass("success").html('$T('smpl-notesent')');
|
||||
} else {
|
||||
\$('#testpushbullet-result').addClass("failure").html(data.error);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
\$('#test_pushover').click(function () {
|
||||
var data = { mode: 'test_pushover', apikey: '$session', output: 'json' };
|
||||
\$("#pushover").extractFormDataTo(data);
|
||||
\$.ajax({
|
||||
type: "GET",
|
||||
url: "../../tapi",
|
||||
data: data,
|
||||
beforeSend: function () {
|
||||
\$('#test_pushover').attr("disabled", "disabled");
|
||||
\$('#testpushover-result').removeClass("success failure").addClass("loading").html('$T('post-Verifying')');
|
||||
},
|
||||
complete: function () {
|
||||
\$('#test_pushover').removeAttr("disabled");
|
||||
\$('#testpushover-result').removeClass("loading");
|
||||
},
|
||||
success: function (data) {
|
||||
if (data.status == true) {
|
||||
\$('#testpushover-result').addClass("success").html('$T('smpl-notesent')');
|
||||
} else {
|
||||
\$('#testpushover-result').addClass("failure").html(data.error);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
\$('#test_prowl').click(function () {
|
||||
var data = { mode: 'test_prowl', apikey: '$session', output: 'json' };
|
||||
\$("#prowl").extractFormDataTo(data);
|
||||
\$.ajax({
|
||||
type: "GET",
|
||||
url: "../../tapi",
|
||||
data: data,
|
||||
beforeSend: function () {
|
||||
\$('#test_prowl').attr("disabled", "disabled");
|
||||
\$('#testprowl-result').removeClass("success failure").addClass("loading").html('$T('post-Verifying')');
|
||||
},
|
||||
complete: function () {
|
||||
\$('#test_prowl').removeAttr("disabled");
|
||||
\$('#testprowl-result').removeClass("loading");
|
||||
},
|
||||
success: function (data) {
|
||||
if (data.status == true) {
|
||||
\$('#testprowl-result').addClass("success").html('$T('smpl-notesent')');
|
||||
} else {
|
||||
\$('#testprowl-result').addClass("failure").html(data.error);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
\$('#test_growl').click(function () {
|
||||
var data = { mode: 'test_growl', apikey: '$session', output: 'json' };
|
||||
\$("#growl").extractFormDataTo(data);
|
||||
\$.ajax({
|
||||
type: "GET",
|
||||
url: "../../tapi",
|
||||
data: data,
|
||||
beforeSend: function () {
|
||||
\$('#test_growl').attr("disabled", "disabled");
|
||||
\$('#testgrowl-result').removeClass("success failure").addClass("loading").html('$T('post-Verifying')');
|
||||
},
|
||||
complete: function () {
|
||||
\$('#test_growl').removeAttr("disabled");
|
||||
\$('#testgrowl-result').removeClass("loading");
|
||||
},
|
||||
success: function (data) {
|
||||
if (data.status == true) {
|
||||
\$('#testgrowl-result').addClass("success").html('$T('smpl-notesent')');
|
||||
} else {
|
||||
\$('#testgrowl-result').addClass("failure").html(data.error);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
\$('#test_osd').click(function () {
|
||||
\$.ajax({
|
||||
type: "GET",
|
||||
url: "../../tapi",
|
||||
data: {mode: 'test_osd', apikey: '$session', output: 'json' },
|
||||
beforeSend: function () {
|
||||
\$('#test_osd').attr("disabled", "disabled");
|
||||
\$('#testosd-result').removeClass("success failure").addClass("loading").html('$T('post-Verifying')');
|
||||
},
|
||||
complete: function () {
|
||||
\$('#test_osdl').removeAttr("disabled");
|
||||
\$('#testosd-result').removeClass("loading");
|
||||
},
|
||||
success: function (data) {
|
||||
if (data.status == true) {
|
||||
\$('#testosd-result').addClass("success").html('$T('smpl-notesent')');
|
||||
} else {
|
||||
\$('#testosd-result').addClass("failure").html(data.error);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!--#set global $pane="RSS"#-->
|
||||
<!--#set global $help_uri="configuration/1.1/rss"#-->
|
||||
<!--#set global $help_uri="configure-rss-1-0"#-->
|
||||
<!--#include $webdir + "/_inc_header_uc.tmpl"#-->
|
||||
<div class="colmask">
|
||||
<!--#if not $active_feed#-->
|
||||
@@ -520,7 +520,6 @@ function urlencode(str) {
|
||||
|
||||
\$('.testFeed').click(function(){
|
||||
var whichFeed = \$(this).attr("rel");
|
||||
\$(this).attr('disabled', true)
|
||||
\$.ajax({
|
||||
type: "POST",
|
||||
url: "test_rss_feed",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!--#set global $pane="Scheduling"#-->
|
||||
<!--#set global $help_uri="configuration/1.1/scheduling"#-->
|
||||
<!--#set global $help_uri="configure-scheduling-1-0"#-->
|
||||
<!--#include $webdir + "/_inc_header_uc.tmpl"#-->
|
||||
|
||||
<%
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<!--#set global $pane="Servers"#-->
|
||||
<!--#set global $help_uri="configuration/1.1/servers"#-->
|
||||
<!--#set global $help_uri="configure-servers-1-0"#-->
|
||||
<!--#include $webdir + "/_inc_header_uc.tmpl"#-->
|
||||
|
||||
<div class="colmask">
|
||||
<form action="addServer" method="post" autocomplete="off" onsubmit="removeObfuscation();" novalidate>
|
||||
<form action="addServer" method="post" autocomplete="off" novalidate>
|
||||
<input type="hidden" name="session" value="$session" />
|
||||
<div id="addServer">
|
||||
<div class="padding alt">
|
||||
@@ -29,14 +29,13 @@
|
||||
<label class="config" for="port">$T('srv-port')</label>
|
||||
<input type="number" name="port" id="port" size="8" />
|
||||
</div>
|
||||
<!-- Tricks to avoid browser auto-fill, fixed on-submit with javascript -->
|
||||
<div class="field-pair">
|
||||
<label class="config" for="${pid}_00">$T('srv-username')</label>
|
||||
<input type="text" name="${pid}_00" id="${pid}_00" data-hide="username" />
|
||||
<label class="config" for="username">$T('srv-username')</label>
|
||||
<input type="text" name="username" id="username" />
|
||||
</div>
|
||||
<div class="field-pair">
|
||||
<label class="config" for="${pid}_01">$T('srv-password')</label>
|
||||
<input type="text" name="${pid}_01" id="${pid}_01" data-hide="password" />
|
||||
<label class="config" for="password">$T('srv-password')</label>
|
||||
<input type="text" name="password" id="password" />
|
||||
</div>
|
||||
<div class="field-pair">
|
||||
<label class="config" for="connections">$T('srv-connections')</label>
|
||||
@@ -116,9 +115,6 @@
|
||||
<button class="btn btn-default"><span class="glyphicon glyphicon-plus"></span> $T('button-addServer')</button>
|
||||
<button class="btn btn-default testServer" type="button"><span class="glyphicon glyphicon-sort"></span> $T('button-testServer')</button>
|
||||
</div>
|
||||
<div class="field-pair result-box">
|
||||
<div class="alert"></div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div><!-- /col1 -->
|
||||
</div><!-- /section -->
|
||||
@@ -158,14 +154,13 @@
|
||||
<label class="config" for="port$cur">$T('srv-port')</label>
|
||||
<input type="number" name="port" id="port$cur" value="$server['port']" size="8" />
|
||||
</div>
|
||||
<!-- Tricks to avoid browser auto-fill, fixed on-submit with javascript -->
|
||||
<div class="field-pair">
|
||||
<label class="config" for="${pid}_${cur}0">$T('srv-username')</label>
|
||||
<input type="text" name="${pid}_${cur}0" id="${pid}_${cur}0" value="$server['username']" data-hide="username" />
|
||||
<label class="config" for="username$cur">$T('srv-username')</label>
|
||||
<input type="text" name="username" id="username$cur" value="$server['username']" />
|
||||
</div>
|
||||
<div class="field-pair">
|
||||
<label class="config" for="${pid}_${cur}1">$T('srv-password')</label>
|
||||
<input type="text" name="${pid}_${cur}1" id="${pid}_${cur}1" value="$server['password']" data-hide="password" />
|
||||
<label class="config" for="password$cur">$T('srv-password')</label>
|
||||
<input type="text" name="password" id="password$cur" value="$server['password']" />
|
||||
</div>
|
||||
<div class="field-pair">
|
||||
<label class="config" for="connections$cur">$T('srv-connections')</label>
|
||||
@@ -232,10 +227,6 @@
|
||||
<!--#end for#-->
|
||||
</select>
|
||||
<span class="desc">$T('srv-explain-categories')</span>
|
||||
<div class="alert alert-info alert-no-category">
|
||||
<span class="glyphicon glyphicon-info-sign"></span>
|
||||
$T('srv-explain-no-categories')
|
||||
</div>
|
||||
</div>
|
||||
<div class="field-pair">
|
||||
<label class="config" for="displayname$cur">$T('srv-displayname')</label>
|
||||
@@ -250,9 +241,6 @@
|
||||
<button class="btn btn-default testServer" type="button"><span class="glyphicon glyphicon-sort"></span> $T('button-testServer')</button>
|
||||
<button class="btn btn-default delServer"><span class="glyphicon glyphicon-trash"></span> $T('button-delServer')</button>
|
||||
</div>
|
||||
<div class="field-pair result-box">
|
||||
<div class="alert"></div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div><!-- /col1 -->
|
||||
<div class="col2" style="display:block;">
|
||||
@@ -297,33 +285,6 @@
|
||||
}
|
||||
\$(this).css('background-color', theColor)
|
||||
})
|
||||
|
||||
/**
|
||||
Message on no Default category selected
|
||||
**/
|
||||
function checkServerCats() {
|
||||
// Now we check all of them
|
||||
var hasDefault = false;
|
||||
// Only check the active servers, not the add-server one
|
||||
\$('.section:not(#addServerContent) select[name="categories"]').each(function() {
|
||||
// See if this server is enabled
|
||||
if(!\$(this).parents('.section').find('.col2').hasClass('server-disabled') ) {
|
||||
// Is there Default?
|
||||
if(\$(this).val().indexOf('Default') > -1) {
|
||||
// Hide
|
||||
\$('.alert-no-category').hide()
|
||||
hasDefault = true
|
||||
// All good!
|
||||
return true
|
||||
}
|
||||
}
|
||||
})
|
||||
// We found nothing.. Let's show a warning
|
||||
if(!hasDefault) \$('.alert-no-category').show()
|
||||
}
|
||||
|
||||
\$('select[name="categories"]').on('change', checkServerCats)
|
||||
checkServerCats()
|
||||
|
||||
/**
|
||||
Click events
|
||||
@@ -345,29 +306,14 @@
|
||||
\$('#addServerContent').show();
|
||||
});
|
||||
\$('.testServer').click(function(event){
|
||||
removeObfuscation()
|
||||
var theButton = \$(this)
|
||||
var resultBox = theButton.parents('.col1').find('.result-box .alert');
|
||||
theButton.attr("disabled", "disabled")
|
||||
theButton.find('span').toggleClass('glyphicon-sort glyphicon-refresh spin-glyphicon')
|
||||
\$(this).attr("disabled", "disabled")
|
||||
\$.ajax({
|
||||
type: "POST",
|
||||
url: "../../tapi",
|
||||
data: "mode=config&output=json&name=test_server&" + \$(this).parents('form:first').serialize()
|
||||
}).then(function(data) {
|
||||
// Fill the box and enable the button
|
||||
resultBox.removeClass('alert-success alert-danger').show()
|
||||
resultBox.text(data.value.message)
|
||||
theButton.removeAttr("disabled")
|
||||
theButton.find('span').toggleClass('glyphicon-sort glyphicon-refresh spin-glyphicon')
|
||||
|
||||
// Succes or not?
|
||||
if(data.value.result) {
|
||||
resultBox.addClass('alert-success')
|
||||
resultBox.prepend('<span class="glyphicon glyphicon-ok-sign"></span> ')
|
||||
} else {
|
||||
resultBox.addClass('alert-danger')
|
||||
resultBox.prepend('<span class="glyphicon glyphicon-exclamation-sign"></span> ')
|
||||
data: "mode=config&name=test_server&" + \$(this).parents('form:first').serialize(),
|
||||
success: function(msg){
|
||||
alert(msg);
|
||||
\$(event.target).removeAttr("disabled")
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -377,7 +323,7 @@
|
||||
// Let us leave!
|
||||
formWasSubmitted = true;
|
||||
formHasChanged = false;
|
||||
setTimeout(function() { location.reload(); }, 500)
|
||||
setTimeout(function() { location.reload(); }, 100)
|
||||
}
|
||||
return false;
|
||||
});
|
||||
@@ -387,7 +333,7 @@
|
||||
// Let us leave!
|
||||
formWasSubmitted = true;
|
||||
formHasChanged = false;
|
||||
setTimeout(function() { location.reload(); }, 500)
|
||||
setTimeout(function() { location.reload(); }, 100)
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!--#set global $pane="Sorting"#-->
|
||||
<!--#set global $help_uri="configuration/1.1/sorting"#-->
|
||||
<!--#set global $help_uri="configure-sorting-1-0"#-->
|
||||
<!--#include $webdir + "/_inc_header_uc.tmpl"#-->
|
||||
|
||||
<div class="colmask">
|
||||
@@ -30,22 +30,24 @@
|
||||
</div>
|
||||
<div class="field-pair">
|
||||
<label class="config" for="tvfoldername">$T('sortString')</label>
|
||||
<input type="text" id="tvfoldername" name="tv_sort_string" value="$tv_sort_string" /><button class="btn btn-default clearBtn" type="button"><span class="glyphicon glyphicon-remove"></span> $T('button-clear')</button>
|
||||
<input type="text" id="tvfoldername" name="tv_sort_string" value="$tv_sort_string" />
|
||||
<button class="btn btn-default clearBtn" type="button"><span class="glyphicon glyphicon-remove"></span> $T('button-clear')</button>
|
||||
</div>
|
||||
<div class="field-pair">
|
||||
<label class="config">$T('presetSort')</label>
|
||||
<div class="presets float-left">
|
||||
<input type="button" class="btn btn-default" onclick="tvSet('%sn/Season %s/%sn - %sx%0e - %en.%ext')" value="$T('button-Season1x05')" />
|
||||
<input type="button" class="btn btn-default" onclick="tvSet('%sn/Season %s/%sn - S%0sE%0e - %en.%ext')" value="$T('button-SeasonS01E05')" />
|
||||
<input type="button" onclick="tvSet('%sn/Season %s/%sn - %sx%0e - %en.%ext')" value="$T('button-Season1x05')" />
|
||||
<input type="button" onclick="tvSet('%sn/Season %s/%sn - S%0sE%0e - %en.%ext')" value="$T('button-SeasonS01E05')" />
|
||||
<br/>
|
||||
<input type="button" class="btn btn-default" onclick="tvSet('%sn/%sx%0e - %en/%sn - %sx%0e - %en.%ext')" value="$T('button-Ep1x05')" />
|
||||
<input type="button" class="btn btn-default" onclick="tvSet('%sn/S%0sE%0e - %en/%sn - S%0sE%0e - %en.%ext')" value="$T('button-EpS01E05')" />
|
||||
<input type="button" onclick="tvSet('%sn/%sx%0e - %en/%sn - %sx%0e - %en.%ext')" value="$T('button-Ep1x05')" />
|
||||
<input type="button" onclick="tvSet('%sn/S%0sE%0e - %en/%sn - S%0sE%0e - %en.%ext')" value="$T('button-EpS01E05')" />
|
||||
</div>
|
||||
</div>
|
||||
<div id="previewtv" class="example">
|
||||
<div class="field-pair">
|
||||
<label class="config" for="tvsamplename">Test Data</label>
|
||||
<input type="text" id="tvsamplename" name="tvsamplename" placeholder="$T('show-name') S01E05 - $T('ep-name') [DTS]" /><button class="btn btn-default clearBtn" type="button"><span class="glyphicon glyphicon-remove"></span> $T('button-clear')</button>
|
||||
<input type="text" id="tvsamplename" name="tvsamplename" placeholder="$T('show-name') S01E05 - $T('ep-name') [DTS]" />
|
||||
<button class="btn btn-default clearBtn" type="button"><span class="glyphicon glyphicon-remove"></span> $T('button-clear')</button>
|
||||
</div>
|
||||
<div class="field-pair">
|
||||
<label class="config">$T('sortResult')</label> <span class="desc path" id="previewtv-result"> </span>
|
||||
@@ -53,7 +55,7 @@
|
||||
</div>
|
||||
<div class="field-pair">
|
||||
<label class="config">$T('sort-legenda')</label>
|
||||
<button type="button" class="btn btn-default patternKey" onclick="jQuery(this).hide(); jQuery('#Key1').show();"><span class="glyphicon glyphicon-list-alt" aria-hidden="true"></span> $T('sort-legenda')</button>
|
||||
<input type="button" value="$T('sort-legenda')" onclick="jQuery(this).hide(); jQuery('#Key1').show();" />
|
||||
<table id="Key1" class="Key">
|
||||
<thead>
|
||||
<tr>
|
||||
@@ -187,24 +189,27 @@
|
||||
</div>
|
||||
<div class="field-pair">
|
||||
<label class="config" for="moviefoldername">$T('sortString')</label>
|
||||
<input type="text" name="movie_sort_string" id="moviefoldername" value="$movie_sort_string" /><button class="btn btn-default clearBtn" type="button"><span class="glyphicon glyphicon-remove"></span> $T('button-clear')</button>
|
||||
<input type="text" name="movie_sort_string" id="moviefoldername" value="$movie_sort_string" />
|
||||
<button class="btn btn-default clearBtn" type="button"><span class="glyphicon glyphicon-remove"></span> $T('button-clear')</button>
|
||||
</div>
|
||||
<div class="field-pair">
|
||||
<label class="config" for="movieextra">$T('multiPartLabel')</label>
|
||||
<input type="text" name="movie_sort_extra" id="movieextra" value="$movie_sort_extra" /><button class="btn btn-default clearBtn" type="button"><span class="glyphicon glyphicon-remove"></span> $T('button-clear')</button>
|
||||
<input type="text" name="movie_sort_extra" id="movieextra" value="$movie_sort_extra" />
|
||||
<button class="btn btn-default clearBtn" type="button"><span class="glyphicon glyphicon-remove"></span> $T('button-clear')</button>
|
||||
</div>
|
||||
<div class="field-pair">
|
||||
<label class="config">$T('presetSort')</label>
|
||||
<div class="presets float-left">
|
||||
<input type="button" class="btn btn-default" onclick="movieSet('%title (%y)/%title (%y).%ext',' CD%1');movieExtraFolder(false)" value="$T('button-inFolders')" />
|
||||
<input type="button" class="btn btn-default" onclick="movieSet('%title (%y).%ext',' CD%1');movieExtraFolder(true)" value="$T('button-noFolders')" />
|
||||
<input type="button" class="btn btn-default" onclick="movieSet('%0decade/%title (%y).%ext',' CD%1');movieExtraFolder(true)" value="Decades 1" />
|
||||
<input type="button" onclick="movieSet('%title (%y)/%title (%y).%ext',' CD%1');movieExtraFolder(false)" value="$T('button-inFolders')" />
|
||||
<input type="button" onclick="movieSet('%title (%y).%ext',' CD%1');movieExtraFolder(true)" value="$T('button-noFolders')" />
|
||||
<input type="button" onclick="movieSet('%0decade/%title (%y).%ext',' CD%1');movieExtraFolder(true)" value="Decades 1" />
|
||||
</div>
|
||||
</div>
|
||||
<div id="previewmovie" class="example">
|
||||
<div class="field-pair">
|
||||
<label class="config" for="moviesamplename">Test Data</label>
|
||||
<input type="text" id="moviesamplename" name="moviesamplename" placeholder="$T('movie-sp-name') (2009)" /><button class="btn btn-default clearBtn" type="button"><span class="glyphicon glyphicon-remove"></span> $T('button-clear')</button>
|
||||
<input type="text" id="moviesamplename" name="moviesamplename" placeholder="$T('movie-sp-name') (2009)" />
|
||||
<button class="btn btn-default clearBtn" type="button"><span class="glyphicon glyphicon-remove"></span> $T('button-clear')</button>
|
||||
</div>
|
||||
<div class="field-pair">
|
||||
<label class="config">$T('sortResult')</label> <span class="desc path" id="previewmovie-result"> </span>
|
||||
@@ -212,7 +217,7 @@
|
||||
</div>
|
||||
<div class="field-pair">
|
||||
<label class="config">$T('sort-legenda')</label>
|
||||
<button type="button" class="btn btn-default patternKey" onclick="jQuery(this).hide(); jQuery('#Key2').show();"><span class="glyphicon glyphicon-list-alt" aria-hidden="true"></span> $T('sort-legenda')</button>
|
||||
<input type="button" value="$T('sort-legenda')" onclick="jQuery(this).hide(); jQuery('#Key2').show();" />
|
||||
<table id="Key2" class="Key">
|
||||
<thead>
|
||||
<tr>
|
||||
@@ -321,20 +326,22 @@
|
||||
</div>
|
||||
<div class="field-pair">
|
||||
<label class="config" for="datefoldername">$T('sortString')</label>
|
||||
<input type="text" name="date_sort_string" id="datefoldername" value="$date_sort_string" /><button class="btn btn-default clearBtn" type="button"><span class="glyphicon glyphicon-remove"></span> $T('button-clear')</button>
|
||||
<input type="text" name="date_sort_string" id="datefoldername" value="$date_sort_string" />
|
||||
<button class="btn btn-default clearBtn" type="button"><span class="glyphicon glyphicon-remove"></span> $T('button-clear')</button>
|
||||
</div>
|
||||
<div class="field-pair">
|
||||
<label class="config">$T('presetSort')</label>
|
||||
<div class="presets float-left">
|
||||
<input type="button" class="btn btn-default" onclick="dateSet('%t/%t - %y-%0m-%0d - %desc.%ext')" value="$T('button-ShowNameF')" />
|
||||
<input type="button" class="btn btn-default" onclick="dateSet('%y-%0m/%t - %y-%0m-%0d - %desc.%ext')" value="$T('button-YMF')" />
|
||||
<input type="button" class="btn btn-default" onclick="dateSet('%y-%0m-%0d/%t - %y-%0m-%0d - %desc.%ext')" value="$T('button-DailyF')" />
|
||||
<input type="button" onclick="dateSet('%t/%t - %y-%0m-%0d - %desc.%ext')" value="$T('button-ShowNameF')" />
|
||||
<input type="button" onclick="dateSet('%y-%0m/%t - %y-%0m-%0d - %desc.%ext')" value="$T('button-YMF')" />
|
||||
<input type="button" onclick="dateSet('%y-%0m-%0d/%t - %y-%0m-%0d - %desc.%ext')" value="$T('button-DailyF')" />
|
||||
</div>
|
||||
</div>
|
||||
<div id="previewdate" class="example">
|
||||
<div class="field-pair">
|
||||
<label class="config" for="datesamplename">Test Data</label>
|
||||
<input type="text" id="datesamplename" name="datesamplename" placeholder="$T('show-name') 2009-01-02" /><button class="btn btn-default clearBtn" type="button"><span class="glyphicon glyphicon-remove"></span> $T('button-clear')</button>
|
||||
<input type="text" id="datesamplename" name="datesamplename" placeholder="$T('show-name') 2009-01-02" />
|
||||
<button class="btn btn-default clearBtn" type="button"><span class="glyphicon glyphicon-remove"></span> $T('button-clear')</button>
|
||||
</div>
|
||||
<div class="field-pair">
|
||||
<label class="config">$T('sortResult')</label> <span class="desc path" id="previewdate-result"> </span>
|
||||
@@ -342,7 +349,7 @@
|
||||
</div>
|
||||
<div class="field-pair">
|
||||
<label class="config">$T('sort-legenda')</label>
|
||||
<button type="button" class="btn btn-default patternKey" onclick="jQuery(this).hide(); jQuery('#Key3').show();"><span class="glyphicon glyphicon-list-alt" aria-hidden="true"></span> $T('sort-legenda')</button>
|
||||
<input type="button" value="$T('sort-legenda')" onclick="jQuery(this).hide(); jQuery('#Key3').show();" />
|
||||
<table id="Key3" class="Key">
|
||||
<thead>
|
||||
<tr>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!--#set global $pane="Special"#-->
|
||||
<!--#set global $help_uri="configuration/1.1/special"#-->
|
||||
<!--#set global $help_uri="configure-special-1-0"#-->
|
||||
<!--#include $webdir + "/_inc_header_uc.tmpl"#-->
|
||||
|
||||
<div class="colmask">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!--#set global $pane="Switches"#-->
|
||||
<!--#set global $help_uri="configuration/1.1/switches"#-->
|
||||
<!--#set global $help_uri="configure-switches-1-0"#-->
|
||||
<!--#include $webdir + "/_inc_header_uc.tmpl"#-->
|
||||
|
||||
<div class="colmask">
|
||||
@@ -25,14 +25,13 @@
|
||||
</select>
|
||||
<span class="desc">$T('explain-check_new_rel')</span>
|
||||
</div>
|
||||
<div class="field-pair">
|
||||
<label class="config" for="enable_https_verification">$T('opt-enable_https_verification')</label>
|
||||
<input type="checkbox" name="enable_https_verification" id="enable_https_verification" value="1" <!--#if int($enable_https_verification) > 0 then 'checked="checked"' else ""#--> />
|
||||
<span class="desc">$T('explain-enable_https_verification')</span>
|
||||
<div class="field-pair <!--#if not $have_ampm then "disabled" else "" #-->">
|
||||
<label class="config" for="ampm">$T('opt-ampm')</label>
|
||||
<input type="checkbox" name="ampm" id="ampm" value="1" <!--#if int($ampm) > 0 then 'checked="checked"' else ""#--> <!--#if not $have_ampm then 'readonly="readonly" disabled="disabled"' else "" #--> />
|
||||
<span class="desc">$T('explain-ampm')</span>
|
||||
</div>
|
||||
<div class="field-pair">
|
||||
<button class="btn btn-default saveButton"><span class="glyphicon glyphicon-ok"></span> $T('button-saveChanges')</button>
|
||||
<button class="btn btn-default restoreDefaults"><span class="glyphicon glyphicon-asterisk"></span> $T('button-restoreDefaults')</button>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div><!-- /col1 -->
|
||||
@@ -69,7 +68,6 @@
|
||||
</div>
|
||||
<div class="field-pair">
|
||||
<button class="btn btn-default saveButton"><span class="glyphicon glyphicon-ok"></span> $T('button-saveChanges')</button>
|
||||
<button class="btn btn-default restoreDefaults"><span class="glyphicon glyphicon-asterisk"></span> $T('button-restoreDefaults')</button>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div><!-- /col1 -->
|
||||
@@ -93,11 +91,6 @@
|
||||
</select>
|
||||
<span class="desc">$T('explain-pre_script')</span>
|
||||
</div>
|
||||
<div class="field-pair">
|
||||
<label class="config" for="propagation_delay">$T('opt-propagation_delay')</label>
|
||||
<input type="number" name="propagation_delay" id="propagation_delay" value="$propagation_delay" /> <i>$T('minutes')</i>
|
||||
<span class="desc">$T('explain-propagation_delay')</span>
|
||||
</div>
|
||||
<div class="field-pair">
|
||||
<label class="config" for="top_only">$T('opt-top_only')</label>
|
||||
<input type="checkbox" name="top_only" id="top_only" value="1" <!--#if int($top_only) > 0 then 'checked="checked"' else ""#--> />
|
||||
@@ -109,9 +102,9 @@
|
||||
<span class="desc">$T('explain-pre_check')</span>
|
||||
</div>
|
||||
<div class="field-pair">
|
||||
<label class="config" for="fail_hopeless_jobs">$T('opt-fail_hopeless_jobs')</label>
|
||||
<input type="checkbox" name="fail_hopeless_jobs" id="fail_hopeless_jobs" value="1" <!--#if int($fail_hopeless_jobs) > 0 then 'checked="checked"' else ""#--> />
|
||||
<span class="desc">$T('explain-fail_hopeless_jobs')</span>
|
||||
<label class="config" for="fail_hopeless">$T('opt-fail_hopeless')</label>
|
||||
<input type="checkbox" name="fail_hopeless" id="fail_hopeless" value="1" <!--#if int($fail_hopeless) > 0 then 'checked="checked"' else ""#--> />
|
||||
<span class="desc">$T('explain-fail_hopeless')</span>
|
||||
</div>
|
||||
<div class="field-pair">
|
||||
<label class="config" for="no_dupes">$T('opt-no_dupes')</label>
|
||||
@@ -154,6 +147,7 @@
|
||||
<input type="text" name="unwanted_extensions" id="unwanted_extensions" value="$unwanted_extensions"/>
|
||||
<span class="desc">$T('explain-unwanted_extensions')</span>
|
||||
</div>
|
||||
|
||||
<div class="field-pair">
|
||||
<label class="config" for="auto_sort">$T('opt-auto_sort')</label>
|
||||
<input type="checkbox" name="auto_sort" id="auto_sort" value="1" <!--#if int($auto_sort) > 0 then 'checked="checked"' else ""#--> />
|
||||
@@ -161,7 +155,6 @@
|
||||
</div>
|
||||
<div class="field-pair">
|
||||
<button class="btn btn-default saveButton"><span class="glyphicon glyphicon-ok"></span> $T('button-saveChanges')</button>
|
||||
<button class="btn btn-default restoreDefaults"><span class="glyphicon glyphicon-asterisk"></span> $T('button-restoreDefaults')</button>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div><!-- /col1 -->
|
||||
@@ -180,7 +173,7 @@
|
||||
<div class="field-pair">
|
||||
<label class="config" for="enable_all_par">$T('opt-enable_all_par')</label>
|
||||
<input type="checkbox" name="enable_all_par" id="enable_all_par" value="1" <!--#if int($enable_all_par) > 0 then 'checked="checked"' else ""#--> />
|
||||
<span class="desc">$T('explain-enable_all_par').replace('.', '<br/>')</span>
|
||||
<span class="desc">$T('explain-enable_all_par')</span>
|
||||
</div>
|
||||
<div class="field-pair">
|
||||
<label class="config" for="quick_check">$T('opt-quick_check')</label>
|
||||
@@ -207,6 +200,21 @@
|
||||
<input type="checkbox" name="safe_postproc" id="safe_postproc" value="1" <!--#if int($safe_postproc) > 0 then 'checked="checked"' else ""#--> />
|
||||
<span class="desc">$T('explain-safe_postproc')</span>
|
||||
</div>
|
||||
<div class="field-pair <!--#if not $have_unrar then "disabled" else "" #-->">
|
||||
<label class="config" for="enable_unrar">$T('opt-enable_unrar')</label>
|
||||
<input type="checkbox" name="enable_unrar" id="enable_unrar" value="1" <!--#if not $have_unrar then 'readonly="readonly" disabled="disabled"' else "" #--> <!--#if int($enable_unrar) > 0 then 'checked="checked"' else ""#--> />
|
||||
<span class="desc">$T('explain-enable_unrar')</span>
|
||||
</div>
|
||||
<div class="field-pair <!--#if not $have_unzip then "disabled" else "" #-->">
|
||||
<label class="config" for="enable_unzip">$T('opt-enable_unzip')</label>
|
||||
<input type="checkbox" name="enable_unzip" id="enable_unzip" value="1" <!--#if not $have_unzip then 'readonly="readonly" disabled="disabled"' else "" #--> <!--#if int($enable_unzip) > 0 then 'checked="checked"' else ""#--> />
|
||||
<span class="desc">$T('explain-enable_unzip')</span>
|
||||
</div>
|
||||
<div class="field-pair <!--#if not $have_7zip then "disabled" else "" #-->">
|
||||
<label class="config" for="enable_7zip">$T('opt-enable_7zip')</label>
|
||||
<input type="checkbox" name="enable_7zip" id="enable_7zip" value="1" <!--#if not $have_7zip then 'readonly="readonly" disabled="disabled"' else "" #--> <!--#if int($enable_7zip) > 0 then 'checked="checked"' else ""#--> />
|
||||
<span class="desc">$T('explain-enable_7zip')</span>
|
||||
</div>
|
||||
<div class="field-pair">
|
||||
<label class="config" for="enable_recursive">$T('opt-enable_recursive')</label>
|
||||
<input type="checkbox" name="enable_recursive" id="enable_recursive" value="1" <!--#if int($enable_recursive) > 0 then 'checked="checked"' else ""#--> />
|
||||
@@ -222,6 +230,16 @@
|
||||
<input type="checkbox" name="overwrite_files" id="overwrite_files" value="1" <!--#if int($overwrite_files) > 0 then 'checked="checked"' else ""#--> />
|
||||
<span class="desc">$T('explain-overwrite_files')</span>
|
||||
</div>
|
||||
<div class="field-pair">
|
||||
<label class="config" for="enable_filejoin">$T('opt-enable_filejoin')</label>
|
||||
<input type="checkbox" name="enable_filejoin" id="enable_filejoin" value="1" <!--#if int($enable_filejoin) > 0 then 'checked="checked"' else ""#--> />
|
||||
<span class="desc">$T('explain-enable_filejoin')</span>
|
||||
</div>
|
||||
<div class="field-pair">
|
||||
<label class="config" for="enable_tsjoin">$T('opt-enable_tsjoin')</label>
|
||||
<input type="checkbox" name="enable_tsjoin" id="enable_tsjoin" value="1" <!--#if int($enable_tsjoin) > 0 then 'checked="checked"' else ""#--> />
|
||||
<span class="desc">$T('explain-ts_join')</span>
|
||||
</div>
|
||||
<div class="field-pair">
|
||||
<label class="config" for="unpack_check">$T('opt-unpack_check')</label>
|
||||
<input type="checkbox" name="unpack_check" id="unpack_check" value="1" <!--#if int($unpack_check) > 0 then 'checked="checked"' else ""#--> />
|
||||
@@ -262,7 +280,6 @@
|
||||
</div>
|
||||
<div class="field-pair">
|
||||
<button class="btn btn-default saveButton"><span class="glyphicon glyphicon-ok"></span> $T('button-saveChanges')</button>
|
||||
<button class="btn btn-default restoreDefaults"><span class="glyphicon glyphicon-asterisk"></span> $T('button-restoreDefaults')</button>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div><!-- /col1 -->
|
||||
@@ -293,13 +310,11 @@
|
||||
<input type="checkbox" name="replace_illegal" id="replace_illegal" value="1" <!--#if int($replace_illegal) > 0 then 'checked="checked"' else ""#--> />
|
||||
<span class="desc">$T('explain-replace_illegal')</span>
|
||||
</div>
|
||||
<!--#if not $nt#-->
|
||||
<div class="field-pair">
|
||||
<label class="config" for="sanitize_safe">$T('opt-sanitize_safe')</label>
|
||||
<input type="checkbox" name="sanitize_safe" id="sanitize_safe" value="1" <!--#if int($sanitize_safe) > 0 then 'checked="checked"' else ""#--> />
|
||||
<span class="desc">$T('explain-sanitize_safe')</span>
|
||||
</div>
|
||||
<!--#end if#-->
|
||||
<div class="field-pair">
|
||||
<label class="config" for="enable_meta">$T('opt-enable_meta')</label>
|
||||
<input type="checkbox" name="enable_meta" id="enable_meta" value="1" <!--#if int($enable_meta) > 0 then 'checked="checked"' else ""#--> />
|
||||
@@ -307,7 +322,6 @@
|
||||
</div>
|
||||
<div class="field-pair">
|
||||
<button class="btn btn-default saveButton"><span class="glyphicon glyphicon-ok"></span> $T('button-saveChanges')</button>
|
||||
<button class="btn btn-default restoreDefaults"><span class="glyphicon glyphicon-asterisk"></span> $T('button-restoreDefaults')</button>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div><!-- /col1 -->
|
||||
@@ -360,20 +374,16 @@
|
||||
<input type="checkbox" name="rating_enable" id="rating_enable" value="1" <!--#if int($rating_enable) > 0 then 'checked="checked"' else ""#--> />
|
||||
<span class="desc">$T('explain-rating_enable')</span>
|
||||
</div>
|
||||
<div class="field-pair">
|
||||
<label class="config" for="rating_feedback">$T('opt-rating_feedback')</label>
|
||||
<input type="checkbox" name="rating_feedback" id="rating_feedback" value="1" <!--#if int($rating_feedback) > 0 then 'checked="checked"' else ""#--> />
|
||||
<span class="desc">$T('explain-rating_feedback')</span>
|
||||
</div>
|
||||
<div class="field-pair">
|
||||
<label class="config" for="rating_host">$T('opt-rating_host')</label>
|
||||
<input type="text" name="rating_host" id="rating_host" value="$rating_host" />
|
||||
</div>
|
||||
<div class="field-pair">
|
||||
<label class="config" for="rating_api_key">$T('opt-rating_api_key')</label>
|
||||
<input type="text" name="rating_api_key" id="rating_api_key" value="$rating_api_key" />
|
||||
<span class="desc">$T('explain-rating_api_key')</span>
|
||||
</div>
|
||||
<div class="field-pair">
|
||||
<label class="config" for="rating_feedback">$T('opt-rating_feedback')</label>
|
||||
<input type="checkbox" name="rating_feedback" id="rating_feedback" value="1" <!--#if int($rating_feedback) > 0 then 'checked="checked"' else ""#--> />
|
||||
<span class="desc">$T('explain-rating_feedback')</span>
|
||||
</div>
|
||||
<div class="field-pair">
|
||||
<label class="config" for="rating_filter_enable">$T('opt-rating_filter_enable')</label>
|
||||
<input type="checkbox" name="rating_filter_enable" id="rating_filter_enable" value="1" <!--#if int($rating_filter_enable) > 0 then 'checked="checked"' else ""#--> />
|
||||
@@ -500,30 +510,6 @@
|
||||
\$("#rating_filter_pause").hide();
|
||||
}
|
||||
});
|
||||
|
||||
\$('.restoreDefaults').click(function(e) {
|
||||
// Get section name
|
||||
var sectionName = \$(this).parents('.section').find('.col2 h3').text().trim()
|
||||
|
||||
// Confirm?
|
||||
if(!confirm("$T('explain-restoreDefaults') \""+sectionName+"\"\n$T('confirm')")) return false
|
||||
e.preventDefault()
|
||||
|
||||
// Need to get all the input values, so same way as saving normally
|
||||
var key_container = {}
|
||||
\$(this).parents('.section').extractFormDataTo(key_container);
|
||||
key_container = Object.keys(key_container)
|
||||
|
||||
// Send request
|
||||
\$.ajax({
|
||||
type: "GET",
|
||||
url: "../../tapi",
|
||||
data: "mode=set_config_default&session=${session}&output=json&keyword=" + key_container.join('&keyword=')
|
||||
}).then(function(data) {
|
||||
// Reload page
|
||||
document.location = document.location
|
||||
})
|
||||
})
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,62 +0,0 @@
|
||||
<html lang="$active_lang">
|
||||
<head>
|
||||
<title>SABnzbd</title>
|
||||
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="apple-mobile-web-app-title" content="SABnzbd" />
|
||||
|
||||
<link rel="apple-touch-icon" sizes="76x76" href="../staticcfg/ico/apple-touch-icon-76x76-precomposed.png" />
|
||||
<link rel="apple-touch-icon" sizes="120x120" href="../staticcfg/ico/apple-touch-icon-120x120-precomposed.png" />
|
||||
<link rel="apple-touch-icon" sizes="152x152" href="../staticcfg/ico/apple-touch-icon-152x152-precomposed.png" />
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="../staticcfg/ico/apple-touch-icon-180x180-precomposed.png" />
|
||||
<link rel="apple-touch-icon" sizes="192x192" href="../staticcfg/ico/android-192x192.png" />
|
||||
<link rel="shortcut icon" href="../staticcfg/ico/favicon.ico?v=1.1.0" />
|
||||
|
||||
<link rel="stylesheet" type="text/css" href="../staticcfg/bootstrap/css/bootstrap.min.css" />
|
||||
<link rel="stylesheet" type="text/css" href="../staticcfg/css/login.css?p=$pid" />
|
||||
|
||||
<script type="text/javascript" src="../staticcfg/js/jquery-1.11.2.min.js"></script>
|
||||
<script type="text/javascript" src="../staticcfg/bootstrap/js/bootstrap.min.js"></script>
|
||||
</head>
|
||||
<html>
|
||||
<body>
|
||||
<div class="account-wall">
|
||||
<div class="text-center logo-header">
|
||||
<!--#include $webdir + "/staticcfg/images/logo-full.svg"#-->
|
||||
<a href="https://sabnzbd.org/wiki/faq#why-login" target="_blank">
|
||||
<span class="glyphicon glyphicon-question-sign"></span>
|
||||
</a>
|
||||
</div>
|
||||
<form class="form-signin" action="./" method="post">
|
||||
<!--#if $error#-->
|
||||
<div class="alert alert-danger" role="alert">$error</div>
|
||||
<!--#end if#-->
|
||||
|
||||
<input type="text" class="form-control" name="username" placeholder="$T('srv-username')" required autofocus>
|
||||
<input type="password" class="form-control" name="password" placeholder="$T('srv-password')" required>
|
||||
|
||||
<button class="btn btn-default"><span class="glyphicon glyphicon-circle-arrow-right"></span> $T('login') </button>
|
||||
|
||||
<div class="checkbox text-center" data-toggle="tooltip" data-placement="bottom" title="$T('explain-sessionExpire')">
|
||||
<label>
|
||||
<input type="checkbox" name="remember_me" value="1"> $T('rememberme')
|
||||
</label>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
// Tooltip
|
||||
\$('[data-toggle="tooltip"]').tooltip()
|
||||
// Try-catch in case somebody disabled localstorage
|
||||
try {
|
||||
// Set what was done previously
|
||||
\$('input[type="checkbox"]').prop('checked', localStorage.getItem("remember_me") === 'true')
|
||||
// Store if we change something
|
||||
\$('input[type="checkbox"]').on('change', function() {
|
||||
localStorage.setItem("remember_me", \$(this).is(':checked'));
|
||||
})
|
||||
} catch(err) { }
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,3 +1,3 @@
|
||||
/* This file was intentionally left blank and is only needed for 'skin' detection routine */
|
||||
/* https://github.com/thezoggy/sabnzbd-uni_Config */
|
||||
/* Updated Nov 2015 by Safihre for 1.0.x */
|
||||
/* Updated Nov 2015 by Safihre for 0.8.x */
|
||||
@@ -1,74 +0,0 @@
|
||||
body {
|
||||
overflow-y: scroll;
|
||||
background-color: #E4E4E4;
|
||||
}
|
||||
|
||||
* {
|
||||
border-radius: 0 !important;
|
||||
}
|
||||
|
||||
.btn, .btn:hover, .btn:active, .btn:focus {
|
||||
box-shadow: 1px 1px 1px rgba(0,0,0,.1) !important;
|
||||
background-color: white !important;
|
||||
}
|
||||
|
||||
.logo-header {
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.logo-header svg {
|
||||
height: 55px;
|
||||
width: 198px;
|
||||
}
|
||||
|
||||
.account-wall {
|
||||
width: 300px;
|
||||
position: absolute;
|
||||
top: 30%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -30%);
|
||||
}
|
||||
|
||||
.text-center a {
|
||||
position: absolute;
|
||||
right: 13px;
|
||||
top: 12px;
|
||||
color: #636363;
|
||||
font-size: 1.2em
|
||||
}
|
||||
|
||||
.text-center a:hover {
|
||||
color: black !important;
|
||||
}
|
||||
|
||||
.form-signin .alert {
|
||||
margin: 5px 0px;
|
||||
}
|
||||
|
||||
|
||||
.form-signin input {
|
||||
margin-top: 5px
|
||||
}
|
||||
|
||||
.form-signin .btn {
|
||||
width: 100%;
|
||||
margin-top: 10px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.btn .glyphicon {
|
||||
margin-left: 3px;
|
||||
float: right;
|
||||
top: 1px;
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
input[type="checkbox"] {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
label {
|
||||
color: #636363;
|
||||
}
|
||||
@@ -83,15 +83,9 @@ body {
|
||||
.example {
|
||||
background-color: #fefeee;
|
||||
}
|
||||
.presets {
|
||||
margin-bottom: -6px;
|
||||
}
|
||||
.presets input {
|
||||
margin: 2px 0;
|
||||
}
|
||||
.presets input[type="button"] {
|
||||
margin: 0px 2px 6px 0px;
|
||||
}
|
||||
label.config,
|
||||
.field-pair h5 {
|
||||
overflow: auto;
|
||||
@@ -117,7 +111,6 @@ label.wide,
|
||||
}
|
||||
.field-pair h5 {
|
||||
margin-bottom: 5px;
|
||||
line-height: normal;
|
||||
}
|
||||
.desc {
|
||||
display: block;
|
||||
@@ -229,13 +222,8 @@ textarea:hover, input[type="date"]:hover, input[type="datetime"]:hover, input[ty
|
||||
.padding {
|
||||
padding: 6px;
|
||||
}
|
||||
.disabled,
|
||||
.disabled input,
|
||||
.disabled .clearBtn,
|
||||
.disabled .patternKey,
|
||||
.disabled .show_qrcode,
|
||||
.disabled .generate_key {
|
||||
color: #aaa !important;
|
||||
.disabled {
|
||||
color: #aaa;
|
||||
}
|
||||
.padTable p {
|
||||
margin-top: 0;
|
||||
@@ -410,18 +398,15 @@ tr.separator {
|
||||
background-color: #3C3C3C;
|
||||
color: white;
|
||||
border: 0;
|
||||
padding:11px 12px 8px;
|
||||
padding: 10px 12px;
|
||||
}
|
||||
.modal-header .close {
|
||||
opacity: 0.5;
|
||||
opacity: 0.7;
|
||||
background: transparent;
|
||||
color: white;
|
||||
text-shadow: none !important;
|
||||
font-weight: normal !important;
|
||||
font-size: 32px;
|
||||
vertical-align: middle;
|
||||
margin-top: -5px;
|
||||
font-family: arial, sans-serif !important;
|
||||
font-size: 28px;
|
||||
}
|
||||
.modal-header .close:hover {
|
||||
opacity: 1;
|
||||
@@ -432,20 +417,14 @@ tr.separator {
|
||||
opacity: 0 !important;
|
||||
transition: all 0.3s !important;
|
||||
}
|
||||
#modal_qr .modal-body canvas {
|
||||
padding: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.modal.in .modal-dialog {
|
||||
transform: scale(1) !important;
|
||||
-webkit-transform: scale(1) !important;
|
||||
opacity: 1 !important;
|
||||
}
|
||||
.btn-default.fileBrowser,
|
||||
.btn-default.clearBtn {
|
||||
margin-left: -1px !important;
|
||||
box-shadow: 1px 0px 1px rgba(0,0,0,.1) !important;
|
||||
.fileBrowser {
|
||||
margin-left: -1px!important;
|
||||
}
|
||||
#filebrowser_modal .list-group-item {
|
||||
font-weight: bold;
|
||||
@@ -618,7 +597,12 @@ ul.tabs li.active a {
|
||||
color: #000;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.ui-tooltip-qrcode {
|
||||
max-width: 320px;
|
||||
}
|
||||
.ui-tooltip-qrcode img {
|
||||
margin: 0 auto;
|
||||
}
|
||||
.checkbox-days {
|
||||
float: left;
|
||||
}
|
||||
@@ -804,53 +788,33 @@ input[type="checkbox"] {
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
.navbar .nav>li>a {
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
.navbar-default {
|
||||
background-color: #FFF;
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.2);
|
||||
margin-bottom: 10px;
|
||||
|
||||
}
|
||||
|
||||
.navbar-fixed-top {
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.navbar-logo {
|
||||
display: inline-block;
|
||||
padding-right: 10px;
|
||||
padding-left: 10px;
|
||||
padding-top: 5px;
|
||||
margin-left: 5px;
|
||||
margin-bottom: -1px;
|
||||
.navbar-default .navbar-brand {
|
||||
padding: 0 !important;
|
||||
margin: 0 !important;
|
||||
padding-right: 5px !important;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.navbar-logo-small svg {
|
||||
height: 40px;
|
||||
width: 40px;
|
||||
.navbar-default .navbar-brand-small {
|
||||
padding-right: 10px !important;
|
||||
}
|
||||
/*
|
||||
.navbar-logo-wide {
|
||||
.navbar-default .navbar-brand-mobile {
|
||||
display: none;
|
||||
}
|
||||
.navbar-logo-wide {
|
||||
padding-top: 9px;
|
||||
}
|
||||
.navbar-logo-wide svg {
|
||||
height: 36px;
|
||||
width: 130px;
|
||||
}
|
||||
*/
|
||||
.navbar-default .navbar-nav>li>a {
|
||||
color: black !important;
|
||||
}
|
||||
|
||||
.navbar-default .navbar-nav>li>a:hover,
|
||||
.navbar-logo:hover {
|
||||
.navbar-default .navbar-nav>li>a:hover {
|
||||
background-color: #e2e2e2 !important;
|
||||
}
|
||||
|
||||
@@ -910,17 +874,6 @@ input[type="checkbox"] {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.result-box .alert {
|
||||
margin-bottom: 0px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.alert-no-category,
|
||||
.alert-translate {
|
||||
display: none;
|
||||
margin: 5px 0px 0px;
|
||||
}
|
||||
|
||||
.server-disabled {
|
||||
background-color: #eee;
|
||||
}
|
||||
@@ -988,15 +941,6 @@ input[type="checkbox"] {
|
||||
100% { opacity: 1; }
|
||||
}
|
||||
|
||||
.spin-glyphicon {
|
||||
animation: spin 2s infinite linear;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(359deg); }
|
||||
}
|
||||
|
||||
@media screen and (min-width: 1200px) {
|
||||
.Categories input[name="dir"] {
|
||||
max-width: 240px !important;
|
||||
@@ -1160,3 +1104,4 @@ input[type="checkbox"] {
|
||||
padding-right: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 9.1 KiB |
|
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 6.1 KiB |
|
Before Width: | Height: | Size: 5.3 KiB After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 5.3 KiB After Width: | Height: | Size: 1.1 KiB |
@@ -1 +0,0 @@
|
||||
<svg viewBox="0 0 608 608" xmlns="http://www.w3.org/2000/svg" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414"><path d="M630.988 320.63V188.324h99.863v-35.62l110.197 101.773L730.85 356.25v-35.62h-99.86z" fill="none" stroke-width="16.62" stroke="#000" transform="matrix(0 2.665 -2.7482 0 1003.016 -1657.67)"/><path d="M121.86 23.913h363.604v266.135h97.89l-279.69 293.667L23.97 290.048h97.89V23.913z" fill="#FFB300" class="logo-arrow-outer"/><path d="M303.664 583.797L122.274 23.975h362.78l-181.39 559.822z" class="logo-arrow-inner" fill="#FFCA28"/></svg>
|
||||
|
Before Width: | Height: | Size: 594 B |
@@ -1 +0,0 @@
|
||||
<svg viewBox="0 0 2175 606" xmlns="http://www.w3.org/2000/svg" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414"><path d="M630.988 320.63V188.324h99.863v-35.62l110.2 101.773-110.2 101.773v-35.62h-99.86z" fill="none" stroke-width="16.62" stroke="#000" transform="matrix(0 2.665 -2.7482 0 1002.24 -1658.936)"/><path d="M121.09 22.648h363.603v266.145h97.89L302.893 582.47 23.2 288.794h97.89V22.648z" fill="#FFB300"/><path d="M302.893 582.47L121.503 22.647h362.78l-181.39 559.82z" fill="#FFCA28"/><path d="M223.63 123.757h1862.4v287.2H223.63z"/><path d="M165.902 268.765h202.5v99.277H165.9z"/><path d="M430.133 388.4H188.357v-50.88h143.46v-45.142h-143.46V146.24h241.776v50.5h-143.46v45.14h143.46V388.4zm143.46-50.88h45.14v-45.142h-45.14v45.142zm143.46 50.88h-241.78V241.88h143.46v-45.14h-143.46v-50.5H717.05V388.4zm143.457-50.88h45.14V196.74h-45.14v140.78zm-98.318 50.88V50.602h98.318v95.64h143.458V388.4H762.192zm385.235 0h-98.317V146.24h241.776V388.4h-98.317V196.74h-45.148V388.4zm430.377 0h-241.776v-50.88h47.82V289.7h47.82v-47.82h47.82v-45.14h-143.46v-50.5h241.776v98.318h-47.82v47.82h-47.82v45.142h95.64v50.88zm143.46-50.88h45.14V196.74h-45.14v140.78zm-98.318 50.88V50.602h98.317v95.64h143.46V388.4h-241.777zm385.234-50.88h45.142V196.74h-45.14v140.78zm-98.317 50.88V146.24h143.46V50.603h98.316V388.4h-241.78z" fill="none" stroke-width="45.001" stroke-linecap="round" stroke="#000"/><path d="M430.133 388.4H188.357v-50.88h143.46v-45.142h-143.46V146.24h241.776v50.5h-143.46v45.14h143.46V388.4zm143.46-50.88h45.14v-45.142h-45.14v45.142zm143.46 50.88h-241.78V241.88h143.46v-45.14h-143.46v-50.5H717.05V388.4zm143.457-50.88h45.14V196.74h-45.14v140.78zm-98.318 50.88V50.602h98.318v95.64h143.458V388.4H762.192zm385.235 0h-98.317V146.24h241.776V388.4h-98.317V196.74h-45.148V388.4zm430.377 0h-241.776v-50.88h47.82V289.7h47.82v-47.82h47.82v-45.14h-143.46v-50.5h241.776v98.318h-47.82v47.82h-47.82v45.142h95.64v50.88zm143.46-50.88h45.14V196.74h-45.14v140.78zm-98.318 50.88V50.602h98.317v95.64h143.46V388.4h-241.777zm385.234-50.88h45.142V196.74h-45.14v140.78zm-98.317 50.88V146.24h143.46V50.603h98.316V388.4h-241.78z" fill="#fff" fill-rule="nonzero"/></svg>
|
||||
|
Before Width: | Height: | Size: 2.1 KiB |
BIN
interfaces/Config/templates/staticcfg/images/logo-small.png
Normal file
|
After Width: | Height: | Size: 5.6 KiB |
@@ -1 +0,0 @@
|
||||
<svg viewBox="0 0 608 608" xmlns="http://www.w3.org/2000/svg" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414"><path d="M630.988 320.63V188.324h99.863v-35.62l110.197 101.773L730.85 356.25v-35.62h-99.86z" fill="none" stroke-width="16.62" stroke="#000" transform="matrix(0 2.665 -2.7482 0 1003.016 -1657.67)"/><path d="M121.86 23.913h363.604v266.135h97.89l-279.69 293.667L23.97 290.048h97.89V23.913z" fill="#FFB300"/><path d="M303.664 583.797L122.274 23.975h362.78l-181.39 559.822z" fill="#FFCA28"/><path d="M200.157 512.87H50.46v-31.503h88.824v-27.95H50.46v-90.48h149.697V394.2h-88.823v27.95h88.823v90.718zm88.823-31.503h27.95v-27.95h-27.95v27.95zm88.824 31.503H228.107v-90.718h88.823v-27.95h-88.823v-31.266h149.697V512.87zm88.823-31.503h27.95v-87.165h-27.95v87.165zm-60.874 31.503V303.72h60.874v59.216h88.823V512.87H405.753z" fill="none" stroke-width="45.004" stroke-linecap="round" stroke="#000"/><path d="M200.157 512.87H50.46v-31.503h88.824v-27.95H50.46v-90.48h149.697V394.2h-88.823v27.95h88.823v90.718zm88.823-31.503h27.95v-27.95h-27.95v27.95zm88.824 31.503H228.107v-90.718h88.823v-27.95h-88.823v-31.266h149.697V512.87zm88.823-31.503h27.95v-87.165h-27.95v87.165zm-60.874 31.503V303.72h60.874v59.216h88.823V512.87H405.753z" fill="#fff" fill-rule="nonzero"/></svg>
|
||||
|
Before Width: | Height: | Size: 1.3 KiB |
BIN
interfaces/Config/templates/staticcfg/images/logo.png
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
@@ -1,12 +1,11 @@
|
||||
<div class="history" id="history-tab" data-bind="visible: hasHistory() || displayTabbed()" style="display: none">
|
||||
<div class="history" data-bind="visible: hasHistory()" style="display: none;">
|
||||
<h2>$T('menu-history')</h2>
|
||||
<table class="table table-hover history-table paginated">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 25px;"></th>
|
||||
<th></th>
|
||||
<th class="table-status-header" data-bind="css: { 'table-header-status-smaller' : extraHistoryColumn }"></th>
|
||||
<th class="table-header-extra" data-bind="css: { 'table-extra-header-visible' : extraHistoryColumn }"></th>
|
||||
<th class="status-header"></th>
|
||||
<th style="width: 130px;"></th>
|
||||
<th style="width: 60px;"></th>
|
||||
</tr>
|
||||
@@ -137,10 +136,6 @@
|
||||
<!-- /ko -->
|
||||
</td>
|
||||
<td class="status row-wrap-text" data-bind="text: statusText()" onclick="showDetails(this)"></td>
|
||||
<td class="row-extra-text" onclick="showDetails(this)">
|
||||
<div class="row-wrap-text" data-bind="text: extraText">
|
||||
</div>
|
||||
</td>
|
||||
<td class="history-completedon row-wrap-text" data-bind="text: completedOn(), attr: { 'data-timestamp': completed }" onclick="showDetails(this)"></td>
|
||||
<td class="delete">
|
||||
<div class="dropdown">
|
||||
@@ -177,7 +172,7 @@
|
||||
<!-- ko foreach: { data: historyStatus.stage_log, afterRender: addHistoryStatusStuff } -->
|
||||
<div class="row">
|
||||
<div class="col-sm-2" data-bind="text: glitterTranslate.status[name()]"></div>
|
||||
<div class="col-sm-10 history-status-modallink" data-bind="longText: actions, longTextType: name()"></div>
|
||||
<div class="col-sm-10 history-status-modallink" data-bind="longText: actions"></div>
|
||||
</div>
|
||||
<!-- /ko -->
|
||||
</div>
|
||||
|
||||
@@ -5,9 +5,11 @@
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
</button>
|
||||
<a class="navbar-logo" href="./">
|
||||
#include $webdir + "/../../Config/templates/staticcfg/images/logo-small.svg"#
|
||||
<a href="./">
|
||||
<img src="./static/images/logo.png" width="120" height="45" alt="" id="navbar-logo-big" />
|
||||
<img src="./static/images/logo-small.png" width="45" height="45" alt="" id="navbar-logo-small" />
|
||||
</a>
|
||||
|
||||
<div class="btn-group">
|
||||
<div class="btn-group">
|
||||
<button type="button" class="btn btn-default navbar-btn" data-bind="click: pauseToggle, attr: { 'title': downloadsPaused() ? '$T('link-resume')' : '$T('link-pause')' }">
|
||||
@@ -89,14 +91,13 @@
|
||||
</a>
|
||||
<ul class="dropdown-menu menu-options">
|
||||
<li><a href="#modal-help" data-toggle="modal"><span class="glyphicon glyphicon-question-sign"></span> $T('menu-help')</a></li>
|
||||
<!--#if $have_logout or $have_quota or $have_rss_defined or $have_watched_dir or $pp_pause_event#--><li class="divider"></li><!--#end if#-->
|
||||
<!--#if $have_logout#--><li><a href="./login/?logout=1"><span class="glyphicon glyphicon-log-out"></span> $T('logout')</a></li><!--#end if#-->
|
||||
<!--#if $have_quota or $have_rss_defined or $have_watched_dir or $pp_pause_event#--><li class="divider"></li><!--#end if#-->
|
||||
<!--#if $have_quota#--><li><a href="#" data-bind="click: doQueueAction" data-mode="reset_quota">$T('link-resetQuota')</a></li><!--#end if#-->
|
||||
<!--#if $have_rss_defined#--><li><a href="#" data-bind="click: doQueueAction" data-mode="rss_now">$T('button-rssNow')</a></li><!--#end if#-->
|
||||
<!--#if $have_watched_dir#--><li><a href="#" data-bind="click: doQueueAction" data-mode="watched_now">$T('sch-scan_folder')</a></li><!--#end if#-->
|
||||
<!--#if $pp_pause_event#--><li><a href="#" data-bind="click: doQueueAction" data-mode="resume_pp">$T('sch-resume_post')</a></li><!--#end if#-->
|
||||
<li class="divider"></li>
|
||||
<li><a href="./shutdown/?session=$session&pid=$pid" data-bind="click: shutdownSAB">$T('shutdownSab')</a></li>
|
||||
<li><a href="./shutdown/?session=$session" data-bind="click: shutdownSAB">$T('shutdownSab')</a></li>
|
||||
<li><a href="#" data-bind="click: restartSAB">$T('Glitter-restartSab')</a></li>
|
||||
<li class="divider"></li>
|
||||
<li class="dropdown-header"><span class="glyphicon glyphicon-off"></span> $T('Glitter-onFinish'):</li>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<div id="queue-messages" class="queue-messages" data-bind="visible: hasMessages() || displayTabbed()" style="display: none;">
|
||||
<div id="queue-messages" class="queue-messages" data-bind="visible: hasMessages()" style="display: none;">
|
||||
<table class="table table-hover table-messages">
|
||||
<thead>
|
||||
<tr>
|
||||
@@ -35,12 +35,6 @@
|
||||
</td>
|
||||
</tr>
|
||||
<!-- /ko -->
|
||||
<!-- ko if: !hasMessages() && displayTabbed() -->
|
||||
<tr>
|
||||
<td>$T('none')</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<!-- /ko -->
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@@ -1,47 +1,3 @@
|
||||
<!-- Notifcation box -->
|
||||
<div class="main-notification-box" style="display: none">
|
||||
<div class="main-notification-box-uploading">
|
||||
<span class="glyphicon glyphicon-open"></span> $T('Glitter-notification-uploading') <span class="main-notification-box-file-count"></span>
|
||||
</div>
|
||||
|
||||
<div class="main-notification-box-queue-repair">
|
||||
<span class="glyphicon glyphicon glyphicon-wrench"></span> $T('Glitter-repairQueue')
|
||||
</div>
|
||||
|
||||
<div class="main-notification-box-disconnect">
|
||||
<span class="glyphicon glyphicon-minus-sign"></span> $T('Glitter-notification-disconnect')
|
||||
</div>
|
||||
|
||||
<div class="main-notification-box-rss_now">
|
||||
<span class="glyphicon glyphicon-chevron-right"></span> $T('button-rssNow')
|
||||
</div>
|
||||
|
||||
<div class="main-notification-box-watched_now">
|
||||
<span class="glyphicon glyphicon-chevron-right"></span> $T('sch-scan_folder')
|
||||
</div>
|
||||
|
||||
<div class="main-notification-box-sorting">
|
||||
<span class="glyphicon glyphicon-sort-by-alphabet"></span> $T('cmenu-sorting')
|
||||
</div>
|
||||
|
||||
<div class="main-notification-box-shutdown">
|
||||
<span class="glyphicon glyphicon-off"></span> $T('Glitter-notification-shutdown')
|
||||
</div>
|
||||
|
||||
<div class="main-notification-box-removing">
|
||||
<span class="glyphicon glyphicon-trash"></span> $T('Glitter-notification-removing1')
|
||||
</div>
|
||||
|
||||
<div class="main-notification-box-sendback">
|
||||
<span class="glyphicon glyphicon-plus"></span> $T('Glitter-backToQueue')
|
||||
</div>
|
||||
|
||||
<div class="main-notification-box-removing-multiple">
|
||||
<span class="glyphicon glyphicon-trash"></span> $T('Glitter-notification-removing') (<span class="main-notification-box-file-count"></span>)
|
||||
</div>
|
||||
<span class="loader-dot-one">.</span><span class="loader-dot-two">.</span><span class="loader-dot-three">.</span>
|
||||
</div>
|
||||
|
||||
<!-- Filedrop and Restarting overlay -->
|
||||
<div class="main-filedrop modal-backdrop fade in">
|
||||
<span class="glyphicon glyphicon-plus-sign"></span>
|
||||
@@ -84,65 +40,61 @@
|
||||
<ul class="nav nav-tabs">
|
||||
<li class="active"><a href="#options-status" data-toggle="tab">$T('menu-cons')</a></li>
|
||||
<li><a href="#options_connections" data-toggle="tab">$T('connections')</a></li>
|
||||
<li><a href="#options-orphans" data-toggle="tab">$T('Glitter-orphanedJobs') <!-- ko if: statusInfo.folders().length > 0 --><span class="label label-warning" data-bind="text: statusInfo.folders().length"></span><!-- /ko --></a></li>
|
||||
<li><a href="#options-orphans" data-toggle="tab">$T('Glitter-orphanedJobs') <!-- ko if: hasStatusInfo() && statusInfo.status.folders().length > 0 --><span class="label label-warning" data-bind="text: statusInfo.status.folders().length"></span><!-- /ko --></a></li>
|
||||
<li><a href="#options-interface" data-toggle="tab">$T('Glitter-interfaceOptions')</a></li>
|
||||
</ul>
|
||||
<div class="tab-content">
|
||||
<div class="tab-pane fade in active" id="options-status">
|
||||
<!-- ko if: !hasStatusInfo() || !statusInfo.hasOwnProperty('dashboard') -->
|
||||
<h4>$T('Glitter-loading')<span class="loader-dot-one">.</span><span class="loader-dot-two">.</span><span class="loader-dot-three">.</span></h4>
|
||||
<!-- /ko -->
|
||||
<!-- ko if: hasStatusInfo() && statusInfo.hasOwnProperty('dashboard') -->
|
||||
<div class="row">
|
||||
<div class="col-sm-6">$T('dashboard-localIP4')</div>
|
||||
<div class="col-sm-6" data-bind="visible: hasStatusInfo, text: !statusInfo.localipv4() ? '$T('dashboard-connectionError')' : statusInfo.localipv4(), css: { 'options-bad-status' : !statusInfo.localipv4() }"></div>
|
||||
<div class="col-sm-6 col-loading" data-bind="visible: !hasStatusInfo()">$T('Glitter-loading')<span class="loader-dot-one">.</span><span class="loader-dot-two">.</span><span class="loader-dot-three">.</span></div>
|
||||
<div class="col-sm-6" data-bind="text: !statusInfo.dashboard.localipv4() ? '$T('dashboard-connectionError')' : statusInfo.dashboard.localipv4(), css: { 'options-bad-status' : !statusInfo.dashboard.localipv4() }"></div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-6">$T('dashboard-publicIP4')</div>
|
||||
<div class="col-sm-6" data-bind="visible: hasStatusInfo, text: !statusInfo.publicipv4() ? '$T('dashboard-connectionError')' : statusInfo.publicipv4(), css: { 'options-bad-status ' : !statusInfo.publicipv4() }"></div>
|
||||
<div class="col-sm-6 col-loading" data-bind="visible: !hasStatusInfo()">$T('Glitter-loading')<span class="loader-dot-one">.</span><span class="loader-dot-two">.</span><span class="loader-dot-three">.</span></div>
|
||||
<div class="col-sm-6" data-bind="text: !statusInfo.dashboard.publicipv4() ? '$T('dashboard-connectionError')' : statusInfo.dashboard.publicipv4(), css: { 'options-bad-status ' : !statusInfo.dashboard.publicipv4() }"></div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="row" data-bind="visible: (statusInfo.dashboard.ipv6()!=='')">
|
||||
<div class="col-sm-6">$T('dashboard-IP6')</div>
|
||||
<div class="col-sm-6" data-bind="visible: hasStatusInfo, text: statusInfo.ipv6"></div>
|
||||
<div class="col-sm-6 col-loading" data-bind="visible: !hasStatusInfo()">$T('Glitter-loading')<span class="loader-dot-one">.</span><span class="loader-dot-two">.</span><span class="loader-dot-three">.</span></div>
|
||||
<div class="col-sm-6" data-bind="text: statusInfo.dashboard.ipv6"></div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-6">$T('dashboard-NameserverDNS')</div>
|
||||
<div class="col-sm-6" data-bind="visible: hasStatusInfo, text: !statusInfo.dnslookup() ? '$T('dashboard-connectionError')' : statusInfo.dnslookup(), css: { 'options-bad-status' : (statusInfo.dnslookup() != 'OK') }"></div>
|
||||
<div class="col-sm-6 col-loading" data-bind="visible: !hasStatusInfo()">$T('Glitter-loading')<span class="loader-dot-one">.</span><span class="loader-dot-two">.</span><span class="loader-dot-three">.</span></div>
|
||||
<div class="col-sm-6" data-bind="text: !statusInfo.dashboard.dnslookup() ? '$T('dashboard-connectionError')' : statusInfo.dashboard.dnslookup(), css: { 'options-bad-status' : (statusInfo.dashboard.dnslookup() != 'OK') }"></div>
|
||||
</div>
|
||||
<hr/>
|
||||
<div class="row">
|
||||
<div class="col-sm-6">$T('cache')</div>
|
||||
<div class="col-sm-6" data-bind="visible: hasStatusInfo">
|
||||
<span data-bind="text: statusInfo.cache_size"></span> (<span data-bind="text: statusInfo.cache_art"></span> $T('Glitter-articles'))
|
||||
</div>
|
||||
<div class="col-sm-6 col-loading" data-bind="visible: !hasStatusInfo()">$T('Glitter-loading')<span class="loader-dot-one">.</span><span class="loader-dot-two">.</span><span class="loader-dot-three">.</span></div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-6">$T('dashboard-systemPerformance')</div>
|
||||
<div class="col-sm-6" data-bind="visible: hasStatusInfo">
|
||||
<span data-bind="text: statusInfo.pystone"></span>
|
||||
<div class="col-sm-6">
|
||||
<span data-bind="text: statusInfo.dashboard.pystone"></span>
|
||||
<a href="#" data-bind="click: loadStatusInfo" data-tooltip="true" data-placement="right" title="$T('dashboard-repeatTest')"><span class="glyphicon glyphicon-repeat"></span></a>
|
||||
<small data-bind="truncatedText: statusInfo.cpumodel, length: 25"></small>
|
||||
<small data-bind="truncatedText: statusInfo.dashboard.cpumodel, length: 25"></small>
|
||||
</div>
|
||||
<div class="col-sm-6 col-loading" data-bind="visible: !hasStatusInfo()">$T('Glitter-loading')<span class="loader-dot-one">.</span><span class="loader-dot-two">.</span><span class="loader-dot-three">.</span></div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-6">$T('dashboard-downloadDirSpeed')</div>
|
||||
<div class="col-sm-6" data-bind="visible: hasDiskStatusInfo">
|
||||
<span data-bind="text: statusInfo.downloaddirspeed()"></span> MB/s
|
||||
<a href="#" class="diskspeed-button" data-bind="click: testDiskSpeed" data-tooltip="true" data-placement="right" title="$T('dashboard-repeatTest')"><span class="glyphicon glyphicon-repeat"></span></a>
|
||||
<small>(<span data-bind="truncatedText: statusInfo.downloaddir, length: 24"></span>)</small>
|
||||
<div class="col-sm-6">
|
||||
<span data-bind="text: (statusInfo.dashboard.downloaddirspeed() > 0 ? statusInfo.dashboard.downloaddirspeed() : '0'), css: { 'options-bad-status' : statusInfo.dashboard.downloaddirspeedbad() }"></span> MB/s
|
||||
<a href="#" data-bind="click: testDiskSpeed" data-tooltip="true" data-placement="right" title="$T('dashboard-repeatTest')"><span class="glyphicon glyphicon-repeat"></span></a>
|
||||
<small>(<span data-bind="truncatedText: statusInfo.dashboard.downloaddir, length: 24"></span>)</small>
|
||||
</div>
|
||||
<div class="col-sm-6 col-loading" data-bind="visible: !hasDiskStatusInfo()">$T('Glitter-loading')<span class="loader-dot-one">.</span><span class="loader-dot-two">.</span><span class="loader-dot-three">.</span></div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-6">$T('dashboard-completeDirSpeed')</div>
|
||||
<div class="col-sm-6" data-bind="visible: hasDiskStatusInfo">
|
||||
<span data-bind="text: statusInfo.completedirspeed()"></span> MB/s
|
||||
<a href="#" class="diskspeed-button" data-bind="click: testDiskSpeed" data-tooltip="true" data-placement="right" title="$T('dashboard-repeatTest')"><span class="glyphicon glyphicon-repeat"></span></a>
|
||||
<small>(<span data-bind="truncatedText: statusInfo.completedir, length: 24"></span>)</small>
|
||||
<div class="col-sm-6">
|
||||
<span data-bind="text: (statusInfo.dashboard.completedirspeed() > 0 ? statusInfo.dashboard.completedirspeed() : '0'), css: { 'options-bad-status' : statusInfo.dashboard.completedirspeedbad() }"></span> MB/s
|
||||
<a href="#" data-bind="click: testDiskSpeed" data-tooltip="true" data-placement="right" title="$T('dashboard-repeatTest')"><span class="glyphicon glyphicon-repeat"></span></a>
|
||||
<small>(<span data-bind="truncatedText: statusInfo.dashboard.completedir, length: 24"></span>)</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-6">$T('cache')</div>
|
||||
<div class="col-sm-6">
|
||||
<span data-bind="text: statusInfo.status.cache.cache_size"></span> (<span data-bind="text: statusInfo.status.cache.cache_current"></span> $T('Glitter-articles'))
|
||||
</div>
|
||||
<div class="col-sm-6 col-loading" data-bind="visible: !hasDiskStatusInfo()">$T('Glitter-loading')<span class="loader-dot-one">.</span><span class="loader-dot-two">.</span><span class="loader-dot-three">.</span></div>
|
||||
</div>
|
||||
<hr />
|
||||
<div class="row options-function-box">
|
||||
@@ -162,7 +114,7 @@
|
||||
<div class="col-sm-6">
|
||||
<div class="input-group" data-tooltip="true" data-placement="top" title="$T('logging')">
|
||||
<span class="input-group-addon"><span class="glyphicon glyphicon-comment"></span></span>
|
||||
<select class="form-control" data-bind="value: statusInfo.loglevel">
|
||||
<select class="form-control" data-bind="value: statusInfo.status.loglevel">
|
||||
<option value="0">$T('log-errWarn')</option>
|
||||
<option value="1">$T('log-info')</option>
|
||||
<option value="2">$T('log-debug')</option>
|
||||
@@ -170,51 +122,48 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- /ko -->
|
||||
</div>
|
||||
<div class="tab-pane fade" id="options_connections">
|
||||
<!-- ko if: !hasStatusInfo() -->
|
||||
<h4>$T('Glitter-loading')<span class="loader-dot-one">.</span><span class="loader-dot-two">.</span><span class="loader-dot-three">.</span></h4>
|
||||
<!-- /ko -->
|
||||
<!-- ko if: hasStatusInfo() -->
|
||||
<div class="options-switch">
|
||||
<label>
|
||||
<input type="checkbox" value="1" name="showConnections" data-bind="checked: showActiveConnections" />
|
||||
<span>$T('Glitter-showActiveConnections')</span>
|
||||
</label>
|
||||
</div>
|
||||
<div data-bind="foreach: statusInfo.servers">
|
||||
<div data-bind="foreach: statusInfo.status.servers">
|
||||
<div class="options-server-box">
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-6">$T('swtag-server')</div>
|
||||
<div class="col-sm-6">
|
||||
<span data-bind="text: servername"></span>
|
||||
<span class="label label-default" data-bind="visible: serveroptional">$T('optional').capitalize()</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row" data-bind="visible: serverblocked()">
|
||||
<div class="col-sm-12">
|
||||
|
||||
<div class="alert alert-danger">
|
||||
<a href="#" data-bind="visible: serverblocked(), click: function() { \$parent.unblockServer(servername()) }" class="btn btn-default"><span class="glyphicon glyphicon-share-alt"></span> $T('Glitter-unblockServer')</a>
|
||||
<span data-bind="text: serverblocked()"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-6">$T('status')</div>
|
||||
<div class="col-sm-6" data-bind="text: serverstatus"></div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-6">$T('priority')</div>
|
||||
<div class="col-sm-6" data-bind="text: serverpriority"></div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-6"># $T('connections')</div>
|
||||
<div class="col-sm-6">
|
||||
<span data-bind="text: serverconnections().length"></span> /
|
||||
<span data-bind="text: servertotalconn"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row" data-bind="visible: servererror()">
|
||||
<div class="col-sm-12">
|
||||
<div class="alert alert-danger">
|
||||
<a href="#" data-bind="visible: !serveractive(), click: function() { \$parent.unblockServer(servername()) }" class="btn btn-default"><span class="glyphicon glyphicon-share-alt"></span> $T('Glitter-unblockServer')</a>
|
||||
<span class="glyphicon glyphicon-exclamation-sign"></span>
|
||||
<span data-bind="text: servererror()"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row" data-bind="visible: !isFinite(serveractiveconn())">
|
||||
<div class="col-sm-12">
|
||||
<div class="alert alert-warning">
|
||||
<span class="glyphicon glyphicon-info-sign"></span>
|
||||
<span data-bind="text: serveractiveconn()"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6" data-bind="text: serveractiveconn"></div>
|
||||
</div>
|
||||
|
||||
<!-- ko if: serverconnections().length > 0 -->
|
||||
@@ -229,9 +178,9 @@
|
||||
<tbody>
|
||||
<!-- ko foreach: serverconnections -->
|
||||
<tr>
|
||||
<td class="row-wrap-text"><span data-bind="text: art_name"></span></td>
|
||||
<td class="row-wrap-text"><span data-bind="text: nzf_name"></span></td>
|
||||
<td class="row-wrap-text"><span data-bind="text: nzo_name"></span></td>
|
||||
<td class="row-wrap-text"><span data-bind="text: articleid, attr: { title: articleid }"></span></td>
|
||||
<td class="row-wrap-text"><span data-bind="text: filename, attr: { title: filename }"></span></td>
|
||||
<td class="row-wrap-text"><span data-bind="text: fileset, attr: { title: fileset }"></span></td>
|
||||
</tr>
|
||||
<!-- /ko -->
|
||||
</tbody>
|
||||
@@ -239,33 +188,38 @@
|
||||
<!-- /ko -->
|
||||
</div>
|
||||
</div>
|
||||
<!-- /ko -->
|
||||
</div>
|
||||
<div class="tab-pane fade" id="options-orphans">
|
||||
<!-- ko if: statusInfo.folders().length == 0 -->
|
||||
<h4>$T('none')</h4>
|
||||
<!-- ko if: !hasStatusInfo() -->
|
||||
<h4>$T('Glitter-loading')<span class="loader-dot-one">.</span><span class="loader-dot-two">.</span><span class="loader-dot-three">.</span></h4>
|
||||
<!-- /ko -->
|
||||
<!-- ko if: statusInfo.folders().length > 0 -->
|
||||
<a href="#" class="hover-button process-all-orphaned" data-bind="click: removeAllOrphaned">$T('Glitter-purgeOrphaned') <span class="glyphicon glyphicon-trash"></span></a>
|
||||
<a href="#" class="hover-button process-all-orphaned" data-bind="click: addAllOrphaned">$T('Glitter-retryAllOrphaned') <span class="glyphicon glyphicon-plus-sign"></span></a>
|
||||
<div class="clearfix"></div>
|
||||
<table class="table table-hover table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 30px;"></th>
|
||||
<th></th>
|
||||
<th style="width: 30px;"></th>
|
||||
<th style="width: 30px;"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody data-bind="foreach: statusInfo.folders">
|
||||
<tr>
|
||||
<td><span class="glyphicon glyphicon-folder-open"></span></td>
|
||||
<td class="row-wrap-text"><strong data-bind="html: \$data"></strong></td>
|
||||
<td><a href="#" data-bind="click: \$root.folderProcess" data-action="add" class="hover-button" data-tooltip="true" data-placement="left" title="$T('Glitter-backToQueue')"><span class="glyphicon glyphicon-plus-sign"></span></a></td>
|
||||
<td><a href="#" data-bind="click: \$root.folderProcess" data-action="delete" class="hover-button" data-tooltip="true" data-placement="left" title="$T('Glitter-deleteJobAndFolders')"><span class="glyphicon glyphicon-trash"></span></a></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<!-- ko if: hasStatusInfo() -->
|
||||
<!-- ko if: statusInfo.status.folders().length == 0 -->
|
||||
<h4>$T('none')</h4>
|
||||
<!-- /ko -->
|
||||
<!-- ko if: statusInfo.status.folders().length > 0 -->
|
||||
<a href="#" class="hover-button remove-all-orphaned" data-bind="click: removeAllOrphaned">$T('Glitter-purgeOrphaned') <span class="glyphicon glyphicon-trash"></span></a>
|
||||
<div class="clearfix"></div>
|
||||
<table class="table table-hover table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 30px;"></th>
|
||||
<th></th>
|
||||
<th style="width: 30px;"></th>
|
||||
<th style="width: 30px;"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody data-bind="foreach: statusInfo.status.folders">
|
||||
<tr>
|
||||
<td><span class="glyphicon glyphicon-folder-open"></span></td>
|
||||
<td class="row-wrap-text"><strong data-bind="html: folder"></strong></td>
|
||||
<td><a href="#" data-bind="click: \$root.folderProcess" data-action="add" class="hover-button" data-tooltip="true" data-placement="left" title="$T('Glitter-backToQueue')"><span class="glyphicon glyphicon-plus-sign"></span></a></td>
|
||||
<td><a href="#" data-bind="click: \$root.folderProcess" data-action="delete" class="hover-button" data-tooltip="true" data-placement="left" title="$T('Glitter-deleteJobAndFolders')"><span class="glyphicon glyphicon-trash"></span></a></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<!-- /ko -->
|
||||
<!-- /ko -->
|
||||
</div>
|
||||
<div class="tab-pane fade" id="options-interface">
|
||||
@@ -345,11 +299,11 @@
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="col-sm-6 control-label">
|
||||
$T('Glitter-showExtraQueueColumn')<br />
|
||||
$T('Glitter-showExtraColumn')<br />
|
||||
<span class="label label-warning">> 1200 pixels</span>
|
||||
</label>
|
||||
<div class="col-sm-4">
|
||||
<select name="general-extra-column" class="form-control" data-bind="value: extraQueueColumn">
|
||||
<select name="general-extra-column" class="form-control" data-bind="value: extraColumn">
|
||||
<option value="">$T('none')</option>
|
||||
<option value="category">$T('category')</option>
|
||||
<option value="priority">$T('priority')</option>
|
||||
@@ -359,36 +313,6 @@
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="col-sm-6 control-label">
|
||||
$T('Glitter-showExtraHistoryColumn')<br />
|
||||
<span class="label label-warning">> 1200 pixels</span>
|
||||
</label>
|
||||
<div class="col-sm-4">
|
||||
<select name="general-extra-column" class="form-control" data-bind="value: extraHistoryColumn">
|
||||
<option value="">$T('none')</option>
|
||||
<option value="category">$T('category')</option>
|
||||
<option value="size">$T('size')</option>
|
||||
<option value="speed">$T('Glitter-speed')</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group form-checkbox">
|
||||
<label class="col-sm-6 control-label">
|
||||
$T("Glitter-displayCompact")
|
||||
</label>
|
||||
<div class="col-sm-4">
|
||||
<input type="checkbox" name="displayCompact" value="true" data-bind="checked: displayCompact" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group form-checkbox">
|
||||
<label class="col-sm-6 control-label">
|
||||
$T("Glitter-displayTabbed")
|
||||
</label>
|
||||
<div class="col-sm-4">
|
||||
<input type="checkbox" name="displayTabbed" value="true" data-bind="checked: displayTabbed" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group form-checkbox">
|
||||
<label class="col-sm-6 control-label">
|
||||
$T("Glitter-confirmDeleteQueue")
|
||||
@@ -440,7 +364,7 @@
|
||||
<div class="input-group" data-tooltip="true" data-placement="bottom" title="$T('Glitter-nzbFormats')">
|
||||
<label class="btn btn-default btn-file">
|
||||
<span class="glyphicon glyphicon-file"></span> <em>$T('Glitter-chooseFile')…</em>
|
||||
<input type="file" multiple name="nzbFile" class="form-control" accept=".nzb,.rar,.zip,.gz,.bz2" data-bind="event : { change: updateBrowseLabel }" />
|
||||
<input type="file" name="nzbFile" class="form-control" accept=".nzb,.rar,.zip,.gz,.bz2" data-bind="event : { change: updateBrowseLabel }" />
|
||||
</label>
|
||||
|
||||
<span class="input-group-btn">
|
||||
@@ -487,7 +411,7 @@
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal">×</button>
|
||||
<h4 class="modal-title row-wrap-text" data-bind="text: filelist.filelist_name">$T('Glitter-loading')</h4>
|
||||
<h4 class="modal-title row-wrap-text" data-bind="text: filelist.modalTitle">$T('Glitter-loading')</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div id="modal-item-filelist">
|
||||
@@ -498,7 +422,7 @@
|
||||
<span class="glyphicon glyphicon-lock"></span>
|
||||
<span class="glyphicon glyphicon-floppy-saved"></span>
|
||||
</span>
|
||||
<input type="text" class="form-control" id="nzb_password" placeholder="$T('srv-password')" data-bind="value: filelist.filelist_password" />
|
||||
<input type="text" class="form-control" id="nzb_password" placeholder="$T('srv-password')" data-bind="value: filelist.modalPassword" />
|
||||
<button type="submit" class="btn btn-default">$T('Glitter-submit')</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -602,34 +526,29 @@
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal">×</button>
|
||||
<h4 class="modal-title">$T('menu-help')</h4>
|
||||
<img src="./static/images/logo.png" width="120" height="45" alt="" />
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
#include $webdir + "/../../Config/templates/staticcfg/images/logo-full.svg"#
|
||||
<p><strong>SABnzbd $('version'):</strong> $version</p>
|
||||
<hr/>
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><strong>$T('menu-wiki'):</strong></td>
|
||||
<td><a href="https://sabnzbd.org/wiki/" target="_blank">https://sabnzbd.org/wiki/</a></td>
|
||||
<td><a href="http://wiki.sabnzbd.org/" target="_blank">http://wiki.sabnzbd.org/</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>$T('menu-forums'):</strong></td>
|
||||
<td><a href="http://forums.sabnzbd.org/" target="_blank">http://forums.sabnzbd.org/</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>GitHub:</strong></td>
|
||||
<td><a href="https://github.com/sabnzbd/sabnzbd" target="_blank">https://github.com/sabnzbd/sabnzbd/</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>$T('menu-irc'):</strong></td>
|
||||
<td><a href="http://www.sabnzbd.org/live-chat/" target="_blank">http://www.sabnzbd.org/live-chat/</a></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<hr/>
|
||||
<p><small>Copyright (C) 2008-2016, The SABnzbd Team <team@sabnzbd.org><br/>$T('yourRights') </small></p>
|
||||
|
||||
<p><strong>SABnzbd $('version'):</strong> $version</p>
|
||||
<p><small>Copyright (C) 2008-2016, The SABnzbd Team <team@sabnzbd.org></small></p>
|
||||
<p><small>$T('yourRights') </small></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,4 +1,4 @@
|
||||
<div class="queue active" id="queue-tab">
|
||||
<div class="queue">
|
||||
<h2>$T('menu-queue')</h2>
|
||||
|
||||
<div class="info-container" data-bind="visible: diskSpaceLeft1()" style="display: none;">
|
||||
@@ -9,12 +9,6 @@
|
||||
</a>
|
||||
</div>
|
||||
<!-- /ko -->
|
||||
<!--#if $loadavg#-->
|
||||
<div class="info-container-box" title="$T('ft-sysload') - $T('menu-config') ➜ $T('cmenu-special') ➜ show_sysload">
|
||||
<span class="glyphicon glyphicon-record"></span>
|
||||
<span data-bind="text: systemLoad"></span>
|
||||
</div>
|
||||
<!--#end if#-->
|
||||
<!-- ko if: (queueDataLeft() != '') -->
|
||||
<div class="info-container-box">
|
||||
<span class="glyphicon glyphicon-save"></span>
|
||||
@@ -28,14 +22,14 @@
|
||||
</span>
|
||||
</div>
|
||||
<!-- /ko -->
|
||||
<div class="info-container-box" data-bind="css: { 'queue-error-info' : diskSpaceExceeded1() }">
|
||||
<div class="info-container-box" data-bind="css: { 'queue-error-info' : diskSpaceExceeded1 }">
|
||||
<span class="glyphicon glyphicon-hdd"></span> <span data-bind="text: diskSpaceLeft1"></span>B $T('Glitter-free')
|
||||
<!-- ko if: diskSpaceLeft2() -->
|
||||
<em>($T('Glitter-freeTemp'))</em>
|
||||
<!-- /ko -->
|
||||
</div>
|
||||
<!-- ko if: diskSpaceLeft2() -->
|
||||
<div class="info-container-box" data-bind="css: { 'queue-error-info' : diskSpaceExceeded2() }">
|
||||
<div class="info-container-box" data-bind="css: { 'queue-error-info' : diskSpaceExceeded2 }">
|
||||
<span class="glyphicon glyphicon-hdd"></span> <span data-bind="text: diskSpaceLeft2"></span>B $T('Glitter-free')
|
||||
</div>
|
||||
<!-- /ko -->
|
||||
@@ -60,10 +54,10 @@
|
||||
<table class="table table-striped table-hover queue-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 25px;"></th>
|
||||
<th style="width: 33px;"></th>
|
||||
<th></th>
|
||||
<th class="table-header-extra" data-bind="css: { 'table-extra-header-visible' : extraQueueColumn }"></th>
|
||||
<th class="table-header-progress" data-bind="css: { 'table-header-progress-smaller' : extraQueueColumn }"></th>
|
||||
<th class="table-header-extra" data-bind="css: { 'table-extra-header-visible' : extraColumn }"></th>
|
||||
<th class="table-header-progress" data-bind="css: { 'table-header-progress-smaller' : extraColumn }"></th>
|
||||
<th style="width: 85px;"></th>
|
||||
<th style="width: 60px;"></th>
|
||||
</tr>
|
||||
@@ -88,13 +82,7 @@
|
||||
</td>
|
||||
<td class="name">
|
||||
<div class="row-wrap-text" data-bind="visible: !editingName()">
|
||||
<span data-bind="text: name, attr: { 'title': name() }"></span>
|
||||
<!-- ko if: password() -->
|
||||
<small class="queue-item-password">
|
||||
<span class="glyphicon glyphicon-lock"></span>
|
||||
<span data-bind="text: password"></span>
|
||||
</small>
|
||||
<!-- /ko -->
|
||||
<span data-bind="html: displayName, attr: { 'title': name() }"></span>
|
||||
<!-- ko if: (rating_avg_video() !== false) -->
|
||||
<div class="name-ratings hover-button">
|
||||
<span class="glyphicon glyphicon-facetime-video"></span> <span data-bind="text: rating_avg_video"></span>
|
||||
@@ -111,7 +99,7 @@
|
||||
<small data-bind="text: avg_age"></small>
|
||||
</div>
|
||||
</td>
|
||||
<td class="row-extra-text">
|
||||
<td class="queue-extra-text">
|
||||
<div class="row-wrap-text" data-bind="text: extraText">
|
||||
</div>
|
||||
</td>
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<meta name="application-name" content="SABnzbd">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<meta name="apple-mobile-web-app-title" content="SABnzbd" />
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black" />
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="#000000" />
|
||||
<meta name="msapplication-navbutton-color" content="#000000" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
|
||||
@@ -29,8 +29,7 @@
|
||||
<link rel="apple-touch-icon" sizes="120x120" href="./staticcfg/ico/apple-touch-icon-120x120-precomposed.png" />
|
||||
<link rel="apple-touch-icon" sizes="152x152" href="./staticcfg/ico/apple-touch-icon-152x152-precomposed.png" />
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="./staticcfg/ico/apple-touch-icon-180x180-precomposed.png" />
|
||||
<link rel="apple-touch-icon" sizes="192x192" href="./staticcfg/ico/android-192x192.png" />
|
||||
<link rel="shortcut icon" type="image/ico" href="./staticcfg/ico/favicon.ico?v=1.0.0" data-bind="attr: { 'href': SABIcon }" />
|
||||
<link rel="shortcut icon" type="image/ico" href="./staticcfg/ico/favicon.ico" data-bind="attr: { 'href': SABIcon }" />
|
||||
|
||||
<link rel="stylesheet" type="text/css" href="./static/bootstrap/css/bootstrap.min.css?p=$pid" />
|
||||
<link rel="stylesheet" type="text/css" href="./static/stylesheets/glitter.css?p=$pid" />
|
||||
@@ -75,9 +74,8 @@
|
||||
glitterTranslate.sendThanks = "$T('Glitter-sendThanks')";
|
||||
glitterTranslate.chooseFile = "$T('Glitter-chooseFile')";
|
||||
glitterTranslate.orphanedJobsMsg = "$T('explain-orphans')";
|
||||
glitterTranslate.useCache = "$T('explain-cache_limitstr').replace("64M", "256M").replace("128M", "512M")";
|
||||
glitterTranslate.useCache = "$T('explain-cache_limitstr')";
|
||||
glitterTranslate.noLocalStorage = "$T('Glitter-noLocalStorage')";
|
||||
glitterTranslate.glitterTips = "$T('Glitter-glitterTips')";
|
||||
glitterTranslate.updateAvailable = "$T('Glitter-updateAvailable')";
|
||||
glitterTranslate.defaultText = "$T('default')";
|
||||
glitterTranslate.noneText = "$T('None')";
|
||||
@@ -122,14 +120,14 @@
|
||||
|
||||
<!-- Inclusion is faster than external scripts. We load momentJS locale seperatly so failure won't break anything -->
|
||||
<script type="text/javascript">
|
||||
<!--#include raw $webdir + "/static/javascripts/jquery.min.js"#-->
|
||||
<!--#include raw $webdir + "/static/javascripts/jquery-1.11.2.min.js"#-->
|
||||
<!--#include raw $webdir + "/static/javascripts/jquery-ui.min.js"#-->
|
||||
<!--#include raw $webdir + "/static/javascripts/jquery.peity.min.js"#-->
|
||||
<!--#include raw $webdir + "/static/javascripts/moment.js"#-->
|
||||
<!--#include raw $webdir + "/static/javascripts/knockout-latest.js"#-->
|
||||
<!--#include raw $webdir + "/static/javascripts/knockout-extensions.js"#-->
|
||||
<!--#include raw $webdir + "/static/bootstrap/js/bootstrap.min.js"#-->
|
||||
<!--#include $webdir + "/static/javascripts/glitter.js"#-->
|
||||
<!--#include raw $webdir + "/static/javascripts/glitter.js"#-->
|
||||
</script>
|
||||
<!--#if active_lang != 'en'#-->
|
||||
<script type="text/javascript" src="./static/javascripts/momentjs_locale/${active_lang}.js"></script>
|
||||
@@ -141,25 +139,11 @@
|
||||
</div>
|
||||
|
||||
<div class="container main-content">
|
||||
<div class="history-queue-swicher">
|
||||
<ul class="nav nav-tabs">
|
||||
<li class="active">
|
||||
<a href="#queue-tab" data-toggle="tab">$T('menu-queue') <span class="badge" data-bind="text: queue.totalItems"></span></a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#history-tab" data-toggle="tab">$T('menu-history')<span class="badge" data-bind="text: history.totalItems"></span></a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#queue-messages" data-toggle="tab">$T('warnings')<span class="badge" data-bind="text: hasMessages, css: { 'badge-warning': hasMessages() }"></span></a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!--#include $webdir + "/include_queue.tmpl"#-->
|
||||
<!--#include $webdir + "/include_messages.tmpl"#-->
|
||||
<!--#include $webdir + "/include_history.tmpl"#-->
|
||||
</div>
|
||||
|
||||
<!--#include $webdir + "/include_overlays.tmpl"#-->
|
||||
<!--#include $webdir + "/include_modals.tmpl"#-->
|
||||
</body>
|
||||
</html>
|
||||
|
||||
BIN
interfaces/Glitter/templates/static/images/logo-small.png
Normal file
|
After Width: | Height: | Size: 4.9 KiB |
BIN
interfaces/Glitter/templates/static/images/logo.png
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
@@ -1,281 +0,0 @@
|
||||
/**
|
||||
Base variables and functions
|
||||
**/
|
||||
var fadeOnDeleteDuration = 400; // ms after deleting a row
|
||||
var isMobile = (/android|webos|iphone|ipad|ipod|blackberry|iemobile|opera mini/i.test(navigator.userAgent.toLowerCase()));
|
||||
|
||||
// To avoid problems when localStorage is disabled
|
||||
var hasLocalStorage = true;
|
||||
function localStorageSetItem(varToSet, valueToSet) { try { return localStorage.setItem(varToSet, valueToSet); } catch(e) { hasLocalStorage = false; } }
|
||||
function localStorageGetItem(varToGet) { try { return localStorage.getItem(varToGet); } catch(e) { hasLocalStorage = false; } }
|
||||
|
||||
// For mobile we disable zoom while a modal is being opened
|
||||
// so it will not zoom unnecessarily on the modal
|
||||
if(isMobile) {
|
||||
$('.modal').on('show.bs.modal', function() {
|
||||
$('meta[name="viewport"]').attr('content', 'width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no');
|
||||
});
|
||||
|
||||
// Restore on modal-close. Need timeout, otherwise it doesn't work
|
||||
$('.modal').on('hidden.bs.modal', function() {
|
||||
setTimeout(function() {
|
||||
$('meta[name="viewport"]').attr('content', 'width=device-width, initial-scale=1');
|
||||
},500);
|
||||
});
|
||||
}
|
||||
|
||||
// Basic API-call
|
||||
function callAPI(data) {
|
||||
// Fill basis var's
|
||||
data.output = "json";
|
||||
data.apikey = apiKey;
|
||||
var ajaxQuery = $.ajax({
|
||||
url: "./tapi",
|
||||
type: "GET",
|
||||
cache: false,
|
||||
data: data,
|
||||
timeout: 10000 // Wait a little longer on mobile connections
|
||||
});
|
||||
|
||||
return $.when(ajaxQuery);
|
||||
}
|
||||
|
||||
// Special API call
|
||||
function callSpecialAPI(url, data) {
|
||||
// Did we get input?
|
||||
if(data == undefined) data = {};
|
||||
// Fill basis var's
|
||||
data.output = "json";
|
||||
data.apikey = apiKey;
|
||||
var ajaxQuery = $.ajax({
|
||||
url: url,
|
||||
type: "GET",
|
||||
cache: false,
|
||||
data: data
|
||||
});
|
||||
|
||||
return $.when(ajaxQuery);
|
||||
}
|
||||
|
||||
/**
|
||||
Handle visibility changes so we
|
||||
do only incremental update when not visible
|
||||
**/
|
||||
var pageIsVisible = true;
|
||||
// Set the name of the hidden property and the change event for visibility
|
||||
var hidden, visibilityChange;
|
||||
if(typeof document.hidden !== "undefined") { // Opera 12.10 and Firefox 18 and later support
|
||||
hidden = "hidden";
|
||||
visibilityChange = "visibilitychange";
|
||||
} else if(typeof document.mozHidden !== "undefined") {
|
||||
hidden = "mozHidden";
|
||||
visibilityChange = "mozvisibilitychange";
|
||||
} else if(typeof document.msHidden !== "undefined") {
|
||||
hidden = "msHidden";
|
||||
visibilityChange = "msvisibilitychange";
|
||||
} else if(typeof document.webkitHidden !== "undefined") {
|
||||
hidden = "webkitHidden";
|
||||
visibilityChange = "webkitvisibilitychange";
|
||||
}
|
||||
|
||||
// Set the global visibility
|
||||
function handleVisibilityChange() {
|
||||
if(document[hidden]) {
|
||||
pageIsVisible = false;
|
||||
} else {
|
||||
pageIsVisible = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Add event listener only for supported browsers
|
||||
if(typeof document.addEventListener !== "undefined" && typeof document[hidden] !== "undefined") {
|
||||
// Handle page visibility change
|
||||
document.addEventListener(visibilityChange, handleVisibilityChange, false);
|
||||
}
|
||||
|
||||
/***
|
||||
GENERAL FUNCTIONS
|
||||
***/
|
||||
// Function to fix percentages
|
||||
function fixPercentages(intPercent) {
|
||||
// Skip NaN's
|
||||
if(isNaN(intPercent))
|
||||
intPercent = 0;
|
||||
return Math.floor(intPercent || 0) + '%';
|
||||
}
|
||||
|
||||
// Convert HTML tags to regular text
|
||||
function convertHTMLtoText(htmltxt) {
|
||||
return $('<div>').text(htmltxt).html().replace(/<br\/>/g, '<br/>')
|
||||
}
|
||||
|
||||
// Function to re-write 0:09:21=>9:21, 0:10:10=>10:10, 0:00:30=>0:30
|
||||
function rewriteTime(timeString) {
|
||||
// Remove "0:0" from start
|
||||
if(timeString.substring(0,3) == '0:0') {
|
||||
timeString = timeString.substring(3)
|
||||
}
|
||||
// Remove "0:" from start
|
||||
else if(timeString.substring(0,2) == '0:') {
|
||||
timeString = timeString.substring(2)
|
||||
}
|
||||
return timeString
|
||||
}
|
||||
|
||||
// How to display the date-time?
|
||||
function displayDateTime(inDate, outFormat, inFormat) {
|
||||
// What input?
|
||||
if(inDate == '') {
|
||||
var theMoment = moment()
|
||||
} else {
|
||||
var theMoment = moment(inDate, inFormat)
|
||||
}
|
||||
// Special format or regular format?
|
||||
if(outFormat == 'fromNow') {
|
||||
return theMoment.fromNow()
|
||||
} else {
|
||||
return theMoment.format(outFormat)
|
||||
}
|
||||
}
|
||||
|
||||
// Keep dropdowns open
|
||||
function keepOpen(thisItem) {
|
||||
// Make sure we clicked the a and not the glyphicon/caret!
|
||||
if(!$(thisItem).is('a') && !$(thisItem).is('button')) {
|
||||
// Do it again on the parent
|
||||
keepOpen(thisItem.parentElement)
|
||||
return;
|
||||
}
|
||||
|
||||
// Onlick so it works for the dynamic items!
|
||||
$(thisItem).siblings('.dropdown-menu').children().click(function(e) {
|
||||
// Not for links
|
||||
if(!$(e.target).is('a')) {
|
||||
e.stopPropagation();
|
||||
}
|
||||
});
|
||||
// Add possible tooltips and make sure they get removed
|
||||
if(!isMobile) {
|
||||
$(thisItem).siblings('.dropdown-menu').children('[data-tooltip="true"]').tooltip({ trigger: 'hover', container: 'body' })
|
||||
$(thisItem).parent().on('hide.bs.dropdown', function() {
|
||||
$(thisItem).siblings('.dropdown-menu').children('[data-tooltip="true"]').tooltip('hide')
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Show history details
|
||||
function showDetails(thisItem) {
|
||||
// Unfortunatly the .dropdown('toggle') doesn't work in this setup, so work-a-round
|
||||
|
||||
// Open the details of this, or close it?
|
||||
if($(thisItem).parent().find('.delete>.dropdown').hasClass('open')) {
|
||||
// One click = close
|
||||
$(thisItem).parent().find('.delete>.dropdown>a').click()
|
||||
} else {
|
||||
// Needs timeout, otherwise it thinks its the 'close' click for some reason
|
||||
setTimeout(function() {
|
||||
$(thisItem).parent().find('.delete>.dropdown>a').click()
|
||||
},1)
|
||||
}
|
||||
}
|
||||
|
||||
// Check all functionality
|
||||
function checkAllFiles(objCheck, onlyCheck) {
|
||||
// Get which ones we care about
|
||||
var allChecks = $($(objCheck).data('checkrange')).filter(':not(:disabled):visible');
|
||||
|
||||
// We need to re-evaltuate the state of this check-all
|
||||
// Otherwise the 'inderterminate' will be overwritten by the click event!
|
||||
setCheckAllState('#'+objCheck.id, $(objCheck).data('checkrange'))
|
||||
|
||||
// Now we can check what happend
|
||||
if(objCheck.indeterminate) {
|
||||
// Uncheck if we don't need trigger
|
||||
if(onlyCheck) {
|
||||
allChecks.filter(":checked").prop('checked', false)
|
||||
} else {
|
||||
allChecks.filter(":checked").trigger("click")
|
||||
}
|
||||
} else {
|
||||
// Toggle their state by a click
|
||||
allChecks.trigger("click")
|
||||
}
|
||||
}
|
||||
|
||||
// To update the check-all button nicely
|
||||
function setCheckAllState(checkSelector, rangeSelector) {
|
||||
// See how many are checked
|
||||
var allChecks = $(rangeSelector).filter(':not(:disabled):visible')
|
||||
var nrChecks = allChecks.filter(":checked");
|
||||
if(nrChecks.length === 0) {
|
||||
$(checkSelector).prop({'checked': false, 'indeterminate': false})
|
||||
} else if(nrChecks.length == allChecks.length) {
|
||||
$(checkSelector).prop({'checked': true, 'indeterminate': false})
|
||||
} else {
|
||||
$(checkSelector).prop({'checked': false, 'indeterminate': true})
|
||||
}
|
||||
}
|
||||
|
||||
// Shift-range functionality for checkboxes
|
||||
function checkShiftRange(strCheckboxes) {
|
||||
// Get them all
|
||||
var arrAllChecks = $(strCheckboxes);
|
||||
// Get index of the first and last
|
||||
var startCheck = arrAllChecks.index($(strCheckboxes + ':checked:first'));
|
||||
var endCheck = arrAllChecks.index($(strCheckboxes + ':checked:last'));
|
||||
// Everything in between click it to trigger addMultiEdit
|
||||
arrAllChecks.slice(startCheck, endCheck).filter(':not(:checked)').trigger('click')
|
||||
}
|
||||
|
||||
// Hide completed files in files-modal
|
||||
function hideCompletedFiles() {
|
||||
if($('#filelist-showcompleted').hasClass('hover-button')) {
|
||||
// Hide all
|
||||
$('.item-files-table tr.files-done').hide();
|
||||
$('#filelist-showcompleted').removeClass('hover-button')
|
||||
// Set storage
|
||||
localStorageSetItem('showCompletedFiles', 'No')
|
||||
} else {
|
||||
// show all
|
||||
$('.item-files-table tr.files-done').show();
|
||||
$('#filelist-showcompleted').addClass('hover-button')
|
||||
// Set storage
|
||||
localStorageSetItem('showCompletedFiles', 'Yes')
|
||||
}
|
||||
}
|
||||
|
||||
// Show status modal and switch to orphaned jobs tab
|
||||
function showOrphans() {
|
||||
$('a[href="#modal-options"]').click().parent().click();
|
||||
$('a[href="#options-orphans"]').click()
|
||||
}
|
||||
|
||||
// Show notification
|
||||
function showNotification(notiName, notiTimeout, fileCounter) {
|
||||
// Set uploadcounter if there is one
|
||||
$('.main-notification-box .main-notification-box-file-count').text(fileCounter)
|
||||
|
||||
// Hide others, show the new one
|
||||
$('.main-notification-box>div').hide()
|
||||
$(notiName).css('display', 'inline')
|
||||
// Only fade in when hidden
|
||||
$('.main-notification-box:hidden').fadeIn()
|
||||
|
||||
// Remove after timeout
|
||||
if(notiTimeout) {
|
||||
setTimeout(function() {
|
||||
hideNotification(true);
|
||||
}, notiTimeout)
|
||||
}
|
||||
}
|
||||
|
||||
// Hide notification
|
||||
function hideNotification(fadeItOut) {
|
||||
// Hide the box with or without effect
|
||||
if(fadeItOut) {
|
||||
$('.main-notification-box').fadeOut()
|
||||
} else {
|
||||
$('.main-notification-box').hide()
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,351 +0,0 @@
|
||||
// For the file-list
|
||||
function Fileslisting(parent) {
|
||||
var self = this;
|
||||
self.parent = parent;
|
||||
self.fileItems = ko.observableArray([]);
|
||||
|
||||
// Need to reserve these names to be overwritten
|
||||
self.filelist_name = ko.observable();
|
||||
self.filelist_password = ko.observable();
|
||||
|
||||
// Load the function and reset everything
|
||||
self.loadFiles = function(queue_item) {
|
||||
// Update
|
||||
self.currentItem = queue_item;
|
||||
self.fileItems.removeAll()
|
||||
self.triggerUpdate()
|
||||
|
||||
// Update name/password
|
||||
self.filelist_name(self.currentItem.name())
|
||||
self.filelist_password(self.currentItem.password())
|
||||
|
||||
// Hide ok button and reset
|
||||
$('#modal-item-filelist .glyphicon-floppy-saved').hide()
|
||||
$('#modal-item-filelist .glyphicon-lock').show()
|
||||
|
||||
// Set state of the check-all
|
||||
setCheckAllState('#modal-item-files .multioperations-selector input[type="checkbox"]', '#modal-item-files .files-sortable input')
|
||||
|
||||
// Show
|
||||
$('#modal-item-files').modal('show');
|
||||
|
||||
// Stop updating on closing of the modal
|
||||
$('#modal-item-files').on('hidden.bs.modal', function() {
|
||||
self.removeUpdate();
|
||||
})
|
||||
}
|
||||
|
||||
// Trigger update
|
||||
self.triggerUpdate = function() {
|
||||
// Call API
|
||||
callAPI({
|
||||
mode: 'get_files',
|
||||
value: self.currentItem.id,
|
||||
limit: 5
|
||||
}).then(function(response) {
|
||||
// When there's no files left we close the modal and the update will be stopped
|
||||
// For example when the job has finished downloading
|
||||
if(response.files.length === 0) {
|
||||
$('#modal-item-files').modal('hide');
|
||||
return;
|
||||
}
|
||||
|
||||
// Go over them all
|
||||
var newItems = [];
|
||||
$.each(response.files, function(index, slot) {
|
||||
// Existing or updating?
|
||||
var existingItem = ko.utils.arrayFirst(self.fileItems(), function(i) {
|
||||
return i.filename() == slot.filename;
|
||||
});
|
||||
|
||||
if(existingItem) {
|
||||
// We skip queued files!
|
||||
// They cause problems because they can have the same filename
|
||||
// as files that we do want to be updated.. The slot.id is not unique!
|
||||
if(slot.status == "queued") return false;
|
||||
|
||||
// Update the rest
|
||||
existingItem.updateFromData(slot);
|
||||
} else {
|
||||
// Add files item
|
||||
newItems.push(new FileslistingModel(self, slot));
|
||||
}
|
||||
})
|
||||
|
||||
// Add new ones in 1 time instead of every single push
|
||||
if(newItems.length > 0) {
|
||||
ko.utils.arrayPushAll(self.fileItems, newItems);
|
||||
self.fileItems.valueHasMutated();
|
||||
}
|
||||
|
||||
// Check if we show/hide completed
|
||||
if(localStorageGetItem('showCompletedFiles') == 'No') {
|
||||
$('.item-files-table tr.files-done').hide();
|
||||
$('#filelist-showcompleted').removeClass('hover-button')
|
||||
}
|
||||
|
||||
// Refresh with same as rest
|
||||
self.setUpdate()
|
||||
})
|
||||
}
|
||||
|
||||
// Set update
|
||||
self.setUpdate = function() {
|
||||
self.updateTimeout = setTimeout(function() {
|
||||
self.triggerUpdate()
|
||||
}, parent.refreshRate() * 1000)
|
||||
}
|
||||
|
||||
// Remove the update
|
||||
self.removeUpdate = function() {
|
||||
clearTimeout(self.updateTimeout)
|
||||
}
|
||||
|
||||
// Move in sortable
|
||||
self.move = function(event) {
|
||||
// How much did we move?
|
||||
var nrMoves = event.sourceIndex - event.targetIndex;
|
||||
var direction = (nrMoves > 0 ? 'Up' : 'Down')
|
||||
|
||||
// We have to create the data-structure before, to be able to use the name as a key
|
||||
var dataToSend = {};
|
||||
dataToSend[event.item.nzf_id()] = 'on';
|
||||
dataToSend['session'] = apiKey;
|
||||
dataToSend['action_key'] = direction;
|
||||
dataToSend['action_size'] = Math.abs(nrMoves);
|
||||
|
||||
// Activate with this weird URL "API"
|
||||
callSpecialAPI("./nzb/" + self.currentItem.id + "/bulk_operation/", dataToSend)
|
||||
};
|
||||
|
||||
// Remove selected files
|
||||
self.removeSelectedFiles = function() {
|
||||
// We have to create the data-structure before, to be able to use the name as a key
|
||||
var dataToSend = {};
|
||||
dataToSend['session'] = apiKey;
|
||||
dataToSend['action_key'] = 'Delete';
|
||||
|
||||
// Get all selected ones
|
||||
$('.item-files-table input:checked:not(:disabled)').each(function() {
|
||||
// Add this item
|
||||
dataToSend[$(this).prop('name')] = 'on';
|
||||
})
|
||||
|
||||
// Activate with this weird URL "API"
|
||||
callSpecialAPI("./nzb/" + self.currentItem.id + "/bulk_operation/", dataToSend).then(function() {
|
||||
// Fade it out
|
||||
$('.item-files-table input:checked:not(:disabled)').parents('tr').fadeOut(fadeOnDeleteDuration, function() {
|
||||
// Set state of the check-all
|
||||
setCheckAllState('#modal-item-files .multioperations-selector input[type="checkbox"]', '#modal-item-files .files-sortable input')
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// For changing the passwords
|
||||
self.setNzbPassword = function() {
|
||||
// Activate with this weird URL "API"
|
||||
callSpecialAPI("./nzb/" + self.currentItem.id + "/save/", {
|
||||
name: self.currentItem.name(),
|
||||
password: $('#nzb_password').val()
|
||||
}).then(function() {
|
||||
// Refresh, reset and close
|
||||
parent.refresh()
|
||||
$('#modal-item-filelist .glyphicon-floppy-saved').show()
|
||||
$('#modal-item-filelist .glyphicon-lock').hide()
|
||||
$('#modal-item-files').modal('hide')
|
||||
})
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check all
|
||||
self.checkAllFiles = function(item, event) {
|
||||
// Get which ones we care about
|
||||
var allChecks = $('#modal-item-files .files-sortable input').filter(':not(:disabled):visible');
|
||||
|
||||
// We need to re-evaltuate the state of this check-all
|
||||
// Otherwise the 'inderterminate' will be overwritten by the click event!
|
||||
setCheckAllState('#modal-item-files .multioperations-selector input[type="checkbox"]', '#modal-item-files .files-sortable input')
|
||||
|
||||
// Now we can check what happend
|
||||
if(event.target.indeterminate) {
|
||||
allChecks.filter(":checked").prop('checked', false)
|
||||
} else {
|
||||
// Toggle their state by a click
|
||||
allChecks.prop('checked', !event.target.checked)
|
||||
event.target.checked = !event.target.checked;
|
||||
event.target.indeterminate = false;
|
||||
}
|
||||
// Set state of all the check-all's
|
||||
setCheckAllState('#modal-item-files .multioperations-selector input[type="checkbox"]', '#modal-item-files .files-sortable input')
|
||||
return true;
|
||||
}
|
||||
|
||||
// For selecting range and the check-all button
|
||||
self.checkSelectRange = function(data, event) {
|
||||
if(event.shiftKey) {
|
||||
checkShiftRange('#modal-item-files .files-sortable input:not(:disabled)')
|
||||
}
|
||||
// Set state of the check-all
|
||||
setCheckAllState('#modal-item-files .multioperations-selector input[type="checkbox"]', '#modal-item-files .files-sortable input')
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Indiviual file models
|
||||
function FileslistingModel(parent, data) {
|
||||
var self = this;
|
||||
// Define veriables
|
||||
self.filename = ko.observable(data.filename);
|
||||
self.nzf_id = ko.observable(data.nzf_id);
|
||||
self.file_age = ko.observable(data.age);
|
||||
self.mb = ko.observable(data.mb);
|
||||
self.percentage = ko.observable(fixPercentages((100 - (data.mbleft / data.mb * 100)).toFixed(0)));
|
||||
self.canselect = ko.observable(data.nzf_id !== undefined);
|
||||
self.isdone = ko.observable(data.status == "finished");
|
||||
|
||||
// Update internally
|
||||
self.updateFromData = function(data) {
|
||||
self.filename(data.filename)
|
||||
self.nzf_id(data.nzf_id)
|
||||
self.file_age(data.age)
|
||||
self.mb(data.mb)
|
||||
self.percentage(fixPercentages((100 - (data.mbleft / data.mb * 100)).toFixed(0)));
|
||||
self.canselect(data.nzf_id !== undefined)
|
||||
self.isdone(data.status == "finished")
|
||||
}
|
||||
}
|
||||
|
||||
// Model for pagination, since we use it multiple times
|
||||
function paginationModel(parent) {
|
||||
var self = this;
|
||||
|
||||
// Var's
|
||||
self.nrPages = ko.observable(0);
|
||||
self.currentPage = ko.observable(1);
|
||||
self.currentStart = ko.observable(0);
|
||||
self.allpages = ko.observableArray([]).extend({ rateLimit: 50 });
|
||||
|
||||
// Has pagination
|
||||
self.hasPagination = ko.pureComputed(function() {
|
||||
return self.nrPages() > 1;
|
||||
})
|
||||
|
||||
// Subscribe to number of items
|
||||
parent.totalItems.subscribe(function() {
|
||||
// Update
|
||||
self.updatePages();
|
||||
})
|
||||
|
||||
// Subscribe to changes of pagination limit
|
||||
parent.paginationLimit.subscribe(function(newValue) {
|
||||
self.updatePages();
|
||||
self.moveToPage(self.currentPage());
|
||||
})
|
||||
|
||||
// Easy handler for adding a page-link
|
||||
self.addPaginationPageLink = function(pageNr) {
|
||||
// Return object for adding
|
||||
return {
|
||||
page: pageNr,
|
||||
isCurrent: pageNr == self.currentPage(),
|
||||
isDots: false,
|
||||
onclick: function(data) {
|
||||
self.moveToPage(data.page);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Easy handler to add dots
|
||||
self.addDots = function() {
|
||||
return {
|
||||
page: '...',
|
||||
isCurrent: false,
|
||||
isDots: true,
|
||||
onclick: function() {}
|
||||
}
|
||||
}
|
||||
|
||||
self.updatePages = function() {
|
||||
// Empty it
|
||||
self.allpages.removeAll();
|
||||
|
||||
// How many pages do we need?
|
||||
if(parent.totalItems() <= parent.paginationLimit()) {
|
||||
// Empty it
|
||||
self.nrPages(1)
|
||||
|
||||
// Reset all to make sure we see something
|
||||
self.currentPage(1);
|
||||
self.currentStart(0);
|
||||
} else {
|
||||
// Calculate number of pages needed
|
||||
var newNrPages = Math.ceil(parent.totalItems() / parent.paginationLimit())
|
||||
|
||||
// Make sure the current page still exists
|
||||
if(self.currentPage() > newNrPages) {
|
||||
self.moveToPage(newNrPages);
|
||||
return;
|
||||
}
|
||||
|
||||
// All the cases
|
||||
if(newNrPages > 7) {
|
||||
// Do we show the first ones
|
||||
if(self.currentPage() < 5) {
|
||||
// Just add the first 4
|
||||
$.each(new Array(5), function(index) {
|
||||
self.allpages.push(self.addPaginationPageLink(index + 1))
|
||||
})
|
||||
// Dots
|
||||
self.allpages.push(self.addDots())
|
||||
// Last one
|
||||
self.allpages.push(self.addPaginationPageLink(newNrPages))
|
||||
} else {
|
||||
// Always add the first
|
||||
self.allpages.push(self.addPaginationPageLink(1))
|
||||
// Dots
|
||||
self.allpages.push(self.addDots())
|
||||
|
||||
// Are we near the end?
|
||||
if((newNrPages - self.currentPage()) < 4) {
|
||||
// We add the last ones
|
||||
$.each(new Array(5), function(index) {
|
||||
self.allpages.push(self.addPaginationPageLink((index - 4) + (newNrPages)))
|
||||
})
|
||||
} else {
|
||||
// We are in the center so display the center 3
|
||||
$.each(new Array(3), function(index) {
|
||||
self.allpages.push(self.addPaginationPageLink(self.currentPage() + (index - 1)))
|
||||
})
|
||||
|
||||
// Dots
|
||||
self.allpages.push(self.addDots())
|
||||
// Last one
|
||||
self.allpages.push(self.addPaginationPageLink(newNrPages))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Just add them
|
||||
$.each(new Array(newNrPages), function(index) {
|
||||
self.allpages.push(self.addPaginationPageLink(index + 1))
|
||||
})
|
||||
}
|
||||
|
||||
// Change of number of pages?
|
||||
if(newNrPages != self.nrPages()) {
|
||||
// Update
|
||||
self.nrPages(newNrPages);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update on click
|
||||
self.moveToPage = function(page) {
|
||||
// Update page and start
|
||||
self.currentPage(page)
|
||||
self.currentStart((page - 1) * parent.paginationLimit())
|
||||
// Re-paginate
|
||||
self.updatePages();
|
||||
// Force full update
|
||||
parent.parent.refresh(true);
|
||||
}
|
||||
}
|
||||
@@ -1,515 +0,0 @@
|
||||
/**
|
||||
Model for the whole History with all its items
|
||||
**/
|
||||
function HistoryListModel(parent) {
|
||||
var self = this;
|
||||
self.parent = parent;
|
||||
|
||||
// Variables
|
||||
self.lastUpdate = 0;
|
||||
self.historyItems = ko.observableArray([])
|
||||
self.showFailed = ko.observable(false);
|
||||
self.isLoading = ko.observable(false).extend({ rateLimit: 100 });
|
||||
self.searchTerm = ko.observable('').extend({ rateLimit: { timeout: 200, method: "notifyWhenChangesStop" } });
|
||||
self.paginationLimit = ko.observable(10).extend({ persist: 'historyPaginationLimit' });
|
||||
self.totalItems = ko.observable(0);
|
||||
self.pagination = new paginationModel(self);
|
||||
|
||||
// Download history info
|
||||
self.downloadedToday = ko.observable();
|
||||
self.downloadedWeek = ko.observable();
|
||||
self.downloadedMonth = ko.observable();
|
||||
self.downloadedTotal = ko.observable();
|
||||
|
||||
// Update function for history list
|
||||
self.updateFromData = function(data) {
|
||||
/***
|
||||
See if there's anything to update
|
||||
***/
|
||||
if(!data) return;
|
||||
self.lastUpdate = data.last_history_update
|
||||
|
||||
/***
|
||||
History list functions per item
|
||||
***/
|
||||
var itemIds = $.map(self.historyItems(), function(i) {
|
||||
return i.historyStatus.nzo_id();
|
||||
});
|
||||
|
||||
// For new items
|
||||
var newItems = [];
|
||||
$.each(data.slots, function(index, slot) {
|
||||
var existingItem = ko.utils.arrayFirst(self.historyItems(), function(i) {
|
||||
return i.historyStatus.nzo_id() == slot.nzo_id;
|
||||
});
|
||||
// Set index in the results
|
||||
slot.index = index
|
||||
|
||||
// Update or add?
|
||||
if(existingItem) {
|
||||
existingItem.updateFromData(slot);
|
||||
itemIds.splice(itemIds.indexOf(slot.nzo_id), 1);
|
||||
} else {
|
||||
// Add history item
|
||||
newItems.push(new HistoryModel(self, slot));
|
||||
}
|
||||
});
|
||||
|
||||
// Remove all items
|
||||
if(itemIds.length == self.paginationLimit()) {
|
||||
// Replace it, so only 1 Knockout DOM-update!
|
||||
self.historyItems(newItems);
|
||||
newItems = [];
|
||||
} else {
|
||||
// Remove the un-used ones
|
||||
$.each(itemIds, function() {
|
||||
var id = this.toString();
|
||||
self.historyItems.remove(ko.utils.arrayFirst(self.historyItems(), function(i) {
|
||||
return i.historyStatus.nzo_id() == id;
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
// Add new ones
|
||||
if(newItems.length > 0) {
|
||||
ko.utils.arrayPushAll(self.historyItems, newItems);
|
||||
self.historyItems.valueHasMutated();
|
||||
|
||||
// We also check if it might be in the Multi-edit
|
||||
if(self.parent.queue.multiEditItems().length > 0) {
|
||||
$.each(newItems, function() {
|
||||
var currentItem = this;
|
||||
self.parent.queue.multiEditItems.remove(function(inList) { return inList.id == currentItem.nzo_id; })
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Sort every time (takes just few msec)
|
||||
self.historyItems.sort(function(a, b) {
|
||||
return a.index < b.index ? -1 : 1;
|
||||
});
|
||||
|
||||
/***
|
||||
History information
|
||||
***/
|
||||
self.totalItems(data.noofslots);
|
||||
self.downloadedToday(data.day_size);
|
||||
self.downloadedWeek(data.week_size);
|
||||
self.downloadedMonth(data.month_size);
|
||||
self.downloadedTotal(data.total_size);
|
||||
};
|
||||
|
||||
// Save pagination state
|
||||
self.paginationLimit.subscribe(function(newValue) {
|
||||
// Save in config if global config
|
||||
if(self.parent.useGlobalOptions()) {
|
||||
callAPI({
|
||||
mode: "set_config",
|
||||
section: "misc",
|
||||
keyword: "history_limit",
|
||||
value: newValue
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
// Retry a job
|
||||
self.retryJob = function(form) {
|
||||
// Adding a extra retry file happens through this special function
|
||||
var data = new FormData();
|
||||
data.append("nzbfile", $(form.nzbFile)[0].files[0]);
|
||||
data.append("job", $('#modal-retry-job input[name="retry_job_id"]').val());
|
||||
data.append("password", $('#retry_job_password').val());
|
||||
data.append("session", apiKey);
|
||||
|
||||
// Add
|
||||
$.ajax({
|
||||
url: "./retry_pp",
|
||||
type: "POST",
|
||||
cache: false,
|
||||
processData: false,
|
||||
contentType: false,
|
||||
data: data
|
||||
}).then(function() {
|
||||
self.parent.refresh(true)
|
||||
});
|
||||
|
||||
$("#modal-retry-job").modal("hide");
|
||||
$('.btn-file em').html(glitterTranslate.chooseFile + '…')
|
||||
form.reset()
|
||||
}
|
||||
|
||||
// Searching in history (rate-limited in decleration)
|
||||
self.searchTerm.subscribe(function() {
|
||||
// Make sure we refresh
|
||||
self.lastUpdate = 0
|
||||
self.parent.refresh();
|
||||
// Go back to page 1
|
||||
if(self.pagination.currentPage() != 1) {
|
||||
self.pagination.moveToPage(1);
|
||||
}
|
||||
})
|
||||
|
||||
// Clear searchterm
|
||||
self.clearSearchTerm = function(data, event) {
|
||||
// Was it escape key or click?
|
||||
if(event.type == 'mousedown' || (event.keyCode && event.keyCode == 27)) {
|
||||
// Set the loader so it doesn't flicker and then switch
|
||||
self.isLoading(true)
|
||||
self.searchTerm('');
|
||||
self.parent.refresh()
|
||||
}
|
||||
// Was it click and the field is empty? Then we focus on the field
|
||||
if(event.type == 'mousedown' && self.searchTerm() == '') {
|
||||
$(event.target).parents('.search-box').find('input[type="text"]').focus()
|
||||
return;
|
||||
}
|
||||
// Need to return true to allow typing
|
||||
return true;
|
||||
}
|
||||
|
||||
// Toggle showing failed
|
||||
self.toggleShowFailed = function(data, event) {
|
||||
// Set the loader so it doesn't flicker and then switch
|
||||
self.isLoading(true)
|
||||
self.showFailed(!self.showFailed())
|
||||
// Forde hide tooltip so it doesn't linger
|
||||
$('#history-options a').tooltip('hide')
|
||||
// Force refresh
|
||||
self.parent.refresh(true)
|
||||
}
|
||||
|
||||
// Empty history options
|
||||
self.emptyHistory = function(data, event) {
|
||||
// Make sure no flickering
|
||||
self.isLoading(true)
|
||||
|
||||
// What event?
|
||||
var whatToRemove = $(event.target).data('action');
|
||||
var del_files, value;
|
||||
|
||||
// Purge failed
|
||||
if(whatToRemove == 'history-purge-failed') {
|
||||
del_files = 0;
|
||||
value = 'failed';
|
||||
}
|
||||
// Also remove files
|
||||
if(whatToRemove == 'history-purgeremove-failed') {
|
||||
del_files = 1;
|
||||
value = 'failed';
|
||||
}
|
||||
// Remove completed
|
||||
if(whatToRemove == 'history-purge-completed') {
|
||||
del_files = 0;
|
||||
value = 'completed';
|
||||
}
|
||||
// Remove the ones on this page
|
||||
if(whatToRemove == 'history-purge-page') {
|
||||
// List all the ID's
|
||||
var strIDs = '';
|
||||
$.each(self.historyItems(), function(index) {
|
||||
// Only append when it's a download that can be deleted
|
||||
if(!this.processingDownload() && !this.processingWaiting()) {
|
||||
strIDs = strIDs + this.nzo_id + ',';
|
||||
}
|
||||
})
|
||||
// Send the command
|
||||
callAPI({
|
||||
mode: 'history',
|
||||
name: 'delete',
|
||||
del_files: 1,
|
||||
value: strIDs
|
||||
}).then(function() {
|
||||
// Clear search, refresh and hide
|
||||
self.searchTerm('');
|
||||
self.parent.refresh();
|
||||
$("#modal-purge-history").modal('hide');
|
||||
})
|
||||
return;
|
||||
}
|
||||
|
||||
// Call API and close the window
|
||||
callAPI({
|
||||
mode: 'history',
|
||||
name: 'delete',
|
||||
value: value,
|
||||
del_files: del_files
|
||||
}).then(function() {
|
||||
self.parent.refresh();
|
||||
$("#modal-purge-history").modal('hide');
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
Model for each History item
|
||||
**/
|
||||
function HistoryModel(parent, data) {
|
||||
var self = this;
|
||||
self.parent = parent;
|
||||
|
||||
// We only update the whole set of information on first add
|
||||
// If we update the full set every time it uses lot of CPU
|
||||
// The Status/Actionline/scriptline/completed we do update every time
|
||||
// When clicked on the more-info button we load the rest again
|
||||
self.nzo_id = data.nzo_id;
|
||||
self.index = data.index;
|
||||
self.updateAllHistory = false;
|
||||
self.hasDropdown = ko.observable(false);
|
||||
self.historyStatus = ko.mapping.fromJS(data);
|
||||
self.status = ko.observable(data.status);
|
||||
self.action_line = ko.observable(data.action_line);
|
||||
self.script_line = ko.observable(data.script_line);
|
||||
self.fail_message = ko.observable(data.fail_message);
|
||||
self.completed = ko.observable(data.completed);
|
||||
self.canRetry = ko.observable(data.retry);
|
||||
|
||||
// Update function
|
||||
self.updateFromData = function(data) {
|
||||
// Fill all the basic info
|
||||
self.index = data.index
|
||||
self.status(data.status)
|
||||
self.action_line(data.action_line)
|
||||
self.script_line(data.script_line)
|
||||
self.fail_message(data.fail_message)
|
||||
self.completed(data.completed)
|
||||
self.canRetry(data.retry)
|
||||
|
||||
// Update all ONCE?
|
||||
if(self.updateAllHistory) {
|
||||
ko.mapping.fromJS(data, {}, self.historyStatus);
|
||||
self.updateAllHistory = false;
|
||||
}
|
||||
};
|
||||
|
||||
// True/false if failed or not
|
||||
self.failed = ko.pureComputed(function() {
|
||||
return self.status() === 'Failed';
|
||||
});
|
||||
|
||||
// Waiting?
|
||||
self.processingWaiting = ko.pureComputed(function() {
|
||||
return(self.status() == 'Queued')
|
||||
})
|
||||
|
||||
// Processing or done?
|
||||
self.processingDownload = ko.pureComputed(function() {
|
||||
var status = self.status();
|
||||
return(status === 'Extracting' || status === 'Moving' || status === 'Verifying' || status === 'Running' || status == 'Repairing')
|
||||
})
|
||||
|
||||
// Format status text
|
||||
self.statusText = ko.pureComputed(function() {
|
||||
if(self.action_line() !== '')
|
||||
return self.action_line();
|
||||
if(self.status() === 'Failed') // Failed
|
||||
return self.fail_message();
|
||||
if(self.status() === 'Queued')
|
||||
return glitterTranslate.status['Queued'];
|
||||
if(self.script_line() === '') // No script line
|
||||
return glitterTranslate.status['Completed']
|
||||
|
||||
return self.script_line();
|
||||
});
|
||||
|
||||
// Extra history column
|
||||
self.extraText = ko.pureComputed(function() {
|
||||
// Picked anything?
|
||||
switch(self.parent.parent.extraHistoryColumn()) {
|
||||
case 'speed':
|
||||
// Anything to calculate?
|
||||
if(self.historyStatus.bytes() > 0 && self.historyStatus.download_time() > 0) {
|
||||
var theSpeed = self.historyStatus.bytes()/self.historyStatus.download_time();
|
||||
theSpeed = theSpeed/1024;
|
||||
|
||||
// MB/s or KB/s
|
||||
if(theSpeed > 1024) {
|
||||
theSpeed = theSpeed/1024;
|
||||
return theSpeed.toFixed(1) + ' MB/s'
|
||||
} else {
|
||||
return Math.round(theSpeed) + ' KB/s'
|
||||
}
|
||||
}
|
||||
return;
|
||||
case 'category':
|
||||
// Exception for *
|
||||
if(self.historyStatus.category() == "*")
|
||||
return glitterTranslate.defaultText
|
||||
return self.historyStatus.category();
|
||||
case 'size':
|
||||
return self.historyStatus.size();
|
||||
}
|
||||
return;
|
||||
})
|
||||
|
||||
// Format completion time
|
||||
self.completedOn = ko.pureComputed(function() {
|
||||
return displayDateTime(self.completed(), parent.parent.dateFormat(), 'X')
|
||||
});
|
||||
|
||||
// Re-try button
|
||||
self.retry = function() {
|
||||
// Set JOB-id
|
||||
$('#modal-retry-job input[name="retry_job_id"]').val(self.nzo_id)
|
||||
// Open modal
|
||||
$('#modal-retry-job').modal("show")
|
||||
};
|
||||
|
||||
// Update information only on click
|
||||
self.updateAllHistoryInfo = function(data, event) {
|
||||
// Show
|
||||
self.hasDropdown(true);
|
||||
|
||||
// Update all info
|
||||
self.updateAllHistory = true;
|
||||
parent.parent.refresh(true);
|
||||
|
||||
// Try to keep open
|
||||
keepOpen(event.target)
|
||||
}
|
||||
|
||||
// Use KO-afterRender to add the click-functionality always
|
||||
self.addHistoryStatusStuff = function(item) {
|
||||
$(item).find('.history-status-modallink a').click(function(e) {
|
||||
// Modal or 'More' click?
|
||||
if($(this).is('.history-status-more')) {
|
||||
// Expand the rest of the text and hide the button
|
||||
$(this).siblings('.history-status-hidden').slideDown()
|
||||
$(this).hide()
|
||||
} else {
|
||||
// Info in modal
|
||||
$('#history-script-log .modal-body').load($(this).attr('href'), function(result) {
|
||||
// Set title and then remove it
|
||||
$('#history-script-log .modal-title').text($(this).find("h3").text())
|
||||
$(this).find("h3, title").remove()
|
||||
$('#history-script-log').modal('show');
|
||||
});
|
||||
}
|
||||
return false;
|
||||
})
|
||||
}
|
||||
|
||||
// Delete button
|
||||
self.deleteSlot = function(item, event) {
|
||||
// Are we not still processing?
|
||||
if(item.processingDownload() || item.processingWaiting()) return false;
|
||||
|
||||
// Confirm?
|
||||
if(!self.parent.parent.confirmDeleteHistory() || confirm(glitterTranslate.removeDow1)) {
|
||||
callAPI({
|
||||
mode: 'history',
|
||||
name: 'delete',
|
||||
del_files: 1,
|
||||
value: self.nzo_id
|
||||
}).then(function(response) {
|
||||
if(response.status) {
|
||||
// Fade and remove
|
||||
$(event.currentTarget).parent().parent().fadeOut(fadeOnDeleteDuration, function() {
|
||||
// Make sure no flickering (if there are more items left) and then remove
|
||||
self.parent.isLoading(self.parent.totalItems() > 1)
|
||||
self.parent.historyItems.remove(self);
|
||||
self.parent.parent.refresh();
|
||||
})
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// User voting
|
||||
self.setUserVote = function(item, event) {
|
||||
// Send vote
|
||||
callAPI({
|
||||
mode: 'queue',
|
||||
name: 'rating',
|
||||
type: 'vote',
|
||||
setting: $(event.target).val(),
|
||||
value: self.nzo_id
|
||||
}).then(function(response) {
|
||||
// Update all info
|
||||
self.updateAllHistory = true;
|
||||
self.parent.parent.refresh(true)
|
||||
})
|
||||
}
|
||||
|
||||
// User rating
|
||||
self.setUserRating = function(item, event) {
|
||||
// Audio or video
|
||||
var changeWhat = 'audio';
|
||||
if($(event.target).attr('name') == 'ratings-video') {
|
||||
changeWhat = 'video';
|
||||
}
|
||||
|
||||
// Only on user-event, not the auto-fired ones
|
||||
if(!event.originalEvent) return;
|
||||
|
||||
// Send vote
|
||||
callAPI({
|
||||
mode: 'queue',
|
||||
name: 'rating',
|
||||
type: changeWhat,
|
||||
setting: $(event.target).val(),
|
||||
value: self.nzo_id
|
||||
}).then(function(response) {
|
||||
// Update all info
|
||||
self.updateAllHistory = true;
|
||||
self.parent.parent.refresh(true)
|
||||
})
|
||||
}
|
||||
|
||||
// User comment
|
||||
self.setUserReport = function(form) {
|
||||
// What are we reporting?
|
||||
var userReport = $(form).find('input[name="rating_flag"]:checked').val();
|
||||
var userDetail = '';
|
||||
|
||||
// Anything selected?
|
||||
if(!userReport) {
|
||||
alert(glitterTranslate.noSelect)
|
||||
return;
|
||||
}
|
||||
|
||||
// Extra info?
|
||||
if(userReport == 'comment') userDetail = $(form).find('input[name="ratings-report-comment"]').val();
|
||||
if(userReport == 'other') userDetail = $(form).find('input[name="ratings-report-other"]').val();
|
||||
|
||||
// Exception for servers
|
||||
if(userReport == 'expired') {
|
||||
// Which server?
|
||||
userDetail = $(form).find('select[name="ratings-report-expired-server"]').val();
|
||||
|
||||
// All?
|
||||
if(userDetail == "") {
|
||||
// Loop over all servers
|
||||
$.each(parent.parent.servers, function(index, server) {
|
||||
// Set timeout because simultanious requests don't work (yet)
|
||||
setTimeout(function() {
|
||||
submitUserReport(server.name)
|
||||
}, index * 1500)
|
||||
})
|
||||
|
||||
} else {
|
||||
// Just the one server
|
||||
submitUserReport(userDetail)
|
||||
}
|
||||
} else {
|
||||
submitUserReport(userDetail)
|
||||
}
|
||||
|
||||
// After all, close it
|
||||
form.reset();
|
||||
$(form).parent().parent().dropdown('toggle');
|
||||
alert(glitterTranslate.sendThanks)
|
||||
|
||||
function submitUserReport(theDetail) {
|
||||
// Send note
|
||||
callAPI({
|
||||
mode: 'queue',
|
||||
name: 'rating',
|
||||
type: 'flag',
|
||||
setting: userReport,
|
||||
detail: theDetail,
|
||||
value: self.nzo_id
|
||||
})
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||