Compare commits

..

129 Commits
1.1.0 ... 1.0.2

Author SHA1 Message Date
shypike
b7e2bd9684 Update text files for 1.0.2 2016-05-03 19:39:45 +02:00
shypike
f0a243e3d3 Fix API status issues.
Remove the "ToPP" status, it serves no purpose.
Only truly deleted jobs should get the "Deleted" status,
not jobs moving from Download to PP queue.
2016-05-01 14:42:07 +02:00
shypike
6e108c9ef2 Prevent Completed and Failed jobs from getting status Deleted. 2016-04-30 11:47:03 +02:00
shypike
89edcc1924 Log the preferred character encoding 2016-04-30 11:18:32 +02:00
shypike
8a6aca47a1 Prevent stalling at 100% when QuickCheck is Off and "Download-all-pars" is On.
The repair function sent all extra par2 files back to the queue
even though they were already downloaded.
2016-04-28 22:42:51 +02:00
shypike
d03e5780b8 Fix API compatibility of queue statuses.
The new statuses TO_PP and DELETED should not be returned by the API.
Tools may not be able to handle them and they are only useful for internal purposes.
2016-04-27 12:03:37 +02:00
shypike
209d8f9b40 NNTP error 502 should not aways be interpreted as bad login.
It can also mean "too many connections".
2016-04-27 11:52:56 +02:00
shypike
c257b1be3d Update text files for 1.0.1 2016-04-26 19:57:22 +02:00
Safihre
2c48c8de2e Force MIME types for CSS and JS files
Caused problems on Windows if external programs overwriten it in registery.
See: http://forums.sabnzbd.org/viewtopic.php?f=2&t=20490
And: https://www.reddit.com/r/usenet/comments/4fkmcx/my_sab_interface_is_text_only/
2016-04-26 19:48:47 +02:00
shypike
a767ef6aed Fix API compatibility of queue.
The new statuses TO_PP and DELETED should not be returned by the API.
Tools may not be able to handle them and they are only useful for internal purposes.
2016-04-26 19:47:38 +02:00
shypike
ad61d1dd03 The pre-queue script can now return an accept value of 2, meaning immediate failure.
Supports front-ends which need the signal that an NZB has been
rejected by the pre-queue script.
2016-04-22 21:54:45 +02:00
shypike
33c3d187a0 Add start script for portable Windows installations 2016-04-22 16:44:37 +02:00
shypike
4eb486d4e2 Update text files for 1.0.0RC1 2016-04-16 15:31:48 +02:00
shypike
bfb6c167a4 Another attempt to set the default cache to 450M. 2016-04-16 15:27:35 +02:00
shypike
44abf3bdf6 Update text files for 1.0.1RC1 2016-04-15 23:12:22 +02:00
shypike
c950572592 Set default cache to 450M 2016-04-15 23:11:53 +02:00
shypike
3999cb13fd Update text files for 1.0.1RC1 2016-04-15 21:06:55 +02:00
shypike
af65075f0c Update text files for 1.0.0RC1 2016-04-15 21:05:53 +02:00
shypike
de2a2b465b Update text files for 1.0.1RC1 2016-04-13 22:41:13 +02:00
shypike
cd7a77f02d Revert "Set default cache size to 750MB on Windows and OSX."
This reverts commit 9b420e91c9.
2016-04-13 22:30:05 +02:00
shypike
f4a5394b63 Prevent creating orphan items in "incomplete" when deleting downloading jobs.
Due to previous issues where articles could be lost,
the nzf.deleted and no.deleted flags were not obeyed.
This could lead to creation of orphans when lost articles would be flushed.

Better solution: drop articles only when job is in a final state.
Also prevent NZO files from being saved when job is in "deleted" state.
2016-04-13 18:32:23 +02:00
jdfalk
3fb6a8dedb Update sabnzbd@.service
1. Added requirement for network to be up before sab starts.
2. Explicitly set service type to simple.
3. Enabled sabnzbd restart on service failure via systemd.
2016-04-13 18:22:11 +02:00
Safihre
50c8f84eba #448-#126 Forced item with missing articles caused overflow in paused queue
Closes #448
Closes #126
2016-04-13 18:21:41 +02:00
Safihre
2c7ecdee92 #464 Grabbing items don't always have status=grabbing
But now they do!
2016-04-07 21:45:28 +02:00
Safihre
72390a793a Add Optional label to Retry password 2016-04-07 21:45:17 +02:00
Safihre
04ad4e5d3e Update cache text to more 2016 values 2016-04-07 21:45:05 +02:00
Safihre
5ef9c6a433 #530 do not ignore files in QuickCheck
Par2 wouldn't ignore them either
2016-04-07 21:44:51 +02:00
shypike
e6baffc839 Prevent API crashes when 'mode' or 'name' have double entries. 2016-04-07 21:35:07 +02:00
shypike
e361eb25a5 Fix potential race condition in BPSmeter. 2016-04-01 21:18:19 +02:00
shypike
9b420e91c9 Set default cache size to 750MB on Windows and OSX.
To make handling of posts with large files more efficient.
2016-04-01 21:09:15 +02:00
Safihre
3a4bf971b2 #529 Fix unicode strip in OptionStr 2016-04-01 20:47:11 +02:00
Safihre
1128691c5d #527 Fix "Download all par2 files" behavior 2016-03-29 21:03:48 +02:00
Safihre
15043aef3f DB Strings should be encapsulated in ' not " 2016-03-29 21:03:30 +02:00
savef
2a3b4afa03 Treat ambiguous numeric values as number of minutes for custom pause time.
Currently if you just type "100" into the custom pause field it'll think you want 143015 minutes, that's useless. A lot of people are probably used to the old Plush behaviour of entering the number of minutes you want to pause for, it's also a much saner default. So in the case that the user just enters some numbers and nothing else, this assumes they want to pause for that many minutes.
2016-03-24 21:01:04 +01:00
shypike
00a98efa81 Fix race-condition when deleting an actively downloading job.
Closes #237
2016-03-24 20:57:27 +01:00
shypike
f013dd7f0d Accept MIME records that have only LF line endings.
Some tool developers just ignore the rule requiring CRLF.
2016-03-23 23:29:12 +01:00
shypike
7b91b1c769 Fix race condition when API and postprocessor both want to delete a history item. 2016-03-23 23:03:28 +01:00
shypike
5583cce322 When urlgrabber receives a 404, stop trying. 2016-03-23 23:00:38 +01:00
shypike
b995c5f992 Fix PushOver support.
Adjust priority levels to the current PushOver API.
Re-enable device field now that the PushOver API supports readable device labels.
2016-03-22 21:25:05 +01:00
shypike
214ac4a53d Prevent incompatibility due to missing 'script_log' field.
Fixes commit c0f2f59fc1
2016-03-20 15:24:07 +01:00
Safihre
c0f2f59fc1 Fix breaking Glitter bug with large script_log 2016-03-18 14:11:54 +01:00
shypike
b90a847a6f Fix omission in README.mkd 2016-03-15 21:42:16 +01:00
shypike
a58bb385f5 Update text files for 1.0.0 2016-03-15 20:12:50 +01:00
shypike
9754baeb1c Fix handling of changed "ignore_samples" option.
Closes #510
2016-03-15 20:08:35 +01:00
shypike
ffcd154966 Update text files for 1.0.0 Final. 2016-03-14 19:12:59 +01:00
shypike
97cfe9488c Update text files for 1.0.0RC5 2016-03-11 19:24:27 +01:00
shypike
374b6f616a Fix crash in CherryPy when it reports problems with some IPv6 addresses.
A bug in Python's traceback logging causes a crash when an IPv6 address with an embedded % is reported.
2016-03-11 19:22:49 +01:00
Sander Jonkers
e2f51595b6 Because of dual IPv4/IPv6 clients, finding the public ipv4 needs special attention 2016-03-10 22:52:22 +01:00
shypike
04091a16aa Fix display of SABnzbd's icon by OSX Notification Center.
El Capitan doesn't accept the -sender parameter, so just omit it.
2016-03-08 16:40:44 +01:00
shypike
9d9d2fd9a2 Revert "Fix Plush dashboard"
This reverts commit 42f1a4926c.
2016-03-08 16:00:24 +01:00
shypike
5746115331 Suppress errors/warnings about bad "Rating" files when the feature is disabled. 2016-03-08 15:53:52 +01:00
Safihre
42f1a4926c Fix Plush dashboard 2016-03-05 11:23:19 +01:00
Safihre
7d87fd461b Fix Glitter display in <1MB/s range 2016-03-05 11:22:57 +01:00
shypike
1ba9976979 Update text files for 1.0.0RC4 2016-03-05 08:52:09 +01:00
shypike
659c199043 Fix --ipv6_hosting option.
Repairs commit 1cbff28
2016-03-05 08:50:13 +01:00
shypike
81a3f53226 Update text files for 1.0.0RC3 2016-03-04 20:39:01 +01:00
shypike
1cbff28f67 Disable listening on IPv6 addresses by the internal web server.
Setting Config->Special->ipv6_hosting to 1 will enable IPv6 listening.
Command line option --ipv6_hosting allows forcing the choice, should SABnzbd not start.
Closes #492
2016-03-04 19:26:50 +01:00
shypike
8e15acbf30 Update translations 2016-03-04 18:55:20 +01:00
Safihre
e07be60db6 #489 wrongly encoded & in the preload 2016-03-01 22:20:51 +01:00
Safihre
539c9662ff 'Default' not translated in Server-category 2016-03-01 22:20:35 +01:00
shypike
b396014f8d Fix IP test at startup.
Correction of problem introduced by commit afff88b "Use self-test.sabnzbd.org for connection tests".
2016-02-24 22:30:02 +01:00
shypike
1db32415b6 Update translations 2016-02-24 21:37:22 +01:00
shypike
b24629db6b Update text files for 1.0.0RC2 2016-02-24 21:11:43 +01:00
Safihre
9b5cdcf8fb Add trailing "/" to href's 2016-02-24 21:05:12 +01:00
shypike
4831415d14 Use self-test.sabnzbd.org for connection tests 2016-02-24 20:34:04 +01:00
Chris Thorn
a4c51f0b20 Parse bandwidth limit as a float instead of an integer so that non-integer values can be used as bandwidth limit 2016-02-24 20:09:02 +01:00
shypike
ec3ba1fb93 Changing server priorities during a download could lead to unexpected results and lockups.
Target priority of articles must be kept at the TryList level so that they
are reset when the try_list is reset.
Closes #378
2016-02-24 20:08:51 +01:00
Safihre
61966f7036 Plush dashboard local IPv4 wouldn't show if external failed
Typo!
For 1.0.0
2016-02-24 20:08:38 +01:00
Safihre
4f69e81841 Add link to information about SSL/yEnc 2016-02-24 20:07:48 +01:00
Safihre
d0d90581df Tweak first Config page 2016-02-24 20:07:35 +01:00
Safihre
8ea5c27633 Improve display of message on blocked server 2016-02-24 20:07:16 +01:00
shypike
517500fdf3 Add link to issue list on support site. 2016-02-19 22:42:30 +01:00
shypike
c4c1c9b6ab Prevent UI errors due to history_db handle not being available. 2016-02-19 22:22:42 +01:00
Safihre
2388889ede Filegrabber did not sanatize filename 2016-02-19 22:21:28 +01:00
Safihre
55cfe878d7 Catch failing Windows Notification
I assumed Windows notifications could not fail, but clearly they can:
http://forums.sabnzbd.org/viewtopic.php?f=11&t=20211&p=104438
When no tray-icon is available, it will fail and in the case of
completed NZB message it will restart the whole of SABnzbd.
2016-02-17 21:25:42 +01:00
Safihre
a2daaee468 Add Pystone score to Glitter status 2016-02-17 21:06:43 +01:00
Safihre
2c360e395e Limit to max 250 items per page (realistic limit) 2016-02-17 21:05:55 +01:00
Jonathon Saine
399cfee594 Add pyOpenSSL info to startup/utility/config base. Add OpenSSL & yEnc to config base as well. 2016-02-17 21:04:13 +01:00
shypike
be646ae6ab Set default for https verification to off. 2016-02-17 20:50:24 +01:00
shypike
b470253d9f Disable https verification when uploading NZB to running instance of SABnzbd. 2016-02-13 16:12:04 +01:00
shypike
b83c493492 Update translations 2016-02-11 01:06:52 +01:00
Safihre
991277bb01 Glitter broke on empty preload strings
See
http://forums.sabnzbd.org/viewtopic.php?f=11&t=20192&p=104368#p104368
In case of UnicodeError
2016-02-10 22:59:11 +01:00
Safihre
5626013b81 Don't allow name change or filesview on grabbing 2016-02-10 22:58:56 +01:00
Safihre
2810d37758 Make disabled servers look more disabled 2016-02-10 22:58:40 +01:00
Safihre
c2f08f01e0 #408 Show red when Add-NZB left empty 2016-02-10 22:57:30 +01:00
Safihre
17ff087e06 Do not confirm clearing warnings 2016-02-05 23:36:47 +01:00
Safihre
77de565b7c Link in message about Orphans was broken 2016-02-05 23:36:36 +01:00
Safihre
54d238aa4d Message labels were not translated
INFO and WARNING
2016-02-05 23:36:23 +01:00
Safihre
379d09f8cc Improve message about no localStorage
It happens more than I expected, so better make a proper message.
2016-02-05 23:35:23 +01:00
shypike
00de72b127 Update text files for 1.0.xRC1 2016-02-03 21:18:42 +01:00
shypike
f9c84fa7dd Fix trouble with disk speed meter (especially on Windows) Part 2.
Improve the benchmark by reducing Python overhead by writing larger blocks.
2016-02-03 21:08:47 +01:00
shypike
c8e46691bb Solve file name encoding issues for OSX.
- Names returned by unrar-commandline need to be Unicoded.
- For yEnc embedded names, only test for UTF-8 and CP1252, but not the local codepage.
- Suppress some bogus warnings
- Log the output of the unrar tool
2016-02-03 20:59:12 +01:00
shypike
df1bb636e5 Fix links in text files to pooit to 1.0.0 entries in the Wiki. 2016-02-03 20:51:19 +01:00
shypike
ff886fad0d Fix all links in the templates to point to 1.0.0 (or 1-0) entries in the Wiki. 2016-02-03 20:50:13 +01:00
shypike
6dbee7a413 Fix trouble with disk speed meter (especially on Windows) Part 2.
Move fix outside of the measurement loop.
Don't remove the test-file within the loop, but only after it's finished,
otherwise we'll still get a "Permission denied".
2016-01-30 10:52:28 +01:00
shypike
3f8fcd7172 Fix trouble with disk speed meter (especially on Windows).
Better handling when folder is not available or writable.
Windows requires some access outside of the Python code to avoid "permission denied".
2016-01-29 23:54:32 +01:00
shypike
d94f7388e6 Add a link on the main Config page, to the "issues" page on the Wiki 2016-01-29 19:55:04 +01:00
Safihre
ad8b49fea8 #408 Firefox-fix: word-wrap instead of word-break 2016-01-29 19:21:59 +01:00
Safihre
ce00270c12 #447 only show arguments-field for speedlimit 2016-01-29 19:21:41 +01:00
Safihre
8c501f8f58 #447 Servers in scheduler more clear 2016-01-29 19:21:30 +01:00
Safihre
ce313ebc65 #445 Reduce statusinfo timeout on startup 2016-01-29 19:21:18 +01:00
Safihre
887ad881a2 #444 HTTPS instead of HTTP for RSS favicon 2016-01-29 19:21:07 +01:00
Safihre
ce40827552 Fix breaking RSS page
Fixes http://forums.sabnzbd.org/viewtopic.php?f=11&t=20114
2016-01-29 19:20:52 +01:00
shypike
2777d89482 Fix typos that prevented notifications about disk full being sent. 2016-01-28 23:19:36 +01:00
shypike
727b300a0e Update translations 2016-01-24 16:34:40 +01:00
shypike
652b021a8e Update text files for 0.8.0Beta6 2016-01-24 16:26:26 +01:00
shypike
fdf33acfbb Allow "None" as selection for secondary skin. 2016-01-24 16:15:02 +01:00
shypike
b001bc9b6f Prevent multiple resume notifications.
Only report "Resume" when coming from Paused mode.
2016-01-24 15:18:24 +01:00
shypike
8802cb1d8c Notifications template contained two instances of "ncenter_enable", leading to problems.
The result was a list instead of a single value.
2016-01-24 15:18:07 +01:00
shypike
e19a2fbae7 Improve handling of an old queue when upgrading to 0.8.0+
Warn only for a non-empty queue.
For Windows, when using a relative "download_dir" based on the user profile,
prepend the path with "Documents".
No action when the -f parameter was used.
This way it will be easier to restore existing jobs.
2016-01-24 15:17:47 +01:00
Safihre
53e38f98f9 Firefox needs more time to process the Server AJAX 2016-01-24 15:16:52 +01:00
Safihre
e783e227f6 #438 Try to stop Firefox from checking checkboxes 2016-01-24 15:16:39 +01:00
shypike
f3dfbe4181 Fix problem where a stray RAR file would cause a failed unpack run.
When a job of which the RAR files are renamed by par2,
needs repair of one more more files, the original damaged files will stay behind.
This will cause SABnzbd to try a doomed attempt at unpacking.
SABnzbd should keep track of such files and delete them after repair.
2016-01-24 15:15:57 +01:00
shypike
bcd8ca8bc4 Fix potential crash when reverting par2 renames.
Can happen when files have been modified outside of SABnzbd's control.
2016-01-24 15:12:06 +01:00
shypike
816d6a63cd For SSL protocol choice, default to auto-negotiation.
The "V23" method is interpreted by OpenSSL as "negotiate the highest available protocol"
and should therefor be a safe choice (best chance of working and best security).
If a user wants to, it is possible to fix the protocol, to prevent interference in the negotiation.
We cannot just assume that we can use our highest fixed protocol, because some Usenet servers
are being slow with implementing TLS1.2
2016-01-24 15:11:30 +01:00
shypike
88d3f25700 Perform IPv6 test on port 443 instead of 80.
Works better with some proxies.
Closes issue #274
2016-01-19 18:13:42 +01:00
shypike
80f69b11db When trying to connect to another SABnzbd instance over HTTPS, don't verify certificates.
Very few SABnzbd installations will have valid certificates.
2016-01-19 18:03:33 +01:00
Safihre
81a11f20c8 Re-order Switches page 2016-01-19 17:30:01 +01:00
Safihre
9e2a839953 Config fixes 2016-01-19 17:30:01 +01:00
Safihre
3cefcde270 #408 Refresh on Config Special save to update the * 2016-01-19 17:30:01 +01:00
Safihre
87a1eacfe7 #408 Also close history-details on history-row click
Before it would only open
2016-01-19 17:30:01 +01:00
Safihre
7cbc1a8419 #408 Browser navbar to black on mobile 2016-01-19 17:30:01 +01:00
Safihre
7b5570eb0b #408 Extra space next to Folder icon 2016-01-19 17:30:00 +01:00
Safihre
1a43a4dcf0 #432 Change filename to name in Add NZB 2016-01-19 17:30:00 +01:00
Safihre
2c2a6592c7 End of queue script was forgotten in Glitter 2016-01-19 17:30:00 +01:00
shypike
f31de6ee4e The compiled OSX build wasn't restarted with original command line arguments.
Rare use case where the App was originally started with parameters.
Essential for correct preservation of the -f parameter.
2016-01-19 17:29:11 +01:00
shypike
8fcd1f6b6c Merge branch 'develop' into R0.8.0 2016-01-16 12:25:38 +01:00
shypike
d7f3a473d7 Merge branch 'develop' into R0.8.0 2016-01-13 21:51:51 +01:00
shypike
ab2eb0c94e Update text files for 0.8.0Beta5 2016-01-13 20:17:32 +01:00
203 changed files with 22614 additions and 26343 deletions

2
.gitignore vendored
View File

@@ -1,5 +1,5 @@
#Compiled python
*.py[cod]
*.py[co]
# Working folders for Win build
build/

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View 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)

View File

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

View File

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

View 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

View File

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

View File

@@ -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 "/"))

View File

@@ -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):]

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

15
cherrypy/daemon.py Executable file → Normal file
View 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()

View File

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

View File

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

View File

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

View File

@@ -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']}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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',
}

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 27 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 38 KiB

BIN
icons/sabnzbd16.ico Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

BIN
icons/sabnzbd16green.ico Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
icons/sabnzbd16paused.ico Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

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

View File

@@ -6,7 +6,7 @@
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">&times;</button>
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<h4 class="modal-title"></h4>
</div>
<div class="modal-body">

View File

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

View File

@@ -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() &nbsp; [$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 &copy; 2008-2016 The SABnzbd Team &lt;<a href="mailto:team@sabnzbd.org">team@sabnzbd.org</a>&gt;</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 &copy; 2008-2016 The SABnzbd Team &lt;<a href="mailto:team@sabnzbd.org">team@sabnzbd.org</a>&gt;</h5>
<p class="copyright"><small>$T('yourRights')</small></p>
</div>
</div>
<!--#include $webdir + "/_inc_footer_uc.tmpl"#-->

View File

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

View File

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

View File

@@ -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')&nbsp;&nbsp;<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

View File

@@ -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">&nbsp;</span>
<span id="config_err_msg" class="icon path darkred">&nbsp;</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">&nbsp;</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">&nbsp;</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">&nbsp;</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">&nbsp;</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">&nbsp;</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">&nbsp;</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">&nbsp;</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>

View File

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

View File

@@ -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"#-->
<%

View File

@@ -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&amp;name=test_server&amp;" + \$(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;
});

View File

@@ -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">&nbsp;</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">&nbsp;</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">&nbsp;</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>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 9.1 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 6.1 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

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

View File

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

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

View File

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

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

File diff suppressed because one or more lines are too long

View File

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

View File

@@ -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&amp;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>

View File

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

View File

@@ -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">&gt; 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">&gt; 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')&hellip;</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">&times;</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">&times;</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 &lt;team@sabnzbd.org&gt;<br/>$T('yourRights') </small></p>
<p><strong>SABnzbd $('version'):</strong> $version</p>
<p><small>Copyright (C) 2008-2016, The SABnzbd Team &lt;team@sabnzbd.org&gt;</small></p>
<p><small>$T('yourRights') </small></p>
</div>
</div>
</div>

View File

@@ -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') &#10140; $T('cmenu-special') &#10140; 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>

View File

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

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

@@ -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(/&lt;br\/&gt;/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()
}
}

View File

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

View File

@@ -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 + '&hellip;')
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
}
}

Some files were not shown because too many files have changed in this diff Show More