Compare commits

...

1223 Commits
1.2.2 ... 2.3.6

Author SHA1 Message Date
Safihre
ee7e209a8b Set version to 2.3.6 2018-12-20 22:52:52 +01:00
Safihre
9bc1601939 Merge branch 'develop' 2018-12-20 22:51:54 +01:00
Safihre
190ec0a472 Update text files for 2.3.6 2018-12-20 22:33:13 +01:00
Safihre
468f01d839 Make clear that require_modern_tls is TLS 1.2 and above
@sanderjo
2018-12-19 11:17:25 +01:00
Erik Berkun-Drevnig
5bbbf602f9 Fix missing unrar, fix Python encoding 2018-12-19 08:58:29 +01:00
Erik Berkun-Drevnig
b03d68b434 Fix missing unrar, fix Python encoding 2018-12-19 08:57:59 +01:00
Safihre
0298beac15 Update text files for 2.3.6 RC 1 2018-12-17 13:56:34 +01:00
SABnzbd Automation
be6f047e31 Automatic translation update 2018-12-16 18:01:50 +00:00
Safihre
e8206371e4 Update Wizard example URL 2018-12-14 08:41:00 +01:00
SABnzbd Automation
6609248fce Automatic translation update 2018-12-06 08:32:33 +00:00
Safihre
0ae5c7f8aa Code improvements 2018-11-28 09:23:11 +01:00
Sander Jo
02b6f63156 Option require_modern_tls if you want TLS 1.2 or higher for NNTPS 2018-11-23 15:28:03 +01:00
Safihre
2b665667af Unavailable feeds would crash reading 2018-11-20 09:42:13 +01:00
jcfp
ad7fc240c7 help output: add -1 param for logging, 7z as supported file ext (#1192)
* help output: add -1 param for logging, 7z as supported file ext

* stop the universe from expanding
2018-11-18 09:02:37 +01:00
jcfp
aef878a0f2 update snapcraft url
the old link only displays a 'your session has expired' message...
2018-11-01 13:08:47 +01:00
Erik Berkun-Drevnig
2a967b62d9 Add Travis and AppVeyor badges (#1186)
* Add Travis and AppVeyor badges

* Add snap, issue resolution and license badges

* Switch master to develop branch

* Update README.md
2018-10-31 09:48:57 +01:00
Erik Berkun-Drevnig
19946684d5 Add initial snap support (#1183)
* Add initial snap support
* Apply review feedback
* Fix armhf and arm64 builds
* Use PPA and build lang files
* Add openssl for x86
* Remove unnecessary stage-packages
* Improve arch grammar
* Add back dev packages for building extensions
* Add back missing SSL
* Add icon
* Update snapcraft.yaml
2018-10-31 07:53:12 +01:00
Safihre
4c5ca149ba Update MultiPar to v1.3.0.2 2018-10-30 09:09:05 +01:00
jcfp
54d6e0dc21 fix extension filters in linux tray
correct typo (nbz) in filter name and bring the extension filters in line with the supported types (cf. sabnzbd/constants.py:99-100)
2018-10-29 22:17:23 +01:00
Erik Berkun-Drevnig
ecb1403776 Add initial snap support (#1183)
* Add initial snap support
* Apply review feedback
* Fix armhf and arm64 builds
* Use PPA and build lang files
* Add openssl for x86
* Remove unnecessary stage-packages
* Improve arch grammar
* Add back dev packages for building extensions
* Add back missing SSL
* Add icon
* Update snapcraft.yaml
2018-10-29 09:50:28 +01:00
Safihre
7e5c6d1c04 Update text files for 2.3.6 Beta 1 2018-10-26 09:27:59 +02:00
Safihre
96b140dee0 Update included Python license 2018-10-26 09:27:42 +02:00
Safihre
2e098b641f Update UnRar to 5.61 for macOS and Windows 2018-10-26 09:15:43 +02:00
Sander Jo
6678cb9d56 Remove \x00 from par2 creator info: uniformed method 2018-10-25 07:58:06 +02:00
Sander Jo
4b67405d16 Remove \x00 from par2 creator info 2018-10-19 08:45:58 +02:00
Safihre
7463a4abdc Existing RSS-feeds don't have the infourl yet
Rookie mistake.
2018-10-14 09:39:38 +02:00
SABnzbd Automation
163523048b Automatic translation update 2018-10-09 13:55:06 +00:00
Safihre
4892bc18f3 Prevent endless loop when disk-space is exceeded
It was a nice idea, to keep retrying to save the job, but it also breaks the pp-actions as it gets removed before it is ready to be removed.
Closes #1095
2018-10-09 15:14:49 +02:00
Safihre
217b2436f2 Detect RSS-feed login redirect and show specific error
Before it would overwrite specific errors detected above by general "No entries" warning.
2018-10-09 14:26:49 +02:00
Safihre
a9247ba934 No URL-encoding of RSS-URL's with comma's
Some indexers don't like that!
2018-10-09 13:55:58 +02:00
Safihre
8b2a6ef825 Link to details page of RSS-feed item if provided 2018-10-09 10:04:21 +02:00
Safihre
320495671b Add RSS-source icon to all tabs 2018-10-08 13:22:34 +02:00
Safihre
5ab872afa0 Assume correct SSL if test-host disabled
Closes #1179
2018-10-03 08:04:43 +02:00
Safihre
7ecb31805e Add API-capability to modify RSS filters
Closes #1154
2018-09-16 14:51:11 +02:00
Safihre
e8ebeb843c Retry All would not Retry URL's
Closes #1164
2018-09-16 13:32:13 +02:00
Safihre
3840678913 Rename thread-database getter
Each thread needs it's own DB-connection (see Python-docs). So for each CherryPy-thread we need to store the thread connection. The name of the function made it sound like we create a whole new connection, which isn't the case.
2018-09-16 13:31:43 +02:00
Safihre
da7082b17e Sending Retry for already completed jobs would give traceback
Due to missing file/folder.
See: https://forums.sabnzbd.org/viewtopic.php?f=2&t=23587
2018-09-16 10:11:32 +02:00
Safihre
6198f95e1e Do not log null-bytes in par2-creator
#1153
2018-09-11 16:21:06 +02:00
Safihre
4075b1accb Set version to 2.3.5 2018-09-07 10:44:18 +02:00
Safihre
6d8a774443 Merge branch 'develop' 2018-09-07 10:41:57 +02:00
Safihre
76c7a6ce95 Update text files for 2.3.5 2018-09-07 10:11:20 +02:00
Safihre
01bd0bdce0 Small cleanup of generated POT file 2018-09-07 10:11:00 +02:00
Safihre
fa908de6e9 Test a unicode download
Broken-unicode job is still work-in-progress
2018-09-05 15:08:50 +02:00
Safihre
f05a6c6f76 Add seperate servers for each test-OS 2018-09-05 15:05:47 +02:00
Safihre
d86fb42d28 Code-style changes 2018-09-05 14:51:16 +02:00
Safihre
7a7ce47769 Remove redundant parentheses 2018-09-05 11:26:38 +02:00
Safihre
40e57845a7 Correctly sort additional blocks to be added
Oops, .pop() gets the last one not the first one.
2018-09-03 13:50:38 +02:00
Safihre
884dedc9d1 Failed file join would not result in failed job 2018-09-03 13:14:29 +02:00
Safihre
8e1f4e14a2 MultiPar repair of joinable files doesn't join them 2018-09-03 13:06:07 +02:00
Safihre
1190742127 Correctly report CRC errors in (7)zip archives
They were reported as password errors.
2018-09-03 09:43:59 +02:00
Safihre
e6d481a2ba Perform functional tests also on macOS 2018-08-28 07:47:33 +02:00
Safihre
0958caf5ed If no newsserver-info, skip tests 2018-08-28 07:47:33 +02:00
Safihre
e2761d967e Functional testing using Selenium 2018-08-28 07:47:33 +02:00
Safihre
b4f36be170 Update text files for 2.3.5RC2 2018-08-23 20:54:12 +02:00
Safihre
5e722b27f3 Fix more errors and warnings found by code validation 2018-08-23 14:31:15 +02:00
Safihre
367a73ef29 Fix errors found by code validation tool 2018-08-22 09:52:58 +02:00
Safihre
9228bc28ff Typos in stylesheets 2018-08-22 07:58:24 +02:00
Sander Jo
02e18be5e1 Better pystone calculation 2018-08-22 07:58:08 +02:00
Safihre
531ef59e0a Small Config > General styling fix 2018-08-21 15:39:31 +02:00
Safihre
54e03fb40a Update text files for 2.3.5RC1 2018-08-08 20:35:01 +02:00
Safihre
904bb9f85a Remove unused imports 2018-08-07 17:10:13 +02:00
Rik Smith
b011e1a518 Direct Unpack would abort if single-file unpack was too slow (#1165) 2018-08-06 08:40:59 +02:00
Rik Smith
f83f71a950 Fix Deobfuscate.py script (#1166)
* Fix deobfuscate script

* Rename for code check

* Rename for code check
2018-08-06 08:35:01 +02:00
Safihre
4dbf5266ef Par2 files with same number of "+x" blocks were not counted seperatly
Strange why I did that before. It does create the possibility that if we have a huge NZB with many sets with similar filenames that we take forever to repair, but for normal NZB's with lot's of "volXX+40" we would ignore all those extra blocks.
2018-08-04 10:38:02 +02:00
Safihre
05aac4e01e Remove redundant integer conversion 2018-08-04 10:36:03 +02:00
Safihre
267c48f9a7 Update gitignore for PyCharm 2018-08-04 10:00:46 +02:00
Safihre
5168915a65 Manualy par2 parsing in Deobfuscate.py for major performance increase
By @P1nGu1n, thanks!
2018-08-03 15:13:20 +02:00
Safihre
71017d0d55 Diskspeed test did not remove test file 2018-08-03 15:06:19 +02:00
Safihre
a5db51a2c5 Windows-tray also show queue size left info when paused 2018-08-01 21:25:17 +02:00
Safihre
0bf2968e6a Windows installer language wasn't parsed for the wizard
It needs a translation table and it was fetched from the wrong register location
2018-08-01 11:42:23 +02:00
Safihre
2ec5918f5e Extend disk-speed time to 1 second for more stable results 2018-08-01 07:57:24 +02:00
Safihre
04f5a63cd7 Backported new-style speed-test from Py3 branch
Old-style would create bad file on Windows. This is more robust. Thanks @albino1 for report!
2018-07-31 22:25:14 +02:00
Safihre
43d8283f5b Wizard final page not linking to Folders config page
Closes #1163
2018-07-31 22:07:01 +02:00
SABnzbd Automation
f8111121c4 Automatic translation update 2018-07-14 20:49:01 +00:00
Safihre
b53b73c135 Only abort active DirectUnpackers
Only when we have assigned a setname we are doing something, otherwise we might just as well leave the files and not delete too much. For example see: https://forums.sabnzbd.org/viewtopic.php?f=2&t=23440
2018-07-14 22:24:06 +02:00
Safihre
bd7b8a975b Wrap is_rarfile so it can't crash when files are gone
Seen in users log-files. Possible when deleting job during assembly-steps.
2018-07-14 21:20:11 +02:00
Safihre
7ca765f276 Lock start and stop of DirectUnpack so they can't overlap
In high-speed situations this could happen.
2018-07-14 21:18:57 +02:00
Safihre
b918a53af5 Add env-variables to pre-queue call 2018-07-14 17:28:37 +02:00
Safihre
525809afc9 Always add basic env-variables to external calls 2018-07-14 17:28:14 +02:00
Safihre
a7048cdc8e Use server hostname in logs and warnings
Closes #1159
2018-07-14 12:14:48 +02:00
Safihre
02888568bd Log par2 creator
Closes #1153
2018-07-02 10:29:07 +02:00
Safihre
203409f02f Update MultiPar to 1.3.0.1 2018-06-29 10:06:15 +02:00
Safihre
ecc8e6ac0e Update UnRar to 5.60 for Windows and macOS
Closes #1156
2018-06-29 10:01:40 +02:00
Safihre
852636acda Add VPN problems to known-issues 2018-06-12 11:14:07 +02:00
Safihre
bc18369552 Spelling fix in Deobfuscate.py
Closes #1152
2018-06-08 10:45:17 +02:00
Safihre
8f248a2219 Could not set single Indexer Tag for a category
"TV > HD" would become ['TV', '>', 'HD'].
See https://forums.sabnzbd.org/viewtopic.php?f=3&t=23409
2018-05-30 07:53:51 +02:00
Safihre
82857afed6 Set version to 2.3.4 2018-05-24 20:18:53 +02:00
Safihre
4e7f0a6a1e Merge branch 'develop' 2018-05-24 20:15:14 +02:00
Safihre
2a113f7f58 Update text files for 2.3.4 2018-05-24 20:11:34 +02:00
Safihre
6b8b9e0238 Update 7zip for Windows to 18.05
Closes #1140
No update for p7zip so far. Still on old old version.
2018-05-24 20:09:57 +02:00
Safihre
1e3e4b4118 Update text files for 2.3.4RC1 2018-05-19 18:24:01 +02:00
Safihre
87dfbe34d4 Use local Windows MailSlot address
In WIndows 10 April Creators update this stopped working.
https://forums.sabnzbd.org/viewtopic.php?f=3&t=23370
2018-05-14 20:11:22 +02:00
Safihre
c56bcfaf61 Only the HTTP status code 200 is required
Closes #1146
2018-05-14 19:45:28 +02:00
Safihre
a947a1d88b Always send NNTP QUIT after server-test 2018-05-14 11:07:48 +02:00
Safihre
ad0d5726ec Add option "--disable-file-log" to disable file-based logging
Closes #1103.
2018-05-10 10:52:56 +02:00
Safihre
c52ce58b6d Handle filenames in redirected URL's better
Originated from https://www.reddit.com/r/usenet/comments/8gfjky/this_started_happening_recently_what_could_be/
2018-05-10 10:17:40 +02:00
Sander
a90356c6e7 Automatically whitelist ".local" or ".local.", aka mDNS name (#1141)
Closes #1138
2018-05-04 16:53:57 +02:00
Safihre
5c78c7855b Use 64bit unrar 5.5.0 compatible with ElCapitan 2018-05-03 08:02:52 +02:00
Safihre
915ee650ee Add CORS * header
Closes #1136
2018-04-30 08:38:53 +02:00
Safihre
58bd12b083 Correctly lowercase host_whitelist
@sanderjo is right, this should always happen no matter how option saved.
2018-04-28 18:02:14 +02:00
Safihre
ecc334360a Auto-detected hostname should be lowercased
https://forums.sabnzbd.org/viewtopic.php?f=3&t=23352
2018-04-25 19:14:45 +02:00
Safihre
730652e3e1 Set version to 2.3.3 2018-04-22 19:15:41 +02:00
Safihre
1aed59d52e Merge branch 'develop' 2018-04-22 19:14:52 +02:00
Safihre
1f04343a4d Update text files for 2.3.3 2018-04-22 18:56:38 +02:00
SABnzbd Automation
70f8509f6e Automatic translation update 2018-04-21 18:57:45 +00:00
jcfp
74a97296a5 Accept cheetah version newer than 2.x
I'm going to update cheetah to v. 3.x in debian soonish. All seems to work fine with sab in a quick test, apart from this version check.
2018-04-21 16:41:28 +02:00
Safihre
45d3440443 Multi-archive and split-files can also have more than 999 parts
https://forums.sabnzbd.org/viewtopic.php?f=3&t=23338
2018-04-18 09:23:19 +02:00
Safihre
c872ee16ab Changing Server priority would give JSON-page 2018-04-17 15:53:09 +02:00
Safihre
da473424f2 Bump SABYenc to 3.3.5 2018-04-17 15:52:20 +02:00
Safihre
e0dc988f94 Update text files for 2.3.3RC2 2018-04-13 13:24:24 +02:00
sanderjo
4021e6098c Improved SAN in SAB's self signed cert.
Closes #1127
Closes #1128
2018-04-13 13:05:14 +02:00
Safihre
f521037669 Files unpacked from 7zip/zip were not sorted
Closes #1124
Introduced new function to recursive list all files in a directory.
2018-04-13 10:04:45 +02:00
Safihre
246e9e421b Re-shuffle deriving of script from category
Closes #1125
2018-04-12 11:54:27 +02:00
Safihre
8aaee09652 Increase some logging and history update 2018-04-10 16:22:28 +02:00
Sander
e36450a666 Better IPv6 address handling in hostname check 2018-04-02 10:38:23 +02:00
Safihre
d84f31c116 Update text files for 2.3.3RC1 2018-03-27 12:52:37 +02:00
Safihre
e5fc51e9d7 Add option to disable X-Frame-Headers
Closes #1118
Now only added to page-requests, not also to every API-request. Only needed for real pages.
2018-03-27 10:51:51 +02:00
Safihre
291a72ec63 Bump SABYenc to 3.3.4 2018-03-25 17:16:47 +02:00
SABnzbd Automation
4dd5115b03 Automatic translation update 2018-03-23 20:09:30 +00:00
Safihre
3bdb8407d2 Hide smpl as option in Config General 2018-03-23 11:38:00 +01:00
Safihre
a88055c491 Restructure interface.py
Such a mess.
Time to clean up bit by bit.
2018-03-23 11:24:56 +01:00
Safihre
8a676aeab4 Use general shutdown_program function everywhere 2018-03-23 11:01:42 +01:00
Safihre
d41276aa82 Make maximum retries of URL fetches configurable
As a Special-option for now.
Closes #1115.
2018-03-21 22:57:04 +01:00
Safihre
96cb0aa8db Run PostProc on failed URL fetches in order to also run scripts
Closes #1108
Linked #1115
2018-03-21 10:06:23 +01:00
Safihre
03e7889d5c Evaluate cat/pp/script/priority also for placeholder NZO's
Needed to resolve #1108.
Also made small changes to improve readability of init() function.
2018-03-20 16:42:34 +01:00
Safihre
5c161b884c Remove unnecessary code from set_cat in interface
Need to get rid of SMPL.
2018-03-20 16:25:21 +01:00
Safihre
f77cc43b7d Indicate that SMPL skin may lose functionality in future releases 2018-03-20 15:15:37 +01:00
Safihre
bd709a7bdd Use VALID_NZB_FILES constant 2018-03-20 11:36:32 +01:00
Safihre
c3832a85f7 Job would stay forever if URLGrabber grabbed nonsense 2018-03-18 18:36:58 +01:00
SABnzbd Automation
52aa7a08d7 Automatic translation update 2018-03-16 12:27:43 +00:00
Safihre
ee0358cf06 Update text-files for 2.3.3Beta1 2018-03-16 13:16:21 +01:00
Safihre
2344a50f6c Update SABYenc to 3.3.3
Fix a problem with false CRC errors.
2018-03-16 11:52:24 +01:00
Safihre
a2774ce762 Prepare text-files for 2.3.3Beta1 2018-03-15 13:44:51 +01:00
Safihre
2cdf284578 Update copyright to 2018 2018-03-15 13:14:08 +01:00
Safihre
3fd9e85236 Warning when we refuse connection to unlisted hostname 2018-03-15 09:06:56 +01:00
SABnzbd Automation
f08eaa4e53 Automatic translation update 2018-03-15 07:27:56 +00:00
Safihre
f47a6a889e Mitigate DNS re-binding attacks by introducing hostname whitelist
By default add the current hostname to the list, since we also show this URL as an option in the wizard. 
Closes #1092
2018-03-14 16:51:23 +01:00
Safihre
14b32f30f0 Remove callback from API
It was only half-implemented.
2018-03-14 09:59:32 +01:00
Safihre
20fbea6e31 Rework security flow of webpages
Have 1 wrapper that checks all secuirty checks and exposes the package for CherryPy.
Cleaner code that can then be used to implement global security checks.
Merged wizard.py into interface.py and removed lots of overhead code that is handled by the general system.
2018-03-13 16:03:49 +01:00
Safihre
53261ad311 Correct creation of Server-instance during test
Forgot to fix after 0b9b28112d
2018-03-13 15:19:12 +01:00
Safihre
4686bc1fa6 Indicate that SMPL is no long supported 2018-03-13 08:21:17 +01:00
sanderjo
0d806305c2 Usage: show NZB adding via command line 2018-03-12 19:58:44 +01:00
Safihre
ee0623d68b Set priority level of Windows subprocess from Config
Closes #1109.
Default changed from IDLE to NORMAL.
2018-03-12 16:34:10 +01:00
Safihre
0b9b28112d Make SSL-Ciphers a per-server setting
Closes #1110
2018-03-12 08:44:04 +01:00
Safihre
3ebe7dff45 Remove references to old 'fillserver'
It's been more than 3 years now.
2018-03-12 08:27:27 +01:00
Safihre
7d4b665cd9 Refresh translation cache also when changed via API
Linked #1106
2018-02-27 15:48:08 +01:00
shypike
5b7224bf4c Add Special option to ignore empty file definitions in NZB
Called "ignore_empty_files".
2018-02-18 21:42:27 +01:00
Safihre
1b67c9c13d 99 is the highest server priority
Closes #1100
2018-02-14 15:15:34 +01:00
Safihre
fe849d8805 Correct error display during adding of server 2018-02-13 08:26:55 +01:00
Safihre
07c3ff9710 Add X-Frame-Options header to prevent click-jacking
Linked #1092
2018-02-07 00:37:50 +01:00
Safihre
f3e18ac355 Set httpOnly for cookies to not allow scripts to access login cookies 2018-02-07 00:14:43 +01:00
Safihre
2bafefa795 Warn about the amount of time server is ignored on failing lookup
And also only look-up when actually something in the queue.
2018-02-05 09:39:14 +01:00
Safihre
c9fcd4cecc Do not assume bad SSL setup due to no network at program start
Close #1091
2018-02-05 09:38:28 +01:00
Safihre
343d9b10cf Prevent false-positives of "password" detection in common media files
Closes #1096
2018-02-02 09:17:29 +01:00
Safihre
3d219c9382 Improve non-resolving server detection 2018-02-02 08:53:12 +01:00
Safihre
031ed3f01e Update 7zip to 18.01 for Windows 2018-02-02 07:35:03 +01:00
Safihre
f20a30cfc2 Show warning and block non-resolving newsservers
Closes #1098
2018-02-01 14:53:01 +01:00
Safihre
b5dcfe0238 Show JSON-returned errors also to user in Config 2018-02-01 09:09:08 +01:00
Sander
d169bb5e28 64-bit version of unrar (for MacOS) 2018-01-31 10:47:21 +01:00
Safihre
25429b5b19 Detect '502 Byte limit exceeded' as payment problem
Was now detected as too-many-connections problem, while it's actually a permanent problem.
Related to: https://forums.sabnzbd.org/viewtopic.php?f=2&t=23151&p=114795#p114795
2018-01-11 17:26:15 +01:00
Safihre
9638eab564 Full server-lookup was not performed during server testing
Because of this, Happy Eyeballs or any other server-load balancing that is performed during downloading was not applied. 
Closes #1089
2018-01-10 14:52:10 +01:00
Safihre
c6b84660e3 Add tooltip to Force Disconnect 2018-01-09 08:08:21 +01:00
Safihre
cc61e669ef Catch checksum errors of MultiPar 2017-12-25 17:27:31 +01:00
Safihre
3a19741edb Add traceback information to Invalid NZB error 2017-12-25 17:11:13 +01:00
Safihre
3c87fd45c3 Set version to 2.3.2 2017-12-21 20:41:52 +01:00
Safihre
d0a258ce28 Merge branch 'develop' 2017-12-21 20:38:19 +01:00
Safihre
1cac5799eb Update text files for 2.3.2 2017-12-21 20:19:15 +01:00
Safihre
a56e4f3650 Small reduction of the unused encoding functions 2017-12-20 15:07:50 +01:00
Safihre
9970d2ee6f Remove obsolete UNTRANS function
We now use subprocessww, to pass real unicode to new processes on Windows.
2017-12-20 14:10:39 +01:00
Safihre
f2695e9305 Correctly handle unicode/deunicode in pre-queue script 2017-12-20 14:10:13 +01:00
Safihre
d333f1b56b Deunicode should make string of everything 2017-12-20 14:10:13 +01:00
shypike
00f5b29caa Fix CP850-Latin1 mapping, prevent "f" from being converted to 0x9F 2017-12-20 13:46:32 +01:00
Safihre
16aa43c120 Also extract password from job filename
Closes #1084
2017-12-19 22:27:36 +01:00
Safihre
b2179d5b3e Small patch of Categorie Config highlights 2017-12-14 15:36:43 +01:00
Safihre
8e2e9fd5c9 Remove extra logging for missing article DB errors
Closes #952
2017-12-14 10:36:17 +01:00
Safihre
2e3181779a Correct highlighting in Config for each sub-section 2017-12-14 10:19:52 +01:00
Safihre
8a270e49be Add caller name to logging for purging of job data 2017-12-11 15:15:47 +01:00
Safihre
32e26c804b Correctly update row colors when selecting Advanced Settings 2017-12-10 15:35:25 +01:00
Safihre
5cf87be51e Update README typo 2017-12-10 15:35:11 +01:00
SABnzbd Automation
781c45bf3b Automatic translation update 2017-12-09 15:32:43 +00:00
Safihre
df5a85f851 Correctly update text files for 2.3.2RC2
Typo!
2017-12-09 16:23:49 +01:00
Safihre
8373994be6 Add extra logging when folder wasn't removed in purge 2017-12-09 16:20:36 +01:00
Safihre
437ff427f8 Improvements to Advanced Settings switching in Config 2017-12-09 15:16:38 +01:00
Safihre
ea5e4dfee1 If Download all par2 was enabled jobs would get orphaned
Mistake in fd1975617b
Jobs were incorrectly re-added after QC failure because already downloaded par2 files were "added" as new ones, resulting in orphaned jobs because there were no new files.
2017-12-09 14:18:22 +01:00
SABnzbd Automation
2e45f4028c Automatic translation update 2017-12-08 11:17:18 +00:00
Safihre
d7cdce9278 Update text files for 2.3.2RC2 2017-12-08 11:49:33 +01:00
Safihre
38c329ade2 Hide Advanced Settings
Closes #1082
2017-12-08 10:15:18 +01:00
Safihre
933d0c073c If wizard started with already setup servers, select the right one 2017-12-06 16:10:50 +01:00
Safihre
1f49bba343 Make warning about SABnzbd possibly being exposed more prominent 2017-12-06 15:49:12 +01:00
Safihre
0ac712dce1 Add 'Job Name as Folder Name' as Sorting Preset for de-obfuscation
Also rename internal variable `dirname`, since it's really just the job-name.
2017-12-06 10:57:36 +01:00
Safihre
f1ae9060c3 Remove white space before some warnings
Closes #1081
2017-12-04 10:30:09 +01:00
Safihre
263231bb62 Lower upper-bound for auto-detected Article Cache to 1G
2G is a bit too much and should only happen in extreme cases
2017-12-03 20:36:30 +01:00
SABnzbd Automation
a7b964c153 Automatic translation update 2017-12-03 16:42:42 +00:00
Safihre
78035eed12 Wizard server test with existing server would require re-entering pass 2017-12-03 13:47:42 +01:00
Safihre
0e7ac8ec5e Add new %dn to Pattern Key for Date Sorting
Closes #1080
2017-12-03 10:49:38 +01:00
Safihre
00f262c90e Rename tvsort.py to sorting.py for consistency 2017-12-03 10:44:20 +01:00
Safihre
68df476603 Rename Generic Sorter to Movie Sorter to match interface name 2017-12-03 10:38:48 +01:00
Safihre
5da03f506d Replace ".avi" by ".mkv" in examples for Sorting 2017-12-03 10:34:40 +01:00
Safihre
0c9540e41e Add %dn to DateSorter 2017-12-03 10:29:59 +01:00
Safihre
c6226c6adb Add per-day download-stats to server_stats API-call
Linked #1079
2017-12-01 19:03:52 +01:00
Safihre
b8aab5c0f8 Lower bound for auto-detected article cache set to 32M again
For systems with 256MB or 512MB memory the auto-detected values are valid (e.g. 64MB and 128MB) and would otherwise be discarded.
2017-12-01 00:22:51 +01:00
Safihre
e29e7a65b5 Limit auto-detected article cache to 2GB
Users can set more if they want, or if they are on 32bit system it will be limited automatically to 1GB.
2017-11-30 23:59:44 +01:00
Safihre
eb46ed80b6 Also use 1/4th of memory on macOS for Article Cache 2017-11-30 23:45:32 +01:00
Safihre
50b2d558eb Also use 1/4th of memory on Windows for Article Cache 2017-11-30 23:45:20 +01:00
Fish2
3b4b3dcca2 Add moment.js Language he.js - Hebrew 2017-11-27 20:54:16 +01:00
Fish2
e0f8410918 Remove Unused Language Files 2017-11-27 20:54:16 +01:00
Fish2
581942ca11 Update bootstrap3-typeahead.js to V4.0.2, Update jQuery Form Plugin To 4.2.2, Update jquery-qrcode To v0.14.0 2017-11-27 20:54:16 +01:00
Fish2
005fd399d0 Update Glitter moment.js to 2.19.2 2017-11-27 20:54:16 +01:00
Fish2
6ff00bc992 Update Six to 1.11.0 2017-11-27 20:54:16 +01:00
Safihre
ca66eb04ad Note restoring of OSX 10.11.6 compatibility for 2.3.2RC1
Closes #1075
2017-11-26 11:07:24 +01:00
Safihre
91a2532c95 Show warning in wizard when language files are not present
Closes #1074
2017-11-25 10:51:31 +01:00
SABnzbd Automation
cb49f7bb53 Automatic translation update 2017-11-23 17:12:04 +00:00
Safihre
0049f14d7f Update text files for 2.3.2RC1 2017-11-23 17:31:22 +01:00
Safihre
e414874910 Only report missing UnZip when missing 7Zip 2017-11-23 13:39:04 +01:00
Safihre
5931beaa5c Quote all arguments to external processes on Windows 2017-11-23 13:36:47 +01:00
Safihre
b3d80b7c65 Remove unzip from Windows distribution 2017-11-20 20:24:01 +01:00
shypike
9aa90083b2 When available, use 7zip instead of built-in unzip.
7zip supports passwords, many old platform unzips do not.
2017-11-20 20:24:01 +01:00
Safihre
0b939a9519 Notification category Default only means "all categories" if non other 2017-11-20 20:08:32 +01:00
Safihre
501867137a Reset also article try-list on dropped server connections
Test release for these users showed that "server dropped connection" also needed a reset of the article trylist.
Linked:
https://forums.sabnzbd.org/viewtopic.php?f=2&t=23111
https://forums.sabnzbd.org/viewtopic.php?f=2&t=23109
2017-11-20 12:06:56 +01:00
Safihre
75d4b5deca Add tooltip to server graph month selector 2017-11-19 15:02:17 +01:00
Safihre
442591f20c Link server graphs (month and scale) and show monthly total
Closes #1028
2017-11-19 14:58:43 +01:00
Safihre
74b7f383db Always show 1 decimal for MB and above
So it is similar to the JS filesize function
2017-11-19 14:55:46 +01:00
Safihre
b966707247 Implement fast and slow queue in postprocessor
So I implemented this in a kind of cheeky easy way: 
There is a fast and a slow queue in the `PostProcessor`, if we see it has completed Direct Unpack or if it is still active (might be unpacking that last volume) it is put in the fast queue.
We always check the fast queue first, then the slow one. To not have slow jobs be stuck in the post-proc queue forever, every 3 fast jobs we force a slow job (if present).
Closes #1014
2017-11-18 19:00:52 +01:00
Safihre
eada7286d1 Small optimization in registering of article for first articles 2017-11-18 16:31:47 +01:00
Safihre
b9c312961b listdir can fail on Windows for C: instead of C:\ and crashing postproc 2017-11-18 09:55:56 +01:00
Safihre
8389b4fedb Re-evaluate filenames after first par2 file is finished
Just in case it came in after all first articles were downloaded.
2017-11-18 00:57:15 +01:00
Safihre
7720cd60ec Decode first articles of each file first to find filenames
Get first article of each file first, so that we can use the par2 information to get the filename. This way on obfuscated posts we can faster start Direct Unpack.
In a bit convoluted way so that we can also stay performance tuned.
Closes #1044
2017-11-18 00:45:40 +01:00
Safihre
d1d437074a Do not flush article cache to disk on pause
Overly cautious
2017-11-18 00:13:50 +01:00
Safihre
30d3d62e09 Correct MultiPar Extra Par2 Option passing 2017-11-17 22:29:15 +01:00
Safihre
fd1975617b Simplify and comment code on par2 errors
Closes #1070 (read for details)
2017-11-17 22:24:55 +01:00
SABnzbd Automation
1d037dcb62 Automatic translation update 2017-11-16 22:12:35 +00:00
Safihre
bd5fb3e88f Remove unused crash_msg postproc code 2017-11-16 22:21:39 +01:00
Safihre
5ca66bfeef Only require SABYenc to match minor version and bump to 3.3.2 2017-11-16 20:27:32 +01:00
Safihre
c9fbadd097 Improve progressbar on new Firefox 2017-11-15 15:33:44 +02:00
Safihre
f4568ad7dd Do not warn about disk-errors when job is deleted/done
Only happens in really fast situations with many server connections for small downloads completed within 1 or 2 seconds.
2017-11-15 13:02:01 +02:00
Safihre
74a395f584 Increase wait-time in URLGrabber and stop after 10 tries
Closes #1069
2017-11-13 18:10:18 +02:00
Safihre
dd703ace7f Fully parse output of Pre-Q script correctly
PP/Priority are different, since 0 is also a valid value for these settings.
2017-11-12 14:19:10 +02:00
Safihre
ddc8396260 Empty values in Pre-Q script should not just be ignored 2017-11-12 13:07:41 +02:00
Safihre
e6724e347c Do not 'fix' sending of priority and pp-setting, removes the 0 setting
Priority Normal and PP-setting Download have a value of 0, so they were not send to the pre-Q script.
2017-11-11 17:26:14 +02:00
Safihre
7fa0508ae8 Show tooltip to explain 'Show Logging' anonymized the data 2017-11-11 17:03:53 +02:00
Safihre
06c6f7d38e Auto-history-purge on start-up
Closes #1065
2017-11-11 16:46:11 +02:00
Safihre
59ef400fec Log history-status of failure during repair
For easier debugging
2017-11-11 16:40:04 +02:00
Safihre
0acdf15755 Always show user-entered password in History-info
People like to have this info, some users want to only unpack later etc.
2017-11-11 16:31:38 +02:00
SABnzbd Automation
40128f59dd Automatic translation update 2017-11-07 13:33:01 +00:00
Safihre
286914f253 Correctly report when repair is performed, but fails
Strange, but it can happen. There has been an uptick in this kind of failing downloads. Seems to be a problem with the original par2-files, but not sure yet what it is.
 https://forums.sabnzbd.org/viewtopic.php?f=2&t=23048&p=114059
2017-11-06 13:02:23 +01:00
Fish2
f9bd58bb74 lossless compression of images 2017-11-04 11:47:21 +01:00
Safihre
30cfb9c6fc Prevent potential pause/unpause loop after tray click 2017-10-27 23:39:01 +02:00
Safihre
5ca4811689 Set version to 2.3.1 and resolve merge conflicts 2017-10-27 10:12:36 +02:00
Safihre
043e5966ff Merge branch 'develop' 2017-10-27 10:07:42 +02:00
Safihre
f1695ec875 Set Windows tray double click timeout to user double-click timeout 2017-10-26 20:30:49 +02:00
Safihre
a3db910a4d Update text files for 2.3.1 Final 2017-10-26 20:15:21 +02:00
Safihre
80a29c50c9 In the binaries (macOS/Windows) getmodule does not work
Because the files are compiled, they loose this info it seems.
2017-10-26 14:23:58 +02:00
SABnzbd Automation
d693e20e1a Automatic translation update 2017-10-26 11:23:00 +00:00
SABnzbd Automation
1a36f548df Automatic translation update 2017-10-25 23:23:37 +00:00
Safihre
a6f6d88ab9 Move get_from_url to sabnzbd.misc 2017-10-25 23:00:32 +02:00
Safihre
aa7fb17b4e Reduce standard timeout of new servers to 60 seconds
In line with other usenet software.
2017-10-25 22:08:14 +02:00
Safihre
a99d333272 Fix racing-condition for files coming in after post-proc is started
Files could still come in after post-proc is already started. Post-proc could even have already deleted the job folder, resulting in Assembler errors for these new files. So when post-proc is active, it is safe to ignore write-errors.
Linked topic: https://forums.sabnzbd.org/viewtopic.php?f=3&t=23049
2017-10-25 11:21:59 +02:00
Safihre
801aadecfc Add logging to stop_idle_jobs 2017-10-24 10:46:18 +02:00
Safihre
124e2b253c Do not crash when unrar gets stopped or killed in Direct Unpacker 2017-10-24 10:32:17 +02:00
Safihre
39cceed580 NZF completed is a property method, not an attribute 2017-10-24 10:07:45 +02:00
Safihre
bfcf56ec45 Log traceback on assembler disk error 2017-10-23 21:58:24 +02:00
Safihre
01603b24f5 Do not log decoding of articles by default to reduce logging density
The Debug logs were getting way too confusing with all the "Decoding X" in there, when 99% of the time it is of no importance.
2017-10-23 20:42:17 +02:00
Safihre
d93d2591b7 Do not pause after sending notifications 2017-10-23 11:33:11 +02:00
Safihre
c17fcec499 Correctly handle single and double clicks on Windows tray-icon
On Windows, double click is actually 1 single click followed by a double-click event, no way to differentiate that first click-event of a double-click from an actual just single-click. So we need a timed callback to cancel when we do get the double-click event. Luckily the win32api provides this function.
2017-10-23 11:11:39 +02:00
Safihre
6804ac20da Remove unused is_utf8 function 2017-10-23 11:09:55 +02:00
Safihre
4ed7ac3dea Do not crash when directory creation fails in Direct Unpack 2017-10-22 13:23:57 +02:00
Safihre
eae8056366 Remove old par2cmdline/par2tbb options for MultiPar 2017-10-22 12:58:07 +02:00
Safihre
df4680b6d0 Simply function-caller detection for performance
Much much faster.
#1059
2017-10-22 11:46:41 +02:00
Safihre
b8f5861044 Add new translatable text 2017-10-22 11:24:46 +02:00
Safihre
8e01ceca7a Show current output of running script in History
For long-running scripts.
2017-10-21 14:31:22 +02:00
Safihre
0e1cdec78f Log actual filename and ID of new files 2017-10-21 14:07:06 +02:00
Safihre
13e5e93953 Tweak display of Direct Unpack and Fetching jobs 2017-10-20 21:56:26 +02:00
Safihre
02d08f38eb Correct handeling of jobs fetching extra blocks
Always need to set status to Fetching when getting more blocks and not show the priority selector in Glitter to prevent it from updating the priority. 
@OneCDOnly
2017-10-20 21:03:58 +02:00
Safihre
770951bfe6 Update text files for 2.3.1RC2 2017-10-20 10:59:21 +02:00
Safihre
022898bf63 ENV fields of 0 should be listed as 0 and not empty
So first convert to string, then de-unicode.
2017-10-20 10:58:48 +02:00
Safihre
4fd2d8505b Reset last_volume_linebuf for multi-sets in Direct Unpacker
And log in case of duplicate lines in Unrar output
2017-10-19 22:32:11 +02:00
Safihre
cc72bb743a Use C for [C]ontinue in Direct Unpack 2017-10-19 17:24:06 +02:00
Safihre
d7869fc3a1 Make Direct Unpack icon always visible
Add also the info to the hover, to somehow still explain the icon. Having this info always visible is more important than being able to hover over it directly.
Closes #1013
2017-10-19 15:07:47 +02:00
Sander Jo
4fbf870028 Example Post-Processing Script, written in Python. 2017-10-16 10:21:08 +02:00
Safihre
306558b52f Tracing of function caller only on Debug level logging
And add caller-name to NzbQueue-remove
2017-10-15 16:26:04 +02:00
Safihre
db19875f5d Add caller name to more logging 2017-10-15 10:46:44 +02:00
Safihre
f8061dc9c8 Log caller of history delete and pickle loading/saving
#1054
2017-10-15 10:30:38 +02:00
Safihre
c73591eb20 Allow up to 4GB Article Cache on 64bit systems 2017-10-15 09:58:51 +02:00
Safihre
ec132374a6 Resolve locking situation by only using 1 lock 2017-10-14 15:06:45 +02:00
Safihre
262964c6c2 Add path to gzipped NZB to script environment variables 2017-10-14 10:42:15 +02:00
OneCDOnly
cdaad3ed90 Correct spelling of 'separate' in a few contexts 2017-10-14 09:51:25 +02:00
SABnzbd Automation
84f54f5c57 Automatic translation update 2017-10-13 14:36:36 +00:00
Safihre
00436dfb2c Update text files for 2.3.1RC1 2017-10-13 16:07:21 +02:00
Safihre
c3ce87bd10 TRANS/UNTRANS fails when input is None 2017-10-13 14:03:55 +02:00
Safihre
c3a48a61b6 Correctly fix deadlock of NZBQUEUE_LOCK and DOWNLOADER_CV 2017-10-13 14:03:49 +02:00
Safihre
0c03476d76 Always send failure_url, like we say in the documentation 2017-10-12 16:23:26 +02:00
Safihre
6148cd5445 NZBQUEUE_LOCK in DirScanner causes lock-up when changing prio during add 2017-10-12 15:39:44 +02:00
Safihre
7f72e2042c Add 'Accept' to 'From Show SxxEyy' 2017-10-12 15:28:06 +02:00
Safihre
638b29819c 'From Show SxxEyy' is an Accept filter
Oops.........
2017-10-11 22:23:25 +02:00
Safihre
b950820099 Testing notification email and limiting to certain categories was broken 2017-10-11 13:27:01 +02:00
Safihre
96adf76ef1 Show NZF-ID in warning to find where files got deleted
Linked: #1034 and #952
2017-10-11 11:59:53 +02:00
Safihre
556a4db186 Log all file and folder removal
Linked: #1034 and #973
2017-10-11 11:55:53 +02:00
Safihre
b9fbd19064 Prevent downloader crash when performing slowdown check 2017-10-11 10:13:30 +02:00
Safihre
167e7f2870 Do not warn too much when renames can't be saved
Probably happens after the sucessfull finishing of the job anyway.
2017-10-10 16:25:19 +02:00
Safihre
4dba5b8caa Make sure that Category folders are not a sub-folder of Incomplete
We only check during adding or updating of categories, but that's already better than before.
Example: #1047
2017-10-09 23:11:11 +02:00
SABnzbd Automation
831b64daa8 Automatic translation update 2017-10-09 21:04:38 +00:00
Safihre
38fd5cde29 Unicode needs to be de-unicoded on all platforms 2017-10-09 22:34:47 +02:00
Safihre
e39456cca1 Filter out any unicode in ENV variables 2017-10-09 22:21:19 +02:00
andofrjando
8e9425855b Much nicer icon and colour 2017-10-08 11:39:37 +02:00
andofrjando
89add6edac New feature: Safari pinned tab icon 2017-10-08 11:39:37 +02:00
SABnzbd Automation
16b85429ae Automatic translation update 2017-10-06 09:14:40 +00:00
Safihre
2482c8e70a Update text files for 2.3.1Beta1 2017-10-06 10:58:23 +02:00
Safihre
ad2bb6c3a7 Also remove trailing .par2 from final folder
Otherwise Deobfuscate.py fails due to bug in par2cmdline that can't handle folders with "par2" in it.
2017-10-06 10:32:53 +02:00
SABnzbd Automation
2c7e725e39 Automatic translation update 2017-10-05 13:29:56 +00:00
Safihre
123f05f164 Use internal list2cmdline to correct path escaping on Windows
More info: #1043
Closes #1043
2017-10-05 14:46:13 +02:00
Safihre
4ade2e0c60 Special Windows-names can break RarFile 2017-10-05 14:29:04 +02:00
Safihre
c908a396df Correct the Deobfuscate script 2017-10-04 19:51:28 +02:00
Safihre
15f2370bca Leave undefined ENV variables empty 2017-10-04 19:51:04 +02:00
Safihre
a5e208eb11 Abort Direct Unpack if stuck for more than 2 minutes
Safety measure
2017-10-04 15:21:22 +02:00
Safihre
d59b3b3679 Also parse extracted files during DirectUnpack 2017-10-04 15:21:22 +02:00
Safihre
476542463a Start-up check for subprocessww 2017-10-04 15:21:21 +02:00
Safihre
52267a9565 Use special patched version of subprocess for Unicode POpen on Windows 2017-10-03 14:31:49 +02:00
SABnzbd Automation
972e708810 Automatic translation update 2017-10-02 20:48:06 +00:00
walgarch
a636f7f18e Added 'with' to list of lowercase words in titles 2017-10-02 14:02:47 +02:00
Safihre
9d5b3e9621 When re-adding set status to Downloading so time left is calculated
@OneCDOnly
2017-09-30 22:54:20 +02:00
SABnzbd Automation
9e2d8e5e55 Automatic translation update 2017-09-30 19:32:39 +00:00
Safihre
d9899cc5cd Increase delay to 2 seconds between sub-processes on Windows
1 was not enough, or something else is wrong
2017-09-30 21:18:44 +02:00
Safihre
650e83e1b8 Datetimes in history/warnings were displayed in UTC and not local time 2017-09-30 17:55:22 +02:00
Safihre
4296e1628b Add NZBQUEUE_LOCK to importing of NZBs
So that nothing can happen between uploading/adding and when they are actually in the Queue. Before this it would show the job as "orphaned" while it was being added.
2017-09-28 17:17:16 +02:00
Safihre
5dddc7ab61 Re-introduce NZBQUEUE_LOCK
There seems to be no performance impact.

Linked #1034 and #952 

Reverts partially 02ebb97a8b
2017-09-28 16:52:31 +02:00
Safihre
529ba69584 Do not save to disk when pausing due to import error
Linked #1034
2017-09-28 16:22:49 +02:00
Safihre
9f35568a24 UnRar prints path including folder, even when set to flat-unpack 2017-09-28 11:13:23 +02:00
Safihre
fe40d49c26 Update server-info on every update
https://forums.sabnzbd.org/viewtopic.php?f=3&t=23003
@OneCDOnly
2017-09-28 10:25:14 +02:00
Safihre
6c0b32004a Update RecursivePar2 to be more general deobfuscation
On top of scanning for the "rename.par2", it will also rename the largest file to the job-name. But only if there's 1 large file, otherwise it could get messy. If they want more, they can do it themselves or submit a PR.
2017-09-27 14:32:12 +02:00
Safihre
bda4e102d6 Correctly display Force priority icon in Glitter Night 2017-09-27 10:27:14 +02:00
Safihre
5e03204dbc The From SxxEyy filters need integers, not strings 2017-09-26 11:53:08 +02:00
Safihre
b5deda4195 Revert "Improve display of quota"
This reverts commit 5076892d83.
2017-09-25 21:57:42 +02:00
Safihre
5076892d83 Improve display of quota 2017-09-25 21:17:17 +02:00
Safihre
6c3a3e1694 Update translatable texts 2017-09-25 21:02:32 +02:00
Safihre
edbbcec272 Remove unused imports 2017-09-24 17:45:08 +02:00
Safihre
aced381763 Add example script for users that will handle "rename.par2" after unpack 2017-09-24 11:46:43 +02:00
Safihre
cdd3f9cc8a Add also program information to PostProcessing-ENV vars 2017-09-24 11:46:33 +02:00
Safihre
a2074f06d5 Only run Pre-Queue script section when it is set 2017-09-24 11:02:06 +02:00
Safihre
39aa3a9c51 Fix the priority assigment when uploading file
Thanks @OneCDOnly

https://forums.sabnzbd.org/viewtopic.php?f=3&p=113482#p113482
2017-09-24 11:02:00 +02:00
Safihre
c6cf3cc45d Left-click on Windows-tray now toggles Pause/Resume (did nothing before) 2017-09-22 13:50:41 +02:00
Safihre
0378f6f8b1 Show pop-up on Windows for some start failures like port occupied 2017-09-22 10:50:01 +02:00
Safihre
d6b48803a6 Priority of category was ignored when adding NZB 2017-09-22 09:42:37 +02:00
Safihre
6da23930bf Let browsers do part of the input validation 2017-09-22 09:19:11 +02:00
Safihre
d4e1464cc0 Server priority is maxium 99, not 100
Otherwise the sorting and selecting does not work.
https://forums.sabnzbd.org/viewtopic.php?f=3&p=113467
2017-09-22 09:10:12 +02:00
Safihre
e8dc3ebd51 Bump version to 2.4.0 2017-09-22 09:05:30 +02:00
onno.vos.dev
4009d855c3 Ensure that Pushover works for Emergency prio as well 2017-09-21 21:12:57 +02:00
Safihre
d69796d351 Set version to 2.3.0 and resolve merge conflict 2017-09-21 10:13:56 +02:00
Safihre
a2d5713477 Merge branch 'develop' 2017-09-21 10:10:45 +02:00
Safihre
ba7d906bea Update text files for 2.3.0 2017-09-20 23:16:26 +02:00
Safihre
68f78b0e71 Subprocess on Windows needs at least 1 second timeout globally 2017-09-20 11:50:41 +02:00
Safihre
d5cd0180d8 Update SABYenc to 3.3.1 2017-09-19 17:23:29 +02:00
Safihre
10265bdfb4 Always use save-routine in database.execute so we can catch locked DB
Linked #1034
2017-09-19 09:18:22 +02:00
Safihre
3d0d67bffc Update SABYenc to 3.3.0
Faster!
2017-09-19 08:31:31 +02:00
Safihre
11bd16a653 Accidentally removed HTML-cleaning from error display
Linked #1034
2017-09-19 06:44:29 +02:00
Safihre
248f2da8a6 Pause job in case of failing imports
Linked #1034
2017-09-19 06:19:06 +02:00
Safihre
7b7aaaf467 Widen Confg Servers month selector
Closes #1032
2017-09-16 10:15:20 +02:00
SABnzbd Automation
0030e4dd36 Automatic translation update 2017-09-15 13:51:26 +00:00
Safihre
c3013d67b4 Show NZB name from RSS-feed when fetching
Of course not the name of the RSS-feed itself.
2017-09-15 14:21:45 +02:00
Safihre
ddb7f2a40c Require SABYenc 3.1.1 due to PyPi mixup
Broken source-distribution resulted in heaving to create new release, PyPi does not allow to re-upload files.
2017-09-15 12:59:24 +02:00
Safihre
167a94736e Update text files for 2.3.0RC2 2017-09-15 09:41:12 +02:00
Safihre
8b3e30f0a1 Require SABYenc 3.1.0 and no longer list CRC values
Since SABYenc 3.1.0 doesn't calculate them anymore for speed improvements.
2017-09-15 09:36:24 +02:00
Safihre
c383fa88fb Update ISSUES to match the website and list Synology being special 2017-09-13 20:51:07 +02:00
Safihre
ee72c1e4d5 Correct the testing-releases feedback slider 2017-09-11 22:13:06 +02:00
Safihre
a80bd826d6 Use the NZO password to save in database
build_history_info is called on every interface update to pretend the post-processing job is a history-job. So a password file (if set) would be loaded every second. Thanks @sanderjo!
2017-09-11 20:43:27 +02:00
Safihre
63e6d45bb1 Make sure users are warned that their settings could be unsafe 2017-09-10 21:45:19 +02:00
SABnzbd Automation
61300db1fb Automatic translation update 2017-09-10 18:24:12 +00:00
Safihre
23d005fc36 Update texts and links for 2.3.0 release to come 2017-09-10 20:01:45 +02:00
Safihre
871b351656 Set could potentially be missing from extrapars
Some bad handeling of newsunpack possibly
2017-09-10 19:29:58 +02:00
Safihre
6b53b9934e Waiting for URL would result in 100% CPU usage due to constant looping 2017-09-10 12:45:30 +02:00
Safihre
e038e08a60 Revert "Permanent notice in Plush to upgrade to Glitter"
6fdeab6948
2017-09-09 23:51:23 +02:00
Safihre
8300cb7762 Correct command-line help 2017-09-09 17:31:00 +02:00
Safihre
ec8302717f Warnings API-call now returns a dict like it should 2017-09-09 16:27:55 +02:00
Safihre
6b872b44db MultiPar can report both "PAR File(s) Incomplete" and "Need more blocks" 2017-09-09 16:20:35 +02:00
Safihre
6e48ebccc7 Correctly display time if server and brower are not in same timezone
#989
2017-09-09 13:16:11 +02:00
Safihre
1cd24d1fd0 Locking when iterating over extrapars for queue-stats
But only when we request all the files.
Closes #1029
2017-09-09 10:45:39 +02:00
Safihre
fa6bb79f53 Fix Chrome 61 table border change 2017-09-08 14:51:43 +02:00
Safihre
22e9086e09 Add AppVeyor for Windows testing
#999
2017-09-08 12:07:28 +02:00
Safihre
408b84e02d Basic NZB adding via upload or from disk 2017-09-08 10:28:01 +02:00
Safihre
16ac4dce21 Use requests module for testing 2017-09-08 10:28:01 +02:00
Safihre
5ce1554a46 Hide/Show Details in Config > Servers 2017-09-08 10:27:46 +02:00
Safihre
2d84ed6813 Always have something in RSS jobs storage 2017-09-07 00:26:59 +02:00
Safihre
06dc5b181e Do not leave rss.jobs empty to prevent crash later on
See: https://forums.sabnzbd.org/viewtopic.php?f=11&t=22952
2017-09-06 20:54:21 +02:00
Safihre
37b759fece Only notify upon testing failures 2017-09-06 20:50:27 +02:00
Safihre
3f8430780d Add test requirements 2017-09-06 20:44:47 +02:00
Safihre
9e9bc5f3b0 Very basic testing
Does it start, does it serve pages, does it serve api-requests
2017-09-06 20:44:47 +02:00
Safihre
85204105c2 Check input when setting priority of job
There was no input validation, via the API you could set any value.
2017-09-06 20:25:08 +02:00
SABnzbd Automation
f13339d64d Automatic translation update 2017-09-05 19:53:42 +00:00
Safihre
fa5b44be99 Update text files for 2.3.0Alpha2 2017-09-05 16:36:12 +02:00
Safihre
08bc7c5b9d Use NZO_LOCK to limit possible dir conflicts when adding NZBs
Hopefully resolving problems with possible overlapping directory names and import errors.
2017-09-05 16:07:43 +02:00
Safihre
d9b5dd549a Revert "Only allow 1 NZB to be added at the same time"
Could result in deadlock for some reason.
This reverts commit e64df8ed60.
2017-09-05 16:05:48 +02:00
Safihre
8ec53a3bce Giving up on elegant solutions to prevent stalling, just use a check
This stupid nightmare still wasn't fixed.
The proper solution creates slowdown on many systems because it's not efficient enough. Instead just do a check every 90 seconds if there's stalled stuff.
2017-09-05 16:05:19 +02:00
Safihre
0aac9a5e5c Correct display of download percentage
No need to calculate in javascript, we get it from API
2017-09-05 16:03:58 +02:00
Safihre
11a880d040 MultiPar shows 'PAR File(s) Incomplete' on verification success
When there are Par2-files with very similar filenames in the folder.
2017-09-05 13:21:48 +02:00
Safihre
67b66beb13 Only count really extra files during Multipar 2017-09-04 22:43:59 +02:00
Safihre
1da633442b Correct byte counts when retrying 2017-09-04 22:43:01 +02:00
Safihre
13de40881e Correct display of Forced items 2017-09-04 21:44:00 +02:00
Safihre
1c6419ea65 Revert "Paused status for individual download trumps Force priority"
Oops, I was wrong. This reverts commit d06c11673f.
2017-09-04 20:51:05 +02:00
Safihre
a2adeffc1a Correctly count all bytes and drop 'missing' use 'mbmissing'
Reporting number of missing articles makes 0 sense, it's the MB that matters.
2017-09-04 20:39:15 +02:00
Safihre
71fa3c544a Also shjow scanning of extra files for par2cmdline/tbb 2017-09-04 14:54:41 +02:00
Safihre
b739fb7f07 Do not count overhead-bytes for NZO statistics 2017-09-04 14:18:55 +02:00
Safihre
860728beae Show counter when Multipar is scanning other files in the directory 2017-09-04 13:58:50 +02:00
Safihre
1bdbf1c6a8 Show different icon when priority is Force 2017-09-04 10:59:48 +02:00
Safihre
abbed4cd77 Show missing articles starting at 2% 2017-09-04 10:59:18 +02:00
Safihre
d06c11673f Paused status for individual download trumps Force priority 2017-09-04 10:57:02 +02:00
Safihre
67d67f5ff6 Correct typo in reject_duplicate_files
Closes #1021
2017-09-03 17:07:41 +02:00
Safihre
2386d65b84 Extrapars could be empty if not +Repair set for job 2017-09-03 16:54:07 +02:00
SABnzbd Automation
be638ecca1 Automatic translation update 2017-09-03 08:23:45 +00:00
Safihre
c32bcea3f6 Re-update text files for 2.3.0 Alpha 1 2017-09-03 10:16:08 +02:00
Safihre
bf0aa6569b Need to do comparison to true number of servers
Previous solution was too simple and could still fail.
2017-09-03 10:12:52 +02:00
Safihre
e64df8ed60 Only allow 1 NZB to be added at the same time 2017-09-02 19:18:13 +02:00
Safihre
f7c3a4381d More efficient to compare TryList sizes
Only less is more.
2017-09-02 17:45:32 +02:00
Safihre
55efb34f03 Allow duplicate filenames by default 2017-09-02 15:39:55 +02:00
Safihre
ae8e9d83f1 Less CPU intensive anti-stall fix 2017-09-02 15:27:31 +02:00
SABnzbd Automation
c4406df73f Automatic translation update 2017-09-02 09:23:23 +00:00
Safihre
12004802b6 Allow users to set custom basepath
Closes #904
https://forums.sabnzbd.org/viewtopic.php?f=11&t=22511
2017-09-02 11:10:13 +02:00
Safihre
6068ca6376 Disable CherryPy timeout monitor
Besides throwing errors, it doesn't really help anything. The actions still get performed. 
See also: https://github.com/cherrypy/cherrypy/issues/1625
2017-09-02 09:50:55 +02:00
Safihre
613ba49165 Only really count passwords that came from the passwords file 2017-09-01 21:21:48 +02:00
Safihre
9cd21d84ee Only reset trylist once when reset of nw
Also happens within the decode()
2017-09-01 13:36:04 +02:00
SABnzbd Automation
8d813f125e Automatic translation update 2017-08-31 14:44:35 +00:00
Safihre
cb66bc28ab create_default_context only availble on Python 2.7.9+ 2017-08-31 13:14:29 +02:00
Safihre
30b13b1856 Small tweaks to startup logging 2017-08-30 23:59:27 +02:00
Safihre
67a133068c Move logging of number of certificates to Debug only
It's a bit slow, can take up to 1.5 seconds of startup time.
2017-08-30 23:48:07 +02:00
Safihre
731a3bcb22 Move Bonjour/ZeroConfig to after SABnzbd start
Slow to mount or to fail (1-2 seconds)
2017-08-30 23:25:49 +02:00
Safihre
724ec8ca9f ZeroConfig could crash due to UTF8 hostname 2017-08-30 23:21:54 +02:00
Safihre
d28f775c71 Remove information about SSL/TSL Protocols because it is inccorect
There is no way to get the actually enabled SSL/TLS protcols on a system, let along from Python. It's not even possible from the `openssl` command line.
See also #994
And: https://stackoverflow.com/questions/45924030/get-available-ssl-tls-protocols-in-python-2-7
2017-08-30 23:15:19 +02:00
Safihre
a834c1c7a7 Add Refresh button to Glitter when Refresh Rate > 2
Linked #842
2017-08-30 15:28:17 +02:00
Safihre
04e595e706 Update text files for 2.3.0Alpha1 (2) 2017-08-29 23:53:36 +02:00
Safihre
46c28dbf68 Do not show dropdown for URL-grabs that are paused
Setting changes are not propegated to the actual downloading, could be optimized.
2017-08-29 23:50:54 +02:00
Safihre
8594bfe817 Timing of try-list check could cause stalling
My god I hate this stalling problem, it haunts me in my dreams.
2017-08-29 23:42:27 +02:00
SABnzbd Automation
e61a01512b Automatic translation update 2017-08-29 16:04:48 +00:00
Safihre
657e3bb594 Update text files for 2.3.0Alpha1 2017-08-29 16:42:34 +02:00
Safihre
bbbdca6a00 Update INSTALL.txt 2017-08-29 16:01:59 +02:00
Safihre
a95c705e4c Auto-disconnect if the last item was deleted from the Queue 2017-08-29 10:02:25 +02:00
Safihre
446c5ba80f Block for more reasons so that try-lists get reset 2017-08-29 09:06:38 +02:00
Safihre
7641c0e1cc Correct Retry function after par2-changes 2017-08-27 23:56:24 +02:00
Safihre
42fdd9c890 Flexible block-search
Also add blocks from setnames that are a lot a like. Especially in those cases with multi-sets where the sets have the same filenames.
2017-08-27 22:48:30 +02:00
Safihre
dca63878db Force priority should be listed as 'Downloading' even if Queue paused
Closes #1012
2017-08-27 22:37:13 +02:00
Safihre
dc67fc414c Do not rely on par2/Multipar parsing to detect par2 files of the parset
We know much bettter now we do full parsing of each par2 file that comes in.
2017-08-27 20:09:40 +02:00
Safihre
90be3cc5a0 Only remove par2's that really belong to the set 2017-08-27 18:49:03 +02:00
Safihre
42cdba5ce3 Improve Prospective Par2 to handle multisets better
Now we get too many blocks, but before the second set wouldn't get enough blocks
2017-08-27 18:32:13 +02:00
Safihre
9c069cfb2c Let par2/MultiPar only remove used par2-files
In case of duplicae filenames we don't want "base.1.par2" to be removed when checking "base.par2".
2017-08-27 17:41:44 +02:00
Safihre
544b420baa Correct get_files API-call to only show really queued files 2017-08-27 16:18:48 +02:00
Safihre
094c96f270 Improve obfuscated and broken par2 handeling
Linked #998.
- In case of full obfuscation we detect the files directly after assembling and rename it such that par2 will pick it up
- Turns out we do need 'partable' to keep track what is the main par2 file
- Handle missing of first par2 file, automatically fetch next one
2017-08-27 14:49:01 +02:00
Safihre
aa7bad56f0 Fix get_unique_filename function 2017-08-27 14:42:08 +02:00
Safihre
977f4e1036 Rework par2-handeling
See #998
- Seperate par2 functions in new file
- Always check for par2-signature if file is not a rar-file
- Always parse par2 contents for signatures (hash -> filename)
- Use the signatures to keep track of par2-packs, not basenames
- Remove seperate 'extrapars' for NZF's, all in the NZO
- Remove 'partable' for NZO, it's all in it's 'extrapars'
2017-08-27 12:23:17 +02:00
Safihre
e05a98d22b Do not show 'Apply filters' from the start
Nothing more we can do to speed up the RSS evaluation.
Closes #679
2017-08-26 18:57:47 +02:00
Safihre
452e955a1e Disable extra features of feedparser
NZB-feeds do not contain the properties that feedparser has fancy parsing for.
The reduces the time spent in feedparser by 40%. 
Linked #679
2017-08-25 22:59:40 +02:00
Safihre
29ec4d9a23 Set 2.2.1 version and update translations 2017-08-25 22:11:47 +02:00
Safihre
22517a7cd7 Merge branch '2.2.x' 2017-08-25 22:07:15 +02:00
Safihre
a724f6a979 Revert "Remove locks from ArticleCache"
This reverts commit 5e7558ce4a.
2017-08-25 21:59:11 +02:00
Safihre
715b25b52f More logging when adding NZB's 2017-08-25 21:59:07 +02:00
Safihre
bcc4dd75cf Update text files for 2.2.1 2017-08-25 21:58:48 +02:00
Safihre
97711ca82e Revert "Remove locks from ArticleCache"
This reverts commit 5e7558ce4a.
2017-08-25 21:57:16 +02:00
Safihre
e782237f27 More logging when adding NZB's 2017-08-25 21:49:24 +02:00
SABnzbd Automation
4d3f370b3a Automatic translation update 2017-08-25 16:04:37 +00:00
Safihre
07f6717728 Duplicate files in NZB could result in broken unpack after repair
Because par2 would detect them, but not use them. So ".1" files would later be used for unpack, even though it's not a real set.
2017-08-25 16:59:01 +02:00
Safihre
35b4aa6b7a Ignore unpack errors in duplicate rarsets
Multipar and par2tbb will detect and log them so we can remove them, but par2cmdline will not.
2017-08-25 16:58:56 +02:00
Safihre
52bb156c08 Ignore unpack errors in duplicate rarsets
Multipar and par2tbb will detect and log them so we can remove them, but par2cmdline will not.
2017-08-25 16:58:38 +02:00
Safihre
4361d82ddd Duplicate files in NZB could result in broken unpack after repair
Because par2 would detect them, but not use them. So ".1" files would later be used for unpack, even though it's not a real set.
2017-08-25 16:57:47 +02:00
Safihre
a7a04d912c Use fileobj to prevent having to chdir, which can crash on macOS
If something is "wrong" with the current directory, for example when SABnzbd is started after downloading in a sandbox by macOS security, this function can error and break the adding of NZB's.
Have to use a fileobj, otherwise GZip will put the whole path inside there.
2017-08-25 10:51:52 +02:00
Safihre
ce558b0850 Original files would be deleted after a MultiPar rename 2017-08-25 10:51:25 +02:00
Safihre
8ab7c294ee Do not fail a job if recursive unpack fails
The user can handle it, we did our part.
2017-08-25 09:19:29 +02:00
Safihre
306228462e Use existing texts for wizard ad translations
Yeah, my fault. I deleted them in a clean-up of the skintext during 1.0.0 development. Very stupid.
2017-08-25 09:19:15 +02:00
Safihre
017cf8f285 Do not fail a job if recursive unpack fails
The user can handle it, we did our part.
2017-08-25 09:15:14 +02:00
Safihre
03cdf6ed5d Sync translatable texts from develop
To avoid conflicts on Launchpad
2017-08-24 23:40:37 +02:00
Safihre
cf347a8e90 Original files would be deleted after a MultiPar rename 2017-08-24 23:36:36 +02:00
SABnzbd Automation
9e7a8468e2 Automatic translation update 2017-08-24 15:24:59 +00:00
Safihre
f06afe43e1 Use fileobj to prevent having to chdir, which can crash on macOS
If something is "wrong" with the current directory, for example when SABnzbd is started after downloading in a sandbox by macOS security, this function can error and break the adding of NZB's.
Have to use a fileobj, otherwise GZip will put the whole path inside there.
2017-08-24 16:36:13 +02:00
SABnzbd Automation
4c2445485a Automatic translation update 2017-08-23 23:13:38 +00:00
Safihre
103c46e2b4 Translation fix for unpack warning 2017-08-23 22:54:40 +02:00
Safihre
b4922d69a2 Pause between unpacks on Windows, otherwise subprocess_fix overloads
Strange but true, but on jobs with many small files to unpack, it would just fail.
2017-08-23 22:50:40 +02:00
Safihre
110a06a3cd Handle '482 Download limt exceeded'
Closes #1009
2017-08-23 22:50:27 +02:00
Safihre
fb301eb5c8 Update text files for 2.2.1RC2 2017-08-23 22:49:59 +02:00
Safihre
6f0f67110f Only auto-disconnect after first run of verification 2017-08-23 22:48:53 +02:00
Safihre
1562c3560b Handle '482 Download limt exceeded'
Closes #1009
2017-08-23 22:48:15 +02:00
Safihre
848721da84 Clean last functions from the real anti-stalling fix 2017-08-23 22:10:06 +02:00
Safihre
9813bc237f Only auto-disconnect after first run of verification 2017-08-23 21:42:56 +02:00
Safihre
b39fe059c6 Pause between unpacks on Windows, otherwise subprocess_fix overloads
Strange but true, but on jobs with many small files to unpack, it would just fail.
2017-08-23 21:42:17 +02:00
Safihre
a56c770a8b The real anti-stalling fix
Woohoo!
For each NZF (file) make sure all articles have tried a server before marking it as tried. Before if articles were still in transit they could be marked as tried on NZF level before the server could get to them,
2017-08-23 16:02:01 +02:00
Safihre
127d7ab40c The real anti-stalling fix
Woohoo!
For each NZF (file) make sure all articles have tried a server before marking it as tried. Before if articles were still in transit they could be marked as tried on NZF level before the server could get to them,
2017-08-23 15:43:54 +02:00
Safihre
4fb7246082 TryList reset at NZO level also nessecary
Timing issue between when a new server is selected and when a job is added to the NZO-level try-list. Locks were tried, but failed.
2017-08-23 09:11:34 +02:00
Safihre
8c42237d51 Correct handeling of TryList when server has timeout 2017-08-23 09:11:29 +02:00
Safihre
6a87f0c4e4 Correctly remove + from INFO label in all languages 2017-08-23 09:11:24 +02:00
Safihre
e3bf0edad8 TryList reset at NZO level also nessecary
Timing issue between when a new server is selected and when a job is added to the NZO-level try-list. Locks were tried, but failed.
2017-08-23 09:11:01 +02:00
Safihre
e35d9e4db3 Correct handeling of TryList when server has timeout 2017-08-23 08:32:47 +02:00
Safihre
c617d4321a Correctly remove + from INFO label in all languages 2017-08-22 16:13:24 +02:00
Safihre
0fd3a2881f Correct redirect after ports change 2017-08-22 10:19:42 +02:00
Safihre
f8630a878c Correct redirect after ports change 2017-08-22 10:11:41 +02:00
Safihre
7f6ef5e204 Only iterate over RSS feeds when there are feeds 2017-08-22 09:52:25 +02:00
Safihre
0c1f7633de Only discard really non-unique hashes from md5of16k 2017-08-22 09:43:33 +02:00
Safihre
b7d5d49c84 Show hover-title that the compress icon is Direct Unpack 2017-08-22 09:43:26 +02:00
Safihre
9911b93ece Add error when NZO creation fails 2017-08-22 09:43:11 +02:00
Safihre
eeaad00968 Also hide email-accounts in logging 2017-08-22 09:43:06 +02:00
Safihre
e1bb8459e3 Take the risk of allowing up to 5 bad articles in jobs without Par2 2017-08-22 09:42:47 +02:00
Safihre
65c3ac0cc0 Warn in case the password file has too many passwords 2017-08-22 09:42:16 +02:00
Safihre
413c02a80f Do not run get_new_id forever in case of problems
#984
2017-08-22 09:41:40 +02:00
Safihre
547d4dbf0a Only discard really non-unique hashes from md5of16k 2017-08-22 09:25:34 +02:00
Safihre
65e70a431c Show hover-title that the compress icon is Direct Unpack 2017-08-22 09:13:40 +02:00
Safihre
f85f4de5ff Error when applying changes to RSS-feeds
Closes #1005
2017-08-21 20:25:34 +02:00
Safihre
97644dea16 Show more clear error message when UnRar of Par2 is missing 2017-08-21 16:27:04 +02:00
SABnzbd Automation
0a6105ebc1 Automatic translation update 2017-08-21 07:18:33 +00:00
Safihre
de3d4f8d14 UnRar is required to read some RAR files 2017-08-21 08:26:12 +02:00
Safihre
80f118f304 UnRar is required to read some RAR files 2017-08-21 08:23:10 +02:00
Safihre
f337053aea Add error when NZO creation fails 2017-08-20 20:45:55 +02:00
Safihre
2a4b49a679 Warning "Invalid par2 files" can also be due to bad Par2-parameters 2017-08-20 16:41:18 +02:00
Safihre
ebe526f8cf Also hide email-accounts in logging 2017-08-20 10:33:24 +02:00
Safihre
926cd7b132 Take the risk of allowing up to 5 bad articles in jobs without Par2 2017-08-20 01:35:06 +02:00
Safihre
99667aa410 Remove obsolete code from RSS 2017-08-20 01:20:56 +02:00
Safihre
67cab3465e Remove extra checks from RSS file-store loading 2017-08-20 00:31:22 +02:00
Safihre
ce68a0654b Scheduler Priority/Category Pause/Resume also affects URL-grabbing
Linked #988
2017-08-19 23:59:14 +02:00
Safihre
dffdc3ae1f Extend Scheduler to Pause/Resume based on category
Closes #549
2017-08-19 23:56:51 +02:00
Safihre
28e5311c6c Change logging line of SSL-protocols
Linked #994
2017-08-19 22:52:33 +02:00
Safihre
ebf0526420 Do not show NZB-age when it is nonsense
For example with a grabbing NZB.
2017-08-19 21:49:03 +02:00
Safihre
a2f73ca1f0 Add feed name to URL-grabbing title
Closes #988
2017-08-19 21:40:43 +02:00
Safihre
8ca150c48d Allow pausing of URL-grabbing
Linked #988
2017-08-19 21:20:28 +02:00
Safihre
d765fa09f1 Change certificate-file location for distributions 2017-08-19 20:33:46 +02:00
Safihre
878d68d343 Warn in case the password file has too many passwords 2017-08-19 16:06:06 +02:00
Safihre
fc7bd78dfa Translation changes 2017-08-19 13:28:21 +02:00
SABnzbd Automation
aca6ed360c Automatic translation update 2017-08-19 09:17:50 +00:00
Safihre
5c0a10e16b Update text files for 2.2.1RC1 2017-08-19 11:06:40 +02:00
Safihre
d9b32261e7 Reset all NZO TryList when doing Prospective Add
I thought in c14b3ed82a that this was enough, but clearly it is not.
2017-08-19 11:03:22 +02:00
Safihre
8d8ce52193 Stall prevention by checking TryList 2017-08-19 11:03:15 +02:00
Safihre
53672d1d73 Reset all NZO TryList when doing Prospective Add
I thought in c14b3ed82a that this was enough, but clearly it is not.
2017-08-18 19:36:20 +02:00
SABnzbd Automation
07d316ed4f Automatic translation update 2017-08-18 15:44:05 +00:00
Safihre
8aa57bf406 Update Config jQuery to 3.2.1 and Peity to 3.2.1 2017-08-17 11:00:47 +02:00
Safihre
d369097573 Update KnockoutJS to 3.4.2 2017-08-17 11:00:47 +02:00
Safihre
f4960715fa Do not run get_new_id forever in case of problems
#984
2017-08-17 11:00:47 +02:00
Safihre
83ba676c43 Correct loading of extra certificate files
Also include logging of number of found certificates
2017-08-17 11:00:47 +02:00
Safihre
12cca9dea1 Provide certificates with macOS and Windows build
We have to bring our own certficates on Homebrew Python 
The certifi package brings the latest certificates on build 
This will cause the create_default_context to load it automatically
We also use this on Windows so users are always up-to-date
2017-08-17 11:00:47 +02:00
Safihre
5f52535c44 Faster look-ups when restoring queue 2017-08-17 11:00:47 +02:00
Safihre
2f95410ab4 Allow for older queue-files 2017-08-17 11:00:47 +02:00
Safihre
5749c0c008 Keep TryList state
Part 1 of #973
2017-08-17 11:00:47 +02:00
Safihre
73c71ef4bf Allow filenames with also 1-char file extension 2017-08-17 11:00:47 +02:00
Safihre
6bc1c51013 Remove server specific categories
Finally. This feature has only caused problems.
2017-08-17 11:00:47 +02:00
Safihre
bcdd3302a6 Stall prevention by checking TryList 2017-08-17 11:00:47 +02:00
Jonathon Saine
0fc3b60054 Add propercheck to allow PROPER/REAL/REPACK releases to bypass the series episode dupe check. 2017-08-17 10:54:57 +02:00
Safihre
e9b0d4d691 On to 2.3.0 we go 2017-08-17 10:53:32 +02:00
Safihre
1cc2e25cda Update to 2.2.0 2017-08-17 10:47:04 +02:00
SABnzbd Automation
0dc2c6687d Automatic translation update 2017-08-17 08:43:38 +00:00
Safihre
b061e582b6 Update text files for 2.2.0 2017-08-16 22:56:10 +02:00
Safihre
690731fd79 Update wizard Ad URL 2017-08-16 13:34:01 +02:00
Safihre
068b7ed7f5 Disk-speed test for Direct Unpack would cause restart 2017-08-15 21:53:24 +02:00
Safihre
aae2fdcd32 Update Unrar to 5.5.0 for Windows and macOS
Right on time.
Closes #935
2017-08-14 20:37:11 +02:00
Safihre
d3628a1eb7 CherryPy 8.1.2 - Catch the [Errno 0] Error
Untill a fix is found.
2017-08-13 16:58:20 +02:00
Safihre
9cc8176d87 Server-tests were broken due to deprecation warning
Linked #996
2017-08-12 14:52:34 +02:00
Safihre
27f83f21be Update text files for 2.2.0RC3 2017-08-12 11:39:28 +02:00
Safihre
5e31a31a21 Make Server charts timezone-proof
Closes #997
2017-08-12 21:23:18 +12:00
Safihre
a077012478 Windows fix for subprocess would break when options were not specified 2017-08-12 09:42:47 +02:00
Safihre
fed0e0f765 Use win32api for power-options on Windows 2017-08-12 09:39:25 +02:00
Safihre
fbdbf7ab22 Improve par2 handeling by always parsing md5of16k and checking new sets
- We postpone only par2-files with actual blocks, in case of duplicate named par2 files that are of different sets we want all base-par2 files.
- The md5of16k is now calculated for every par2 file we get so we can rename everything.
- We also check during assembly if maybe a md5of16k is now available, in case the par2 file came in later.
- If a par2 file comes in, we double check if maybe this pack was not known yet. The setname might not be unique. This way we make sure that everything gets verified at the end.

Still need obfuscation improvements, but that's for later.
Linked #998
2017-08-12 00:33:32 +02:00
SABnzbd Automation
f013d38d00 Automatic translation update 2017-08-10 11:45:54 +00:00
Safihre
93b9c8a6da Correctly escape the values in EN.po 2017-08-10 13:33:11 +02:00
SABnzbd Automation
e3a779bbc6 Automatic translation update 2017-08-09 21:44:13 +00:00
Safihre
adfce8c8b6 Update text files for 2.2.0RC2 2017-08-09 23:03:44 +02:00
Safihre
a49d68c0db Double check that we have pynotify version we can work with
Closes #992
2017-08-09 22:56:07 +02:00
Safihre
e4156e76d1 Disable auto-zoom on mobile for adaptive pages 2017-08-09 22:50:53 +02:00
Safihre
35b66eea0e Add more space to Config header just in case 2017-08-09 18:45:01 +03:00
Safihre
4d0cf8d45f Correct naming and small style fix 2017-08-09 18:36:20 +03:00
Safihre
ad9fef5f41 Prevent SQL injection via category-argument with ' in them 2017-08-09 17:43:17 +03:00
Safihre
6235174995 CherryPy 8.1.2 - Correct f6c163b: macOS "Protocol wrong type for socket"
https://github.com/cherrypy/cheroot/pull/31
https://github.com/cherrypy/cheroot/pull/44
2017-08-09 09:55:04 +03:00
Safihre
4b9ca989c4 Correctly log the rar-files used by DirectUnpack
Not just assume we used them all.
2017-08-08 13:39:03 +03:00
Safihre
4d54aecceb Check the workdir after extraction if we got everything
Sometimes there might be a file left that we did not extract. For example there are NZBs that have the main download in abc.part01.rar etc but also a abc.rar file with the NFO or other things. 
SABnzbd would detect this as 1 set and not unpack both.
2017-08-08 13:37:09 +03:00
Safihre
11eeb6f2e9 Glitter filelist would not show 100% for very small files
Plus some cleanup of move-to-top/bottom code
2017-08-08 12:19:07 +03:00
Safihre
00364b1317 Only update RSS URI if it was modified
See #993
2017-08-08 12:04:54 +03:00
shypike
6666663f78 Fix typo in text about "allow duplicate files" 2017-08-08 09:17:25 +02:00
Safihre
3d6dfec47a Disk-space check in Assembler and check if space to write current file 2017-08-06 19:01:59 +03:00
Safihre
0f3d44aa4b safe_fnmatch should only do the matching
Oops, globber and globber_full are not the same!
2017-08-06 12:57:23 +03:00
SABnzbd Automation
d2d2471950 Automatic translation update 2017-08-06 09:42:54 +00:00
Safihre
b71343e8ab Safe fnmatch everywhere, just to be sure
Linked #990
2017-08-06 11:46:00 +03:00
Safihre
489f3f4ba0 Catch special chars like "[]!*" to break fnmatch and thus repair
Closes #990
2017-08-06 11:25:27 +03:00
Safihre
3765e8c350 Add warning when many duplicate files were discarded
Linked #531, Closes #986
2017-08-02 12:50:26 +02:00
Safihre
28d4f527b8 Fix the server-graphs
They did not display anything if it was the first of the month. Plus some style-fixes.
2017-08-01 13:31:18 +02:00
Safihre
1d8af8f97d Correct counts if one_folder is enabled 2017-08-01 11:28:34 +02:00
Safihre
829ef4bee8 Only one_folder is a reason not to delete the folder in DircetUnpack 2017-08-01 11:06:40 +02:00
Safihre
7e40c12e47 Get rarfiles to delete from both RarFile and parsing Unrar output
RarFile will fail to list all volumes when the job is encrypted, it will only list the first volume. But parsing the output of Unrar will fail on special-char filenames (probably limited to Windows). So now only jobs that are encrypted *and* have many special-chars will not have all rars deleted correctly,
2017-07-31 23:45:56 +02:00
Safihre
37d8d659f5 Show deprication warning for Server Categories 2017-07-31 22:26:21 +02:00
Safihre
0a29291be2 Abort all direct unpackers when disk-full 2017-07-31 22:26:04 +02:00
Safihre
7f3a5f309b Don't postpone if all par2 are desired and should be kept
Prevents downloading of all par2 in case people just wanted to keep some par2, but not all. If people really want all and no-cleanup, they should enable 'All par2'
2017-07-31 16:51:48 +02:00
Safihre
60ec5f9191 Less aggresive cache-busting 2017-07-31 10:38:45 +02:00
Safihre
d03e801e74 Only show import warnings when not gone
Can be removed if #952 is resolved
2017-07-30 20:29:28 +02:00
Safihre
56bf484e77 Also show Verifying Repair status for par2cmdline 2017-07-30 16:51:28 +02:00
Safihre
66674469d5 Return of the DIR_LOCK
Yeah, turns out we probably do need that. In case automation adds a new job while the old one is still getting deleted. Hopefully solving #952
2017-07-30 15:57:34 +02:00
Safihre
09a86683e5 Retry icon was too red in Glitter Night 2017-07-30 15:56:49 +02:00
Safihre
fc9a13879e CSS Tweaks to Night theme 2017-07-30 11:32:35 +02:00
Safihre
73f0885566 Schedule to deprecate Categories setting for Servers in next release
This option only gives headaches because users do not use it correctly or edge cases where downloads stall. 
If users report that they really want to keep this option, we can of course consider it.
2017-07-29 13:29:10 +02:00
Safihre
090b22f193 Revert "CherryPy 8.1.2 - Catch error of Python 2 in combination with new OpenSSL"
Not catching the right error because it's of the general Error class, not SSLError.
2017-07-28 18:46:46 +02:00
Safihre
f9c092ae8f Move Notification Script up on the Config page and add Wiki-link
So they can see other services
2017-07-28 09:52:25 +02:00
Safihre
4246bc2aea Move enable_meta to more logical spot 2017-07-27 16:00:40 +02:00
Safihre
c4fa047393 Automatic redirect to login page in Glitter 2017-07-27 14:56:34 +02:00
SABnzbd Automation
22e4d24a71 Automatic translation update 2017-07-27 12:29:06 +00:00
Safihre
96fccff63b Update text files for 2.2.0RC1 2017-07-27 13:50:41 +02:00
Safihre
6b46a15b49 Include indexer-hostname in the API-key missing warning 2017-07-27 11:50:12 +02:00
Safihre
98cc0dad55 API-key was always required for Indexer-feedback
Remove oznzb specific links for realy now.
Indexers should provide their rating-url in the headers/meta-data. Keeping the Specials setting for backwards compatibility for current OZnzb users.
2017-07-27 11:48:00 +02:00
Safihre
6fdeab6948 Permanent notice in Plush to upgrade to Glitter 2017-07-27 10:35:26 +02:00
Safihre
553dd04cea Restore 'Use tags from indexer' switch
https://forums.sabnzbd.org/viewtopic.php?f=11&t=22864
2017-07-27 09:52:59 +02:00
Safihre
0baa316a72 Revert "Remove enable_meta"
This reverts commit ff3c46fe1f.
2017-07-27 08:35:06 +02:00
Safihre
3ac209f9a9 CherryPy 8.1.2 - Catch error of Python 2 in combination with new OpenSSL
Closes #853
2017-07-26 16:34:45 +02:00
Safihre
ec55f64a8a Correctly remove files when flat_unpack or no-job-folders set
Results could be pretty bad otherwise: https://forums.sabnzbd.org/viewtopic.php?f=11&p=112774
2017-07-26 09:30:39 +02:00
Safihre
932e1e577b Do not double check 'part' in RAR filename
Already caught by the regex, otherwise filenames that have "part" somwhere in the name will not be used for direct-unpack.
2017-07-26 08:59:26 +02:00
Hellowlol
56d5b1d9f8 Make bonjour easier to setup. 2017-07-25 15:52:30 +02:00
Hellowlol
28bdebb147 Let zeroconf know if we are using https. 2017-07-25 15:52:30 +02:00
Safihre
827fc7b64e Direct Unpack would block forever if file was missing 2017-07-25 11:58:29 +02:00
Safihre
f5dde93644 Change History Retention label to fit
Closes #978
2017-07-24 19:21:43 +02:00
Safihre
60c574828e Sort Servers in Download Report
Closes #979
2017-07-24 19:14:31 +02:00
Safihre
14ca8342f9 Update wiki URL to 2.2 2017-07-24 13:36:16 +02:00
Travis
5d5b1bf053 Automatic translation update 2017-07-24 08:16:18 +00:00
Safihre
ea4cdba3eb Update text files for 2.2.0Beta2 2017-07-24 10:08:21 +02:00
Safihre
d6ecebc75a Use existing texts for Tag duplicate 2017-07-24 10:07:15 +02:00
Jonathon Saine
b0af6a1761 User requested a way to track dupes but not have sab block/discard/pause the items (as he doesnt want to have to manually unpause). Figured we could add a 'Tag' switch for the Dupe detection. Works same way as Pause (shows duplicate tag in queue/adds warning/internally tags it) but does not pause the item.
Once the file is done downloading and makes it to the queue, you have no idea it was a duplicate.
You would have to use the 'Warning' and search for that.. and then assume the newer one was the dupe... we really should expose in the History that an item was a duplicate. Right now the 'search' box only looks at the name.. and I dont think we should be messing with the name to add duplicate there. I'd rather us just add a tag/flag whatever to notate this.. and can filter/sort on it. We also should do the same to the Queue as well.. since changing the name of the item to be `DUPLICATE / XXXXX` is a little jarring.

Now if we just exposed the dupe flag in the history, then the whole 'tag' option really isnt needed as it was just a means to an end.
2017-07-24 10:00:15 +02:00
Safihre
6e350f30fc Add midnight auto history-purge and modify texts 2017-07-24 09:47:05 +02:00
Safihre
169137c631 Implement History Retention setting
Closes #678
2017-07-24 09:47:05 +02:00
Safihre
6393dc0dca Remove history_limit from Specials 2017-07-24 09:47:05 +02:00
Safihre
1a27b4824b Style improvements to Queue and Server Graphs
Closes #977
2017-07-24 09:36:48 +02:00
Safihre
2b59a383cf Change label in History-status to 'Direct Unpack' 2017-07-24 09:05:02 +02:00
Safihre
efbaaade22 Correct server graph timezone effects and only show months that we have 2017-07-23 23:14:10 +02:00
Safihre
cf7e7b1f62 Only show usage data for days that have passed 2017-07-23 23:14:10 +02:00
Safihre
2c1746a92d Show Montly usage graphs per server in Config 2017-07-23 23:14:10 +02:00
Travis
a2e57fd3d8 Automatic translation update 2017-07-22 17:27:02 +00:00
Safihre
932f8d9176 Wizard access was not limited by login and external access rules
Bad bad 
Closes #972
2017-07-22 18:54:33 +02:00
Safihre
5ffd82da89 Show vote up/down instead of Video/Audio score 2017-07-22 00:19:40 +02:00
Safihre
8b3de191d9 Add Retry All Failed button to Glitter 2017-07-22 00:11:13 +02:00
Travis
83d8a23e2c Automatic translation update 2017-07-20 22:13:49 +00:00
Safihre
58b107a4b5 Allow up to 5 missing/CRC'ed errors before cancelling Direct Unpack
Sometimes a CRC error is not so bad it turns out
2017-07-20 23:55:36 +02:00
Safihre
a40609b39d Direct Unpack was started before whole file was written to disk
In case part 2 came in before part 1.
2017-07-20 23:55:36 +02:00
Sander Jo
0faa5d3dff Print applied permissions in octal 2017-07-20 21:04:00 +02:00
Travis
374239777e Automatic translation update 2017-07-19 07:55:50 +00:00
Safihre
9a7701d7e6 Update text files for 2.2.0Beta1 2017-07-19 09:33:00 +02:00
Safihre
01ff04f338 Allow Aborting of Direct Unpack during PP and add Completed label 2017-07-19 09:27:24 +02:00
Safihre
eac39767dd Renames on Retry only when defined
Otherwise if it's None, later this will happen:
original_filename = self.renames.get(nzf.filename, '')
AttributeError: 'NoneType' object has no attribute 'get'
2017-07-19 09:23:58 +02:00
Safihre
0d0adf99fa Proper counting of bad articles for DirectUnpack & Prospective Par2 2017-07-18 22:07:56 +02:00
Safihre
16905ce34f Show filename for Unzip instead of Path and show start of Verification 2017-07-18 21:16:05 +02:00
Safihre
5287fa8a0c Stability improvements for Direct Unpack
Now shows the time spent in unpacking and many other bugs squased.
2017-07-18 21:15:30 +02:00
Safihre
b72ab4fb8e Allow concurrent unpacking 2017-07-18 15:14:28 +02:00
Safihre
81054c675c Mimimum speed for Direct Unpack lowered to 40MB/s
It is tested during downloading, so if 40MB/s is still possible then we should be good to go.
2017-07-18 13:51:13 +02:00
Safihre
7362be8748 Group cfg settings by Config section
It was a big mess. 
Now they still could be sorted within each section.. next time.
2017-07-17 20:42:54 +02:00
Travis
b4ba2b3463 Automatic translation update 2017-07-17 18:33:42 +00:00
Safihre
8bed6938c1 Change text in DirectUnpack Enabled message
See also #966
2017-07-17 20:11:50 +02:00
Safihre
ecf16f6201 Show DirectUnpack progress the same as Unpack progress: xx/xx 2017-07-17 17:07:44 +02:00
Safihre
bf240357df Regressions in preparation of extraction path
Thanks @Cpuroast
2017-07-17 16:45:58 +02:00
Safihre
ddcf447957 Add missing save_config after modifying settings
Closes #966
2017-07-17 10:10:20 +02:00
Safihre
d9642611e2 Correct error in missing notify options
#966
2017-07-16 20:28:15 +02:00
Safihre
0018c6f263 Move regex to top and increase save-timeout 2017-07-16 19:26:35 +02:00
Safihre
6398bfa12f Use speed from download-log instead of re-calculating
Closes #829
2017-07-16 19:22:52 +02:00
Safihre
01dfb7538d Correct FileList Move to Top/Bottom CSS for Firefox 2017-07-16 14:28:04 +02:00
Safihre
3f0d4675b6 Fix CSS for Direct Unpack and Move to Top/Bottom 2017-07-16 14:18:50 +02:00
Safihre
f23c5caf80 Fix typo in DirectRenamer for non-Windows 2017-07-16 13:55:36 +02:00
Safihre
bd22430b26 Update text files for 2.2.0Alpha3 2017-07-16 11:04:17 +02:00
Safihre
1189a7fdbc Use tuple in endswith for Direct Unpack
Thanks @hellowlol
2017-07-16 10:59:00 +02:00
Safihre
f3aa4f84fc Remove waiting-time between URLGrab's
Other newsreaders grab multiple URL's at once, so no need for us to wait.
2017-07-16 10:40:39 +02:00
Safihre
ea26ce4700 Remove non-seperator RSS-url commas by detecting if they are valid URLs
Closes #965
2017-07-16 10:30:09 +02:00
Safihre
a1e649b7e2 Correct error in PAR_Verify with renames 2017-07-15 23:43:32 +02:00
Safihre
3b9f2b2cf0 Remove par2classic/cmdline for Windows and macOS 2017-07-15 23:33:20 +02:00
Safihre
7333d19e1c Notifications selection based on Categories
Closes #716
2017-07-15 22:22:20 +02:00
Safihre
232d537d23 Correct Direct Unpack locking behavior for multisets 2017-07-15 17:02:20 +02:00
Safihre
c6e17e7bcb Duplicate par2-16k values need force-remove 2017-07-15 17:02:20 +02:00
Safihre
54c6fd55dd Detection of forbidden-Windows names altered
Now we already sanatize the name during Assembler and when we have to make decisions for Unrar/Par2 we need to know if they might create something unsafe.
2017-07-15 17:02:20 +02:00
Safihre
0625aa1ca8 Make sure all Par2-16k signatures are unique, also in multisets 2017-07-15 17:02:20 +02:00
Safihre
83643f3298 Remove allow_streaming
Bit redundant now we have DirectUnpack
2017-07-15 17:02:20 +02:00
Safihre
ff3c46fe1f Remove enable_meta 2017-07-15 17:02:20 +02:00
Safihre
0930f0dcee Test disk-speed first time DirectUnpack is called 2017-07-15 17:02:20 +02:00
Safihre
3221257310 UnRar's ERROR is also an error
And add starting file to log.
2017-07-15 17:02:20 +02:00
Safihre
8048a73156 Handle active DirectUnpacker in postproc better 2017-07-15 17:02:20 +02:00
Safihre
ea552cd402 Cancel DirectUnpack when the final name changes 2017-07-15 17:02:20 +02:00
Safihre
dcb925f621 Case insensitive matching for DirectUnpack sets 2017-07-15 17:02:20 +02:00
Safihre
cce91e1985 DirectUnpacker should stay to listen to new sets 2017-07-15 17:02:20 +02:00
Safihre
e17d417c2e Re-introduce locks for TryList
After studying everything, it really needs it. Closes #738
2017-07-15 17:02:20 +02:00
Safihre
a69f5bd2df Prevent DirectUnpack locking the PostProcessing 2017-07-15 17:02:20 +02:00
Safihre
97e53eb4d3 Better DirectUnpack percentage counter 2017-07-15 17:02:20 +02:00
Safihre
a6da2b7bee Prevent possible crash in par2_repair 2017-07-15 17:02:20 +02:00
Safihre
4a21e7c217 Show percentage of DirectUnpack, when available 2017-07-15 17:02:20 +02:00
Safihre
9bd3c7be44 Increase maximum number of unpackers
Unrar takes almost no memory anyway
2017-07-15 17:02:20 +02:00
Safihre
434f5c4b2d Remove Audio/Video quality rating icons from Queue 2017-07-15 17:02:20 +02:00
Safihre
d3cc4f9f07 Direct Unpack indicator for Queue 2017-07-15 17:02:20 +02:00
Safihre
a16aa17c17 Don't start when not set to +Unpack and abort if Category changed 2017-07-15 17:02:20 +02:00
Safihre
68445d0409 Full working implementation of DirectUnpack with multi-sets 2017-07-15 17:02:20 +02:00
Safihre
32b68a45cc Integrate with PostProc 2017-07-15 17:02:20 +02:00
Safihre
345f8359cc Unpack to the right directory (with Sorter support) 2017-07-15 17:02:20 +02:00
Safihre
81f9886584 Add Direct Unpack to Config 2017-07-15 17:02:20 +02:00
Safihre
adbc618808 Improvements to detection of volumes 2017-07-15 17:02:20 +02:00
Safihre
41eafc6b4b Become set-specific 2017-07-15 17:02:20 +02:00
Safihre
9f18d8e8c1 Basic working Direct Unpack
Lots to do
2017-07-15 17:02:20 +02:00
Safihre
8c2c853166 Make sure to always have lowest part number 2017-07-15 17:02:20 +02:00
Safihre
97914906a0 Also handle GNTP errors during sending 2017-07-14 14:43:39 +02:00
Safihre
f1ce4ed19b Correctly handle new GNTP errors 2017-07-14 14:41:03 +02:00
Safihre
99185d8151 Update GNTP to 1.0.3
Closes #334
2017-07-14 14:25:07 +02:00
Safihre
385b6b7ade Remove QCHECK_FILE again 2017-07-14 14:25:07 +02:00
gwyden
81ea513f8c Added buttons and logic to move to top and bottom of download queue (#962)
* added buttons and logic to move to top and bottom of queue
* allowed for a larger control box for the new buttons
* Cleanup of unnecessary code
* Simple top and bottom of queue using existing queue data
2017-07-13 23:52:43 +02:00
Safihre
336b1ddba3 Always remove forbidden Win-devices from filenames
This breaks support for par2cmdline on Windows with forbidden names. Assuming no users that have disabled both Multipar *and* par2_multicore
2017-07-12 18:38:19 +02:00
Safihre
7274973322 Shorten par_cleanup code 2017-07-12 18:38:19 +02:00
Safihre
af132965de Revert "Remove QCHECK_FILE, not needed"
This reverts commit 4f8cc3f697.
2017-07-12 18:38:19 +02:00
Safihre
5586742886 Use RarFile.volumelist to get list of used rar-volumes 2017-07-12 18:38:19 +02:00
Safihre
5868b51490 Use fix to allow unicode arguments to POpen on Windows 2017-07-12 18:38:19 +02:00
Travis
7f17a38b9b Automatic translation update 2017-07-12 15:10:14 +00:00
Safihre
415e843ebb Remove 'WARNING:' label from Assembler warnings
It was inconsistent with other messages
2017-07-11 13:33:50 +02:00
Safihre
7ffc1192bb Only par2-rename when actually different 2017-07-11 12:00:36 +02:00
Safihre
945e769a03 Also performe prospective-par2 on renamed files 2017-07-10 23:06:05 +02:00
Safihre
86c7fb86cc Ignore first-16k par2 info if it's not unique 2017-07-10 22:51:17 +02:00
Safihre
ff20f3f620 Fix possible unicode error in tvsort and typo in newsunpack
Closes #950
2017-07-10 21:56:07 +02:00
Safihre
e8bef94706 Correctly handle renames on (multiple) retries 2017-07-10 21:03:37 +02:00
Safihre
d05fe2d680 More uniform handeling of renames 2017-07-10 20:53:31 +02:00
Safihre
4f8cc3f697 Remove QCHECK_FILE, not needed 2017-07-10 19:54:59 +02:00
Safihre
6fa619fa37 More robust renaming based on par2 first-16k info
Also when the correct name is
2017-07-10 17:40:39 +02:00
Safihre
a43f5369ea Do not rename .par2 filenames from NZB
They are usually correct, if mentioned at all
2017-07-10 17:29:34 +02:00
Safihre
2040173dc2 Rename parts of Assembler to be more coherent 2017-07-10 17:20:03 +02:00
Safihre
a15b7ec7ac Remove Windows utf8 detection using par2
Obsolute now we have Multipar
2017-07-10 17:17:12 +02:00
Safihre
6adcf2ce10 Stylistic changes from previous commits 2017-07-10 17:11:32 +02:00
Safihre
e756b9b5c1 Correct filenames while downloading using first-16kb par2 info
Maybe we can also do DirectUnpack!
2017-07-10 17:07:16 +02:00
Safihre
b3de745849 Do not use article-filename if it looks obfuscated 2017-07-10 15:54:17 +02:00
Safihre
77f3dc18b5 Corrections of Move To Top for filelists 2017-07-09 19:51:18 +03:00
gwyden
6b2f15f82e Move To Top/Move To Bottom buttons for filelists (#959)
* Control creation

* JQuery to make the buttons work

* minor text fixes

* tab to spaces cleanup

* style additions and removed hard text from code

* Moved button control to modal finish render event, gave file details a little more room

* Moved control to replace age and size on mouseover

* Added margins and color corrected for the night theme

* resolved night theme readability

* move to working top and bottom

* controls would lose event bindings after the append.  Detach first then insert

* Move to Top and Bottom buttons for files in each NZB
2017-07-09 18:34:33 +02:00
Safihre
570e58611d Repair would fail if extrapars were deleted by previous run
Closes #961
2017-07-06 18:30:31 +03:00
Safihre
6b69010aec Add logging for missing NZF database to debug #952 2017-06-28 11:35:52 +02:00
Travis
e3e2fb7057 Automatic translation update 2017-06-27 09:23:17 +00:00
Safihre
ece04909e7 Add latest changes to changelog 2017-06-27 00:13:24 +02:00
Safihre
963920eb88 Semi-correct missing MB counter for Pre-check
It's still off (for Precheck only), but not sure why
2017-06-26 22:03:50 +02:00
Safihre
cf5fa542b6 Don't show import errors when NZO is gone 2017-06-26 21:36:49 +02:00
Safihre
1be7e99754 Remove last hashlib workaround 2017-06-26 21:00:17 +02:00
Safihre
14e3334682 Correctly handle disk-space calculations
No more glitches in the interface during downloading.
2017-06-26 16:25:03 +02:00
Safihre
b1e033dd55 Update text files for 2.2.0Alpa2 2017-06-26 14:28:22 +02:00
Safihre
111feb1b57 Show missing articles as MB instead of number of articles 2017-06-26 13:46:54 +02:00
Safihre
886b23d034 Update translatable texts 2017-06-26 10:40:02 +02:00
Safihre
f2590792b3 Download all par2 always when enable_par_cleanup is disabled
https://forums.sabnzbd.org/viewtopic.php?f=2&t=22744
2017-06-26 09:19:05 +02:00
Safihre
02a497ed74 Only set Post-processing Completed/Failed at the very end
To prevent race-issues
2017-06-25 20:32:45 +02:00
Safihre
48df0eed84 Add logging for user-actions
We were missing way too many things
2017-06-24 23:18:14 +02:00
Travis
0f58cbb671 Automatic translation update 2017-06-24 18:51:32 +00:00
Safihre
9d71670f59 Full hearted 2017-06-24 20:33:16 +02:00
Safihre
7f838ebb38 Move Donate-link in Glitter 2017-06-24 13:51:19 +02:00
Safihre
ef1cb05bc8 Store result of MultiPar verification
Just in case prospective par2 didn't catch them all
2017-06-24 10:47:33 +02:00
Safihre
c14b3ed82a Prospective Par2 correct TryList reset
I think
2017-06-24 00:34:57 +02:00
Safihre
792e337936 Rename increase_last_history_update to history_updated 2017-06-23 22:58:29 +02:00
Safihre
6cd2e66052 Use actual counter for LAST_HISTORY_UPDATE
Would otherwise miss some updates
2017-06-23 12:49:40 +02:00
Safihre
728022b86d Show Verifying Repair stage for MultiPar 2017-06-23 11:10:47 +02:00
Safihre
7718446313 Convert HTML to text in warning messages
Related: #952
2017-06-23 08:30:54 +02:00
Travis
66dea54053 Automatic translation update 2017-06-22 20:34:50 +00:00
Safihre
f19b60bd41 Also don't list line numbers for NSIS pot file 2017-06-22 21:46:55 +02:00
Safihre
09f1c92856 Move enable_multipar to Specials
Moving forward to make MultiPar the only used par2-solution on Windows.
2017-06-22 11:03:53 +02:00
Safihre
589715901d Correct counting during Checking/Verification in MultiPar 2017-06-22 10:50:00 +02:00
Safihre
3f1a5ff5e0 Fix typo in extract_pot.py 2017-06-22 09:32:59 +02:00
Safihre
49cd956d4c Do not list line-number for POT files
To avoid commit-overhead when updating texts
2017-06-21 22:17:49 +02:00
Safihre
f9acde862f Correct counting in MultiPar Checking 2017-06-21 21:40:24 +02:00
Safihre
503e1dd899 Re-work NZO_LOCK to actually lock when saving 2017-06-21 20:54:00 +02:00
Safihre
c8e12b948d Mixed up application of use_pickle 2017-06-21 17:18:16 +02:00
Safihre
18949d68c0 Fix wrong addition to en.po
Thx @thezoggy!
2017-06-21 17:12:19 +02:00
Safihre
0c51b6c016 Add Donate links to main Config page and Glitter help modal 2017-06-21 09:57:56 +02:00
Safihre
63a5c22c1f Don't continue when fetching failed
Possibly: #914
2017-06-21 09:19:25 +02:00
Safihre
f76e2a7b56 All links to sabnzbd.org should be HTTPS 2017-06-20 23:15:15 +02:00
Safihre
bab151d6f5 Properly fix redirect after enabeling/disabeling HTTPS 2017-06-20 22:46:48 +02:00
Safihre
d43fec088b Fix typo in Correct redirect when enabeling HTTPS 2017-06-20 19:48:18 +02:00
Safihre
a8ca1cbcd7 Correct redirect when enabeling HTTPS 2017-06-20 19:47:45 +02:00
Safihre
ada3494483 Fix typo in Config JavaScript 2017-06-20 19:04:38 +02:00
Safihre
43c238b7f1 Update translations 2017-06-17 11:23:50 +02:00
Safihre
128d10c51e Restart-text was always shown in English 2017-06-17 11:19:59 +02:00
Safihre
1a1e01f9f6 Correct upgrade-notice 2017-06-17 11:13:53 +02:00
Safihre
8483e4ab8a Add last minor change to changelog 2017-06-17 09:01:53 +02:00
Safihre
f6c163b505 CherryPy 8.1.2 - Catch OSX "Protocol wrong type for socket" 2017-06-17 08:40:14 +02:00
Safihre
8f30173db0 Update text files for 2.2.0Alpha1 2017-06-16 15:38:17 +02:00
Brendan Ball
0372ff95bb Added support for systemd power controls
added systemd support to powersup.linux_shutdown, linux_hibernate, linux_standby
2017-06-16 15:19:32 +02:00
Safihre
6fa29c7877 Update translatable texts 2017-06-15 20:58:48 +02:00
Safihre
d4c9121593 Remove NZB_LOCK
Not required
2017-06-15 20:54:13 +02:00
Safihre
76a8df0282 Make it more clear that Hostname verification is a server problem 2017-06-15 20:51:27 +02:00
Safihre
0b6d8309a0 Format the SSL certificate messages more for humans 2017-06-15 20:51:21 +02:00
Safihre
10a9bc0817 Don't show Advanded on Config>General if HTTPS disabled 2017-06-15 16:09:01 +02:00
Safihre
2a14af4ffa Firefox doesn't suck at animations anymore 2017-06-15 14:47:04 +02:00
Safihre
d1a4a292e3 Prevent log-flooding when job is too old for server 2017-06-13 21:40:45 +02:00
Safihre
14c0efa151 Fix error in MultiPar repair when first .par2-file was broken 2017-06-13 21:40:45 +02:00
Safihre
4fc03f2581 Discard all articles at once when too old for server 2017-06-13 21:40:45 +02:00
Safihre
3205b9fda9 Don't fill anything for bandwith limit if nothing is set
Now it would just fill "M" when nothing was set.
2017-06-13 21:40:45 +02:00
Safihre
953e0d6c22 Remove DIR_LOCK
The operations it was locking were always performed from 1 thread anyway.
2017-06-13 21:40:45 +02:00
Safihre
b50ce54ca9 Reformat IO_LOCK to really only protect against NZO-saving collisions
The main intent was not to read/write to same file, but this can (as far as I can see) never happen anyway. 
Before this change 2 threads could not be writing data at the same time, even if they were writing to completly different directories.
2017-06-13 21:40:45 +02:00
Safihre
5e7558ce4a Remove locks from ArticleCache
All operations on the list are atomic or modify objects in place that can't be read at the same time.
2017-06-13 21:40:45 +02:00
Safihre
8aa6362432 Remove NZBQueue wrapper functions
Direct-access!
2017-06-13 21:40:45 +02:00
Safihre
02ebb97a8b Remove NZBQUEUE_LOCK and only use synced wrappers when needed
Less locks = Less waiting
2017-06-13 21:40:45 +02:00
Safihre
b36063403d Remove legacy asserts and work-a-rounds 2017-06-13 21:40:45 +02:00
Safihre
526ffa2afb Add new translation to Changelog 2017-06-13 17:26:37 +02:00
Safihre
5b3fd812d8 Don't break MO-creation on missing Email templates 2017-06-13 14:40:47 +02:00
Safihre
af6dac9cdc Refresh Config > General when submitting language change 2017-06-13 14:14:26 +02:00
Safihre
bc25d936bb Show Hebrew in language menu
Correction of previous commit: I intended to write that it's an experiment because Hebrew is RTL. It seems to work!
2017-06-13 14:14:01 +02:00
Safihre
b497fe1444 Add Hebrew as language
This is an experiment, since Hebrew is LTR language. But a translator translated almost all the texts, so we want to use his efforts!
2017-06-13 11:43:18 +02:00
Safihre
3f456cce05 Move max_art_opt to Specials
Only for special cases (don't know which ones, but I can imagine it could be usefull..)
Deprecate later!
2017-06-13 00:40:02 +02:00
Safihre
4dd2f089ec Move replace_illegal to Specials
Who doesn't want that
2017-06-13 00:37:20 +02:00
Safihre
b1b1bc248d Reformat startswith() to use tuples when testing multiple options 2017-06-11 22:01:45 +02:00
Safihre
d9e675469c Don't throw errors when silent-saving fails 2017-06-11 22:01:45 +02:00
Safihre
ede0ca1772 Catch new way of par2 reporting bad parameters
See: https://forums.sabnzbd.org/viewtopic.php?f=2&t=22713&p=112209#p112209
2017-06-11 22:01:45 +02:00
Safihre
2d098a1477 Defend against possible NTFS crash
Closes #930
2017-06-11 22:01:45 +02:00
Safihre
e5f014b68e Replace spaces/dots in the order written in the Config 2017-06-11 22:01:45 +02:00
Safihre
b3a9dc9eeb Fix old code throughout 2017-06-11 22:01:45 +02:00
Safihre
2a06cec27c Seperate compatibility-check logic in NZBQueue 2017-06-11 22:01:45 +02:00
Safihre
19230c889d Remove unused functions and constants (vulture) 2017-06-11 22:01:45 +02:00
Safihre
c969ce552c Remove unused constants (vulture) 2017-06-11 22:01:45 +02:00
Safihre
2def600d21 Remove unused imports and functions (pyflakes) 2017-06-11 22:01:45 +02:00
Safihre
02aa8f18c8 Trylist doesn't need locks, all atomic operations 2017-06-11 22:01:45 +02:00
Safihre
fcd9522dae Remove unused import and define NzbQueue as proper class 2017-06-11 22:01:45 +02:00
Safihre
72d3ce885e Remove un-used version definitions from __init__ 2017-06-11 22:01:45 +02:00
Safihre
b428996eb7 Convert pickles and keep queue order
Also restore future jobs


dewd
2017-06-11 22:01:45 +02:00
Safihre
2b4eb58fad Correctly show message about old Queue-version
Now it's also upgrade-proof, and not just works for 1 version.
2017-06-11 22:01:45 +02:00
Safihre
240e8dff60 Bump Queue-Version to force Queue-Repair 2017-06-11 22:01:45 +02:00
Safihre
1c286afde6 Implement __slots__ to conserve memory
Objects such as Article() get created a lot. By using the __slots__ property, python will only reserve space for the give keywords instead of a whole diectonary. 

Testing showed that (on WinX64) 1 job now takes between 2-3MB of memory when loaded, compared to 4MB before.
2017-06-11 22:01:45 +02:00
Safihre
2eeb908540 Revert "Remove enable_par_cleanup"
This reverts commit f5ab4a2253.
2017-06-11 16:10:25 +02:00
Safihre
562e6ecce9 Fix Untill typos in texts and comments
Closes #943
2017-06-11 11:51:50 +02:00
Safihre
4bd0d32508 Bump version to 2.2.0-develop 2017-06-11 11:46:36 +02:00
Safihre
6f2ccbef80 Always show Par2-Multicore status on first Config page on Linux 2017-06-09 16:04:30 +02:00
Safihre
4605c3fd30 Set version to 2.1.0 and make identical to develop 2017-06-09 11:35:46 +02:00
Safihre
ed7dc3f827 Merge branch 'develop' 2017-06-09 11:30:43 +02:00
Safihre
61a6cb6d96 Update translations 2017-06-09 11:27:46 +02:00
Safihre
443efb5eda Update text files for 2.1.0 2017-06-09 11:10:48 +02:00
Safihre
ba3c731fee Correctly switch HTTPS port if occupied on first start 2017-06-08 10:50:26 +02:00
Safihre
e55f72dd1d Limit the maxium extension of the bps_list with zeros
If the last bps measurment was very long ago, this could cause thousands of zeros to be pre-loaded. This could cause MemoryErrors on low-spec devices: 
https://forums.sabnzbd.org/viewtopic.php?f=3&t=22709&p=112149
2017-06-07 18:36:36 +02:00
Safihre
b28c0a60a1 Remove option to disable Par2 Multicore on macOS
In the hope to remove par2-classic from the macOS packages in later stage.
2017-06-04 20:30:54 +02:00
Safihre
b7a80bf026 Remove nr_decoders
2 seems to be perfect for now, it was always intended as a temporary setting.
2017-06-04 00:14:52 +02:00
Safihre
fe7218e64b Remove log_new
Not used anywhere..
2017-06-04 00:11:15 +02:00
Safihre
0857a9046d Remove login_realm
Never used nor shown to users to be able to modify it
2017-06-04 00:08:02 +02:00
Safihre
354131b78a Remove prio_sort_list
Nobody seems to care about it
2017-06-04 00:05:06 +02:00
Fish2
e3ae91a4f8 lossless compression of images saved 40KB (87%) 2017-06-04 00:00:25 +02:00
Safihre
52cc5e2e4f Remove create_group_folder NZO attribute 2017-06-03 23:53:48 +02:00
Safihre
0c04451442 Remove create_group_folder
Latest forum topic about this was in 2008
2017-06-03 23:52:54 +02:00
Safihre
f5ab4a2253 Remove enable_par_cleanup 2017-06-03 23:48:51 +02:00
Safihre
1303dfe17a Remove allow_64bit_tools
None of the tools have ever shown problems on their intended platform. We can opt to remove them from their respective builds for the releases, to reduce download-size.
2017-06-03 23:38:57 +02:00
Safihre
55d80f26fa Update wiki links for 2.1
Only tiny changes, but still different from 2.0.
2017-06-03 23:30:52 +02:00
Safihre
7aee585748 Remove depricated functionality to remove samples when adding NZB
Was removed long ago because removing Sample files before downloading could give verification problems.
2017-06-03 14:07:47 +02:00
Safihre
196858409c Update text files for 2.1.0RC1 2017-06-01 22:44:34 +02:00
Safihre
21467dd62f Only download all par2 when there was a problem 2017-06-01 22:43:24 +02:00
Safihre
bb30eb7d11 Always enable QuickCheck 2017-06-01 22:35:54 +02:00
Safihre
fb9f4a7373 User Par2 Parameters for Multipar 2017-06-01 22:35:29 +02:00
Safihre
ccd5c1c75e Show par2 Extra Parameter problems as Error 2017-06-01 22:02:00 +02:00
Safihre
015c578cdd Add par2_multicore to Specials (just in case)
#902
2017-05-30 23:04:18 +02:00
Safihre
8eb4ce2914 On Windows, only show MultiPar 2017-05-30 22:45:26 +02:00
Safihre
a6b8108ee6 Support macOS >= 10.9 (removes 32bit support) 2017-05-30 14:16:34 +02:00
Safihre
181a56218a Support macOS >= 10.8 2017-05-30 14:01:34 +02:00
Safihre
24feaaebd6 Remove macOS PPC support 2017-05-30 13:56:13 +02:00
Safihre
e1945e7a35 Add license file and README for MultiPar 2017-05-28 10:15:12 +02:00
Safihre
072f65dd9c Show history status if par2 was aborted due to Disk full 2017-05-27 19:04:22 +02:00
Safihre
bde03ecc63 False-positive showing disk-full in Glitter 2017-05-27 17:10:00 +02:00
Safihre
73c2e23da4 Remove 'never_repair'
Should just use only Download feature
2017-05-27 14:32:17 +02:00
Safihre
e6233831d1 Fix typo in Multipar text 2017-05-27 01:15:20 +02:00
Safihre
82ccbdaa7b Make human-readable dates the default date-format 2017-05-25 12:24:34 +02:00
Safihre
bf5212a81c Allow aborting running PP-script
Closes #313
2017-05-25 12:23:46 +02:00
Safihre
5c6cc932cf Update text files for 2.1.0Beta1 2017-05-25 11:02:03 +02:00
Safihre
ed4430a7e0 Update translatable texts 2017-05-24 12:39:29 +02:00
Safihre
eb73f78b1f Remove unused try/except and allow cancelling of MultiPar 2017-05-24 12:39:29 +02:00
Safihre
314aad0009 Bring back unique-unpack staging 2017-05-24 12:39:29 +02:00
Safihre
98d0c5c52f Update versioning 2017-05-24 12:39:29 +02:00
Safihre
4d4da889ec Clear text in Config > Switches about Multipar 2017-05-24 12:39:29 +02:00
Safihre
4a622f59ba Updates to Multipar code and unpack-info saving 2017-05-24 12:39:29 +02:00
Safihre
913b92088a Update Multipar to v1.2.9 2017-05-24 12:39:29 +02:00
Safihre
80f4690df8 Enable and notify users about Multipar 2017-05-24 12:39:29 +02:00
Safihre
f552531703 Adding MultiPar interpreter 2017-05-24 12:39:29 +02:00
Safihre
707d4a7a0c Adding Multipar executables 2017-05-24 12:39:29 +02:00
Safihre
e69eeebdd8 Merge branch 'develop' - Update to 2.0.1 2017-05-24 12:14:56 +02:00
Safihre
be5bebb574 Update text files 2.0.1 - Nr2 2017-05-23 13:12:02 +02:00
Safihre
4ca2a7a65e Also match 'proof' when ignoring samples 2017-05-22 22:07:33 +02:00
Safihre
eed8c9bf50 Update text files for 2.0.1 2017-05-22 21:14:17 +02:00
Safihre
11d5855430 Always show Script box on Categories config page
Otherwise people might not be aware of it
2017-05-22 21:10:17 +02:00
Safihre
b13413b1e5 Only perform end-of-queue action when one is set 2017-05-22 10:38:52 +02:00
Safihre
9809474615 Remove cache from first Config page
It's a dynamic thing, not static like the Config. It can be observed live from the Status and Interface window.
2017-05-21 21:23:10 +02:00
Safihre
9ee3a61ae9 Convert HTML " in Repair confirmation 2017-05-21 21:05:12 +02:00
Safihre
c71ffa02f8 Remove ending \ in extraction path after long-path unrar retry
So, turns out that when not using the \\?\ notation, Unrar does not like the \ at the end of the path (which it does need otherwise). Linked #771
2017-05-21 13:53:38 +02:00
Safihre
2e862da292 Remember all unpack/repair info, no more overwrite
Keeps forgetting all the previous stages, no idea why that's usefull
2017-05-21 13:00:20 +02:00
Safihre
08d762c6c9 No longer try to verify using SFV and/or RAR-check when Par2 failed 2017-05-21 12:23:06 +02:00
Safihre
2ef6f9e0e7 Repair would be skipped if first par2-file was not downloaded 2017-05-20 19:14:04 +02:00
Safihre
b8f84cf18d Renames of QuickCheck were not saved if following files failed
Now we do as much as possible in QuickCheck before moving on, much faster then letting par2 do it
2017-05-20 15:52:36 +02:00
Safihre
7e88af7047 Small cleanup of par2repair code 2017-05-20 14:52:18 +02:00
Safihre
2fc365dd57 Update text-files for 2.0.1RC2 2017-05-20 11:45:59 +02:00
Jonathon Saine
434170862c Upgrade moment 2.10.6 -> 2.18.1 (inc locales). Corrected filename to reflect its minified.
> https://github.com/moment/moment/blob/develop/CHANGELOG.md
2017-05-20 09:51:18 +02:00
Safihre
1eb6c426fd Correctly handle already bound port with proper command-line message
Closes #921 
Closes #923
2017-05-20 00:03:57 +02:00
Safihre
e2c46d73e4 Remove unused panic message 2017-05-19 23:44:05 +02:00
Safihre
eef02ac7ce Properly handle Queue-finish-action in Glitter
Could get lost, now it sticks of first try. Closes #924
2017-05-19 23:27:20 +02:00
Safihre
0d9614755e Make DateJS bugfix more general for m/h/d etc notations 2017-05-19 23:07:42 +02:00
Safihre
aa0557656c Optional tag on dark theme not visible
Closes #905
2017-05-19 23:03:28 +02:00
Safihre
2c750f98cb Do not remove folder on re-use
This can cause issues on some file-systems, resulting in infinite loops: https://forums.sabnzbd.org/viewtopic.php?f=2&t=22637#p111875
2017-05-19 15:14:52 +02:00
Alishan Ladhani
82cf2b33cf Fix bug with DateJS
Fix DateJS bug where sometimes >12 minutes/hours/days converts into the wrong timestamp. Removing the space makes DateJS work properly.
2017-05-19 14:36:10 +02:00
Phil R
b9cfe0d6f0 URL Grabber crashes on unhandled socket exceptions
URL grabber handles `httplib.IncompleteRequest` exceptions.
This exception is not returned when the HTTP responses closes in the body, rather than the headers.
2017-05-19 13:57:33 +02:00
Safihre
a0166a4011 Correct command-line parameters 2017-04-21 15:53:24 +02:00
Safihre
c9f765813c Exit more cleanly when ports are occupied 2017-04-21 15:35:10 +02:00
Safihre
2f52590587 Update translations 2017-04-19 16:09:15 +02:00
Safihre
fd581fffa5 Prepare text-files for 2.0.1RC1 2017-04-19 15:58:43 +02:00
Safihre
fcdac1cb32 On retry, make sure we also do a check for failed recursive unpacks
Closes #900
2017-04-19 11:26:00 +02:00
Safihre
a824fa617b Proper messages in case of 7zip encryption 2017-04-19 11:05:24 +02:00
Safihre
1fd0c23e55 Apply overwrite_files() setting also to 7zip 2017-04-19 11:04:27 +02:00
Safihre
884d4ee91b Remove Special-switch prospective_par_download
It works, no need to disable anymore.

Possibly also fixes #903
2017-04-18 21:28:29 +02:00
Safihre
0c7d303568 Stop further Assembler processing after IOError 2017-04-18 21:15:49 +02:00
Safihre
a8ac8609d6 Revert "Don't use decoder for missing articles"
Turns out that it is really slow for some people

https://forums.sabnzbd.org/viewtopic.php?f=2&t=22592
2017-04-18 15:43:54 +02:00
Safihre
0d98a24c67 Only search for free port when SAB is started for first time
Closes #875. Removed 'fixed_ports' from Specials because now it's just an internal variable.
2017-04-18 13:56:21 +02:00
Safihre
31eeb5e539 Filter out tab-char (\t) for filenames on Windows
Closes #897
2017-04-18 13:29:46 +02:00
Safihre
82dacda359 Need to wait longer after restart is triggered in Config 2017-04-18 13:15:55 +02:00
Safihre
c3f82e49bf Log X-Forwarded-For in API calls and logins
Closes #884. But we don't overwrite the other found IP otherwise it might be used to remove correct logging.
2017-04-18 13:00:17 +02:00
Safihre
86a0e734b1 Avoid ZeroDivisionError in PP-script call
See #900
2017-04-18 09:00:17 +02:00
Safihre
934db752bf Restore Downloader-slowdown to more conservative value
More slowdown was causing problems for some users
2017-04-14 17:07:54 +02:00
Safihre
ed379da657 Update versioning file
On to 2.1.0!
2017-04-09 12:39:36 +02:00
Safihre
5da5f1adc1 Set version to 2.0.0 2017-04-09 12:25:05 +02:00
Safihre
f47e92dec0 Merge branch 'develop' 2017-04-09 11:59:13 +02:00
shypike
55c4bef524 Update translations 2017-04-09 10:22:25 +02:00
Safihre
ccb329160d Update text files for 2.0.0 Final (again) 2017-04-08 07:53:24 +02:00
Safihre
424626d64b Correct the SABYenc version matching 2017-04-07 08:56:45 +02:00
SanderJo
026893e10d SABYenc: explicitly define "SABYENC_VERSION = None" if none installed 2017-04-07 07:45:42 +02:00
Safihre
840b03c875 Update text files for 2.0.0RC3 2017-04-06 09:49:56 +02:00
Safihre
325f876010 Bump SABYenc to 3.0.2
Because the source-distribution on PyPi failed (missing .h file), on pypi are not allowed to replace a file for a release.
2017-04-06 09:35:22 +02:00
Safihre
91606a24b8 Correct translatable texts formatting 2017-04-06 08:55:21 +02:00
SanderJo
f001d8b749 SABYenc: Reporting of Required and Found version. Plus better var naming. 2017-04-06 08:47:20 +02:00
SanderJo
31c0c239f9 getformance.getcpu(): if no cpu info, fallback to platform.platform() 2017-04-05 17:34:34 +02:00
Safihre
918c4dbfce Save renames on join and move the join
It needs to happen first, so it doesn't get canceled
2017-04-05 14:41:56 +02:00
Safihre
f587319ef0 Only retry once after joining files 2017-04-05 13:59:01 +02:00
Safihre
b4dd942899 Fix problems with joinable files and par2cmdline
Closes #885. For some magical crazy reason, par2cmdline just doesn't scan .001 files, only all the other ones. Causing unjust verification failures.
2017-04-05 13:39:04 +02:00
Safihre
97a6720fba SABYenc bump to 3.0.1
Clumsy
2017-04-05 10:08:32 +02:00
Safihre
f05c1ef9e8 Bump SABYenc to 3.0.0
Important changes.
2017-04-05 09:35:24 +02:00
Safihre
91c1ea97fd Update display of Certificate Validation
Unfortunately requires new translations. "Default" is just not correct anymore.
2017-04-03 15:24:58 +02:00
Safihre
74faa159e7 Rename 'Default' Certificate Validation setting to 'Minimal' 2017-04-03 12:52:10 +02:00
Safihre
7e9892bb8d Prepare text files for 2.0.0 Final
@sanderjo @thezoggy @shypike: Let me know if things are missing or should be formulated better!
2017-04-02 17:43:54 +02:00
Safihre
0a9e54e5c5 Update some general text-files 2017-04-02 17:16:51 +02:00
Safihre
0f0d16a104 Update translatable texts
@shypike, I updated, the pot-file, can you send it to LP?
2017-04-02 13:25:01 +02:00
Safihre
7d7ee6ca6a When detecting cloaked job, tell this to the user 2017-04-02 12:47:46 +02:00
Safihre
0d96cd3fe8 Better handeling of filetypes-to-ignore during QuickCheck
@thezoggy Closes #880
2017-04-02 12:05:33 +02:00
Safihre
596244543c Config - Specials did not show default-values for lists
Linked to #880
2017-04-02 11:20:50 +02:00
Safihre
237d6b9414 Revert "Bump SABYenc to 2.9.0"
Staying with 2.8.0 for compatibility untill we can do another SABnzbd release.
2017-03-31 15:12:43 +02:00
Safihre
a7c42779f8 Bump SABYenc to 2.9.0 2017-03-31 13:50:04 +02:00
Safihre
c49e5f2054 Also remove IPv4/6 address from logs 2017-03-31 10:54:10 +02:00
Safihre
6ca6037aa0 Don't sort when loading Queue from disk on start-up 2017-03-31 09:57:22 +02:00
Safihre
cc7f360c04 Only trigger Script change in Glitter when there are scripts 2017-03-31 09:39:54 +02:00
Safihre
75991bffea Stray unicode in CRC should not crash the decoder
This actually happened..
2017-03-30 15:11:59 +02:00
Safihre
afd1b1968c Update text files for 2.0.0RC2 2017-03-29 17:22:27 +02:00
Safihre
746e9d2a6d Bump required SABYenc 2017-03-29 16:46:40 +02:00
Safihre
36b5b5d0f3 RarFile needs path to unrar before starting 2017-03-28 21:34:39 +02:00
Safihre
dd603cfcc8 Non-SABYenc situations could result in crashes 2017-03-28 17:00:35 +02:00
Safihre
cefce9913a QuickCheck would fail renaming when file does not exist
For example if par2 already renamed it before
2017-03-28 16:44:33 +02:00
Safihre
0b29f27fcd Use popen instead of system for diskspeed initialization on Windows
In the binary otherwise a command window pops up
2017-03-23 16:18:47 +01:00
Safihre
f3f3e27bfe Move dot-stripping to last step of sanatizing 2017-03-22 17:20:33 +01:00
Safihre
519c44a72a skip_dashboard=1 default for fullstatus API-call 2017-03-22 11:35:24 +01:00
Safihre
7e28da0530 Set a develop-version to distinguish which develop users are on
Closes #872
2017-03-22 09:13:30 +01:00
Jonathon Saine
af70d98b50 Fixes: #860
Handle: `SSLError: [SSL: HTTPS_PROXY_REQUEST] https proxy request (_ssl.c:661)`
2017-03-21 12:53:16 +01:00
Safihre
3f0b84ea22 Update text files for 2.0.0RC1 2017-03-20 22:33:01 +01:00
sanderjo
e58abd45ec newsunpack: unrar says "too large" => give error 2017-03-20 21:55:49 +01:00
Safihre
abeee263f0 Use constants.REC_RAR_VERSION in all checks 2017-03-20 15:10:44 +01:00
Safihre
30f68bd7b9 Warning about bad Unrar was never shown in Glitter
Now it's a proper warning
2017-03-20 15:04:03 +01:00
Safihre
f13394d27f Change Error to Warning for missing SABYenc
In preperation of RC1
2017-03-19 16:59:36 +01:00
shypike
88e0617429 Update translatable texts 2017-03-18 22:34:29 +01:00
shypike
ab94ffc055 Update translatable texts 2017-03-18 22:13:56 +01:00
shypike
265ab99cc7 Remove unused languges 2017-03-18 21:12:53 +01:00
shypike
738adbe38e Update translation 2017-03-18 21:09:36 +01:00
Safihre
a894ca5171 Set version to 1.2.3 2017-03-18 13:05:04 +01:00
Safihre
5abe1140ae Merge branch '1.2.x' 2017-03-18 13:03:36 +01:00
Safihre
4e6862cef9 Chain certificates not loaded in all situations of extra servers 2017-03-18 12:23:48 +01:00
Safihre
7aff60b24d CherryPy would fallback to (non-existing) pyopenssl for extra servers
Causing failed starts
2017-03-18 12:23:12 +01:00
Safihre
d34e14370c Chain certificates not loaded in all situations of extra servers 2017-03-18 11:43:46 +01:00
Safihre
c4f4a3131c Update 1.2.3 text files for extra bugfix 2017-03-18 11:40:57 +01:00
Safihre
dcbd9b57f3 CherryPy would fallback to (non-existing) pyopenssl for extra servers
Causing failed starts
2017-03-18 11:39:40 +01:00
Jonathon Saine
8819e38073 We only used 1 line from listquote, just copied over that line to eliminate the need for the superfluously import. 2017-03-18 11:10:44 +01:00
Safihre
aad3b54a17 Update Text Files for 1.2.3
These patches were already tested within 2.0.0Alpha/Beta, so considerd safe.
2017-03-18 11:04:57 +01:00
Safihre
cde142a371 Windows paths can't end in a dot 2017-03-18 11:02:14 +01:00
Safihre
8bfc98ffc6 Glitter server unblock button wasn't working
Closes #864
2017-03-18 11:01:51 +01:00
Jonathon Saine
e46f21d566 Fix -w option as it no longer needs arguments. 2017-03-18 11:00:46 +01:00
Safihre
0e45fdcdfd Fix BPS manager breaking the downloader due to Quota 2017-03-18 10:28:31 +01:00
Safihre
eec7af16d7 Improve log obfuscation
It needs to match any charachters not just alpha-numeric
2017-03-18 10:28:03 +01:00
Safihre
6532425902 Kill the UnRar after a Windows failure
On Windows Server it seems unrar otherwise won't stop: https://forums.sabnzbd.org/viewtopic.php?f=3&t=22492
2017-03-18 10:27:57 +01:00
Safihre
44b896522c Unzip needs clipped paths
(7zip doesn't)
2017-03-18 10:26:32 +01:00
Safihre
2bbac91436 Fix BPS manager breaking the downloader due to Quota 2017-03-18 09:41:01 +01:00
Safihre
cf4dea432b Fix SMPL after reverting qstatus 2017-03-17 22:47:55 +01:00
Safihre
bd70df1f05 Remove non queue/history info from those API calls 2017-03-17 22:29:10 +01:00
Safihre
b2638c1fac Make API call qstatus identical to queue call 2017-03-17 21:39:24 +01:00
Safihre
4aa9409f5d Revert "Remove API-call 'qstatus'"
CouchPotato also uses it and doesn't get updated.
2017-03-17 21:35:38 +01:00
Safihre
dfa863a54a Reame cat_list > categories and script_list > scripts 2017-03-17 20:40:40 +01:00
Safihre
626c04df48 Don't need password parameter 2017-03-17 20:19:17 +01:00
Safihre
6026fa57f0 Remember password in history
Closes #855
2017-03-17 20:17:45 +01:00
shypike
0db28fb5e2 Add new column to database.
Bump version to 2.
Add "password" column in new database.
Insert "password" column in existing databases.
2017-03-17 19:34:13 +01:00
Safihre
c9bf8ced99 Remove unused function format_history_for_queue 2017-03-17 16:58:11 +01:00
Safihre
18a55db245 Remove undocumented 'sort' and 'trans' options from queue API call 2017-03-17 16:30:44 +01:00
Jonathon Saine
efb9664761 Update windows section to use win32api 2017-03-17 15:09:09 +01:00
Safihre
3ee412c7a5 Update translatable texts 2017-03-17 13:22:18 +01:00
Safihre
5afc00a502 Remove API-call 'qstatus'
SMPL specifc thing that can also be done from data in regular queue call. To keep SMPL working.
2017-03-16 22:09:33 +01:00
Safihre
e5ca0e6415 Remove legacy detection when not to show API warnings 2017-03-16 22:09:33 +01:00
SanderJo
98f121258c Everything in a try/except as we're dealing with OS calls. 2017-03-16 22:06:47 +01:00
Safihre
b2474c51fd Glitter server unblock button wasn't working
Closes #864
2017-03-16 14:19:37 +01:00
Jonathon Saine
1bd6ebdb41 Fix -w option as it no longer needs arguments. 2017-03-16 08:24:36 +01:00
SanderJo
20ef99326d Check if Completed Download Folder is on FAT. If so, give a warning 2017-03-15 08:58:58 +01:00
Safihre
171a1b9ae3 Do not handle first article seperate
Was build to detect filename early on, but we can also do this later. This way not everything gets loaded into memory right away. Part of #535
2017-03-14 16:48:27 +01:00
Safihre
cd2c9d151a Correctly check for NOT is_gone when deleting
Oops
2017-03-14 16:41:26 +01:00
Safihre
8fbfe9a76a Always provide nzf_id to get_files
Otherwise we can loose track of which file we are talking about. Especially because nowadays files are obfuscated on article-level.
2017-03-14 16:16:56 +01:00
Safihre
63cf0d4f97 Top-only now really really only downloads the top job 2017-03-14 13:55:20 +01:00
Safihre
faa98126f8 More general multicore-par2 detection 2017-03-11 20:24:02 +01:00
Safihre
0dd70249b9 Windows paths can't end in a dot 2017-03-10 16:48:09 +01:00
Safihre
aadd99cac3 Update text files for 2.0.0Beta1 2017-03-10 15:16:11 +01:00
Safihre
5bede842ba Avoid double checks for saving SABnzbd password 2017-03-09 17:37:45 +01:00
Safihre
1f2ac77b5e SABnzbd password should show stars
Closes #851
2017-03-09 17:29:27 +01:00
Jonathon Saine
3f4f35c6d1 More PEP8 cleanup, spelling fixes in comments and reserved word changes, and make IDE ignore debug asserts. 2017-03-09 16:22:30 +01:00
Jonathon Saine
9575ddbdb4 PEP8 cleanup -- compare instance not type 2017-03-09 16:22:25 +01:00
Jonathon Saine
3ed918d98d PEP8 related cleanup (most whitespace) 2017-03-09 16:22:18 +01:00
Safihre
89d5af3372 Correct typo (Successful) 2017-03-09 15:55:24 +01:00
Safihre
fa8f40eee3 Detect multicore par2 and show link how to install
Now also available in @jcfp's new PPA (both MT and TBB) versions. Installing MT is also very easy on other platforms. For example SynoCommunity also uses it for their packages.
2017-03-08 18:24:48 +01:00
Safihre
8f06035500 Log succes and failure of logins
Might consider adding a special later to stop the warnings... https://forums.sabnzbd.org/viewtopic.php?f=2&t=22494
2017-03-08 16:59:21 +01:00
Safihre
8f7d969099 Do the Restart async to allow finishing of request to browser
For Shutdown this does not seem to be required
#843
2017-03-08 16:19:13 +01:00
Safihre
5422785feb Improve log obfuscation
It needs to match any charachters not just alpha-numeric
2017-03-08 15:26:08 +01:00
Safihre
911f82c00b Kill the UnRar after a Windows failure
On Windows Server it seems unrar otherwise won't stop: https://forums.sabnzbd.org/viewtopic.php?f=3&t=22492
2017-03-08 15:16:16 +01:00
Safihre
fbff8c991b Update translatable texts
@shypike Can you push to Launchpad? :)
2017-03-07 17:29:29 +01:00
Safihre
f1b139d55d Make extract_pot also work on Windows 2017-03-07 17:25:03 +01:00
Safihre
9c7f196e20 Incorrect detection of servers with same priority 2017-03-07 16:13:28 +01:00
Safihre
4d8a37006e CRC error is not the same as unknown encoding
With SABYenc it would report single-article files with CRC errors as 'Warning: unknown encoding'
2017-03-07 15:45:16 +01:00
Safihre
bc9d3d561f Missed to convert one Raiser() in interface 2017-03-07 15:02:54 +01:00
Safihre
fd9e80bdf5 CherryPy 8.1.2 - Also catch 'unknown error' from SSL
@sanderjo I forgot!
2017-03-04 16:50:12 +01:00
Safihre
aee2f71170 Log where a restart was triggered 2017-03-04 15:51:42 +01:00
Safihre
102160d651 Don't add prospective par2 on Pre-check 2017-03-03 23:08:34 +01:00
Safihre
e33f26d33c Log which server has the article
(in the hopes to debug the Check before download-feature)
2017-03-03 22:43:45 +01:00
Safihre
57113fa02f Check before download were decoded twice 2017-03-03 22:24:25 +01:00
Safihre
2be7575f98 Only show Multi-edit-Search when there is a Queue 2017-03-03 14:01:38 +01:00
Safihre
c5647b46e1 Show Queue search when Multi-editing
#842
2017-03-03 13:51:08 +01:00
Safihre
3450cff92f Unzip needs clipped paths
(7zip doesn't)
2017-03-02 15:41:40 +01:00
Safihre
1b16ee44cb Set version to 1.2.2 2017-02-28 11:17:24 +01:00
Safihre
d5f608c28c Merged branch 1.2.x into master 2017-02-28 11:16:59 +01:00
Safihre
13a11411a9 Update README 2017-02-26 20:20:44 +01:00
Safihre
6e7eb9dec4 Small Changelog typos 2017-02-26 12:37:52 +01:00
Safihre
c74eed7c0e Add new PP-script feature to Changelog
Url only for the Alpha, later information will be on Wiki
2017-02-26 12:31:50 +01:00
Safihre
2fc6811495 Remove enabled_sabyenc
Too much was intertwined. If users want to experiment with/without SAByenc they can download seperate releases.
2017-02-26 12:23:25 +01:00
Safihre
57e0dac45b If no SABYenc or _yenc, give ERROR
Performance of Python-yEnc is lower in 2.0.0, so people need to fix things
2017-02-26 12:23:24 +01:00
Safihre
537e31000e Python-yEnc cannot handle \n or \r in data
_yenc and SABYenc ignores them
2017-02-26 12:23:24 +01:00
Safihre
e0872f4536 Convert python types in environment fields
True/False=1/0 and None=''
2017-02-25 21:46:26 +01:00
Safihre
9dddf6dd2e Make more information available to PP-scripts via enviroment variables
Closes #785
2017-02-25 21:22:11 +01:00
Safihre
7a0c5feed3 Update text files for 2.0.0Alpha1 2017-02-25 14:21:33 +01:00
Safihre
42d154f0b7 Accidentally deleted StringIO import 2017-02-25 14:02:21 +01:00
Safihre
44b0ab2203 Remove macOS IOPolicy throttling
#830. Currently it's set to IOPOL_THROTTLE, meaning lowest priority. 
Have to await feedback from users, potentially we can create a Special option for it.
2017-02-25 13:39:48 +01:00
Safihre
4af59b50ad Update translatable texts 2017-02-25 13:28:45 +01:00
SanderJo
b309099f0b Warning in case of non-UTF-8 setting on Linux/Unix 2017-02-25 13:27:49 +01:00
Safihre
affea99cb1 Retry pickle-saving 3x
Especially on Windows the file sometimes gets locked, and users have reported 'dictionary changed size during iteration' errors, so somewhere a Lock is missing.
2017-02-25 13:11:45 +01:00
Safihre
9bc35a3026 Always use hashlib
Part of Python standard library now
2017-02-25 13:02:54 +01:00
Safihre
57e9d499fb Slowdown downloader more
Decoder doesn't use GIL, so all power now goes to the Downloader that loops too much
2017-02-24 17:36:06 +01:00
Safihre
95a7924b31 Set version to 1.2.1 and fix small discrepancies 2017-02-24 11:22:55 +01:00
Safihre
5830bebd95 Merge branch '1.2.x' 2017-02-24 11:16:18 +01:00
shypike
d53cf598a4 Update translations 2017-02-23 19:55:51 +01:00
Safihre
bdaca2bd37 When retry of unpack due to Windows bug, send the actual password
We converted the password to "-p<password>" and when we had to retry it due to a Windows long-path fail, it would become "-p-p<password" and would incorrectly report that the password was wrong.
2017-02-21 20:09:22 +01:00
Safihre
53e3af9b30 Update changelog for Script changes 2017-02-21 13:31:18 +01:00
Safihre
9cfef895f8 Don't shorten, but clip paths given to external scripts
Windows-only: Either we give long-path notation paths (\\?\) or we just give the path. Either way, user scripts have to be modified.
2017-02-20 16:12:11 +01:00
Safihre
56fa9644a5 Forced diskspace check seperate from cached one
Closes #826. Otherwise the complete/incomplete get out of sync.
2017-02-20 15:35:16 +01:00
Safihre
8eff51a96b Tooltip for Download/Incomplete in Status Windows
Closes #827
2017-02-20 09:03:24 +01:00
Safihre
d130a1d44a Update Wiki help links for 2.0.0 2017-02-19 17:17:04 +01:00
Safihre
98316fd282 Correct mistake in Diskspace calculation for Unix 2017-02-19 12:36:36 +01:00
Safihre
59f1ea3073 Diskspace for Complete folder was not calculated 2017-02-19 11:47:11 +01:00
Safihre
270757f3bd Consistently check for cfg.use_pickle() 2017-02-19 10:20:13 +01:00
Safihre
843c6b36a8 Modify RarFile to properly handle testrar on Windows
Because unrar doesn't support \\?\ notation for the path to the rarfile we used to clip the path. However, the Python functions that RarFile uses then fail on unicode or jobs with '?' in the filename. Now this is handled correctly, at the very last moment before testing the RAR.
2017-02-19 01:45:03 +01:00
Safihre
f71a2a8fc2 Use pythonic-style for data reading/writing 2017-02-18 23:38:39 +01:00
Safihre
63fc763958 Don't use decoder for missing articles
Don't occupy the decoder-queue with all these empty articles, but handle them directly.
Requires less CPU.
2017-02-18 22:52:42 +01:00
Safihre
9bc0aac63d CherryPy 8.1.2 - Use relative HTTPredirects instead
CherryPy is using the oudated redirects with full URL, but that's not needed anymore and is much more flexable when used behind a proxy.
https://github.com/cherrypy/cherrypy/issues/1355#issuecomment-270669958
2017-02-18 22:52:42 +01:00
Safihre
ff529da874 Update Readme for changeset 2017-02-18 22:52:42 +01:00
Safihre
ff40944f00 Disable schedule items without deleting them
Requires conversion of the schedules!
2017-02-18 22:52:42 +01:00
Safihre
1d0ac46c7e More correct handeling of CherryPy logging
Use CherryPy's options to handle the logging.
2017-02-18 22:52:42 +01:00
Safihre
d62bb1e5b6 Move overwrite_files to Specials
Very special option, only usuefull if you dump all output in 1 directory. But this rarely happens.
2017-02-18 22:52:42 +01:00
Safihre
89f91e46b7 Add Certificate Validation option to Wizard
Otherwise a user would be stuck.
2017-02-18 22:52:42 +01:00
Safihre
bc2daa5f8b Enabled Hostname verification by Default
We will try this and see the feedback if users understand. We have improved the linked page with information for users: https://sabnzbd.org/certificate-errors
2017-02-18 22:52:42 +01:00
Safihre
0fbf240a58 Simplify CherryPy logging
No filter required anymore, CherryPy provides it's own config switches for it.
2017-02-18 22:52:42 +01:00
Safihre
83d57b33f7 Rewrite Raisers in Interface.py
Raisers don't need extra information anymore, there is only 1 type of redirect to be used.
2017-02-18 22:52:42 +01:00
Safihre
59ae23e315 Correct redirect for each situation
Some redirects were not performed correctly in the new situation.
2017-02-18 22:52:42 +01:00
Safihre
df26adfe89 Accidentally removed SSL-choice from Wizard 2017-02-18 22:52:42 +01:00
Safihre
d1a92aeb36 Restructure interface.py
We now only have 1 directory that has all the template files, so the directory does not have to be a variable for each page anymore.
There should be caution when doing redirects, to make sure the correct /sabnzbd/ or just / is used.
2017-02-18 22:52:42 +01:00
Safihre
c09f1b2f1c Move rating_host back to Specials
Possibly to be deprecated later on. Indexers should supply this info via the headers: https://sabnzbd.org/wiki/extra/indexer-feedback
Indexers like to use an URL, instead of just providen a host.
2017-02-18 22:52:42 +01:00
Safihre
a70a1e6290 Assume Rating feedback if Indexer integration enabled
With the changes in 1.1.0 we now have more indexers in the wild actually use the system, by sending the headers or using the fields in the NZB.
But nowadays very few files have actual audio/video ratings of users, so the main purpose of this feature has evolved to automatically report back if the download failed on the server.
The text is also updated as such, that enabling it will send to the indexer if a download failed, only if it failed.
2017-02-18 22:52:42 +01:00
Safihre
dde5258b59 Move 'Use tags from indexer' to Specials
The user should always use this, there is no bad thing about it.
This cleans up the Switches page a bit. Eventually the option can just be completly removed.
2017-02-18 22:52:42 +01:00
Safihre
dc438e6eb7 Remove Secondary Skin option
Major cleanup, now doesn't need any special links to file locations anymore.
Closes #778
2017-02-18 22:52:42 +01:00
Safihre
37a9a97f4f Move QuickCheck to Specials
Closes #788. It's been tested, it works, nobody should not use it.
2017-02-18 22:52:42 +01:00
Safihre
4fb6e3fe7b Remove check for ssl module
Any Python setup has it, it was only introduced to check for PyOpenSSL which is now dropped.
2017-02-18 22:52:42 +01:00
Safihre
7884848c78 Don't run PyStone for every Status Window update
Only diagnostic info, no need to refresh every time and blocks CPU when window is refreshed during downloading.
2017-02-18 17:54:12 +01:00
Safihre
2ece328e50 Update Cache-info in Status-window with Queue
Info is already in the API-call for queue, so can be updated with same frequency
2017-02-18 17:30:06 +01:00
Safihre
b8ac3cd22f Catch bad article from SABYenc 2017-02-18 00:56:40 +01:00
Safihre
9b4bd7a3f0 Only warn about _yenc when no SABYenc 2017-02-18 00:56:40 +01:00
Safihre
facefc5c58 Option to disable SABYenc didn't actually work
But now it does!
2017-02-18 00:56:40 +01:00
Safihre
5d6a6b1af7 Show SABYencversion in log 2017-02-18 00:56:40 +01:00
Safihre
916e0ead99 Ignore non-essential files during QuickCheck 2017-02-18 00:56:40 +01:00
Safihre
aff7b07f33 Make sure the error-code is logged 2017-02-18 00:56:40 +01:00
Safihre
8267b429ca Some cleanup of SABYenc code 2017-02-18 00:56:40 +01:00
Safihre
5759bee1df Small changes to Decoder 2017-02-18 00:56:40 +01:00
Safihre
f2648ec85c Require SABYenc 2.7.0 2017-02-18 00:56:40 +01:00
Safihre
e083722f0b Make sure to do DMCA/Pre-Check checks in right data 2017-02-18 00:56:40 +01:00
Safihre
f03e63fa54 Make sure Decoder's get shutdown 2017-02-18 00:56:40 +01:00
Safihre
f33c3e30eb Robust detection of end-of-article 2017-02-18 00:56:40 +01:00
Safihre
c9ee0b0fcb Check for specific SABYenc version 2017-02-18 00:56:40 +01:00
Safihre
7aaa8036bc Setup multiple Decoders 2017-02-18 00:56:40 +01:00
Safihre
00f2410d2d Show SABYenc detection at startup and in Config 2017-02-18 00:56:40 +01:00
Safihre
be77a494db Correctly handle end-of-article 2017-02-18 00:56:40 +01:00
Safihre
787a95bdd2 Reduce timeouts for decoder/assembler switches 2017-02-18 00:56:40 +01:00
Safihre
720ce591b7 First work on SABYenc integration 2017-02-18 00:56:40 +01:00
Safihre
494e72a996 Update to 1.2.0 and set version 2017-01-13 17:00:51 +01:00
Safihre
84cc86f1d3 Update to 1.1.1 and set version (#731) 2016-11-11 13:58:07 +01:00
shypike
64479e2e5d Correct the merge of release 1.1.0 to the "master" branch. 2016-09-14 08:26:02 +02:00
shypike
13b523d9bd Merge pull request #681 from Safihre/master
Master to 1.1.0
2016-09-13 16:25:05 +02:00
Safihre
181881a21b Merge branch 'develop' 2016-09-11 22:16:42 +02:00
shypike
86d11095ac Merge pull request #592 from sabnzbd/1.0.x
Update master to 1.0.3
2016-06-10 19:01:03 +02:00
shypike
927ba3cd9d Update text files for 1.0.3 2016-06-04 13:34:11 +02:00
Safihre
6296fc1762 #568 Add code 482 to check for too-many-connections 2016-06-03 23:24:07 +02:00
shypike
60fbe44724 Support X-DNZB-PASSWORD header. 2016-06-03 22:18:56 +02:00
shypike
29e45da431 Fix NZB association for Windows.
Make sure that the second SABnzbd instance sends an UTF-8 encoded URL to the first instance.
Otherwise CherryPy will reject the API call.
2016-06-03 22:07:44 +02:00
shypike
d82e69eef4 Handle checksum error reports from unrar. 2016-06-02 23:19:57 +02:00
shypike
8c7d557252 Prevent job from hanging when adding back par2 files.
Sometimes already completed par2 files are being re-added to the queue as extra par2 files.
Because these files are already complete, there will be no attempt to download them
and as a result they will never leave the queue.
2016-06-02 22:11:59 +02:00
shypike
a56d6e5517 Merge pull request #577 from sabnzbd/1.0.x
1.0.x to master
2016-05-23 18:11:23 +02:00
shypike
7548d9e975 Correct base version number for 1.0.x releases. 2016-05-10 14:43:14 +02:00
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
shypike
fc7e87f0df Merge branch '1.0.x' 2016-03-19 11:49:11 +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
shypike
e51f4fc45a Merge pull request #181 from discordianfish/add-dockerfile
Add Dockerfile
2014-10-08 21:38:15 +02:00
Johannes 'fish' Ziemke
65278120e2 Add Dockerfile
Usage:

        docker build -t sabnzbd .
        docker run -p 127.0.0.1:8080:8080 sabnzbd
2014-09-24 13:46:43 +02:00
shypike
2eed355e9c Revert "newsunpack.py: also handle output of unrar5 with password protected files"
This reverts commit 12fd63c1cf.
2013-11-11 20:24:11 +01:00
shypike
018955f4d5 Merge pull request #122 from sanderjo/patch-4
newsunpack.py: also handle output of unrar5 with password protected file...
2013-11-11 11:19:20 -08:00
sanderjo
12fd63c1cf newsunpack.py: also handle output of unrar5 with password protected files
as discussed http://forums.sabnzbd.org/viewtopic.php?f=3&t=16166&p=89054#p89054
2013-11-07 17:02:24 +01:00
302 changed files with 54059 additions and 46036 deletions

20
.gitignore vendored
View File

@@ -1,4 +1,4 @@
#Compiled python
# Compiled python
*.py[cod]
# Working folders for Win build
@@ -7,6 +7,13 @@ dist/
locale/
srcdist/
# Snapcraft
parts/
prime/
stage/
snap/.snapcraft/
*.snap
# Generated email templates
email/*.tmpl
@@ -16,8 +23,14 @@ SABnzbd*.exe
SABnzbd*.gz
SABnzbd*.dmg
# WingIDE project files
# WingIDE/PyCharm project files
*.wp[ru]
.idea
# Testing folders
.cache
.xprocess
tests/cache
# General junk
*.keep
@@ -27,3 +40,6 @@ SABnzbd*.dmg
# Some people use Emacs as an editor
\#*
.\#*
.DS_Store
/venv

38
.travis.yml Normal file
View File

@@ -0,0 +1,38 @@
# Include the host/username/password for the test-servers
matrix:
include:
- os: linux
language: python
env:
- secure: X5MY2HAtCxBI84IySY/XroFsFy2RIVhfsX+P1y3WXfvwBHYKCgrPV6BgwCg93ttkPmMS/IslP5Vp4F1OGqC9AZdxtxfHKpIPlIVxIHj6Lf6xwynmbGDQXjy73K13gjznK2mkGA0jcsp4Q5POS4ZKVkd6aOXnc8l8xS08+ztAvfxCC3tsMj2oMLEPP92j6zqb/1x2aX5+gVyVzrKgQQVKIk6R6jTxhIiOMPzj4+VMLXK8NEZqjV6RPwUjSoKHqJiV5URyf6/+2Ojmem3ilnpktn7xIJ/ZO1UNnZCrElOGZtmbryZFMctJvEAIQCOSdzsq/MACk0gocnOL3JQfDF5sYPOjJmc6sZI9PL78oFhwKaLkWEx565u8kdkLTMvv4A02HAmPzV1rKE1CTlEhsy0djU8mueCr9Ep1WyLJdY/igbyhR+dOd8fVo9Y1tY2o+ZisCsO5+PRfzhypK9xukqmWDJSXIWSuExUU/becXJ4IaTmlYJ+ArhKvkL90GmckH/zt9ZPIgr9Lq0OFva9uVHX+sbbsQZZ48lAmgiiiX335dONj8MxO8cDKsUT9FWQ8PzeJ8g8PErv5pmVVVODoOdKZg2Oo4jUsZG2yV8uUt9j87I2DPou4WiJ7wcTzQCPdzlaA5hdixPMyVUF/yCL+eKdJQFaKy3eaKwCxnAVp3WA2WdA=
- secure: gzvbgg+rdM/TfPOlXGE/JOW8VEfIJxwUnzt2bH07nj/PbREXQXrImC1SreMtwQX+gvd7bjIBZU/sgNSgRYcWKaSim4uNtAbinkcKQeP+V844ZY2Fabq/xpLluSP61vJLQ+hOLbnvilxmuI0E1Divahxo5qZJlwzCdk5u2txJRT/2LKGDT1lpGgIyDj9u0/ViAcpaAyfFR2Zd6ydHKbx+eFBE21LkyH/+GJmRiO0+qLIuCa2knmOJYjwBxRcPiAEDpbrRUbYDiNyzPqEVxJfCbsGYlo/QN/SnV6kTqM1WoFzvi4d1pDxDGRFLQj+KigihF6uY4eLC1e6yVQrDy0tyWKt6E+1tc8fH5dRS7AYtWMzURn/7Ebd72AiNIYaeAL8ZPqI7vw3ovFTqBS0h8Mg2uUZ503ytUvfwKyU9MgIkzXwmGuE37MCd0bRJ/blPS2DT+IMbrbEP90K5VrDrN/AGiYHR1TZ9GKUZd6xHibulEh2nNFMMQEga8nE2CWaJ3uJrCN7ud+4OJ0zCZFF7JiJTbOGApHg/aGWD/bYfg9sIh7up4PcxVs6RFxbf+M1aB8GO2A9aEZFow+djYVxiqf6esmzrnlsTfz16f8Txmez3BRftjVULre03a3Rt7WRxwYLveNlJos1nMw3G0CnruCe+wJbHEK4tEiqIXqB8UemT4zw=
- secure: f5QGjZESH4khjLOO1Ntgtiol4ZvwcqHLIV1sdK162dVkNT6UKOTRQflj2UmRXzwiRzWtVX/Ri0zT0j+SUJy2+aqJY/gxvisdTIWzRQ3w/CJPGgCizSkTQEWJ2V/n7DUAJ4xerme36zYi21S3d8VEWVDzU/duLu3yhlN5x0wMCY+dDPSDTFubmptGeCmyxqBqGVd7gD3PaiK7fDBB/eAXbW3QxLLQfxLHmPsx8vzPhDTQiLFtY43jfnVGEBdUbxSMXbq2NRB5eXH3bBkW8u/5y9uoyuF45CQn8f3UB6F84L+/n9M2ryCGeSJOFuZqSUHXvRF2acON40jx3t4PVocEzYguPwewoiFxfFHjRWmiI4WljiN30taK0pgstmzLTedozK+NdZ0M8vD7MCyK0yegPQolzFRngWW5Y8NY1XwlBT9W2lqGmrFge+dB86wOArMcRlY62PTOJ9Zqspbe/6mBT4Tq4O2OsXxGX/x60W/NJynva9WAz2SLEi5Pjs6r1a3tyXssw4/8KVhWl92WfpOnWrZrnZlsxOTmcS2OhLB0FQikTv9T/i3CZNcCI4PELkExeIwh4JW1MY0iGeLDHcAUKryJGrRZj1x32Nt1uUPTPBi8l8EzNyNOUvbHYTdpBr5r2JW1orvT55OhvKauc3wB7ogj673iYsqx5jeazHhgJMs=
- os: osx
env:
- HOMEBREW_NO_AUTO_UPDATE=1
- secure: RI/WxBI5kTP5v0qZ6im5BwqPurzzwKIl8GSbM2dFSEiWYEbKwHTDJ3KDDsXqS8YMNaropNVgzsdpCGXYUWRNTraKVa9GZEGNJ+fQuBWK9wkJ0MDTYfL/QFSN1CDXXlg7k26VXu6PgvEFA5kyDfSpxrxXJC6yXOUeJqmebkU2fiQo7/5Vpb1WAwpYlBP6zL5lYt2lpJ85fhYEjuAeuP/9zdVIlgCB7rDCgUX7tCKKXgwbKXfcff7lOCneB00/RCmRuNp3/tohGlgrSXh4ivHx4XEQgRoiVdeR3RCKZa5tBIXANefuJ2VopBrAbSRmVBexQP1818IU/XLlwtEEpC1vulpkx+5JolWksCrx4uJkKdlH0KA4k1m88L0Q1Rmmnp9LgRgeEl5xqt5s6RR6lS63ChQYkVFgWandwlrWu7Uenne4401KbG58PzDXEGlsKhUXnYBX+SU6gwejImCMb3vszKRAge5QAQlkiruCu31W9tWpY9ezHYrbv9ckOqdFXf9qsPEnU352v/8qHFe7jT/+7RSYdUzuo/d2aQqPKfkb7sy1VLEznmbGmv1BH4rGNpxd5inlcFKsR099Hx7PWgY8MHZcnEP3PJ2kBseFzVP3WKXHDTcv8yR0w6EgQyMzSHl9Ah3WJJ7TXZQ82gcqF8LcmuKcqXcwTkffG3ww7Vzuq4M=
- secure: uXHKYgQAwnfhWKi7RKAEumMMZZTJBb878KpodRfs1fz0NffdPo5+Ak1ricNzOJ8wti8/lXycDS+YmnFs64lGUxL+zvbQlFv7QuKfN0uHfPlo6zux9Ha9pg1rSUI4zqZ9kmbtwc0I2mdy1VeWwHvnbQDXUIt6a+tTwYZL3MGdP6kNvtSXaYhbEoHExjqeHUtVhUTafvWGtwE7uN+sdvhwXQ0dWlz6HGub8qYjkKCmF9VG+OyLKjFHjLVDMQ7Jnng2l1ZOgHSh5g5m6r++NEwSzZ8wFVULdzv5eEcR9U+mHmonFKOA/ICcZGd8MhEuvz9BupfgDWFqSTb5JGxzlZ28YdtjcAudzrWQMSpP2R0ks2Ttxz9Kpgw1L75HMvj0smazHs7IEEiXf2Yr03bzeHg7CGXNqOYyEOxxrPaJekCjMlX/YGqT/iv/8pZPfew7k/iVJlvCam76WNXABjJncHJeMsCgkItYZAoRZJDc+7z8J4g4ys1Rk0V/difjjwc/pSeKbt6wDA/9cmZ7r4Cs1Yh9Pl/mw6kzWGGpejO7lmsayQN3Pw99QMcZByUHx5BR+ZtIfF7Sl+F0uDQJ0MntJcteF7z1Dam2jHlkLckb85j6YWup5ItLAj5Hz7V2YUwqFmQhfOWEAjxagNSNnB8we4YBWS4KDTBEVDm6ITTfddlYvCw=
- secure: HKaT52NUQh18kllFQTjpKC64KlDkWEz0XnIEKJffumctrJjCvoFZFNC7ip3j7Bi3yp2IeD2SMsdxrrT6YFKxx5FfSdPqpQnsY34bzdEFZQomNJg4n/tmBc350PoVQ0PvLQiVoCCfVbdS/b4makNK7A+d9KED+SEsQMAqKp2mSnGhATB9MwFaZL5S4nGnEkqW5+eeAQxJ8JRawwumOOx/xhPOoEMIfHMpyTwFI1yUh1nJhZ9k1nxHzPlM78goyIuf0MjeZfSZ2fIlNZGVruYM28i9hpO4bzPFhk51uryWv8DQZiZlpCkHl6Po7rVVf5pNqm+l9SD/t0DnhS2rJHdeFSI2lM/uZtdOxaY5fTTj83LbCGhFtuZnZRwoQ73tpda8J7Z1E5Ni9bi7vOiZQ4pEIPt4LLu0X607sPWMkqrmgalKQQS13b5oliyMpkIguvmj9822BpaNVqamIrfn0z38+0Gog8iuGlMAQnRO9tGDO4kbVLcZQTRWpSwIC3niTPjPgLq/N92XQ9xmccrFT7efwemgF65FNM5ltv8+9AmI+hsuyXfqeHaAV9wmxRAAhaqvRgnSLYa3u1CPn5fF2CDvPvPcyCEIWnyxc7dYHDpzAQDcyuSejtbnL8gpkDqEHpy23hTjgZnZD7Pk7PQ7ayA8zBumTMGZ+/GAn5Wmgce+w0M=
addons:
chrome: stable
before_script:
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then
brew cask install chromedriver;
else
sudo add-apt-repository ppa:jcfp -y;
sudo apt-get update -q;
sudo apt-get install unrar p7zip-full par2 chromium-chromedriver -y;
ln -s /usr/lib/chromium-browser/chromedriver ~/bin/chromedriver;
fi;
install:
- pip install --upgrade -r tests/requirements.txt
script:
- python ./tests/test_functional.py
notifications:
email:
on_success: never
on_failure: always

View File

@@ -1,5 +1,5 @@
*******************************************
*** This is SABnzbd 1.2.x ***
*** This is SABnzbd 2.3.6 ***
*******************************************
SABnzbd is an open-source cross-platform binary newsreader.
It simplifies the process of downloading from Usenet dramatically,
@@ -12,9 +12,6 @@ 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/
IMPORTANT INFORMATION about release 1.x.x:
https://sabnzbd.org/wiki/new-features-and-changes
Please also read the file "ISSUES.txt"
The organization of the download queue is different from 0.7.x (and older).

View File

@@ -1,5 +1,5 @@
(c) Copyright 2007-2017 by "The SABnzbd-team" <team@sabnzbd.org>
(c) Copyright 2007-2018 by "The SABnzbd-team" <team@sabnzbd.org>
The SABnzbd-team is:

View File

@@ -1,10 +1,10 @@
SABnzbd 1.2.1
SABnzbd 2.3.6
-------------------------------------------------------------------------------
0) LICENSE
-------------------------------------------------------------------------------
(c) Copyright 2007-2017 by "The SABnzbd-team" <team@sabnzbd.org>
(c) Copyright 2007-2018 by "The SABnzbd-team" <team@sabnzbd.org>
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
@@ -21,67 +21,63 @@ along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
-------------------------------------------------------------------------------
1) INSTALL with the Win32 installer
1) INSTALL with the Windows installer
-------------------------------------------------------------------------------
Just run the downloaded EXE file and the installer will start.
It's just a simple standard installer.
After installation, find the SABnzbd program in the Start menu and start it.
Within 5-10 seconds your web browser will start and show the user interface.
Within a few seconds your web browser will start and show the user interface.
Use the "Help" button in the web-interface to be directed to the Help Wiki.
-------------------------------------------------------------------------------
2) INSTALL pre-built Win32 binaries
2) INSTALL pre-built Windows binaries
-------------------------------------------------------------------------------
Unzip pre-built version to any folder of your liking.
Start the SABnzbd.exe program.
Within 5-10 seconds your web browser will start and show the user interface.
Within a few seconds your web browser will start and show the user interface.
Use the "Help" button in the web-interface to be directed to the Help Wiki.
-------------------------------------------------------------------------------
3) INSTALL pre-built OSX binaries
3) INSTALL pre-built macOS binaries
-------------------------------------------------------------------------------
Download the DMG file, mount and drag the SABnzbd icon to Programs.
Just like you do with so many apps.
Make sure you pick the right folder, depending on your OSX version.
-------------------------------------------------------------------------------
4) INSTALL with only sources
-------------------------------------------------------------------------------
Specific guides to install from source are available for Windows and macOS:
https://sabnzbd.org/wiki/installation/install-macos
https://sabnzbd.org/wiki/installation/install-from-source-windows
You need to have Python installed plus some non-standard Python modules
and a few tools.
Unix/Linux/OSX
All platforms
Python-2.7.latest http://www.python.org (2.7.9+ recommended)
OSX Mavericks or newer
Apple Python 2.7 Included in OSX (default)
Windows
Python-2.7.latest http://www.python.org (2.7.9+ recommended)
PyWin32 use "pip install pypiwin32"
subprocessww use "pip install subprocessww"
Essential modules
cheetah-2.0.1+ use "pip install cheetah"
par2cmdline >= 0.4 http://parchive.sourceforge.net/
Note: https://sabnzbd.org/wiki/configuration/1.2/switches#par2cmdline
par2cmdline >= 0.4 https://github.com/Parchive/par2cmdline/releases
See also: https://sabnzbd.org/wiki/installation/multicore-par2
unrar >= 5.00+ http://www.rarlab.com/rar_add.htm
openssl >= 1.0.0 http://www.openssl.org/
Optional modules
unzip >= 6.00 http://www.info-zip.org/
7zip >= 9.20 http://www.7zip.org/
yenc module >= 0.4 use "pip install yenc"
https://sabnzbd.org/wiki/installation/yenc-0.4_py2.7.rar (Win32-only)
openssl => 1.0.0 http://www.openssl.org/
v0.9.8 will work, but limits certificate validation
sabyenc == 3.3.1 use "pip install sabyenc"
More information: https://sabnzbd.org/sabyenc
cryptography >= 1.0 use "pip install cryptography"
Enables certificate generation and detection of encrypted RAR-files
Optional modules Unix/Linux/OSX
Optional modules Linux
pynotify Should be part of GTK for Python support on Debian/Ubuntu
If not, you cannot use the NotifyOSD feature.
python-dbus Enable option to Shutdown/Restart/Standby PC on queue finish.
@@ -91,17 +87,17 @@ Embedded modules (preferably use the included version)
Unpack the ZIP-file containing the SABnzbd sources to any folder of your liking.
If you want multiple languages, you need to compile the translations.
Start this from a shell terminal (or command prompt):
python tools/make_mo.py
Start this from a shell terminal (or command prompt):
python SABnzbd.py
python -OO SABnzbd.py
Within 5-10 seconds your web browser will start and show the user interface.
Within a few seconds your web browser will start and show the user interface.
Use the "Help" button in the web-interface to be directed to the Help Wiki.
-------------------------------------------------------------------------------
5) TROUBLESHOOTING
-------------------------------------------------------------------------------
@@ -116,7 +112,7 @@ or
You may of course try other port numbers too.
For troubleshooting you can use the program SABnzbd-console.exe.
For troubleshooting on Windows you can use the program SABnzbd-console.exe.
This will show a black window where logging information will be shown. This
may help you solve problems easier.
@@ -124,14 +120,13 @@ may help you solve problems easier.
6) MORE INFORMATION
-------------------------------------------------------------------------------
Visit the WIKI site:
Visit our wiki:
https://sabnzbd.org/wiki/
-------------------------------------------------------------------------------
7) CREDITS
-------------------------------------------------------------------------------
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

@@ -9,28 +9,18 @@
- When par2 or unrar hang up, never just stop SABnzbd.
Instead use your operating system's task manager to stop the par2 or unrar program.
Forcing SABnzbd to quit may damage your queues.
Windows-only:
If you keep having trouble with par2 multicore you can disable it
in Config->Switches.
This will force the use of the old and tried, but slower par2cmdline program.
- A bug in Windows 7 may cause severe memory leaks when you use SABnzbd in
combination with some virus scanners and firewalls.
Install this hotfix:
Description: http://support.microsoft.com/kb/979223/en-us
Download location: http://support.microsoft.com/hotfix/KBHotfix.aspx?kbnum=979223&kbln=en-us
- Some Usenet servers have intermittent login (or other) problems.
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.2/special
See: https://sabnzbd.org/wiki/configuration/2.3/special
- 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.2/special
See: https://sabnzbd.org/wiki/configuration/2.3/special
- 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 +31,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.2/special
See: https://sabnzbd.org/wiki/configuration/2.3/special
- The "Watched Folder" sometimes fails to delete the NZB files it has
processed. This happens when other software still accesses these files.
@@ -51,6 +41,10 @@
- Memory usage can sometimes have high peaks. This makes using SABnzbd on very low
memory systems (e.g. a NAS device or a router) a challenge.
In particular on Synology (SynoCommunity) the device may report that SABnzbd is using
a lot of memory even when idle. In this case the memory is usually not actually used by
SABnzbd and will be available if required by other apps or the system. More information
can be found in the discussion here: https://github.com/SynoCommunity/spksrc/issues/2856
- SABnzbd is not compatible with some software firewall versions.
The Microsoft Windows Firewall works fine, but remember to tell this
@@ -72,13 +66,7 @@
Config->Special->wait_for_dfolder to 1.
SABnzbd will appear to hang until the drive is mounted.
- On some operating systems it looks like there is a problem with one of the standard Python libraries.
It is possible that you get errors about saving admin files and even unexplained crashes.
If so, you can enable the option for the alternative library.
It has the same functionality, but is slower.
We've had reports about this issue on non-mainstream Linux platforms.
- OpenElec
- 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.2/special
- If you experience speed-drops to KB/s when using a VPN, try setting the number of connections
to your servers to a total of 7. There is a CPU-usage reduction feature in SABnzbd that
gets confused by the way some VPN's handle the state of a connection. Below 8 connections
this feature is not active.

View File

@@ -1,4 +1,4 @@
(c) Copyright 2007-2017 by "The SABnzbd-team" <team@sabnzbd.org>
(c) Copyright 2007-2018 by "The SABnzbd-team" <team@sabnzbd.org>
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License

View File

@@ -1,8 +1,8 @@
Metadata-Version: 1.0
Name: SABnzbd
Version: 1.2.2
Summary: SABnzbd-1.2.2
Home-page: http://sabnzbd.org
Version: 2.3.6
Summary: SABnzbd-2.3.6
Home-page: https://sabnzbd.org
Author: The SABnzbd Team
Author-email: team@sabnzbd.org
License: GNU General Public License 2 (GPL2 or later)

View File

@@ -1,40 +1,32 @@
SABnzbd - The automated Usenet download tool
============================================
This Unicode release is not compatible with 0.7.x queues!
There is also an issue with upgrading of the "sabnzbd.ini" file.
Make sure that you have a backup!
Saved queues may not be compatible after updates.
----
[![Average time to resolve an issue](https://isitmaintained.com/badge/resolution/sabnzbd/sabnzbd.svg)](https://isitmaintained.com/project/sabnzbd/sabnzbd "Average time to resolve an issue")
[![Travis CI](https://travis-ci.org/sabnzbd/sabnzbd.svg?branch=develop)](https://travis-ci.org/sabnzbd/sabnzbd)
[![AppVeryor](https://ci.appveyor.com/api/projects/status/github/sabnzbd/sabnzbd?svg=true&branch=develop)](https://ci.appveyor.com/project/Safihre/sabnzbd)
[![Snap Status](https://build.snapcraft.io/badge/sabnzbd/sabnzbd.svg)](https://snapcraft.io/sabnzbd)
[![License](https://img.shields.io/badge/license-GPL%20v2-blue.svg)](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html)
SABnzbd is an Open Source Binary Newsreader written in Python.
It's totally free, incredibly easy to use, and works practically everywhere.
SABnzbd makes Usenet as simple and streamlined as possible by automating everything we can. All you have to do is add an .nzb. SABnzbd takes over from there, where it will be automatically downloaded, verified, repaired, extracted and filed away with zero human interaction.
If you want to know more you can head over to our website: http://sabnzbd.org.
SABnzbd makes Usenet as simple and streamlined as possible by automating everything we can. All you have to do is add an `.nzb`. SABnzbd takes over from there, where it will be automatically downloaded, verified, repaired, extracted and filed away with zero human interaction.
If you want to know more you can head over to our website: https://sabnzbd.org.
## Resolving Dependencies
SABnzbd has a good deal of dependencies you'll need before you can get running. If you've previously run SABnzbd from one of the various Linux packages floating around (Ubuntu, Debian, Fedora, etc), then you likely already have all the needed dependencies. If not, here's what you're looking for:
SABnzbd has a good deal of dependencies you'll need before you can get running. If you've previously run SABnzbd from one of the various Linux packages, then you likely already have all the needed dependencies. If not, here's what you're looking for:
- `python` (only 2.7.x and higher, but not 3.x.x)
- `python-cheetah`
- `python-support`
- `par2` (Multi-threaded par2 installation guide can be found [here](https://forums.sabnzbd.org/viewtopic.php?f=16&t=18793#p99702))
- `par2` (Multi-threaded par2 installation guide can be found [here](https://sabnzbd.org/wiki/installation/multicore-par2))
- `unrar` (Make sure you get the "official" non-free version of unrar)
- `sabyenc` (installation guide can be found [here](https://sabnzbd.org/sabyenc))
Optional:
- `python-cryptography` (enables certificate generation and detection of encrypted RAR-files during download)
- `python-yenc`
- `python-dbus` (enable option to Shutdown/Restart/Standby PC on queue finish)
- `7zip`
- `unzip`
Your package manager should supply these. If not, we've got links in our more in-depth [installation guide](https://github.com/sabnzbd/sabnzbd/blob/master/INSTALL.txt).
@@ -43,13 +35,13 @@ Your package manager should supply these. If not, we've got links in our more in
Once you've sorted out all the dependencies, simply run:
```
python SABnzbd.py
python -OO SABnzbd.py
```
Or, if you want to run in the background:
```
python SABnzbd.py -d -f /path/to/sabnzbd.ini
python -OO SABnzbd.py -d -f /path/to/sabnzbd.ini
```
If you want multi-language support, run:
@@ -68,7 +60,7 @@ Basically:
- `develop` is the target for integration and is **not** intended for end-users.
- `1.1.x` is a release and maintenance branch for 1.1.x (1.1.0 -> 1.1.1 -> 1.1.2) and is **not** intended for end-users.
- `feature/my_feature` is a temporary feature branch based on `develop`.
- `hotfix/my_hotfix` is an optional temporary branch for bugfix(es) based on `develop`.
- `bugfix/my_bugfix` is an optional temporary branch for bugfix(es) based on `develop`.
Conditions:
- Merging of a stable release into `master` will be simple: the release branch is always right.

View File

@@ -1,64 +1,47 @@
Release Notes - SABnzbd 1.2.2
==============================================
Release Notes - SABnzbd 2.3.6
=========================================================
## Bug fix in 1.2.2
- Windows: job-directory incorrectly passed to PostProcessing-script
## Improvements and bug fixes since 2.3.5
- New option require_modern_tls forces TLSv1.2+ for SSL-connections
- RSS source icon on all tabs of feed overview
- RSS source icon now links to feed details page (if available)
- RSS feed URL's with commas would be wrongly escaped
- Common RSS login problems will show more appropriate error
- Added API-call to modify RSS-filters
- Exceeding disk space could result in endless retry-loop
- History Retry All would not retry failed NZB URL-fetches
- API-call to retry a job could result in unexpected error
- Assume correct SSL/certificate setup if test-host was disabled
- The par2-file creator was logged incorrectly
- Linux: Correct supported file extensions of tray icon
- Windows: Update MultiPar to 1.3.0.2
- Windows and macOS: Update UnRar to 5.61
## What's new in 1.2.1
- QuickCheck will perform fast rename of obfuscated posts
- RSS Downloaded page now shows icon to indicate source
- HTML tags are filtered from single-line script output
- New self-signed certificates now list local IP in SAN-list
- Handle jobs on Windows with forbidden names (Con.*, Aux.*,..)
Still looking for help with SABnzbd (Python 3) development!
https://www.reddit.com/r/usenet/comments/918nxv/
## Bug fixes in 1.2.1
- Fix crashing Assembler
- 'Only Download Top of Queue' was broken for a long time
- Cloaked files (RAR within RAR) were not detected anymore
- Incorrectly labeled some downloads as Encrypted
- Passwords were not parsed correctly from filenames
- RSS reading could fail on missing attributes
- Multi-feed RSS will not stop if only 1 feed is not functioning
- Duplicate detection set to Fail would not work for RSS feeds
- Incorrectly marking jobs with folders inside as failed
- Categories were not matched properly if a list of tags was set
- PostProcessing-script was not called on Accept&Fail or Dupe detect
- Support for newer par2cmdline(-mt) versions that need -B parameter
- Some newsservers would timeout when connecting
- More robust detection of execute permissions for scripts
- CPU type reporting on Windows and macOS
- Failed to start with some localhost configs
- Removed some more stalling issues
- Retry rename 3x before falling back to copy during "Moving"
- Catch several SSL errors of the webserver
- Disk-space information is now only checked every 10 seconds
## Translations
- Many translations updated, thanks to our translators!
## About
SABnzbd is an open-source cross-platform binary newsreader.
It simplifies the process of downloading from Usenet dramatically,
thanks to its web-based user interface and advanced
built-in post-processing options that automatically verify, repair,
extract and clean up posts downloaded from Usenet.
(c) Copyright 2007-2017 by "The SABnzbd-team" \<team@sabnzbd.org\>
### IMPORTANT INFORMATION about release 1.x.x
<https://sabnzbd.org/wiki/new-features-and-changes>
### Known problems and solutions
- Read the file "ISSUES.txt"
### Upgrading from 0.7.x and older
## Upgrading from 2.2.x and older
- Finish queue
- Stop SABnzbd
- Install new version
- Start SABnzbd
The organization of the download queue is different from older versions.
1.x.x will not see the existing queue, but you can go to
Status->QueueRepair and "Repair" the old queue.
Also, your sabnzbd.ini file will be upgraded, making it
incompatible with releases older than 0.7.9
## Upgrade notices
- When upgrading from 2.2.0 or older the queue will be converted. Job order,
settings and data will be preserved, but all jobs will be unpaused and
URL's that did not finish fetching before the upgrade will be lost.
- The organization of the download queue is different from 0.7.x releases.
This version will not see the 0.7.x queue, but you can restore the jobs
by going to Status page and using Queue Repair.
## Known problems and solutions
- Read the file "ISSUES.txt"
## About
SABnzbd is an open-source cross-platform binary newsreader.
It simplifies the process of downloading from Usenet dramatically, thanks
to its web-based user interface and advanced built-in post-processing options
that automatically verify, repair, extract and clean up posts downloaded
from Usenet.
(c) Copyright 2007-2018 by "The SABnzbd-team" \<team@sabnzbd.org\>

View File

@@ -1,5 +1,5 @@
#!/usr/bin/python -OO
# Copyright 2008-2017 The SABnzbd-Team <team@sabnzbd.org>
# Copyright 2007-2018 The SABnzbd-Team <team@sabnzbd.org>
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -20,7 +20,6 @@ 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)
import os
import time
import subprocess

View File

@@ -1,5 +1,5 @@
#!/usr/bin/python -OO
# Copyright 2008-2017 The SABnzbd-Team <team@sabnzbd.org>
# Copyright 2007-2018 The SABnzbd-Team <team@sabnzbd.org>
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -32,17 +32,19 @@ except:
import logging
import logging.handlers
import traceback
import os
import getopt
import signal
import socket
import platform
import ssl
import time
import re
try:
import Cheetah
if Cheetah.Version[0] != '2':
if Cheetah.Version[0] < '2':
raise ValueError
except ValueError:
print "Sorry, requires Python module Cheetah 2.0rc7 or higher."
@@ -56,8 +58,6 @@ if [int(n) for n in cherrypy.__version__.split('.')] < [8, 1, 2]:
print 'Sorry, requires Python module Cherrypy 8.1.2+ (use the included version)'
sys.exit(1)
from cherrypy import _cpserver
SQLITE_DLL = True
try:
from sqlite3 import version as sqlite3_version
@@ -87,11 +87,11 @@ import sabnzbd.interface
from sabnzbd.constants import *
import sabnzbd.newsunpack
from sabnzbd.misc import real_path, \
check_latest_version, exit_sab, \
check_latest_version, exit_sab, get_from_url, \
split_host, get_ext, create_https_certificates, \
windows_variant, ip_extract, set_serv_parms, get_serv_parms, globber_full
from sabnzbd.panic import panic_tmpl, panic_port, panic_host, panic_fwall, \
panic_sqlite, panic, launch_a_browser, panic_xport
from sabnzbd.panic import panic_tmpl, panic_port, panic_host, \
panic_sqlite, panic, launch_a_browser
import sabnzbd.scheduler as scheduler
import sabnzbd.config as config
import sabnzbd.cfg
@@ -99,7 +99,6 @@ import sabnzbd.downloader
from sabnzbd.encoding import unicoder, deunicode
import sabnzbd.notifier as notifier
import sabnzbd.zconfig
import sabnzbd.utils.sslinfo
from threading import Thread
@@ -130,24 +129,6 @@ def guard_loglevel():
LOG_FLAG = True
class FilterCP3:
# Filter out all CherryPy3-Access logging that we receive,
# because we have the root logger
def __init__(self):
pass
def filter(self, record):
_cplogging = record.module == '_cplogging'
# Python2.4 fix
# record has no attribute called funcName under python 2.4
if hasattr(record, 'funcName'):
access = record.funcName == 'access'
else:
access = True
return not (_cplogging and access)
class guiHandler(logging.Handler):
""" Logging handler collects the last warnings/errors/exceptions
to be displayed in the web-gui
@@ -170,7 +151,11 @@ class guiHandler(logging.Handler):
# Loose the oldest record
self.store.pop(0)
try:
self.store.append(self.format(record))
# Append traceback, if available
warning = {'type': record.levelname, 'text': record.msg % record.args, 'time': int(time.time())}
if record.exc_info:
warning['text'] = '%s\n%s' % (warning['text'], traceback.format_exc())
self.store.append(warning)
except UnicodeDecodeError:
# Catch elusive Unicode conversion problems
pass
@@ -181,12 +166,6 @@ class guiHandler(logging.Handler):
def count(self):
return len(self.store)
def last(self):
if self.store:
return self.store[len(self.store) - 1]
else:
return ""
def content(self):
""" Return an array with last records """
return self.store
@@ -194,7 +173,7 @@ class guiHandler(logging.Handler):
def print_help():
print
print "Usage: %s [-f <configfile>] <other options>" % sabnzbd.MY_NAME
print "Usage: %s [-f <configfile>] <other options> [NZB (or related) file]" % sabnzbd.MY_NAME
print
print "Options marked [*] are stored in the config file"
print
@@ -202,10 +181,9 @@ def print_help():
print " -f --config-file <ini> Location of config file"
print " -s --server <srv:port> Listen on server:port [*]"
print " -t --templates <templ> Template directory [*]"
print " -2 --template2 <templ> Secondary template dir [*]"
print
print " -l --logging <0..2> Set logging level (-1=off, 0= least, 2= most) [*]"
print " -w --weblogging <0..2> Set cherrypy logging (0= off, 1= on, 2= file-only) [*]"
print " -l --logging <-1..2> Set logging level (-1=off, 0= least, 2= most) [*]"
print " -w --weblogging Enable cherrypy access logging"
print
print " -b --browser <0..1> Auto browser launch (0= off, 1= on) [*]"
if sabnzbd.WIN32:
@@ -215,7 +193,6 @@ def print_help():
print " --pid <path> Create a PID file in the given folder (full path)"
print " --pidfile <path> Create a PID file with the given name (full path)"
print
print " --force Discard web-port timeout (see Wiki!)"
print " -h --help Print this message"
print " -v --version Print version information"
print " -c --clean Remove queue, cache and logs"
@@ -224,17 +201,23 @@ def print_help():
print " --repair-all Try to reconstruct the queue from the incomplete folder"
print " with full data reconstruction"
print " --https <port> Port to use for HTTPS server"
print " --ipv6_hosting <0|1> Listen on IPv6 address [::1] [*]"
print " --no-login Start with username and password reset"
print " --log-all Log all article handling (for developers)"
print " --disable-file-log Logging is only written to console"
print " --console Force console logging for OSX app"
print " --new Run a new instance of SABnzbd"
print " --ipv6_hosting <0|1> Listen on IPv6 address [::1]"
print ""
print "NZB (or related) file:"
print " NZB or compressed NZB file, with extension .nzb, .zip, .rar, .7z, .gz, or .bz2"
print ""
def print_version():
print """
%s-%s
Copyright (C) 2008-2017, The SABnzbd-Team <team@sabnzbd.org>
Copyright (C) 2007-2018, The SABnzbd-Team <team@sabnzbd.org>
SABnzbd comes with ABSOLUTELY NO WARRANTY.
This is free software, and you are welcome to redistribute it
under certain conditions. It is licensed under the
@@ -274,9 +257,7 @@ def Bail_Out(browserhost, cherryport, err=''):
""" Abort program because of CherryPy troubles """
logging.error(T('Failed to start web-interface') + ' : ' + str(err))
if not sabnzbd.DAEMON:
if '13' in err:
panic_xport(browserhost, cherryport)
elif '49' in err:
if '49' in err:
panic_host(browserhost, cherryport)
else:
panic_port(browserhost, cherryport)
@@ -304,9 +285,6 @@ def Web_Template(key, defweb, wdir):
logging.info("Web dir is %s", full_dir)
if not os.path.exists(full_main):
# Temporarily fix that allows missing Config
if defweb == DEF_STDCONFIG:
return ''
# end temp fix
logging.warning(T('Cannot find web template: %s, trying standard template'), full_main)
full_dir = real_path(sabnzbd.DIR_INTERFACES, DEF_STDINTF)
@@ -316,8 +294,6 @@ def Web_Template(key, defweb, wdir):
panic_tmpl(full_dir)
exit_sab(1)
# sabnzbd.lang.install_language(real_path(full_dir, DEF_INT_LANGUAGE), sabnzbd.cfg.language(), wdir)
return real_path(full_dir, "templates")
@@ -428,10 +404,23 @@ def GetProfileInfo(vista_plus):
def print_modules():
""" Log all detected optional or external modules """
if sabnzbd.decoder.HAVE_YENC:
logging.info("_yenc module... found!")
if sabnzbd.decoder.SABYENC_ENABLED:
# Yes, we have SABYenc, and it's the correct version, so it's enabled
logging.info("SABYenc module (v%s)... found!", sabnzbd.decoder.SABYENC_VERSION)
else:
logging.warning(T('_yenc module... NOT found!'))
# Something wrong with SABYenc, so let's determine and print what:
if sabnzbd.decoder.SABYENC_VERSION:
# We have a VERSION, thus a SABYenc module, but it's not the correct version
logging.warning(T("SABYenc disabled: no correct version found! (Found v%s, expecting v%s)") % (sabnzbd.decoder.SABYENC_VERSION, sabnzbd.constants.SABYENC_VERSION_REQUIRED))
else:
# No SABYenc module at all
logging.warning(T("SABYenc module... NOT found! Expecting v%s - https://sabnzbd.org/sabyenc") % sabnzbd.constants.SABYENC_VERSION_REQUIRED)
# No correct SABYenc version or no SABYenc at all, so now we care about old-yEnc
if sabnzbd.decoder.HAVE_YENC:
logging.info("_yenc module... found!")
else:
logging.error(T('_yenc module... NOT found!'))
if sabnzbd.HAVE_CRYPTOGRAPHY:
logging.info('Cryptography module (v%s)... found!', sabnzbd.HAVE_CRYPTOGRAPHY)
@@ -441,16 +430,25 @@ def print_modules():
if sabnzbd.newsunpack.PAR2_COMMAND:
logging.info("par2 binary... found (%s)", sabnzbd.newsunpack.PAR2_COMMAND)
else:
logging.error(T('par2 binary... NOT found!'))
logging.error('%s %s' % (T('par2 binary... NOT found!'), T('Verification and repair will not be possible.')))
if sabnzbd.newsunpack.PAR2C_COMMAND:
logging.info("par2cmdline binary... found (%s)", sabnzbd.newsunpack.PAR2C_COMMAND)
if sabnzbd.newsunpack.MULTIPAR_COMMAND:
logging.info("MultiPar binary... found (%s)", sabnzbd.newsunpack.MULTIPAR_COMMAND)
elif sabnzbd.WIN32:
logging.error('%s %s' % (T('MultiPar binary... NOT found!'), T('Verification and repair will not be possible.')))
if sabnzbd.newsunpack.RAR_COMMAND:
logging.info("UNRAR binary... found (%s)", sabnzbd.newsunpack.RAR_COMMAND)
# Report problematic unrar
if sabnzbd.newsunpack.RAR_PROBLEM and not sabnzbd.cfg.ignore_wrong_unrar():
have_str = '%.2f' % (float(sabnzbd.newsunpack.RAR_VERSION) / 100)
want_str = '%.2f' % (float(sabnzbd.constants.REC_RAR_VERSION) / 100)
logging.warning(T('Your UNRAR version is %s, we recommend version %s or higher.<br />') % (have_str, want_str))
elif not (sabnzbd.WIN32 or sabnzbd.DARWIN):
logging.info('UNRAR binary version %.2f', (float(sabnzbd.newsunpack.RAR_VERSION) / 100))
else:
logging.warning(T('unrar binary... NOT found'))
logging.error('%s %s' % (T('unrar binary... NOT found'), T('Downloads will not unpacked.')))
if sabnzbd.newsunpack.ZIP_COMMAND:
logging.info("unzip binary... found (%s)", sabnzbd.newsunpack.ZIP_COMMAND)
@@ -504,7 +502,7 @@ def all_localhosts():
def check_resolve(host):
""" Return True if 'host' resolves """
try:
dummy = socket.getaddrinfo(host, None)
socket.getaddrinfo(host, None)
except:
# Does not resolve
return False
@@ -600,7 +598,7 @@ def get_webhost(cherryhost, cherryport, https_port):
cherryhost = cherryhost.strip('[]')
else:
try:
info = socket.getaddrinfo(cherryhost, None)
socket.getaddrinfo(cherryhost, None)
except:
cherryhost = cherryhost.strip('[]')
@@ -651,7 +649,7 @@ def attach_server(host, port, cert=None, key=None, chain=None):
http_server = cherrypy._cpserver.Server()
http_server.bind_addr = (host, port)
if cert and key:
http_server.ssl_provider = 'builtin'
http_server.ssl_module = 'builtin'
http_server.ssl_certificate = cert
http_server.ssl_private_key = key
http_server.ssl_certificate_chain = chain
@@ -661,12 +659,12 @@ def attach_server(host, port, cert=None, key=None, chain=None):
def is_sabnzbd_running(url):
""" Return True when there's already a SABnzbd instance running. """
try:
url = '%s&mode=version' % (url)
url = '%s&mode=version' % url
# Do this without certificate verification, few installations will have that
prev = sabnzbd.set_https_verification(False)
ver = sabnzbd.newsunpack.get_from_url(url)
ver = get_from_url(url)
sabnzbd.set_https_verification(prev)
return (ver and (re.search(r'\d+\.\d+\.', ver) or ver.strip() == sabnzbd.__version__))
return ver and (re.search(r'\d+\.\d+\.', ver) or ver.strip() == sabnzbd.__version__)
except:
return False
@@ -730,25 +728,7 @@ def evaluate_inipath(path):
return path
def cherrypy_logging(log_path, log_handler):
""" Setup CherryPy logging """
log = cherrypy.log
log.access_file = ''
log.error_file = ''
# Max size of 512KB
maxBytes = getattr(log, "rot_maxBytes", 524288)
# cherrypy.log cherrypy.log.1 cherrypy.log.2
backupCount = getattr(log, "rot_backupCount", 3)
# Make a new RotatingFileHandler for the error log.
fname = getattr(log, "rot_error_file", log_path)
h = log_handler(fname, 'a', maxBytes, backupCount)
h.setLevel(logging.DEBUG)
h.setFormatter(cherrypy._cplogging.logfmt)
log.error_log.addHandler(h)
def commandline_handler(frozen=True):
def commandline_handler():
""" Split win32-service commands are true parameters
Returns:
service, sab_opts, serv_opts, upload_nzbs
@@ -778,10 +758,10 @@ def commandline_handler(frozen=True):
info.extend(sys.argv[slice:])
try:
opts, args = getopt.getopt(info, "phdvncw:l:s:f:t:b:2:",
opts, args = getopt.getopt(info, "phdvncwl:s:f:t:b:2:",
['pause', 'help', 'daemon', 'nobrowser', 'clean', 'logging=',
'weblogging=', 'server=', 'templates', 'ipv6_hosting=',
'template2', 'browser=', 'config-file=', 'force',
'weblogging', 'server=', 'templates', 'ipv6_hosting=',
'template2', 'browser=', 'config-file=', 'force', 'disable-file-log',
'version', 'https=', 'autorestarted', 'repair', 'repair-all',
'log-all', 'no-login', 'pid=', 'new', 'console', 'pidfile=',
# Below Win32 Service options
@@ -800,7 +780,7 @@ def commandline_handler(frozen=True):
if not service:
# Get and remove any NZB file names
for entry in args:
if get_ext(entry) in ('.nzb', '.zip', '.rar', '.gz', '.bz2'):
if get_ext(entry) in VALID_NZB_FILES + VALID_ARCHIVES:
upload_nzbs.append(os.path.abspath(entry))
for opt, arg in opts:
@@ -844,13 +824,11 @@ def main():
cherrypylogging = None
clean_up = False
logging_level = None
no_file_log = False
web_dir = None
web_dir2 = None
vista_plus = False
vista64 = False
force_web = False
win64 = False
repair = 0
api_url = None
no_login = False
sabnzbd.RESTART_ARGS = [sys.argv[0]]
pid_path = None
@@ -879,8 +857,6 @@ def main():
exit_sab(0)
elif opt in ('-t', '--templates'):
web_dir = arg
elif opt in ('-2', '--template2'):
web_dir2 = arg
elif opt in ('-s', '--server'):
(cherryhost, cherryport) = split_host(arg)
elif opt in ('-n', '--nobrowser'):
@@ -888,20 +864,14 @@ def main():
elif opt in ('-b', '--browser'):
try:
autobrowser = bool(int(arg))
except:
except ValueError:
autobrowser = True
elif opt in ('--autorestarted', ):
elif opt == '--autorestarted':
autorestarted = True
elif opt in ('-c', '--clean'):
clean_up = True
elif opt in ('-w', '--weblogging'):
try:
cherrypylogging = int(arg)
except:
cherrypylogging = -1
if cherrypylogging < 0 or cherrypylogging > 2:
print_help()
exit_sab(1)
cherrypylogging = True
elif opt in ('-l', '--logging'):
try:
logging_level = int(arg)
@@ -915,37 +885,36 @@ def main():
exit_sab(0)
elif opt in ('-p', '--pause'):
pause = True
elif opt in ('--force',):
force_web = True
sabnzbd.RESTART_ARGS.append(opt)
elif opt in ('--https',):
elif opt == '--https':
https_port = int(arg)
sabnzbd.RESTART_ARGS.append(opt)
sabnzbd.RESTART_ARGS.append(arg)
elif opt in ('--repair',):
elif opt == '--repair':
repair = 1
pause = True
elif opt in ('--repair-all',):
elif opt == '--repair-all':
repair = 2
pause = True
elif opt in ('--log-all',):
elif opt == '--log-all':
sabnzbd.LOG_ALL = True
elif opt in ('--no-login',):
elif opt == '--disable-file-log':
no_file_log = True
elif opt == '--no-login':
no_login = True
elif opt in ('--pid',):
elif opt == '--pid':
pid_path = arg
sabnzbd.RESTART_ARGS.append(opt)
sabnzbd.RESTART_ARGS.append(arg)
elif opt in ('--pidfile',):
elif opt == '--pidfile':
pid_file = arg
sabnzbd.RESTART_ARGS.append(opt)
sabnzbd.RESTART_ARGS.append(arg)
elif opt in ('--new',):
elif opt == '--new':
new_instance = True
elif opt in ('--console',):
elif opt == '--console':
sabnzbd.RESTART_ARGS.append(opt)
osx_console = True
elif opt in ('--ipv6_hosting',):
elif opt == '--ipv6_hosting':
ipv6_hosting = arg
sabnzbd.MY_FULLNAME = os.path.normpath(os.path.abspath(sabnzbd.MY_FULLNAME))
@@ -984,8 +953,8 @@ def main():
# Detect Windows variant
if sabnzbd.WIN32:
vista_plus, vista64 = windows_variant()
sabnzbd.WIN64 = vista64
vista_plus, win64 = windows_variant()
sabnzbd.WIN64 = win64
if not SQLITE_DLL:
panic_sqlite(sabnzbd.MY_FULLNAME)
@@ -1035,14 +1004,14 @@ def main():
if sabnzbd.DAEMON:
if enable_https and https_port:
try:
cherrypy.process.servers.check_port(cherryhost, https_port, timeout=0.025)
except IOError, error:
cherrypy.process.servers.check_port(cherryhost, https_port, timeout=0.05)
except IOError:
Bail_Out(browserhost, cherryport)
except:
Bail_Out(browserhost, cherryport, '49')
try:
cherrypy.process.servers.check_port(cherryhost, cherryport, timeout=0.025)
except IOError, error:
cherrypy.process.servers.check_port(cherryhost, cherryport, timeout=0.05)
except IOError:
Bail_Out(browserhost, cherryport)
except:
Bail_Out(browserhost, cherryport, '49')
@@ -1054,63 +1023,63 @@ def main():
if url and check_for_sabnzbd(url, upload_nzbs, autobrowser):
exit_sab(0)
# If an instance of sabnzbd(same version) is already running on this port, launch the browser
# If another program or sabnzbd version is on this port, try 10 other ports going up in a step of 5
# If 'Port is not bound' (firewall) do not do anything (let the script further down deal with that).
notify_port_change = False
# SSL
if enable_https:
port = https_port or cherryport
try:
cherrypy.process.servers.check_port(browserhost, port, timeout=0.025)
cherrypy.process.servers.check_port(browserhost, port, timeout=0.05)
except IOError, error:
if str(error) == 'Port not bound.':
pass
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:
notify_port_change = True
# Save the new port
if https_port:
https_port = newport
sabnzbd.cfg.https_port.set(newport)
else:
# In case HTTPS == HTTP port
http_port = newport
sabnzbd.cfg.port.set(newport)
url = 'https://%s:%s%s/api?' % (browserhost, port, sabnzbd.cfg.url_base())
if new_instance or not check_for_sabnzbd(url, upload_nzbs, autobrowser):
# Bail out if we have fixed our ports after first start-up
if sabnzbd.cfg.fixed_ports():
Bail_Out(browserhost, cherryport)
# Find free port to bind
newport = find_free_port(browserhost, port)
if newport > 0:
# Save the new port
if https_port:
https_port = newport
sabnzbd.cfg.https_port.set(newport)
else:
# In case HTTPS == HTTP port
cherryport = newport
sabnzbd.cfg.cherryport.set(newport)
except:
# Something else wrong, probably badly specified host
Bail_Out(browserhost, cherryport, '49')
# NonSSL check if there's no HTTPS or we only use 1 port
if not (enable_https and not https_port):
try:
cherrypy.process.servers.check_port(browserhost, cherryport, timeout=0.025)
cherrypy.process.servers.check_port(browserhost, cherryport, timeout=0.05)
except IOError, error:
if str(error) == 'Port not bound.':
pass
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)
notify_port_change = True
cherryport = port
url = 'http://%s:%s%s/api?' % (browserhost, cherryport, sabnzbd.cfg.url_base())
if new_instance or not check_for_sabnzbd(url, upload_nzbs, autobrowser):
# Bail out if we have fixed our ports after first start-up
if sabnzbd.cfg.fixed_ports():
Bail_Out(browserhost, cherryport)
# Find free port to bind
port = find_free_port(browserhost, cherryport)
if port > 0:
sabnzbd.cfg.cherryport.set(port)
cherryport = port
except:
# Something else wrong, probably badly specified host
Bail_Out(browserhost, cherryport, '49')
if logging_level is None:
logging_level = sabnzbd.cfg.log_level()
else:
sabnzbd.cfg.log_level.set(logging_level)
# We found a port, now we never check again
sabnzbd.cfg.fixed_ports.set(True)
# Logging-checks
logdir = sabnzbd.cfg.log_dir.get_path()
if fork and not logdir:
print "Error:"
@@ -1129,20 +1098,24 @@ def main():
# Prevent the logger from raising exceptions
# primarily to reduce the fallout of Python issue 4749
logging.raiseExceptions = 0
# Log-related constants we always need
if logging_level is None:
logging_level = sabnzbd.cfg.log_level()
else:
sabnzbd.cfg.log_level.set(logging_level)
sabnzbd.LOGFILE = os.path.join(logdir, DEF_LOG_FILE)
logformat = '%(asctime)s::%(levelname)s::[%(module)s:%(lineno)d] %(message)s'
logger.setLevel(LOGLEVELS[logging_level + 1])
try:
rollover_log = logging.handlers.RotatingFileHandler(
sabnzbd.LOGFILE, 'a+',
sabnzbd.cfg.log_size.get_int(),
sabnzbd.cfg.log_backups())
logformat = '%(asctime)s::%(levelname)s::[%(module)s:%(lineno)d] %(message)s'
rollover_log.setFormatter(logging.Formatter(logformat))
rollover_log.addFilter(FilterCP3())
sabnzbd.LOGHANDLER = rollover_log
logger.addHandler(rollover_log)
logger.setLevel(LOGLEVELS[logging_level + 1])
if not no_file_log:
rollover_log = logging.handlers.RotatingFileHandler(
sabnzbd.LOGFILE, 'a+',
sabnzbd.cfg.log_size.get_int(),
sabnzbd.cfg.log_backups())
rollover_log.setFormatter(logging.Formatter(logformat))
logger.addHandler(rollover_log)
except IOError:
print "Error:"
@@ -1169,10 +1142,11 @@ def main():
if consoleLogging:
console = logging.StreamHandler()
console.addFilter(FilterCP3())
console.setLevel(LOGLEVELS[logging_level + 1])
console.setFormatter(logging.Formatter(logformat))
logger.addHandler(console)
if no_file_log:
logging.info('Console logging only')
if noConsoleLoggingOSX:
logging.info('Console logging for OSX App disabled')
so = file('/dev/null', 'a+')
@@ -1186,25 +1160,47 @@ def main():
logging.info('Full executable path = %s', sabnzbd.MY_FULLNAME)
if sabnzbd.WIN32:
suffix = ''
if vista_plus:
suffix = ' (=Vista+)'
if vista64:
suffix = ' (=Vista+ x64)'
if win64:
suffix = '(win64)'
try:
logging.info('Platform=%s%s Class=%s', platform.platform(), suffix, os.name)
logging.info('Platform = %s %s', platform.platform(), suffix)
except:
logging.info('Platform=%s <unknown> Class=%s', suffix, os.name)
logging.info('Platform = %s <unknown>', suffix)
else:
logging.info('Platform = %s', os.name)
logging.info('Python-version = %s', sys.version)
logging.info('Arguments = %s', sabnzbd.CMDLINE)
# Find encoding; relevant for unrar activities
try:
logging.info('Preferred encoding = %s', locale.getpreferredencoding())
preferredencoding = locale.getpreferredencoding()
logging.info('Preferred encoding = %s', preferredencoding)
except:
logging.info('Preferred encoding = ERROR')
preferredencoding = ''
# On Linux/FreeBSD/Unix "UTF-8" is strongly, strongly advised:
if not sabnzbd.WIN32 and not sabnzbd.DARWIN and not ('utf' in preferredencoding.lower() and '8' in preferredencoding.lower()):
logging.warning(T("SABnzbd was started with encoding %s, this should be UTF-8. Expect problems with Unicoded file and directory names in downloads.") % preferredencoding)
# SSL Information
logging.info("SSL version = %s", ssl.OPENSSL_VERSION)
# Load (extra) certificates in the binary distributions
if hasattr(sys, "frozen") and (sabnzbd.WIN32 or sabnzbd.DARWIN):
# The certifi package brings the latest certificates on build
# This will cause the create_default_context to load it automatically
os.environ["SSL_CERT_FILE"] = os.path.join(sabnzbd.DIR_PROG, 'cacert.pem')
logging.info('Loaded additional certificates from %s', os.environ["SSL_CERT_FILE"])
# Extra startup info
if sabnzbd.cfg.log_level() > 1:
# List the number of certificates available (can take up to 1.5 seconds)
if sabnzbd.HAVE_SSL_CONTEXT:
ctx = ssl.create_default_context()
logging.debug('Available certificates: %s', repr(ctx.cert_store_stats()))
# Show IPv4/IPv6 address
from sabnzbd.getipaddress import localipv4, publicipv4, ipv6
mylocalipv4 = localipv4()
@@ -1229,30 +1225,17 @@ def main():
from sabnzbd.utils.getperformance import getpystone, getcpu
pystoneperf = getpystone()
if pystoneperf:
logging.debug('CPU Pystone available performance is %s', pystoneperf)
logging.debug('CPU Pystone available performance = %s', pystoneperf)
else:
logging.debug('CPU Pystone available performance could not be calculated')
cpumodel = getcpu() # Linux only
if cpumodel:
logging.debug('CPU model name is %s', cpumodel)
# OSX 10.5 I/O priority setting
if sabnzbd.DARWIN:
logging.info('[osx] IO priority setting')
try:
from ctypes import cdll
libc = cdll.LoadLibrary('/usr/lib/libc.dylib')
boolSetResult = libc.setiopolicy_np(0, 1, 3)
logging.info('[osx] IO priority set to throttle for process scope')
except:
logging.info('[osx] IO priority setting not supported')
logging.debug('CPU model = %s', cpumodel)
logging.info('Read INI file %s', inifile)
if autobrowser is not None:
sabnzbd.cfg.autobrowser.set(autobrowser)
else:
autobrowser = sabnzbd.cfg.autobrowser()
if not sabnzbd.WIN_SERVICE and not getattr(sys, 'frozen', None) == 'macosx_app':
signal.signal(signal.SIGINT, sabnzbd.sig_handler)
@@ -1262,21 +1245,12 @@ def main():
os.chdir(sabnzbd.DIR_PROG)
web_dir = Web_Template(sabnzbd.cfg.web_dir, DEF_STDINTF, fix_webname(web_dir))
web_dir2 = Web_Template(sabnzbd.cfg.web_dir2, '', fix_webname(web_dir2))
web_dirc = Web_Template(None, DEF_STDCONFIG, '')
sabnzbd.WEB_DIR = Web_Template(sabnzbd.cfg.web_dir, DEF_STDINTF, fix_webname(web_dir))
sabnzbd.WEB_DIR_CONFIG = Web_Template(None, DEF_STDCONFIG, '')
sabnzbd.WIZARD_DIR = os.path.join(sabnzbd.DIR_INTERFACES, 'wizard')
wizard_dir = os.path.join(sabnzbd.DIR_INTERFACES, 'wizard')
sabnzbd.WEB_DIR = web_dir
sabnzbd.WEB_DIR2 = web_dir2
sabnzbd.WEB_DIRC = web_dirc
sabnzbd.WIZARD_DIR = wizard_dir
sabnzbd.WEB_COLOR = CheckColor(sabnzbd.cfg.web_color(), web_dir)
sabnzbd.WEB_COLOR = CheckColor(sabnzbd.cfg.web_color(), sabnzbd.WEB_DIR)
sabnzbd.cfg.web_color.set(sabnzbd.WEB_COLOR)
sabnzbd.WEB_COLOR2 = CheckColor(sabnzbd.cfg.web_color2(), web_dir2)
sabnzbd.cfg.web_color2.set(sabnzbd.WEB_COLOR2)
if fork and not sabnzbd.WIN32:
daemonize()
@@ -1298,29 +1272,9 @@ def main():
# Find external programs
sabnzbd.newsunpack.find_programs(sabnzbd.DIR_PROG)
print_modules()
logging.info("SSL version %s", sabnzbd.utils.sslinfo.ssl_version())
logging.info("SSL supported protocols %s", str(sabnzbd.utils.sslinfo.ssl_protocols_labels()))
cherrylogtoscreen = False
sabnzbd.WEBLOGFILE = None
if cherrypylogging:
if logdir:
sabnzbd.WEBLOGFILE = os.path.join(logdir, DEF_LOG_CHERRY)
# Define our custom logger for cherrypy errors
cherrypy_logging(sabnzbd.WEBLOGFILE, logging.handlers.RotatingFileHandler)
if not fork:
try:
x = sys.stderr.fileno
x = sys.stdout.fileno
if cherrypylogging == 1:
cherrylogtoscreen = True
except:
pass
# HTTPS certificate generation
https_cert = sabnzbd.cfg.https_cert.get_path()
https_key = sabnzbd.cfg.https_key.get_path()
https_chain = sabnzbd.cfg.https_chain.get_path()
@@ -1336,6 +1290,7 @@ def main():
logging.warning(T('Disabled HTTPS because of missing CERT and KEY files'))
enable_https = False
# Starting of the webserver
# Determine if this system has multiple definitions for 'localhost'
hosts = all_localhosts()
multilocal = len(hosts) > 1 and cherryhost in ('localhost', '0.0.0.0')
@@ -1360,7 +1315,7 @@ def main():
cherryport = https_port
elif multilocal:
# Extra HTTPS port for secondary localhost
attach_server(hosts[1], cherryport, https_cert, https_key)
attach_server(hosts[1], cherryport, https_cert, https_key, https_chain)
cherrypy.config.update({'server.ssl_module': 'builtin',
'server.ssl_certificate': https_cert,
@@ -1387,60 +1342,52 @@ def main():
'server.socket_host': cherryhost,
'server.socket_port': cherryport,
'server.shutdown_timeout': 0,
'log.screen': cherrylogtoscreen,
'log.screen': False,
'engine.timeout_monitor.on': False,
'engine.autoreload.on': False,
'tools.encode.on': True,
'tools.gzip.on': True,
'tools.gzip.mime_types': mime_gzip,
'request.show_tracebacks': True,
'checker.check_localhost': bool(consoleLogging),
'error_page.401': sabnzbd.panic.error_page_401,
'error_page.404': sabnzbd.panic.error_page_404
})
forced_mime_types = {'css': 'text/css', 'js': 'application/javascript'}
static = {'tools.staticdir.on': True, 'tools.staticdir.dir': os.path.join(web_dir, 'static'), 'tools.staticdir.content_types': forced_mime_types}
staticcfg = {'tools.staticdir.on': True, 'tools.staticdir.dir': os.path.join(web_dirc, 'staticcfg'), 'tools.staticdir.content_types': forced_mime_types}
wizard_static = {'tools.staticdir.on': True, 'tools.staticdir.dir': os.path.join(wizard_dir, 'static'), 'tools.staticdir.content_types': forced_mime_types}
# Do we want CherryPy Logging? Cannot be done via the config
if cherrypylogging:
sabnzbd.WEBLOGFILE = os.path.join(logdir, DEF_LOG_CHERRY)
cherrypy.log.screen = True
cherrypy.log.access_log.propagate = True
cherrypy.log.access_file = str(sabnzbd.WEBLOGFILE)
else:
cherrypy.log.access_log.propagate = False
appconfig = {'/sabnzbd/api': {'tools.basic_auth.on': False},
'/api': {'tools.basic_auth.on': False},
'/m/api': {'tools.basic_auth.on': False},
'/rss': {'tools.basic_auth.on': False},
'/sabnzbd/rss': {'tools.basic_auth.on': False},
'/m/rss': {'tools.basic_auth.on': False},
'/sabnzbd/shutdown': {'streamResponse': True},
'/sabnzbd/static': static,
# Force mimetypes (OS might overwrite them)
forced_mime_types = {'css': 'text/css', 'js': 'application/javascript'}
static = {'tools.staticdir.on': True, 'tools.staticdir.dir': os.path.join(sabnzbd.WEB_DIR, 'static'), 'tools.staticdir.content_types': forced_mime_types}
staticcfg = {'tools.staticdir.on': True, 'tools.staticdir.dir': os.path.join(sabnzbd.WEB_DIR_CONFIG, 'staticcfg'), 'tools.staticdir.content_types': forced_mime_types}
wizard_static = {'tools.staticdir.on': True, 'tools.staticdir.dir': os.path.join(sabnzbd.WIZARD_DIR, 'static'), 'tools.staticdir.content_types': forced_mime_types}
appconfig = {'/api': {
'tools.basic_auth.on': False,
'tools.response_headers.on': True,
'tools.response_headers.headers': [('Access-Control-Allow-Origin', '*')]
},
'/static': static,
'/sabnzbd/wizard/static': wizard_static,
'/wizard/static': wizard_static,
'/favicon.ico': {'tools.staticfile.on': True, 'tools.staticfile.filename': os.path.join(web_dirc, 'staticcfg', 'ico', 'favicon.ico')},
'/sabnzbd/staticcfg': staticcfg,
'/favicon.ico': {'tools.staticfile.on': True, 'tools.staticfile.filename': os.path.join(sabnzbd.WEB_DIR_CONFIG, 'staticcfg', 'ico', 'favicon.ico')},
'/staticcfg': staticcfg
}
if web_dir2:
static2 = {'tools.staticdir.on': True, 'tools.staticdir.dir': os.path.join(web_dir2, 'static'), 'tools.staticdir.content_types': forced_mime_types}
appconfig['/sabnzbd/m/api'] = {'tools.basic_auth.on': False}
appconfig['/sabnzbd/m/rss'] = {'tools.basic_auth.on': False}
appconfig['/sabnzbd/m/shutdown'] = {'streamResponse': True}
appconfig['/sabnzbd/m/static'] = static2
appconfig['/m/static'] = static2
appconfig['/sabnzbd/m/wizard/static'] = wizard_static
appconfig['/m/wizard/static'] = wizard_static
appconfig['/sabnzbd/m/staticcfg'] = staticcfg
appconfig['/m/staticcfg'] = staticcfg
login_page = sabnzbd.interface.MainPage(web_dir, '/', web_dir2, '/m/', web_dirc, first=2)
cherrypy.tree.mount(login_page, '/', config=appconfig)
# Make available from both URLs
main_page = sabnzbd.interface.MainPage()
cherrypy.tree.mount(main_page, '/', config=appconfig)
cherrypy.tree.mount(main_page, sabnzbd.cfg.url_base(), config=appconfig)
# Set authentication for CherryPy
sabnzbd.interface.set_auth(cherrypy.config)
# Notify if port was changed
if notify_port_change:
logging.warning(T('Could not bind to configured port. Port changed to %s') % cherryport)
logging.info('Starting web-interface on %s:%s', cherryhost, cherryport)
sabnzbd.cfg.log_level.callback(guard_loglevel)
@@ -1453,15 +1400,15 @@ def main():
# Wait for server to become ready
cherrypy.engine.wait(cherrypy.process.wspbus.states.STARTED)
sabnzbd.zconfig.set_bonjour(cherryhost, cherryport)
# Window Service support
mail = None
if sabnzbd.WIN32:
if enable_https:
mode = 's'
else:
mode = ''
api_url = 'http%s://%s:%s/sabnzbd/api?apikey=%s' % (mode, browserhost, cherryport, sabnzbd.cfg.api_key())
api_url = 'http%s://%s:%s%s/api?apikey=%s' % (mode, browserhost, cherryport, sabnzbd.cfg.url_base(), sabnzbd.cfg.api_key())
if sabnzbd.WIN_SERVICE:
mail = MailSlot()
@@ -1494,9 +1441,9 @@ def main():
# Set URL for browser
if enable_https:
browser_url = "https://%s:%s/sabnzbd" % (browserhost, cherryport)
browser_url = "https://%s:%s%s" % (browserhost, cherryport, sabnzbd.cfg.url_base())
else:
browser_url = "http://%s:%s/sabnzbd" % (browserhost, cherryport)
browser_url = "http://%s:%s%s" % (browserhost, cherryport, sabnzbd.cfg.url_base())
sabnzbd.BROWSER_URL = browser_url
if not autorestarted:
@@ -1510,6 +1457,13 @@ def main():
check_latest_version()
autorestarted = False
# ZeroConfig/Bonjour needs a ip. Lets try to find it.
try:
z_host = socket.gethostbyname(socket.gethostname())
except socket.gaierror:
z_host = cherryhost
sabnzbd.zconfig.set_bonjour(z_host, cherryport)
# Have to keep this running, otherwise logging will terminate
timer = 0
while not sabnzbd.SABSTOP:
@@ -1567,9 +1521,7 @@ def main():
# Or special restart cases like Mac and WindowsService
if sabnzbd.TRIGGER_RESTART:
# Shutdown
cherrypy.engine.exit()
sabnzbd.halt()
sabnzbd.SABSTOP = True
sabnzbd.shutdown_program()
if sabnzbd.downloader.Downloader.do.paused:
sabnzbd.RESTART_ARGS.append('-p')
@@ -1646,7 +1598,7 @@ if sabnzbd.WIN32:
win32serviceutil.ServiceFramework.__init__(self, args)
self.hWaitStop = win32event.CreateEvent(None, 0, 0, None)
self.overlapped = pywintypes.OVERLAPPED() # @UndefinedVariable
self.overlapped = pywintypes.OVERLAPPED()
self.overlapped.hEvent = win32event.CreateEvent(None, 0, 0, None)
sabnzbd.WIN_SERVICE = self
@@ -1758,9 +1710,8 @@ if __name__ == '__main__':
main()
elif getattr(sys, 'frozen', None) == 'macosx_app':
# OSX binary
try:
# OSX binary runner
from PyObjCTools import AppHelper
from sabnzbd.osxmenu import SABnzbdDelegate
@@ -1777,17 +1728,13 @@ if __name__ == '__main__':
def stop(self):
logging.info('[osx] sabApp Quit - stopping main thread ')
sabnzbd.halt()
cherrypy.engine.exit()
sabnzbd.SABSTOP = True
sabnzbd.shutdown_program()
logging.info('[osx] sabApp Quit - main thread stopped')
sabApp = startApp()
sabApp.start()
AppHelper.runEventLoop()
except:
main()
else:
main()

14
appveyor.yml Normal file
View File

@@ -0,0 +1,14 @@
environment:
SAB_NEWSSERVER_HOST:
secure: UNnTfVHDugC9amTucdTRyxe8RZfVBLYfI1EOTaDUjNM=
SAB_NEWSSERVER_USER:
secure: npe0D4TiEzXMUVMCH3+SHA==
SAB_NEWSSERVER_PASSWORD:
secure: 28COv3RG+KAnBLxIrR1EDw==
install:
- pip install --upgrade -r tests/requirements.txt
- pip install pypiwin32 subprocessww
build_script:
- python ./tests/test_functional.py

View File

@@ -214,18 +214,7 @@ class HTTPRedirect(CherryPyException):
if isinstance(urls, text_or_bytes):
urls = [urls]
abs_urls = []
for url in urls:
url = tonative(url, encoding or self.encoding)
# Note that urljoin will "do the right thing" whether url is:
# 1. a complete URL with host (e.g. "http://www.example.com/test")
# 2. a URL relative to root (e.g. "/dummy")
# 3. a URL relative to the current path
# Note that any query string in cherrypy.request is discarded.
url = _urljoin(cherrypy.url(), url)
abs_urls.append(url)
self.urls = abs_urls
self.urls = [tonative(url, encoding or self.encoding) for url in urls]
# RFC 2616 indicates a 301 response code fits our goal; however,
# browser support for 301 is quite messy. Do 302/303 instead. See
@@ -241,7 +230,7 @@ class HTTPRedirect(CherryPyException):
raise ValueError('status must be between 300 and 399.')
self.status = status
CherryPyException.__init__(self, abs_urls, status)
CherryPyException.__init__(self, self.urls, status)
def set_response(self):
"""Modify cherrypy.response status, headers, and body to represent

View File

@@ -596,7 +596,7 @@ class MemcachedSession(Session):
# Wrap all .get and .set operations in a single lock.
mc_lock = threading.RLock()
# This is a seperate set of locks per session id.
# This is a separate set of locks per session id.
locks = {}
servers = ['127.0.0.1:11211']

View File

@@ -196,12 +196,13 @@ 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')
if sys.platform == 'darwin':
socket_errors_to_ignore.extend(plat_specific_errors('EPROTOTYPE'))
socket_errors_nonblocking.extend(plat_specific_errors('EPROTOTYPE'))
comma_separated_headers = [
ntob(h) for h in
['Accept', 'Accept-Charset', 'Accept-Encoding',

View File

@@ -85,8 +85,8 @@ class BuiltinSSLAdapter(wsgiserver.SSLAdapter):
# Check if it's one of the known errors
# Errors that are caught by PyOpenSSL, but thrown by built-in ssl
_block_errors = ('unknown protocol', 'unknown ca', 'unknown_ca',
'inappropriate fallback', 'wrong version number',
_block_errors = ('unknown protocol', 'unknown ca', 'unknown_ca', 'unknown error',
'https proxy request', 'inappropriate fallback', 'wrong version number',
'no shared cipher', 'certificate unknown', 'ccs received early')
for error_text in _block_errors:
if error_text in e.args[1].lower():
@@ -98,6 +98,12 @@ class BuiltinSSLAdapter(wsgiserver.SSLAdapter):
# The connection can safely be dropped.
return None, {}
raise
except:
# Temporary fix for https://github.com/cherrypy/cherrypy/issues/1618
e = sys.exc_info()[1]
if e.args == (0, 'Error'):
return None, {}
raise
return s, self.get_environ(s)
# TODO: fill this out more with mod ssl env

View File

@@ -1,16 +0,0 @@
import six
import mock
from cherrypy import wsgiserver
class TestWSGIGateway_u0:
@mock.patch('cherrypy.wsgiserver.WSGIGateway_10.get_environ',
lambda self: {'foo': 'bar'})
def test_decodes_items(self):
req = mock.MagicMock(path=b'/', qs=b'')
gw = wsgiserver.WSGIGateway_u0(req=req)
env = gw.get_environ()
assert env['foo'] == 'bar'
assert isinstance(env['foo'], six.text_type)

View File

@@ -1,509 +0,0 @@
import re
import hashlib
import time
import StringIO
__version__ = '0.8'
#GNTP/<version> <messagetype> <encryptionAlgorithmID>[:<ivValue>][ <keyHashAlgorithmID>:<keyHash>.<salt>]
GNTP_INFO_LINE = re.compile(
'GNTP/(?P<version>\d+\.\d+) (?P<messagetype>REGISTER|NOTIFY|SUBSCRIBE|\-OK|\-ERROR)' +
' (?P<encryptionAlgorithmID>[A-Z0-9]+(:(?P<ivValue>[A-F0-9]+))?) ?' +
'((?P<keyHashAlgorithmID>[A-Z0-9]+):(?P<keyHash>[A-F0-9]+).(?P<salt>[A-F0-9]+))?\r\n',
re.IGNORECASE
)
GNTP_INFO_LINE_SHORT = re.compile(
'GNTP/(?P<version>\d+\.\d+) (?P<messagetype>REGISTER|NOTIFY|SUBSCRIBE|\-OK|\-ERROR)',
re.IGNORECASE
)
GNTP_HEADER = re.compile('([\w-]+):(.+)')
GNTP_EOL = '\r\n'
class BaseError(Exception):
def gntp_error(self):
error = GNTPError(self.errorcode, self.errordesc)
return error.encode()
class ParseError(BaseError):
errorcode = 500
errordesc = 'Error parsing the message'
class AuthError(BaseError):
errorcode = 400
errordesc = 'Error with authorization'
class UnsupportedError(BaseError):
errorcode = 500
errordesc = 'Currently unsupported by gntp.py'
class _GNTPBuffer(StringIO.StringIO):
"""GNTP Buffer class"""
def writefmt(self, message="", *args):
"""Shortcut function for writing GNTP Headers"""
self.write((message % args).encode('utf8', 'replace'))
self.write(GNTP_EOL)
class _GNTPBase(object):
"""Base initilization
:param string messagetype: GNTP Message type
:param string version: GNTP Protocol version
:param string encription: Encryption protocol
"""
def __init__(self, messagetype=None, version='1.0', encryption=None):
self.info = {
'version': version,
'messagetype': messagetype,
'encryptionAlgorithmID': encryption
}
self.headers = {}
self.resources = {}
def __str__(self):
return self.encode()
def _parse_info(self, data):
"""Parse the first line of a GNTP message to get security and other info values
:param string data: GNTP Message
:return dict: Parsed GNTP Info line
"""
match = GNTP_INFO_LINE.match(data)
if not match:
raise ParseError('ERROR_PARSING_INFO_LINE')
info = match.groupdict()
if info['encryptionAlgorithmID'] == 'NONE':
info['encryptionAlgorithmID'] = None
return info
def set_password(self, password, encryptAlgo='MD5'):
"""Set a password for a GNTP Message
:param string password: Null to clear password
:param string encryptAlgo: Supports MD5, SHA1, SHA256, SHA512
"""
hash = {
'MD5': hashlib.md5,
'SHA1': hashlib.sha1,
'SHA256': hashlib.sha256,
'SHA512': hashlib.sha512,
}
self.password = password
self.encryptAlgo = encryptAlgo.upper()
if not password:
self.info['encryptionAlgorithmID'] = None
self.info['keyHashAlgorithm'] = None
return
if not self.encryptAlgo in hash.keys():
raise UnsupportedError('INVALID HASH "%s"' % self.encryptAlgo)
hashfunction = hash.get(self.encryptAlgo)
password = password.encode('utf8')
seed = time.ctime()
salt = hashfunction(seed).hexdigest()
saltHash = hashfunction(seed).digest()
keyBasis = password + saltHash
key = hashfunction(keyBasis).digest()
keyHash = hashfunction(key).hexdigest()
self.info['keyHashAlgorithmID'] = self.encryptAlgo
self.info['keyHash'] = keyHash.upper()
self.info['salt'] = salt.upper()
def _decode_hex(self, value):
"""Helper function to decode hex string to `proper` hex string
:param string value: Human readable hex string
:return string: Hex string
"""
result = ''
for i in range(0, len(value), 2):
tmp = int(value[i:i + 2], 16)
result += chr(tmp)
return result
def _decode_binary(self, rawIdentifier, identifier):
rawIdentifier += '\r\n\r\n'
dataLength = int(identifier['Length'])
pointerStart = self.raw.find(rawIdentifier) + len(rawIdentifier)
pointerEnd = pointerStart + dataLength
data = self.raw[pointerStart:pointerEnd]
if not len(data) == dataLength:
raise ParseError('INVALID_DATA_LENGTH Expected: %s Recieved %s' % (dataLength, len(data)))
return data
def _validate_password(self, password):
"""Validate GNTP Message against stored password"""
self.password = password
if password == None:
raise AuthError('Missing password')
keyHash = self.info.get('keyHash', None)
if keyHash is None and self.password is None:
return True
if keyHash is None:
raise AuthError('Invalid keyHash')
if self.password is None:
raise AuthError('Missing password')
password = self.password.encode('utf8')
saltHash = self._decode_hex(self.info['salt'])
keyBasis = password + saltHash
key = hashlib.md5(keyBasis).digest()
keyHash = hashlib.md5(key).hexdigest()
if not keyHash.upper() == self.info['keyHash'].upper():
raise AuthError('Invalid Hash')
return True
def validate(self):
"""Verify required headers"""
for header in self._requiredHeaders:
if not self.headers.get(header, False):
raise ParseError('Missing Notification Header: ' + header)
def _format_info(self):
"""Generate info line for GNTP Message
:return string:
"""
info = u'GNTP/%s %s' % (
self.info.get('version'),
self.info.get('messagetype'),
)
if self.info.get('encryptionAlgorithmID', None):
info += ' %s:%s' % (
self.info.get('encryptionAlgorithmID'),
self.info.get('ivValue'),
)
else:
info += ' NONE'
if self.info.get('keyHashAlgorithmID', None):
info += ' %s:%s.%s' % (
self.info.get('keyHashAlgorithmID'),
self.info.get('keyHash'),
self.info.get('salt')
)
return info
def _parse_dict(self, data):
"""Helper function to parse blocks of GNTP headers into a dictionary
:param string data:
:return dict:
"""
dict = {}
for line in data.split('\r\n'):
match = GNTP_HEADER.match(line)
if not match:
continue
key = unicode(match.group(1).strip(), 'utf8', 'replace')
val = unicode(match.group(2).strip(), 'utf8', 'replace')
dict[key] = val
return dict
def add_header(self, key, value):
if isinstance(value, unicode):
self.headers[key] = value
else:
self.headers[key] = unicode('%s' % value, 'utf8', 'replace')
def add_resource(self, data):
"""Add binary resource
:param string data: Binary Data
"""
identifier = hashlib.md5(data).hexdigest()
self.resources[identifier] = data
return 'x-growl-resource://%s' % identifier
def decode(self, data, password=None):
"""Decode GNTP Message
:param string data:
"""
self.password = password
self.raw = data
parts = self.raw.split('\r\n\r\n')
self.info = self._parse_info(data)
self.headers = self._parse_dict(parts[0])
def encode(self):
"""Encode a generic GNTP Message
:return string: GNTP Message ready to be sent
"""
buffer = _GNTPBuffer()
buffer.writefmt(self._format_info())
#Headers
for k, v in self.headers.iteritems():
buffer.writefmt('%s: %s', k, v)
buffer.writefmt()
#Resources
for resource, data in self.resources.iteritems():
buffer.writefmt('Identifier: %s', resource)
buffer.writefmt('Length: %d', len(data))
buffer.writefmt()
buffer.write(data)
buffer.writefmt()
buffer.writefmt()
return buffer.getvalue()
class GNTPRegister(_GNTPBase):
"""Represents a GNTP Registration Command
:param string data: (Optional) See decode()
:param string password: (Optional) Password to use while encoding/decoding messages
"""
_requiredHeaders = [
'Application-Name',
'Notifications-Count'
]
_requiredNotificationHeaders = ['Notification-Name']
def __init__(self, data=None, password=None):
_GNTPBase.__init__(self, 'REGISTER')
self.notifications = []
if data:
self.decode(data, password)
else:
self.set_password(password)
self.add_header('Application-Name', 'pygntp')
self.add_header('Notifications-Count', 0)
def validate(self):
'''Validate required headers and validate notification headers'''
for header in self._requiredHeaders:
if not self.headers.get(header, False):
raise ParseError('Missing Registration Header: ' + header)
for notice in self.notifications:
for header in self._requiredNotificationHeaders:
if not notice.get(header, False):
raise ParseError('Missing Notification Header: ' + header)
def decode(self, data, password):
"""Decode existing GNTP Registration message
:param string data: Message to decode
"""
self.raw = data
parts = self.raw.split('\r\n\r\n')
self.info = self._parse_info(data)
self._validate_password(password)
self.headers = self._parse_dict(parts[0])
for i, part in enumerate(parts):
if i == 0:
continue # Skip Header
if part.strip() == '':
continue
notice = self._parse_dict(part)
if notice.get('Notification-Name', False):
self.notifications.append(notice)
elif notice.get('Identifier', False):
notice['Data'] = self._decode_binary(part, notice)
#open('register.png','wblol').write(notice['Data'])
self.resources[notice.get('Identifier')] = notice
def add_notification(self, name, enabled=True):
"""Add new Notification to Registration message
:param string name: Notification Name
:param boolean enabled: Enable this notification by default
"""
notice = {}
notice['Notification-Name'] = u'%s' % name
notice['Notification-Enabled'] = u'%s' % enabled
self.notifications.append(notice)
self.add_header('Notifications-Count', len(self.notifications))
def encode(self):
"""Encode a GNTP Registration Message
:return string: Encoded GNTP Registration message
"""
buffer = _GNTPBuffer()
buffer.writefmt(self._format_info())
#Headers
for k, v in self.headers.iteritems():
buffer.writefmt('%s: %s', k, v)
buffer.writefmt()
#Notifications
if len(self.notifications) > 0:
for notice in self.notifications:
for k, v in notice.iteritems():
buffer.writefmt('%s: %s', k, v)
buffer.writefmt()
#Resources
for resource, data in self.resources.iteritems():
buffer.writefmt('Identifier: %s', resource)
buffer.writefmt('Length: %d', len(data))
buffer.writefmt()
buffer.write(data)
buffer.writefmt()
buffer.writefmt()
return buffer.getvalue()
class GNTPNotice(_GNTPBase):
"""Represents a GNTP Notification Command
:param string data: (Optional) See decode()
:param string app: (Optional) Set Application-Name
:param string name: (Optional) Set Notification-Name
:param string title: (Optional) Set Notification Title
:param string password: (Optional) Password to use while encoding/decoding messages
"""
_requiredHeaders = [
'Application-Name',
'Notification-Name',
'Notification-Title'
]
def __init__(self, data=None, app=None, name=None, title=None, password=None):
_GNTPBase.__init__(self, 'NOTIFY')
if data:
self.decode(data, password)
else:
self.set_password(password)
if app:
self.add_header('Application-Name', app)
if name:
self.add_header('Notification-Name', name)
if title:
self.add_header('Notification-Title', title)
def decode(self, data, password):
"""Decode existing GNTP Notification message
:param string data: Message to decode.
"""
self.raw = data
parts = self.raw.split('\r\n\r\n')
self.info = self._parse_info(data)
self._validate_password(password)
self.headers = self._parse_dict(parts[0])
for i, part in enumerate(parts):
if i == 0:
continue # Skip Header
if part.strip() == '':
continue
notice = self._parse_dict(part)
if notice.get('Identifier', False):
notice['Data'] = self._decode_binary(part, notice)
#open('notice.png','wblol').write(notice['Data'])
self.resources[notice.get('Identifier')] = notice
class GNTPSubscribe(_GNTPBase):
"""Represents a GNTP Subscribe Command
:param string data: (Optional) See decode()
:param string password: (Optional) Password to use while encoding/decoding messages
"""
_requiredHeaders = [
'Subscriber-ID',
'Subscriber-Name',
]
def __init__(self, data=None, password=None):
_GNTPBase.__init__(self, 'SUBSCRIBE')
if data:
self.decode(data, password)
else:
self.set_password(password)
class GNTPOK(_GNTPBase):
"""Represents a GNTP OK Response
:param string data: (Optional) See _GNTPResponse.decode()
:param string action: (Optional) Set type of action the OK Response is for
"""
_requiredHeaders = ['Response-Action']
def __init__(self, data=None, action=None):
_GNTPBase.__init__(self, '-OK')
if data:
self.decode(data)
if action:
self.add_header('Response-Action', action)
class GNTPError(_GNTPBase):
"""Represents a GNTP Error response
:param string data: (Optional) See _GNTPResponse.decode()
:param string errorcode: (Optional) Error code
:param string errordesc: (Optional) Error Description
"""
_requiredHeaders = ['Error-Code', 'Error-Description']
def __init__(self, data=None, errorcode=None, errordesc=None):
_GNTPBase.__init__(self, '-ERROR')
if data:
self.decode(data)
if errorcode:
self.add_header('Error-Code', errorcode)
self.add_header('Error-Description', errordesc)
def error(self):
return (self.headers.get('Error-Code', None),
self.headers.get('Error-Description', None))
def parse_gntp(data, password=None):
"""Attempt to parse a message as a GNTP message
:param string data: Message to be parsed
:param string password: Optional password to be used to verify the message
"""
match = GNTP_INFO_LINE_SHORT.match(data)
if not match:
raise ParseError('INVALID_GNTP_INFO')
info = match.groupdict()
if info['messagetype'] == 'REGISTER':
return GNTPRegister(data, password=password)
elif info['messagetype'] == 'NOTIFY':
return GNTPNotice(data, password=password)
elif info['messagetype'] == 'SUBSCRIBE':
return GNTPSubscribe(data, password=password)
elif info['messagetype'] == '-OK':
return GNTPOK(data)
elif info['messagetype'] == '-ERROR':
return GNTPError(data)
raise ParseError('INVALID_GNTP_MESSAGE')

141
gntp/cli.py Normal file
View File

@@ -0,0 +1,141 @@
# Copyright: 2013 Paul Traylor
# These sources are released under the terms of the MIT license: see LICENSE
import logging
import os
import sys
from optparse import OptionParser, OptionGroup
from gntp.notifier import GrowlNotifier
from gntp.shim import RawConfigParser
from gntp.version import __version__
DEFAULT_CONFIG = os.path.expanduser('~/.gntp')
config = RawConfigParser({
'hostname': 'localhost',
'password': None,
'port': 23053,
})
config.read([DEFAULT_CONFIG])
if not config.has_section('gntp'):
config.add_section('gntp')
class ClientParser(OptionParser):
def __init__(self):
OptionParser.__init__(self, version="%%prog %s" % __version__)
group = OptionGroup(self, "Network Options")
group.add_option("-H", "--host",
dest="host", default=config.get('gntp', 'hostname'),
help="Specify a hostname to which to send a remote notification. [%default]")
group.add_option("--port",
dest="port", default=config.getint('gntp', 'port'), type="int",
help="port to listen on [%default]")
group.add_option("-P", "--password",
dest='password', default=config.get('gntp', 'password'),
help="Network password")
self.add_option_group(group)
group = OptionGroup(self, "Notification Options")
group.add_option("-n", "--name",
dest="app", default='Python GNTP Test Client',
help="Set the name of the application [%default]")
group.add_option("-s", "--sticky",
dest='sticky', default=False, action="store_true",
help="Make the notification sticky [%default]")
group.add_option("--image",
dest="icon", default=None,
help="Icon for notification (URL or /path/to/file)")
group.add_option("-m", "--message",
dest="message", default=None,
help="Sets the message instead of using stdin")
group.add_option("-p", "--priority",
dest="priority", default=0, type="int",
help="-2 to 2 [%default]")
group.add_option("-d", "--identifier",
dest="identifier",
help="Identifier for coalescing")
group.add_option("-t", "--title",
dest="title", default=None,
help="Set the title of the notification [%default]")
group.add_option("-N", "--notification",
dest="name", default='Notification',
help="Set the notification name [%default]")
group.add_option("--callback",
dest="callback",
help="URL callback")
self.add_option_group(group)
# Extra Options
self.add_option('-v', '--verbose',
dest='verbose', default=0, action='count',
help="Verbosity levels")
def parse_args(self, args=None, values=None):
values, args = OptionParser.parse_args(self, args, values)
if values.message is None:
print('Enter a message followed by Ctrl-D')
try:
message = sys.stdin.read()
except KeyboardInterrupt:
exit()
else:
message = values.message
if values.title is None:
values.title = ' '.join(args)
# If we still have an empty title, use the
# first bit of the message as the title
if values.title == '':
values.title = message[:20]
values.verbose = logging.WARNING - values.verbose * 10
return values, message
def main():
(options, message) = ClientParser().parse_args()
logging.basicConfig(level=options.verbose)
if not os.path.exists(DEFAULT_CONFIG):
logging.info('No config read found at %s', DEFAULT_CONFIG)
growl = GrowlNotifier(
applicationName=options.app,
notifications=[options.name],
defaultNotifications=[options.name],
hostname=options.host,
password=options.password,
port=options.port,
)
result = growl.register()
if result is not True:
exit(result)
# This would likely be better placed within the growl notifier
# class but until I make _checkIcon smarter this is "easier"
if options.icon and growl._checkIcon(options.icon) is False:
logging.info('Loading image %s', options.icon)
f = open(options.icon, 'rb')
options.icon = f.read()
f.close()
result = growl.notify(
noteType=options.name,
title=options.title,
description=message,
icon=options.icon,
sticky=options.sticky,
priority=options.priority,
callback=options.callback,
identifier=options.identifier,
)
if result is not True:
exit(result)
if __name__ == "__main__":
main()

77
gntp/config.py Normal file
View File

@@ -0,0 +1,77 @@
# Copyright: 2013 Paul Traylor
# These sources are released under the terms of the MIT license: see LICENSE
"""
The gntp.config module is provided as an extended GrowlNotifier object that takes
advantage of the ConfigParser module to allow us to setup some default values
(such as hostname, password, and port) in a more global way to be shared among
programs using gntp
"""
import logging
import os
import gntp.notifier
import gntp.shim
__all__ = [
'mini',
'GrowlNotifier'
]
logger = logging.getLogger(__name__)
class GrowlNotifier(gntp.notifier.GrowlNotifier):
"""
ConfigParser enhanced GrowlNotifier object
For right now, we are only interested in letting users overide certain
values from ~/.gntp
::
[gntp]
hostname = ?
password = ?
port = ?
"""
def __init__(self, *args, **kwargs):
config = gntp.shim.RawConfigParser({
'hostname': kwargs.get('hostname', 'localhost'),
'password': kwargs.get('password'),
'port': kwargs.get('port', 23053),
})
config.read([os.path.expanduser('~/.gntp')])
# If the file does not exist, then there will be no gntp section defined
# and the config.get() lines below will get confused. Since we are not
# saving the config, it should be safe to just add it here so the
# code below doesn't complain
if not config.has_section('gntp'):
logger.info('Error reading ~/.gntp config file')
config.add_section('gntp')
kwargs['password'] = config.get('gntp', 'password')
kwargs['hostname'] = config.get('gntp', 'hostname')
kwargs['port'] = config.getint('gntp', 'port')
super(GrowlNotifier, self).__init__(*args, **kwargs)
def mini(description, **kwargs):
"""Single notification function
Simple notification function in one line. Has only one required parameter
and attempts to use reasonable defaults for everything else
:param string description: Notification message
"""
kwargs['notifierFactory'] = GrowlNotifier
gntp.notifier.mini(description, **kwargs)
if __name__ == '__main__':
# If we're running this module directly we're likely running it as a test
# so extra debugging is useful
logging.basicConfig(level=logging.INFO)
mini('Testing mini notification')

518
gntp/core.py Normal file
View File

@@ -0,0 +1,518 @@
# Copyright: 2013 Paul Traylor
# These sources are released under the terms of the MIT license: see LICENSE
import hashlib
import re
import time
import gntp.shim
import gntp.errors as errors
__all__ = [
'GNTPRegister',
'GNTPNotice',
'GNTPSubscribe',
'GNTPOK',
'GNTPError',
'parse_gntp',
]
#GNTP/<version> <messagetype> <encryptionAlgorithmID>[:<ivValue>][ <keyHashAlgorithmID>:<keyHash>.<salt>]
GNTP_INFO_LINE = re.compile(
'GNTP/(?P<version>\d+\.\d+) (?P<messagetype>REGISTER|NOTIFY|SUBSCRIBE|\-OK|\-ERROR)' +
' (?P<encryptionAlgorithmID>[A-Z0-9]+(:(?P<ivValue>[A-F0-9]+))?) ?' +
'((?P<keyHashAlgorithmID>[A-Z0-9]+):(?P<keyHash>[A-F0-9]+).(?P<salt>[A-F0-9]+))?\r\n',
re.IGNORECASE
)
GNTP_INFO_LINE_SHORT = re.compile(
'GNTP/(?P<version>\d+\.\d+) (?P<messagetype>REGISTER|NOTIFY|SUBSCRIBE|\-OK|\-ERROR)',
re.IGNORECASE
)
GNTP_HEADER = re.compile('([\w-]+):(.+)')
GNTP_EOL = gntp.shim.b('\r\n')
GNTP_SEP = gntp.shim.b(': ')
class _GNTPBuffer(gntp.shim.StringIO):
"""GNTP Buffer class"""
def writeln(self, value=None):
if value:
self.write(gntp.shim.b(value))
self.write(GNTP_EOL)
def writeheader(self, key, value):
if not isinstance(value, str):
value = str(value)
self.write(gntp.shim.b(key))
self.write(GNTP_SEP)
self.write(gntp.shim.b(value))
self.write(GNTP_EOL)
class _GNTPBase(object):
"""Base initilization
:param string messagetype: GNTP Message type
:param string version: GNTP Protocol version
:param string encription: Encryption protocol
"""
def __init__(self, messagetype=None, version='1.0', encryption=None):
self.info = {
'version': version,
'messagetype': messagetype,
'encryptionAlgorithmID': encryption
}
self.hash_algo = {
'MD5': hashlib.md5,
'SHA1': hashlib.sha1,
'SHA256': hashlib.sha256,
'SHA512': hashlib.sha512,
}
self.headers = {}
self.resources = {}
# For Python2 we can just return the bytes as is without worry
# but on Python3 we want to make sure we return the packet as
# a unicode string so that things like logging won't get confused
if gntp.shim.PY2:
def __str__(self):
return self.encode()
else:
def __str__(self):
return gntp.shim.u(self.encode())
def _parse_info(self, data):
"""Parse the first line of a GNTP message to get security and other info values
:param string data: GNTP Message
:return dict: Parsed GNTP Info line
"""
match = GNTP_INFO_LINE.match(data)
if not match:
raise errors.ParseError('ERROR_PARSING_INFO_LINE')
info = match.groupdict()
if info['encryptionAlgorithmID'] == 'NONE':
info['encryptionAlgorithmID'] = None
return info
def set_password(self, password, encryptAlgo='MD5'):
"""Set a password for a GNTP Message
:param string password: Null to clear password
:param string encryptAlgo: Supports MD5, SHA1, SHA256, SHA512
"""
if not password:
self.info['encryptionAlgorithmID'] = None
self.info['keyHashAlgorithm'] = None
return
self.password = gntp.shim.b(password)
self.encryptAlgo = encryptAlgo.upper()
if not self.encryptAlgo in self.hash_algo:
raise errors.UnsupportedError('INVALID HASH "%s"' % self.encryptAlgo)
hashfunction = self.hash_algo.get(self.encryptAlgo)
password = password.encode('utf8')
seed = time.ctime().encode('utf8')
salt = hashfunction(seed).hexdigest()
saltHash = hashfunction(seed).digest()
keyBasis = password + saltHash
key = hashfunction(keyBasis).digest()
keyHash = hashfunction(key).hexdigest()
self.info['keyHashAlgorithmID'] = self.encryptAlgo
self.info['keyHash'] = keyHash.upper()
self.info['salt'] = salt.upper()
def _decode_hex(self, value):
"""Helper function to decode hex string to `proper` hex string
:param string value: Human readable hex string
:return string: Hex string
"""
result = ''
for i in range(0, len(value), 2):
tmp = int(value[i:i + 2], 16)
result += chr(tmp)
return result
def _decode_binary(self, rawIdentifier, identifier):
rawIdentifier += '\r\n\r\n'
dataLength = int(identifier['Length'])
pointerStart = self.raw.find(rawIdentifier) + len(rawIdentifier)
pointerEnd = pointerStart + dataLength
data = self.raw[pointerStart:pointerEnd]
if not len(data) == dataLength:
raise errors.ParseError('INVALID_DATA_LENGTH Expected: %s Recieved %s' % (dataLength, len(data)))
return data
def _validate_password(self, password):
"""Validate GNTP Message against stored password"""
self.password = password
if password is None:
raise errors.AuthError('Missing password')
keyHash = self.info.get('keyHash', None)
if keyHash is None and self.password is None:
return True
if keyHash is None:
raise errors.AuthError('Invalid keyHash')
if self.password is None:
raise errors.AuthError('Missing password')
keyHashAlgorithmID = self.info.get('keyHashAlgorithmID','MD5')
password = self.password.encode('utf8')
saltHash = self._decode_hex(self.info['salt'])
keyBasis = password + saltHash
self.key = self.hash_algo[keyHashAlgorithmID](keyBasis).digest()
keyHash = self.hash_algo[keyHashAlgorithmID](self.key).hexdigest()
if not keyHash.upper() == self.info['keyHash'].upper():
raise errors.AuthError('Invalid Hash')
return True
def validate(self):
"""Verify required headers"""
for header in self._requiredHeaders:
if not self.headers.get(header, False):
raise errors.ParseError('Missing Notification Header: ' + header)
def _format_info(self):
"""Generate info line for GNTP Message
:return string:
"""
info = 'GNTP/%s %s' % (
self.info.get('version'),
self.info.get('messagetype'),
)
if self.info.get('encryptionAlgorithmID', None):
info += ' %s:%s' % (
self.info.get('encryptionAlgorithmID'),
self.info.get('ivValue'),
)
else:
info += ' NONE'
if self.info.get('keyHashAlgorithmID', None):
info += ' %s:%s.%s' % (
self.info.get('keyHashAlgorithmID'),
self.info.get('keyHash'),
self.info.get('salt')
)
return info
def _parse_dict(self, data):
"""Helper function to parse blocks of GNTP headers into a dictionary
:param string data:
:return dict: Dictionary of parsed GNTP Headers
"""
d = {}
for line in data.split('\r\n'):
match = GNTP_HEADER.match(line)
if not match:
continue
key = match.group(1).strip()
val = match.group(2).strip()
d[key] = val
return d
def add_header(self, key, value):
self.headers[key] = value
def add_resource(self, data):
"""Add binary resource
:param string data: Binary Data
"""
data = gntp.shim.b(data)
identifier = hashlib.md5(data).hexdigest()
self.resources[identifier] = data
return 'x-growl-resource://%s' % identifier
def decode(self, data, password=None):
"""Decode GNTP Message
:param string data:
"""
self.password = password
self.raw = gntp.shim.u(data)
parts = self.raw.split('\r\n\r\n')
self.info = self._parse_info(self.raw)
self.headers = self._parse_dict(parts[0])
def encode(self):
"""Encode a generic GNTP Message
:return string: GNTP Message ready to be sent. Returned as a byte string
"""
buff = _GNTPBuffer()
buff.writeln(self._format_info())
#Headers
for k, v in self.headers.items():
buff.writeheader(k, v)
buff.writeln()
#Resources
for resource, data in self.resources.items():
buff.writeheader('Identifier', resource)
buff.writeheader('Length', len(data))
buff.writeln()
buff.write(data)
buff.writeln()
buff.writeln()
return buff.getvalue()
class GNTPRegister(_GNTPBase):
"""Represents a GNTP Registration Command
:param string data: (Optional) See decode()
:param string password: (Optional) Password to use while encoding/decoding messages
"""
_requiredHeaders = [
'Application-Name',
'Notifications-Count'
]
_requiredNotificationHeaders = ['Notification-Name']
def __init__(self, data=None, password=None):
_GNTPBase.__init__(self, 'REGISTER')
self.notifications = []
if data:
self.decode(data, password)
else:
self.set_password(password)
self.add_header('Application-Name', 'pygntp')
self.add_header('Notifications-Count', 0)
def validate(self):
'''Validate required headers and validate notification headers'''
for header in self._requiredHeaders:
if not self.headers.get(header, False):
raise errors.ParseError('Missing Registration Header: ' + header)
for notice in self.notifications:
for header in self._requiredNotificationHeaders:
if not notice.get(header, False):
raise errors.ParseError('Missing Notification Header: ' + header)
def decode(self, data, password):
"""Decode existing GNTP Registration message
:param string data: Message to decode
"""
self.raw = gntp.shim.u(data)
parts = self.raw.split('\r\n\r\n')
self.info = self._parse_info(self.raw)
self._validate_password(password)
self.headers = self._parse_dict(parts[0])
for i, part in enumerate(parts):
if i == 0:
continue # Skip Header
if part.strip() == '':
continue
notice = self._parse_dict(part)
if notice.get('Notification-Name', False):
self.notifications.append(notice)
elif notice.get('Identifier', False):
notice['Data'] = self._decode_binary(part, notice)
#open('register.png','wblol').write(notice['Data'])
self.resources[notice.get('Identifier')] = notice
def add_notification(self, name, enabled=True):
"""Add new Notification to Registration message
:param string name: Notification Name
:param boolean enabled: Enable this notification by default
"""
notice = {}
notice['Notification-Name'] = name
notice['Notification-Enabled'] = enabled
self.notifications.append(notice)
self.add_header('Notifications-Count', len(self.notifications))
def encode(self):
"""Encode a GNTP Registration Message
:return string: Encoded GNTP Registration message. Returned as a byte string
"""
buff = _GNTPBuffer()
buff.writeln(self._format_info())
#Headers
for k, v in self.headers.items():
buff.writeheader(k, v)
buff.writeln()
#Notifications
if len(self.notifications) > 0:
for notice in self.notifications:
for k, v in notice.items():
buff.writeheader(k, v)
buff.writeln()
#Resources
for resource, data in self.resources.items():
buff.writeheader('Identifier', resource)
buff.writeheader('Length', len(data))
buff.writeln()
buff.write(data)
buff.writeln()
buff.writeln()
return buff.getvalue()
class GNTPNotice(_GNTPBase):
"""Represents a GNTP Notification Command
:param string data: (Optional) See decode()
:param string app: (Optional) Set Application-Name
:param string name: (Optional) Set Notification-Name
:param string title: (Optional) Set Notification Title
:param string password: (Optional) Password to use while encoding/decoding messages
"""
_requiredHeaders = [
'Application-Name',
'Notification-Name',
'Notification-Title'
]
def __init__(self, data=None, app=None, name=None, title=None, password=None):
_GNTPBase.__init__(self, 'NOTIFY')
if data:
self.decode(data, password)
else:
self.set_password(password)
if app:
self.add_header('Application-Name', app)
if name:
self.add_header('Notification-Name', name)
if title:
self.add_header('Notification-Title', title)
def decode(self, data, password):
"""Decode existing GNTP Notification message
:param string data: Message to decode.
"""
self.raw = gntp.shim.u(data)
parts = self.raw.split('\r\n\r\n')
self.info = self._parse_info(self.raw)
self._validate_password(password)
self.headers = self._parse_dict(parts[0])
for i, part in enumerate(parts):
if i == 0:
continue # Skip Header
if part.strip() == '':
continue
notice = self._parse_dict(part)
if notice.get('Identifier', False):
notice['Data'] = self._decode_binary(part, notice)
#open('notice.png','wblol').write(notice['Data'])
self.resources[notice.get('Identifier')] = notice
class GNTPSubscribe(_GNTPBase):
"""Represents a GNTP Subscribe Command
:param string data: (Optional) See decode()
:param string password: (Optional) Password to use while encoding/decoding messages
"""
_requiredHeaders = [
'Subscriber-ID',
'Subscriber-Name',
]
def __init__(self, data=None, password=None):
_GNTPBase.__init__(self, 'SUBSCRIBE')
if data:
self.decode(data, password)
else:
self.set_password(password)
class GNTPOK(_GNTPBase):
"""Represents a GNTP OK Response
:param string data: (Optional) See _GNTPResponse.decode()
:param string action: (Optional) Set type of action the OK Response is for
"""
_requiredHeaders = ['Response-Action']
def __init__(self, data=None, action=None):
_GNTPBase.__init__(self, '-OK')
if data:
self.decode(data)
if action:
self.add_header('Response-Action', action)
class GNTPError(_GNTPBase):
"""Represents a GNTP Error response
:param string data: (Optional) See _GNTPResponse.decode()
:param string errorcode: (Optional) Error code
:param string errordesc: (Optional) Error Description
"""
_requiredHeaders = ['Error-Code', 'Error-Description']
def __init__(self, data=None, errorcode=None, errordesc=None):
_GNTPBase.__init__(self, '-ERROR')
if data:
self.decode(data)
if errorcode:
self.add_header('Error-Code', errorcode)
self.add_header('Error-Description', errordesc)
def error(self):
return (self.headers.get('Error-Code', None),
self.headers.get('Error-Description', None))
def parse_gntp(data, password=None):
"""Attempt to parse a message as a GNTP message
:param string data: Message to be parsed
:param string password: Optional password to be used to verify the message
"""
data = gntp.shim.u(data)
match = GNTP_INFO_LINE_SHORT.match(data)
if not match:
raise errors.ParseError('INVALID_GNTP_INFO')
info = match.groupdict()
if info['messagetype'] == 'REGISTER':
return GNTPRegister(data, password=password)
elif info['messagetype'] == 'NOTIFY':
return GNTPNotice(data, password=password)
elif info['messagetype'] == 'SUBSCRIBE':
return GNTPSubscribe(data, password=password)
elif info['messagetype'] == '-OK':
return GNTPOK(data)
elif info['messagetype'] == '-ERROR':
return GNTPError(data)
raise errors.ParseError('INVALID_GNTP_MESSAGE')

25
gntp/errors.py Normal file
View File

@@ -0,0 +1,25 @@
# Copyright: 2013 Paul Traylor
# These sources are released under the terms of the MIT license: see LICENSE
class BaseError(Exception):
pass
class ParseError(BaseError):
errorcode = 500
errordesc = 'Error parsing the message'
class AuthError(BaseError):
errorcode = 400
errordesc = 'Error with authorization'
class UnsupportedError(BaseError):
errorcode = 500
errordesc = 'Currently unsupported by gntp.py'
class NetworkError(BaseError):
errorcode = 500
errordesc = "Error connecting to growl server"

View File

@@ -1,3 +1,6 @@
# Copyright: 2013 Paul Traylor
# These sources are released under the terms of the MIT license: see LICENSE
"""
The gntp.notifier module is provided as a simple way to send notifications
using GNTP
@@ -9,10 +12,15 @@ using GNTP
`Original Python bindings <http://code.google.com/p/growl/source/browse/Bindings/python/Growl.py>`_
"""
import gntp
import socket
import logging
import platform
import socket
import sys
from gntp.version import __version__
import gntp.core
import gntp.errors as errors
import gntp.shim
__all__ = [
'mini',
@@ -22,45 +30,6 @@ __all__ = [
logger = logging.getLogger(__name__)
def mini(description, applicationName='PythonMini', noteType="Message",
title="Mini Message", applicationIcon=None, hostname='localhost',
password=None, port=23053, sticky=False, priority=None,
callback=None, notificationIcon=None, identifier=None):
"""Single notification function
Simple notification function in one line. Has only one required parameter
and attempts to use reasonable defaults for everything else
:param string description: Notification message
.. warning::
For now, only URL callbacks are supported. In the future, the
callback argument will also support a function
"""
growl = GrowlNotifier(
applicationName=applicationName,
notifications=[noteType],
defaultNotifications=[noteType],
applicationIcon=applicationIcon,
hostname=hostname,
password=password,
port=port,
)
result = growl.register()
if result is not True:
return result
return growl.notify(
noteType=noteType,
title=title,
description=description,
icon=notificationIcon,
sticky=sticky,
priority=priority,
callback=callback,
identifier=identifier,
)
class GrowlNotifier(object):
"""Helper class to simplfy sending Growl messages
@@ -99,8 +68,9 @@ class GrowlNotifier(object):
If it's a simple URL icon, then we return True. If it's a data icon
then we return False
'''
logger.debug('Checking icon')
return data.startswith('http')
logger.info('Checking icon')
return gntp.shim.u(data)[:4] in ['http', 'file']
def register(self):
"""Send GNTP Registration
@@ -109,8 +79,8 @@ class GrowlNotifier(object):
Before sending notifications to Growl, you need to have
sent a registration message at least once
"""
logger.debug('Sending registration to %s:%s', self.hostname, self.port)
register = gntp.GNTPRegister()
logger.info('Sending registration to %s:%s', self.hostname, self.port)
register = gntp.core.GNTPRegister()
register.add_header('Application-Name', self.applicationName)
for notification in self.notifications:
enabled = notification in self.defaultNotifications
@@ -119,8 +89,8 @@ class GrowlNotifier(object):
if self._checkIcon(self.applicationIcon):
register.add_header('Application-Icon', self.applicationIcon)
else:
id = register.add_resource(self.applicationIcon)
register.add_header('Application-Icon', id)
resource = register.add_resource(self.applicationIcon)
register.add_header('Application-Icon', resource)
if self.password:
register.set_password(self.password, self.passwordHash)
self.add_origin_info(register)
@@ -128,7 +98,7 @@ class GrowlNotifier(object):
return self._send('register', register)
def notify(self, noteType, title, description, icon=None, sticky=False,
priority=None, callback=None, identifier=None):
priority=None, callback=None, identifier=None, custom={}):
"""Send a GNTP notifications
.. warning::
@@ -141,14 +111,16 @@ class GrowlNotifier(object):
:param boolean sticky: Sticky notification
:param integer priority: Message priority level from -2 to 2
:param string callback: URL callback
:param dict custom: Custom attributes. Key names should be prefixed with X-
according to the spec but this is not enforced by this class
.. warning::
For now, only URL callbacks are supported. In the future, the
callback argument will also support a function
"""
logger.debug('Sending notification [%s] to %s:%s', noteType, self.hostname, self.port)
logger.info('Sending notification [%s] to %s:%s', noteType, self.hostname, self.port)
assert noteType in self.notifications
notice = gntp.GNTPNotice()
notice = gntp.core.GNTPNotice()
notice.add_header('Application-Name', self.applicationName)
notice.add_header('Notification-Name', noteType)
notice.add_header('Notification-Title', title)
@@ -162,8 +134,8 @@ class GrowlNotifier(object):
if self._checkIcon(icon):
notice.add_header('Notification-Icon', icon)
else:
id = notice.add_resource(icon)
notice.add_header('Notification-Icon', id)
resource = notice.add_resource(icon)
notice.add_header('Notification-Icon', resource)
if description:
notice.add_header('Notification-Text', description)
@@ -172,6 +144,9 @@ class GrowlNotifier(object):
if identifier:
notice.add_header('Notification-Coalescing-ID', identifier)
for key in custom:
notice.add_header(key, custom[key])
self.add_origin_info(notice)
self.notify_hook(notice)
@@ -179,7 +154,7 @@ class GrowlNotifier(object):
def subscribe(self, id, name, port):
"""Send a Subscribe request to a remote machine"""
sub = gntp.GNTPSubscribe()
sub = gntp.core.GNTPSubscribe()
sub.add_header('Subscriber-ID', id)
sub.add_header('Subscriber-Name', name)
sub.add_header('Subscriber-Port', port)
@@ -195,7 +170,7 @@ class GrowlNotifier(object):
"""Add optional Origin headers to message"""
packet.add_header('Origin-Machine-Name', platform.node())
packet.add_header('Origin-Software-Name', 'gntp.py')
packet.add_header('Origin-Software-Version', gntp.__version__)
packet.add_header('Origin-Software-Version', __version__)
packet.add_header('Origin-Platform-Name', platform.system())
packet.add_header('Origin-Platform-Version', platform.platform())
@@ -214,34 +189,78 @@ class GrowlNotifier(object):
packet.validate()
data = packet.encode()
#logger.debug('To : %s:%s <%s>\n%s', self.hostname, self.port, packet.__class__, data)
#Less verbose
logger.debug('To : %s:%s <%s>', self.hostname, self.port, packet.__class__)
logger.debug('To : %s:%s <%s>\n%s', self.hostname, self.port, packet.__class__, data)
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.settimeout(self.socketTimeout)
s.connect((self.hostname, self.port))
s.send(data)
recv_data = s.recv(1024)
while not recv_data.endswith("\r\n\r\n"):
recv_data += s.recv(1024)
response = gntp.parse_gntp(recv_data)
try:
s.connect((self.hostname, self.port))
s.send(data)
recv_data = s.recv(1024)
while not recv_data.endswith(gntp.shim.b("\r\n\r\n")):
recv_data += s.recv(1024)
except socket.error:
# Python2.5 and Python3 compatibile exception
exc = sys.exc_info()[1]
raise errors.NetworkError(exc)
response = gntp.core.parse_gntp(recv_data)
s.close()
#logger.debug('From : %s:%s <%s>\n%s', self.hostname, self.port, response.__class__, response)
#Less verbose
logger.debug('From : %s:%s <%s>', self.hostname, self.port, response.__class__)
logger.debug('From : %s:%s <%s>\n%s', self.hostname, self.port, response.__class__, response)
if type(response) == gntp.GNTPOK:
return True
if response.error()[0] == '404' and 'disabled' in response.error()[1]:
# Ignore message saying that user has disabled this class
if type(response) == gntp.core.GNTPOK:
return True
logger.error('Invalid response: %s', response.error())
return response.error()
def mini(description, applicationName='PythonMini', noteType="Message",
title="Mini Message", applicationIcon=None, hostname='localhost',
password=None, port=23053, sticky=False, priority=None,
callback=None, notificationIcon=None, identifier=None,
notifierFactory=GrowlNotifier):
"""Single notification function
Simple notification function in one line. Has only one required parameter
and attempts to use reasonable defaults for everything else
:param string description: Notification message
.. warning::
For now, only URL callbacks are supported. In the future, the
callback argument will also support a function
"""
try:
growl = notifierFactory(
applicationName=applicationName,
notifications=[noteType],
defaultNotifications=[noteType],
applicationIcon=applicationIcon,
hostname=hostname,
password=password,
port=port,
)
result = growl.register()
if result is not True:
return result
return growl.notify(
noteType=noteType,
title=title,
description=description,
icon=notificationIcon,
sticky=sticky,
priority=priority,
callback=callback,
identifier=identifier,
)
except Exception:
# We want the "mini" function to be simple and swallow Exceptions
# in order to be less invasive
logger.exception("Growl error")
if __name__ == '__main__':
# If we're running this module directly we're likely running it as a test
# so extra debugging is useful
logging.basicConfig(level=logging.DEBUG)
logging.basicConfig(level=logging.INFO)
mini('Testing mini notification')

46
gntp/shim.py Normal file
View File

@@ -0,0 +1,46 @@
# Copyright: 2013 Paul Traylor
# These sources are released under the terms of the MIT license: see LICENSE
"""
Python2.5 and Python3.3 compatibility shim
Heavily inspirted by the "six" library.
https://pypi.python.org/pypi/six
"""
import sys
PY2 = sys.version_info[0] == 2
PY3 = sys.version_info[0] == 3
if PY3:
def b(s):
if isinstance(s, bytes):
return s
return s.encode('utf8', 'replace')
def u(s):
if isinstance(s, bytes):
return s.decode('utf8', 'replace')
return s
from io import BytesIO as StringIO
from configparser import RawConfigParser
else:
def b(s):
if isinstance(s, unicode):
return s.encode('utf8', 'replace')
return s
def u(s):
if isinstance(s, unicode):
return s
if isinstance(s, int):
s = str(s)
return unicode(s, "utf8", "replace")
from StringIO import StringIO
from ConfigParser import RawConfigParser
b.__doc__ = "Ensure we have a byte string"
u.__doc__ = "Ensure we have a unicode string"

4
gntp/version.py Normal file
View File

@@ -0,0 +1,4 @@
# Copyright: 2013 Paul Traylor
# These sources are released under the terms of the MIT license: see LICENSE
__version__ = '1.0.3'

View File

@@ -22,7 +22,7 @@
</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no, maximum-scale=1" />
<meta name="apple-mobile-web-app-title" content="SABnzbd" />
<link rel="apple-touch-icon" sizes="76x76" href="${root}staticcfg/ico/apple-touch-icon-76x76-precomposed.png" />
@@ -32,7 +32,9 @@
<link rel="apple-touch-icon" sizes="192x192" href="${root}staticcfg/ico/android-192x192.png" />
<link rel="stylesheet" type="text/css" href="${root}staticcfg/bootstrap/css/bootstrap.min.css?v=$version" />
<link rel="stylesheet" type="text/css" href="${root}staticcfg/css/style.css?p=$pid" />
<link rel="stylesheet" type="text/css" href="${root}staticcfg/css/chartist.min.css" />
<link rel="stylesheet" type="text/css" href="${root}staticcfg/css/style.css?v=$version" />
<link rel="shortcut icon" href="${root}staticcfg/ico/favicon.ico?v=$version" />
<script type="text/javascript">
@@ -43,6 +45,7 @@
// Information we need
var sabSession = '$session';
var rootURL = '${root}'
var urlBase = '${url_base}'
var folderBrowseUrl = '${root}tapi?mode=browse&output=json&apikey=$session';
var folderSeperator = '#if $os.sep == '\\' then '\\\\' else '/'#'
@@ -59,7 +62,7 @@
configTranslate.confirmLeave = "$T('confirmWithoutSavingPrompt')";
configTranslate.searchPages = ['$T('cmenu-general')', '$T('cmenu-folders')', '$T('cmenu-switches')', '$T('cmenu-sorting')', '$T('cmenu-notif')', '$T('cmenu-special')']
</script>
<script type="text/javascript" src="${root}staticcfg/js/jquery-3.1.1.min.js?v=$version"></script>
<script type="text/javascript" src="${root}staticcfg/js/jquery-3.2.1.min.js?v=$version"></script>
<script type="text/javascript" src="${root}staticcfg/bootstrap/js/bootstrap.min.js?v=$version"></script>
<script type="text/javascript" src="${root}staticcfg/js/script.js?v=$version"></script>
<script type="text/javascript">

View File

@@ -1,5 +1,5 @@
<!--#set global $pane="Config"#-->
<!--#set global $help_uri="configuration/1.2/configure"#-->
<!--#set global $help_uri="configuration/2.3/configure"#-->
<!--#include $webdir + "/_inc_header_uc.tmpl"#-->
<!--#from locale import getpreferredencoding#-->
@@ -19,10 +19,6 @@
<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>
@@ -34,12 +30,7 @@
<tr>
<th scope="row">OpenSSL:</th>
<td>
<!--#if $have_ssl#-->
$ssl_version &nbsp; [$ssl_protocols]
<!--#else#-->
<span class="label label-danger">$T('notAvailable')</span>
<a href="$helpuri$help_uri#no_ssl" target="_blank"><span class="glyphicon glyphicon-question-sign"></span></a>
<!--#end if#-->
$ssl_version
</td>
</tr>
<!--#if not $have_ssl_context#-->
@@ -50,6 +41,19 @@
</td>
</tr>
<!--#end if#-->
<!--#if not $nt and not $darwin#-->
<tr>
<th scope="row">$T('opt-multicore-par2')</th>
<td>
<!--#if $have_mt_par2#-->
<span class="glyphicon glyphicon-ok"></span>
<!--#else#-->
<span class="label label-warning">$T('notAvailable')</span> $T('explain-getpar2mt')
<a href="${helpuri}installation/multicore-par2" target="_blank">${helpuri}installation/multicore-par2</a>
<!--#end if#-->
</td>
</tr>
<!--#end if#-->
<!--#if not $have_cryptography #-->
<tr>
<th scope="row">Python Cryptography:</th>
@@ -59,7 +63,7 @@
</td>
</tr>
<!--#end if#-->
<!--#if not $have_yenc#-->
<!--#if not $have_yenc and not $have_sabyenc#-->
<tr>
<th scope="row">yEnc:</th>
<td>
@@ -68,7 +72,16 @@
</td>
</tr>
<!--#end if#-->
<!--#if not $have_unzip #-->
<!--#if not $have_sabyenc#-->
<tr>
<th scope="row">SABYenc:</th>
<td>
<span class="label label-danger">$T('notAvailable')</span>
<a href="$helpuri$help_uri#no_sabyenc" target="_blank"><span class="glyphicon glyphicon-question-sign"></span></a>
</td>
</tr>
<!--#end if#-->
<!--#if not $have_unzip and not $have_7zip #-->
<tr>
<th scope="row">$T('opt-enable_unzip'):</th>
<td>
@@ -96,7 +109,7 @@
<tbody>
<tr>
<th scope="row">$T('homePage') </th>
<td><a href="http://sabnzbd.org/" target="_blank">http://sabnzbd.org/</a></td>
<td><a href="https://sabnzbd.org/" target="_blank">https://sabnzbd.org/</a></td>
</tr>
<tr>
<th scope="row">$T('menu-wiki') </th>
@@ -104,7 +117,7 @@
</tr>
<tr>
<th scope="row">$T('menu-forums') </th>
<td><a href="http://forums.sabnzbd.org/" target="_blank">http://forums.sabnzbd.org/</a></td>
<td><a href="https://forums.sabnzbd.org/" target="_blank">https://forums.sabnzbd.org/</a></td>
</tr>
<tr>
<th scope="row">$T('source') </th>
@@ -118,6 +131,10 @@
<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>
<tr>
<th scope="row">$T('menu-donate') </th>
<td><a href="https://sabnzbd.org/donate" target="_blank">https://sabnzbd.org/donate</a></td>
</tr>
</tbody>
</table>
</div>
@@ -125,7 +142,7 @@
<div class="colmask">
<div class="padding alt">
<h5 class="copyright">Copyright &copy; 2008-2017 The SABnzbd Team &lt;<a href="mailto:team@sabnzbd.org">team@sabnzbd.org</a>&gt;</h5>
<h5 class="copyright">Copyright &copy; 2007-2018 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>

View File

@@ -1,5 +1,5 @@
<!--#set global $pane="Categories"#-->
<!--#set global $help_uri="configuration/1.2/categories"#-->
<!--#set global $help_uri="configuration/2.3/categories"#-->
<!--#include $webdir + "/_inc_header_uc.tmpl"#-->
<div class="colmask">
<div class="section">
@@ -17,9 +17,7 @@
<th>$T('category')</th>
<th>$T('priority')</th>
<th>$T('mode')</th>
<!--#if $script_list#-->
<th>$T('script')</th>
<!--#end if#-->
<th>$T('catFolderPath')</th>
<th colspan="2">$T('catTags')</th>
</tr>
@@ -61,17 +59,21 @@
<option value="3" <!--#if $slot.pp=="3" then 'selected="selected"' else ""#-->>$T('pp-delete')</option>
</select>
</td>
<!--#if $script_list#-->
<td>
<!--#if $scripts#-->
<select name="script">
<!--#for $sc in $script_list#-->
<!--#for $sc in $scripts#-->
<!--#if not ($sc == 'Default' and $slot.name == '*')#-->
<option value="$sc" <!--#if $slot.script.lower()==$sc.lower() then 'selected="selected"' else ""#-->>$Tspec($sc)</option>
<!--#end if#-->
<!--#end for#-->
</select>
<!--#else#-->
<select name="script" disabled>
</select>
<!--#end if#-->
</td>
<!--#end if#-->
<td class="nowrap">
<input type="text" name="dir" class="fileBrowserSmall" value="$slot.dir" size="20" data-initialdir="$defdir" data-title="$T('catFolderPath')" title="$T('explain-catTags2')" />
</td>

View File

@@ -1,8 +1,13 @@
<!--#set global $pane="Folders"#-->
<!--#set global $help_uri="configuration/1.2/folders"#-->
<!--#set global $help_uri="configuration/2.3/folders"#-->
<!--#include $webdir + "/_inc_header_uc.tmpl"#-->
<div class="colmask">
<div class="padding alt section">
<label for="advanced-settings-button" class="form-control advanced-button ">
<input type="checkbox" id="advanced-settings-button" name="advanced-settings-button"> $T('button-advanced')
</label>
</div>
<form action="saveDirectories" method="post" name="fullform" class="fullform" autocomplete="off">
<input type="hidden" id="session" name="session" value="$session" />
<input type="hidden" id="ajax" name="ajax" value="1" />
@@ -21,7 +26,7 @@
<input type="text" name="download_dir" id="download_dir" value="$download_dir" data-initialdir="$my_home" />
<span class="desc">$T('explain-download_dir')</span>
</div>
<div class="field-pair">
<div class="field-pair advanced-settings">
<label class="config" for="download_free">$T('opt-download_free')</label>
<input type="text" name="download_free" id="download_free" value="$download_free" class="smaller_input" />
<span class="desc">$T('explain-download_free')</span>
@@ -32,7 +37,7 @@
<span class="desc">$T('explain-complete_dir')</span>
</div>
<!--#if not $nt#-->
<div class="field-pair">
<div class="field-pair advanced-settings">
<label class="config" for="permissions">$T('opt-permissions')</label>
<input type="text" name="permissions" id="permissions" value="$permissions" class="smaller_input" />
<span class="desc">$T('explain-permissions')</span>
@@ -43,7 +48,7 @@
<input type="text" name="dirscan_dir" id="dirscan_dir" value="$dirscan_dir" data-initialdir="$my_home" />
<span class="desc">$T('explain-dirscan_dir')</span>
</div>
<div class="field-pair">
<div class="field-pair advanced-settings">
<label class="config" for="dirscan_speed">$T('opt-dirscan_speed')</label>
<input type="number" name="dirscan_speed" id="dirscan_speed" value="$dirscan_speed" min="0" max="3600" />
<span class="desc">$T('explain-dirscan_speed')</span>
@@ -53,12 +58,12 @@
<input type="text" name="script_dir" id="script_dir" value="$script_dir" data-initialdir="$my_home" />
<span class="desc">$T('explain-script_dir')</span>
</div>
<div class="field-pair">
<div class="field-pair advanced-settings">
<label class="config" for="email_dir">$T('opt-email_dir')</label>
<input type="text" name="email_dir" id="email_dir" value="$email_dir" data-initialdir="$my_home" />
<span class="desc">$T('explain-email_dir')</span>
</div>
<div class="field-pair">
<div class="field-pair advanced-settings">
<label class="config" for="password_file">$T('opt-password_file')</label>
<input type="text" name="password_file" id="password_file" value="$password_file" />
<span class="desc">$T('explain-password_file')</span>
@@ -72,7 +77,7 @@
</fieldset>
</div><!-- /col1 -->
</div><!-- /section -->
<div class="section">
<div class="section advanced-settings">
<div class="col2">
<h3>$T('systemFolders') <a href="$helpuri$help_uri#toc1" target="_blank"><span class="glyphicon glyphicon-question-sign"></span></a></h3>
<p>$T('explain-folderConfig')</p>

View File

@@ -1,9 +1,14 @@
<!--#set global $pane="General"#-->
<!--#set global $help_uri="configuration/1.2/general"#-->
<!--#set global $help_uri="configuration/2.3/general"#-->
<!--#include $webdir + "/_inc_header_uc.tmpl"#-->
<div class="colmask">
<form action="saveGeneral" method="post" name="fullform" class="fullform" autocomplete="off" novalidate>
<div class="padding alt section">
<label for="advanced-settings-button" class="form-control advanced-button ">
<input type="checkbox" id="advanced-settings-button" name="advanced-settings-button"> $T('button-advanced')
</label>
</div>
<form action="saveGeneral" method="post" name="fullform" class="fullform" autocomplete="off">
<input type="hidden" id="session" name="session" value="$session" />
<input type="hidden" id="ajax" name="ajax" value=1 />
<div class="section">
@@ -23,12 +28,12 @@
<input type="number" name="port" id="port" value="$port" size="8" data-original="$port" />
<span class="desc">$T('explain-port')</span>
</div>
<div class="field-pair <!--#if int($have_ssl) == 0 then "disabled" else ""#-->">
<div class="field-pair">
<label class="config" for="enable_https">$T('opt-enable_https')</label>
<input type="checkbox" name="enable_https" id="enable_https" value="1" <!--#if int($enable_https) > 0 then 'checked="checked"' else ""#--> <!--#if int($have_ssl) == 0 then "disabled" else ""#--> />
<input type="checkbox" name="enable_https" id="enable_https" value="1" <!--#if int($enable_https) > 0 then 'checked="checked" data-original="1"' else ""#-->/>
<span class="desc">$T('explain-enable_https')</span>
</div>
<div class="field-pair">
<div class="field-pair advanced-settings">
<label class="config" for="web_dir">$T('opt-web_dir')</label>
<select name="web_dir" id="web_dir">
<!--#for $webline in $web_list#-->
@@ -39,21 +44,7 @@
<!--#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">
<option value="None" selected="selected">$T("None")</option>
<!--#for $webline in $web_list#-->
<!--#if $webline.lower() == $web_dir2.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_dir2')&nbsp;&nbsp;<a href="$caller_url2">$caller_url2</a></span>
<span class="desc">$T('explain-web_dir')&nbsp;&nbsp;<a href="$caller_url">$caller_url</a></span>
</div>
<div class="field-pair">
<label class="config" for="language">$T('opt-language')</label>
@@ -103,7 +94,6 @@
<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 sabnzbd_restart"><span class="glyphicon glyphicon-refresh"></span> $T('button-restart') SABnzbd</button>
<button class="btn btn-default advancedButton enable_https_options"><span class="glyphicon glyphicon-cog"></span> $T('button-advanced')</button>
</div>
</fieldset>
</div>
@@ -187,7 +177,7 @@
</select>
<span class="desc">$T('explain-check_new_rel')</span>
</div>
<div class="field-pair <!--#if int($have_ssl_context) == 0 then "disabled" else ""#-->">
<div class="field-pair advanced-settings <!--#if int($have_ssl_context) == 0 then "disabled" else ""#-->">
<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 ""#--> <!--#if int($have_ssl_context) == 0 then "disabled=\"disabled\"" else ""#--> />
<span class="desc">$T('explain-enable_https_verification')</span>
@@ -214,12 +204,12 @@
</select>
<input type="hidden" name="bandwidth_max" id="bandwidth_max" value="$bandwidth_max" />
</div>
<div class="field-pair">
<div class="field-pair advanced-settings">
<label class="config" for="bandwidth_perc">$T('opt-bandwidth_perc')</label>
<input type="number" name="bandwidth_perc" id="bandwidth_perc" value="$bandwidth_perc" step="10" min="0" max="100"/>
<span class="desc">$T('explain-bandwidth_perc')</span>
</div>
<div class="field-pair">
<div class="field-pair advanced-settings">
<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>
@@ -254,9 +244,45 @@
\$('.alert-translate').show()
}
}
\$('#language').on('change', hideOrShowTranslate)
\$('#language').on('change', function() {
// Show message
hideOrShowTranslate()
// Re-load page on submit
\$('.fullform').submit(function() {
// Skip the fancy stuff, just submit
this.submit()
})
// No JSON reponse
\$('#ajax').val('')
})
hideOrShowTranslate()
// Highlight in case user is not safe
// So when exposed to internet and no password, no external limit or no username/password
var safeCheck = \$('#host, #local_ranges, #inet_exposure, #${pid}_wu, #${pid}_wp')
function checkSafety() {
if(\$('#host').val() != 'localhost' && \$('#host').val() != '127.0.0.1') {
// No limitation on local-network
if(!\$('#local_ranges').val() || \$('#inet_exposure').val() > 3) {
// And no username and password?
if(!\$('#${pid}_wu').val() || !\$('#${pid}_wp').val()) {
// Add warning icon if not there already
if(!\$('.host-warning').length) {
safeCheck.after('<span class="glyphicon glyphicon-alert host-warning"></span>')
\$('.host-warning').tooltip({'title': '$T('checkSafety')'})
}
return
}
}
}
// Remove warnings
\$('.host-warning').remove()
safeCheck.removeClass('host-warning-highlight')
}
checkSafety()
safeCheck.on('change', checkSafety)
// Click functions
\$('#apikey, #nzbkey').click(function () { \$(this).select() });
\$('#generate_new_apikey').click(function () {
@@ -328,14 +354,19 @@
if(bandwidthLimit) {
var bandwithLimitNumber = parseFloat(bandwidthLimit)
var bandwithLimitText = bandwidthLimit.replace(/[^a-zA-Z]+/g, '');
\$('#bandwidth_max_value').val(bandwithLimitNumber)
\$('#bandwidth_max_dropdown').val(bandwithLimitText)
if(bandwithLimitNumber) {
\$('#bandwidth_max_value').val(bandwithLimitNumber)
\$('#bandwidth_max_dropdown').val(bandwithLimitText)
}
}
// Update the value
\$('#bandwidth_max_value, #bandwidth_max_dropdown').on('change', function() {
\$('#bandwidth_max').val(\$('#bandwidth_max_value').val() + \$('#bandwidth_max_dropdown').val())
if(\$('#bandwidth_max_value').val()) {
\$('#bandwidth_max').val(\$('#bandwidth_max_value').val() + \$('#bandwidth_max_dropdown').val())
} else {
\$('#bandwidth_max').val('')
}
})
});

View File

@@ -1,5 +1,5 @@
<!--#set global $pane="Email"#-->
<!--#set global $help_uri="configuration/1.2/notifications"#-->
<!--#set global $help_uri="configuration/2.3/notifications"#-->
<!--#include $webdir + "/_inc_header_uc.tmpl"#-->
<!--#def show_notify_checkboxes($section_label)#-->
@@ -13,14 +13,34 @@
<!--#end for#-->
<!--#end def#-->
<!--#def show_cat_box($section_label)#-->
<div class="col2-cats" <!--#if int($getVar($section_label + '_enable')) > 0 then '' else 'style="display:none"'#-->>
<hr>
<b>$T('affectedCat')</b><br/>
<select name="${section_label}_cats" multiple="multiple" class="multiple_cats">
<!--#for $ct in $categories#-->
<option value="$ct" <!--#if $ct in $getVar($section_label + '_cats') then 'selected="selected"' else ""#-->>$Tspec($ct)</option>
<!--#end for#-->
</select>
</div>
<!--#end def#-->
<div class="colmask">
<form action="saveEmail" method="post" name="fullform" class="fullform" autocomplete="off" novalidate>
<form action="saveEmail" method="post" name="fullform" class="fullform" autocomplete="off">
<input type="hidden" id="session" name="session" value="$session" />
<input type="hidden" id="ajax" name="ajax" value="1" />
<div class="section" id="email">
<div class="col2">
<h3>$T('cmenu-email') <a href="$helpuri$help_uri#toc0" target="_blank"><span class="glyphicon glyphicon-question-sign"></span></a></h3>
</div><!-- /col2 -->
<div class="col2-cats" <!--#if int($email_endjob) > 0 then '' else 'style="display:none"'#-->>
<b>$T('affectedCat')</b><br/>
<select name="email_cats" multiple="multiple" class="multiple_cats">
<!--#for $ct in $categories#-->
<option value="$ct" <!--#if $ct in $email_cats then 'selected="selected"' else ""#-->>$Tspec($ct)</option>
<!--#end for#-->
</select>
</div>
</div>
<div class="col1">
<fieldset>
<div class="field-pair">
@@ -71,7 +91,7 @@
<input type="text" name="email_pwd" id="email_pwd" value="$email_pwd" />
<span class="desc">$T('explain-email_pwd')</span>
</div>
<div class="field-pair">
<div class="field-pair no-field-pair-bg">
<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>
@@ -79,8 +99,8 @@
<div class="alert"></div>
</div>
</fieldset>
</div><!-- /col1 -->
</div><!-- /section -->
</div>
</div>
<!--#if $have_ncenter#-->
<div class="section">
<div class="col2">
@@ -91,11 +111,11 @@
<td><label for="ncenter_enable"> $T('opt-ncenter_enable')</label></td>
</tr>
</table>
</div><!-- /col2 -->
</div>
<div class="col1" <!--#if int($ncenter_enable) > 0 then '' else 'style="display:none"'#-->>
<fieldset>
$show_notify_checkboxes('ncenter')
<div class="field-pair">
<div class="field-pair no-field-pair-bg">
<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>
</div>
@@ -103,8 +123,8 @@
<div class="alert"></div>
</div>
</fieldset>
</div><!-- /col1 -->
</div><!-- /section -->
</div>
</div>
<!--#end if#-->
<!--#if $nt#-->
<div class="section">
@@ -116,11 +136,12 @@
<td><label for="acenter_enable"> $T('opt-acenter_enable')</label></td>
</tr>
</table>
</div><!-- /col2 -->
$show_cat_box('acenter')
</div>
<div class="col1" <!--#if int($acenter_enable) > 0 then '' else 'style="display:none"'#-->>
<fieldset>
$show_notify_checkboxes('acenter')
<div class="field-pair">
<div class="field-pair no-field-pair-bg">
<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>
</div>
@@ -128,8 +149,8 @@
<div class="alert"></div>
</div>
</fieldset>
</div><!-- /col1 -->
</div><!-- /section -->
</div>
</div>
<!--#end if#-->
<!--#if $have_ntfosd#-->
<div class="section">
@@ -141,11 +162,12 @@
<td><label for="ntfosd_enable"> $T('opt-ntfosd_enable')</label></td>
</tr>
</table>
</div><!-- /col2 -->
$show_cat_box('ntfosd')
</div>
<div class="col1" <!--#if int($ntfosd_enable) > 0 then '' else 'style="display:none"'#-->>
<fieldset>
$show_notify_checkboxes('ntfosd')
<div class="field-pair">
<div class="field-pair no-field-pair-bg">
<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>
</div>
@@ -153,9 +175,48 @@
<div class="alert"></div>
</div>
</fieldset>
</div><!-- /col1 -->
</div><!-- /section -->
</div>
</div>
<!--#end if#-->
<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><br><a href="$helpuri$help_uri#nscript" target="_blank">$T('readwiki')</a>
$show_cat_box('nscript')
</div>
<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" id="nscript_script">
<!--#for $sc in $scripts#-->
<option value="$sc" <!--#if $nscript_script == $sc 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>
$show_notify_checkboxes('nscript')
<div class="field-pair no-field-pair-bg">
<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>
</div>
<div class="section" id="growl">
<div class="col2">
<h3>$T('growlSettings') <a href="$helpuri$help_uri#toc3" target="_blank"><span class="glyphicon glyphicon-question-sign"></span></a></h3>
@@ -165,7 +226,8 @@
<td><label for="growl_enable"> $T('opt-growl_enable')</label></td>
</tr>
</table>
</div><!-- /col2 -->
$show_cat_box('growl')
</div>
<div class="col1" <!--#if int($growl_enable) > 0 then '' else 'style="display:none"'#-->>
<fieldset>
<div class="field-pair">
@@ -179,7 +241,7 @@
<span class="desc">$T('explain-growl_password')</span>
</div>
$show_notify_checkboxes('growl')
<div class="field-pair">
<div class="field-pair no-field-pair-bg">
<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>
@@ -187,8 +249,8 @@
<div class="alert"></div>
</div>
</fieldset>
</div><!-- /col1 -->
</div><!-- /section -->
</div>
</div>
<div class="section" id="prowl">
<div class="col2">
<h3>$T('section-Prowl')</h3>
@@ -199,7 +261,8 @@
</tr>
</table>
<em>$T('explain-prowl_enable')</em>
</div><!-- /col2 -->
$show_cat_box('prowl')
</div>
<div class="col1" <!--#if int($prowl_enable) > 0 then '' else 'style="display:none"'#-->>
<fieldset>
<div class="field-pair">
@@ -223,7 +286,7 @@
</select>
</div>
<!--#end for#-->
<div class="field-pair">
<div class="field-pair no-field-pair-bg">
<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>
@@ -231,8 +294,8 @@
<div class="alert"></div>
</div>
</fieldset>
</div><!-- /col1 -->
</div><!-- /section -->
</div>
</div>
<div class="section" id="pushover">
<div class="col2">
@@ -244,7 +307,8 @@
</tr>
</table>
<em>$T('explain-pushover_enable')</em>
</div><!-- /col2 -->
$show_cat_box('pushover')
</div>
<div class="col1" <!--#if int($pushover_enable) > 0 then '' else 'style="display:none"'#-->>
<fieldset>
<div class="field-pair">
@@ -262,6 +326,16 @@
<input type="text" name="pushover_device" id="pushover_device" value="$pushover_device" />
<span class="desc">$T('explain-pushover_device')</span>
</div>
<div class="field-pair">
<label class="config" for="pushover_emergency_retry">$T('opt-pushover_emergency_retry')</label>
<input type="text" name="pushover_emergency_retry" id="pushover_emergency_retry" value="$pushover_emergency_retry" />
<span class="desc">$T('explain-pushover_emergency_retry')</span>
</div>
<div class="field-pair">
<label class="config" for="pushover_emergency_expire">$T('opt-pushover_emergency_expire')</label>
<input type="text" name="pushover_emergency_expire" id="pushover_emergency_expire" value="$pushover_emergency_expire" />
<span class="desc">$T('explain-pushover_emergency_expire')</span>
</div>
<!--#set $section_label = 'pushover'#-->
<!--#for $type in $notify_keys#-->
<div class="field-pair">
@@ -278,7 +352,7 @@
</select>
</div>
<!--#end for#-->
<div class="field-pair">
<div class="field-pair no-field-pair-bg">
<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>
@@ -286,8 +360,8 @@
<div class="alert"></div>
</div>
</fieldset>
</div><!-- /col1 -->
</div><!-- /section -->
</div>
</div>
<div class="section" id="pushbullet">
<div class="col2">
<h3>$T('section-Pushbullet')</h3>
@@ -298,7 +372,8 @@
</tr>
</table>
<em>$T('explain-pushbullet_enable')</em>
</div><!-- /col2 -->
$show_cat_box('pushbullet')
</div>
<div class="col1" <!--#if int($pushbullet_enable) > 0 then '' else 'style="display:none"'#-->>
<fieldset>
<div class="field-pair">
@@ -314,7 +389,7 @@
</div>
<!--#end if#-->
$show_notify_checkboxes('pushbullet')
<div class="field-pair">
<div class="field-pair no-field-pair-bg">
<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>
@@ -322,46 +397,8 @@
<div class="alert"></div>
</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 == $sc 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>
$show_notify_checkboxes('nscript')
<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 -->
</div>
</div>
</form>
</div><!-- /colmask -->
@@ -374,10 +411,20 @@
\$('.col2 input[name$="enable"]').change(function() {
if(this.checked) {
\$(this).parents('.section').find('.col1').show()
\$(this).parents('.col2').find('.col2-cats').show()
} else {
\$(this).parents('.section').find('.col1').hide()
\$(this).parents('.col2').find('.col2-cats').hide()
}
\$('form').submit()
addRowColor()
})
\$('#email_endjob').change(function() {
if(\$(this).val() > 0) {
\$(this).parents('.section').find('.col2-cats').show()
} else {
\$(this).parents('.section').find('.col2-cats').hide()
}
})
/**

View File

@@ -1,5 +1,5 @@
<!--#set global $pane="RSS"#-->
<!--#set global $help_uri="configuration/1.2/rss"#-->
<!--#set global $help_uri="configuration/2.3/rss"#-->
<!--#include $webdir + "/_inc_header_uc.tmpl"#-->
<div class="colmask">
<!--#if not $active_feed#-->
@@ -7,7 +7,7 @@
<div class="padTable">
<a class="main-helplink" href="$helpuri$help_uri" target="_blank"><span class="glyphicon glyphicon-question-sign"></span></a>
<p>$T('explain-RSS')</p>
<form action="add_rss_feed" method="post" autocomplete="off" novalidate>
<form action="add_rss_feed" method="post" autocomplete="off">
<input type="hidden" name="session" value="$session" />
<table class="catTable">
<tr>
@@ -37,7 +37,7 @@
<!--#if $rss#-->
<div class="section">
<div class="padTable">
<form action="save_rss_feed" method="post" autocomplete="off" novalidate>
<form action="save_rss_feed" method="post" autocomplete="off">
<input type="hidden" name="session" value="$session" />
<table id="subscriptions">
<tbody>
@@ -73,7 +73,7 @@
</form>
<!--#if $feeds#-->
<br/>
<form action="rss_now" method="post" autocomplete="off" novalidate>
<form action="rss_now" method="post" autocomplete="off">
<input type="hidden" name="session" value="$session" />
<button type="submit" class="btn btn-default readAll"><span class="glyphicon glyphicon-sort"></span> $T('button-rssNow')</button>
</form>
@@ -154,7 +154,7 @@
<option value=">"> $T('rss-atleast')</option>
<option value="<"> $T('rss-atmost')</option>
<option value="F"> $T('rss-from')</option>
<option value="F"> $T('rss-from-show')</option>
<option value="F"> $T('rss-from-show') ($T('rss-accept'))</option>
</select>
</td>
<td>
@@ -163,7 +163,7 @@
<!--#if $rss[$feed]['pick_cat']#-->
<td>
<select name="cat">
<!--#for $ct in $cat_list#-->
<!--#for $ct in $categories#-->
<option value="$ct" <!--#if $ct==$rss[$feed]['cat'] then 'selected="selected"' else ""#-->>$Tspec($ct)</option>
<!--#end for#-->
</select>
@@ -191,7 +191,7 @@
<!--#if $rss[$feed]['pick_script']#-->
<td>
<select name="script">
<!--#for $sc in $script_list#-->
<!--#for $sc in $scripts#-->
<option value="$sc" <!--#if $sc==$rss[$feed]['script'] then 'selected="selected"' else ""#-->>$Tspec($sc)</option>
<!--#end for#-->
</select>
@@ -226,8 +226,8 @@
<option value="C"> $T('rss-mustcat')</option>
<option value=">"> $T('rss-atleast')</option>
<option value="<"> $T('rss-atmost')</option>
<option value="S"> $T('rss-from-show')</option>
<option value="F"> $T('rss-from')</option>
<option value="S"> $T('rss-from-show') ($T('rss-accept'))</option>
</select>
</td>
<td>
@@ -236,7 +236,7 @@
<!--#if $rss[$feed]['pick_cat']#-->
<td>
<select name="cat">
<!--#for $ct in $cat_list#-->
<!--#for $ct in $categories#-->
<option value="$ct" <!--#if $ct=='Default' then 'selected="selected"' else ""#-->>$Tspec($ct)</option>
<!--#end for#-->
</select>
@@ -264,7 +264,7 @@
<!--#if $rss[$feed]['pick_script']#-->
<td>
<select name="script">
<!--#for $sc in $script_list#-->
<!--#for $sc in $scripts#-->
<option value="$sc" <!--#if $sc=='Default' then 'selected="selected"' else ""#-->>$Tspec($sc)</option>
<!--#end for#-->
</select>
@@ -287,7 +287,7 @@
<input type="hidden" name="feed" value="$feed" />
<table class="catTable">
<tbody>
<tr class="<!--#if $odd then " alt " else " "#--> <!--#if $filter[3]!="A" then 'disabled_options_rule' else ""#-->">
<tr class="<!--#if $odd then " alt " else " "#--> <!--#if $filter[3]!="A" and $filter[3]!="S" then 'disabled_options_rule' else ""#-->">
<td>
<input type="checkbox" name="enabled" value="1" <!--#if $filter[6]=='1' then 'checked="checked"' else ""#--> />
</td>
@@ -302,8 +302,8 @@
<option value="C" <!--#if $filter[3]=="C" then 'selected="selected"' else ""#-->> $T('rss-mustcat')</option>
<option value=">" <!--#if $filter[3]==">" then 'selected="selected"' else ""#-->> $T('rss-atleast')</option>
<option value="<" <!--#if $filter[3]=="<" then 'selected="selected"' else ""#-->> $T('rss-atmost')</option>
<option value="S" <!--#if $filter[3]=="S" then 'selected="selected"' else ""#-->> $T('rss-from-show')</option>
<option value="F" <!--#if $filter[3]=="F" then 'selected="selected"' else ""#-->> $T('rss-from')</option>
<option value="S" <!--#if $filter[3]=="S" then 'selected="selected"' else ""#-->> $T('rss-from-show') ($T('rss-accept'))</option>
</select>
</td>
<td>
@@ -312,7 +312,7 @@
<!--#if $rss[$feed]['pick_cat']#-->
<td>
<select name="cat">
<!--#for $ct in $cat_list#-->
<!--#for $ct in $categories#-->
<option value="$ct" <!--#if $ct==$filter[0] then 'selected="selected"' else ""#-->>$Tspec($ct)</option>
<!--#end for#-->
</select>
@@ -340,7 +340,7 @@
<!--#if $rss[$feed]['pick_script']#-->
<td>
<select name="script">
<!--#for $sc in $script_list#-->
<!--#for $sc in $scripts#-->
<option value="$sc" <!--#if $sc==$filter[2] then 'selected="selected"' else ""#-->>$Tspec($sc)</option>
<!--#end for#-->
</select>
@@ -390,9 +390,10 @@
<th class="no-sort">$T('link-download')</th>
<th>$T('rss-filter')</th>
<th>$T('size')</th>
<th width="65%">$T('sort-title')</th>
<th width="60%">$T('sort-title')</th>
<th>$T('category')</th>
<th class="default-sort">$T('nzo-age')</th>
<th>$T('source')</th>
</tr>
</thead>
<!--#for $job in $matched#-->
@@ -411,6 +412,13 @@
<td>$job['title']</td>
<td>$job['cat']</td>
<td data-sort-value="$job['age_ms']">$job['age']</td>
<td data-sort-value="$job['baselink']" title="$job['baselink']">
<!--#if not $job['infourl']#-->
<div class="favicon source-icon" style="background-image: url(//$job['baselink']/favicon.ico);" data-domain="$job['baselink']"></div>
<!--#else#-->
<a class="favicon source-icon" href="$job['infourl']" target="_blank" style="background-image: url(//$job['baselink']/favicon.ico);" data-domain="$job['baselink']"></a>
<!--#end if#-->
</td>
</tr>
<!--#end for#-->
</table>
@@ -426,9 +434,10 @@
<th class="no-sort">$T('link-download')</th>
<th>$T('rss-filter')</th>
<th>$T('size')</th>
<th width="65%">$T('sort-title')</th>
<th width="60%">$T('sort-title')</th>
<th>$T('category')</th>
<th class="default-sort">$T('nzo-age')</th>
<th>$T('source')</th>
</tr>
</thead>
<!--#for $job in $unmatched#-->
@@ -447,6 +456,13 @@
<td>$job['title']</td>
<td>$job['cat']</td>
<td data-sort-value="$job['age_ms']">$job['age']</td>
<td data-sort-value="$job['baselink']" title="$job['baselink']">
<!--#if not $job['infourl']#-->
<div class="favicon source-icon" style="background-image: url(//$job['baselink']/favicon.ico);" data-domain="$job['baselink']"></div>
<!--#else#-->
<a class="favicon source-icon" href="$job['infourl']" target="_blank" style="background-image: url(//$job['baselink']/favicon.ico);" data-domain="$job['baselink']"></a>
<!--#end if#-->
</td>
</tr>
<!--#end for#-->
</table>
@@ -476,8 +492,10 @@
<td>$job['title']</td>
<td>$job['cat']</td>
<td data-sort-value="$job['baselink']" title="$job['baselink']">
<!--#if $job['baselink']#-->
<!--#if not $job['infourl']#-->
<div class="favicon source-icon" style="background-image: url(//$job['baselink']/favicon.ico);" data-domain="$job['baselink']"></div>
<!--#else#-->
<a class="favicon source-icon" href="$job['infourl']" target="_blank" style="background-image: url(//$job['baselink']/favicon.ico);" data-domain="$job['baselink']"></a>
<!--#end if#-->
</td>
</tr>
@@ -568,7 +586,7 @@ function urlencode(str) {
// Only the Accept filter needs all the options
\$('form[action="upd_rss_filter"]').find('select[name="filter_type"]').change(function() {
\$(this).parent().parent().find('select:not([name="filter_type"])').attr('disabled', \$(this).val() != "A")
\$(this).parent().parent().find('select:not([name="filter_type"])').attr('disabled', \$(this).val() != "A" && \$(this).val() != "S")
})
// Trigger on-load for all
\$('.disabled_options_rule').find('td select:not([name="filter_type"])').attr('disabled', true)

View File

@@ -1,5 +1,5 @@
<!--#set global $pane="Scheduling"#-->
<!--#set global $help_uri="configuration/1.2/scheduling"#-->
<!--#set global $help_uri="configuration/2.3/scheduling"#-->
<!--#include $webdir + "/_inc_header_uc.tmpl"#-->
<%
@@ -59,6 +59,13 @@ else:
<option value="$server" data-action="0" data-noarg="1">$T('sch-disable_server') "$actions_servers[$server]"</option>
<!--#end for#-->
</optgroup>
<optgroup label="$T('cmenu-cat')">
<!--#for $cat in $categories#-->
<!--#set $cat_text = $T('Default') if $cat == '*' else $cat#-->
<option value="pause_cat" data-action="$cat" data-noarg="1">$T('sch-pause_cat') "$cat_text"</option>
<option value="resume_cat" data-action="$cat" data-noarg="1">$T('sch-resume_cat') "$cat_text"</option>
<!--#end for#-->
</optgroup>
</select>
</div>
<div class="field-pair" id="hidden_arguments" style="display: none">
@@ -80,21 +87,20 @@ else:
<div class="col1">
<fieldset>
<!--#if $schedlines#-->
<!--#set $schednum = 0#-->
<!--#set $odd = True#-->
<!--#for $line in $schedlines#-->
<!--#for $schednum, $line in enumerate($schedlines)#-->
<!--#set $odd = not $odd#-->
<form action="delSchedule" method="post">
<input type="hidden" name="session" value="$session"/>
<input type="hidden" name="line" id="line" value="$line"/>
<div class="field-pair infoTableSeperator <!--#if $odd then "" else " alt"#-->">
<input type="checkbox" name="schedenabled" value="$line" <!--#if int($taskinfo[$schednum][5]) > 0 then 'checked="checked"' else ""#-->>
<button class="btn btn-default float-left"><span class="glyphicon glyphicon-trash"></span></button>
<div class="scheduleEntry">
<span class="time">$taskinfo[$schednum][1]:$taskinfo[$schednum][2]</span><span class="frequency">$taskinfo[$schednum][3]</span> <span class="darkred">$taskinfo[$schednum][4]</span>
</div>
</div>
</form>
<!--#set $schednum = $schednum+1#-->
<!--#end for#-->
<!--#else#-->
<div class="field-pair">
@@ -126,5 +132,18 @@ else:
\$('#hidden_arguments').show()
}*/
})
\$('[name="schedenabled"]').click(function() {
\$.ajax({
type: "POST",
url: "toggleSchedule",
data: {line: \$(this).val(), session: "$session" }
}).done(function() {
// Let us leave!
formWasSubmitted = true;
formHasChanged = false;
location.reload();
});
});
</script>
<!--#include $webdir + "/_inc_footer_uc.tmpl"#-->

View File

@@ -1,396 +1,553 @@
<!--#set global $pane="Servers"#-->
<!--#set global $help_uri="configuration/1.2/servers"#-->
<!--#set global $help_uri="configuration/2.3/servers"#-->
<!--#include $webdir + "/_inc_header_uc.tmpl"#-->
<!--
We need to find how many months we have recorded so far, so we
loop over all the dates to find the lowest value and then use
this to calculate the date-selector and maximum value per month.
-->
<!--#import json#-->
<!--#import datetime#-->
<!--#import sabnzbd.misc#-->
<!--#set month_names = [$T('January'), $T('February'), $T('March'), $T('April'), $T('May'), $T('June'), $T('July'), $T('August'), $T('September'), $T('October'), $T('November'), $T('December')] #-->
<!--#set min_date = datetime.date.today()#-->
<!--#set max_data_all = {}#-->
<!--#for $server in $servers #-->
<!--#if 'amounts' in $server#-->
<!--#set max_data_server = {}#-->
<!--#for date in $server['amounts'][4]#-->
<!--#set split_date = $date.split('-')#-->
<!--#set min_date = min(min_date, datetime.date(int(split_date[0]), int(split_date[1]), 1))#-->
<!--#set month_date = $date[:7]#-->
<!--#if $month_date not in $max_data_server#-->
<!--#set max_data_server[$month_date] = 0#-->
<!--#end if#-->
<!--#set max_data_server[$month_date] = max(max_data_server[$month_date], $server['amounts'][4][$date])#-->
<!--#end for#-->
<!--#for month_date in max_data_server#-->
<!--#if $month_date not in $max_data_all#-->
<!--#set max_data_all[$month_date] = 0#-->
<!--#end if#-->
<!--#set max_data_all[$month_date] = max(max_data_all[$month_date], max_data_server[$month_date])#-->
<!--#end for#-->
<!--#end if#-->
<!--#end for#-->
<!--#set months_recorded = list(sabnzbd.misc.monthrange(min_date, datetime.date.today()))#-->
<!--#$months_recorded.reverse()#-->
<script type="text/javascript">
// Define variable needed for the server-plots
var serverData = {}
</script>
<div class="colmask">
<form action="addServer" method="post" autocomplete="off" onsubmit="removeObfuscation();" novalidate>
<input type="hidden" name="session" value="$session" />
<div id="addServer">
<div class="padding alt">
<button type="button" class="btn btn-default" id="addServerButton"><span class="glyphicon glyphicon-plus"></span> $T('button-addServer')</button>
<div class="padding alt section">
<button type="button" class="btn btn-default" id="addServerButton"><span class="glyphicon glyphicon-plus"></span> $T('button-addServer')</button>
<label for="advanced-settings-button" class="form-control advanced-button ">
<input type="checkbox" id="advanced-settings-button" name="advanced-settings-button"> $T('button-advanced')
</label>
<!--#if $months_recorded#-->
<div class="advanced-buttonSeperator"></div>
<div class="chart-selector-container" title="$T('srv-bandwidth')">
<span class="glyphicon glyphicon-signal"></span>
<select name="chart-selector" id="chart-selector">
<!--#for $cur_date in months_recorded#-->
<!--#set month_date = '%d-%02d' % ($cur_date.year, $cur_date.month)#-->
<!--#if $month_date not in $max_data_all#-->
<!--#set max_data_all[$month_date] = 0#-->
<!--#end if#-->
<option value="$month_date" data-max="$max_data_all[$month_date]">$month_names[$cur_date.month-1] $cur_date.year</option>
<!--#end for#-->
</select>
</div>
<!--#end if#-->
</div>
<div class="section" id="addServerContent" style="display: none;">
<div class="col2">
<h3>$T('addServer') <a href="$helpuri$help_uri" target="_blank"><span class="glyphicon glyphicon-question-sign"></span></a></h3>
</div><!-- /col2 -->
<div class="col1">
<fieldset>
<div class="field-pair">
<label class="config" for="enable">$T('srv-enable')</label>
<input type="checkbox" name="enable" id="enable" value="1" checked="checked" />
<span class="desc">$T('srv-enable')</span>
</div>
<div class="field-pair">
<label class="config" for="host">$T('srv-host')</label>
<input type="text" name="host" id="host" />
</div>
<div class="field-pair">
<label class="config" for="port">$T('srv-port')</label>
<input type="number" name="port" id="port" size="8" value="119" />
</div>
<div class="field-pair <!--#if int($have_ssl) == 0 then "disabled" else ""#-->">
<label class="config" for="ssl">$T('srv-ssl')</label>
<input type="checkbox" name="ssl" id="ssl" value="1" <!--#if int($have_ssl) == 0 then "disabled=\"disabled\"" else ""#--> />
<span class="desc">$T('explain-ssl')</span>
</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" />
</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" />
</div>
<div class="field-pair">
<label class="config" for="connections">$T('srv-connections')</label>
<input type="number" name="connections" id="connections" min="0" max="100" value="8" />
</div>
<div class="field-pair">
<label class="config" for="priority">$T('srv-priority')</label>
<input type="number" name="priority" id="priority" min="0" max="100" /> <i>$T('explain-svrprio')</i>
</div>
<div class="field-pair advanced-settings">
<label class="config" for="retention">$T('srv-retention')</label>
<input type="number" name="retention" id="retention" min="0" /> <i>$T('days')</i>
</div>
<div class="field-pair advanced-settings">
<label class="config" for="timeout">$T('srv-timeout')</label>
<input type="number" name="timeout" id="timeout" min="30" /> <i>$T('seconds')</i>
</div>
<div class="field-pair <!--#if int($have_ssl_context) == 0 then "disabled" else ""#--> advanced-settings">
<label class="config" for="ssl_verify">$T('opt-ssl_verify')</label>
<select name="ssl_verify" id="ssl_verify" <!--#if int($have_ssl_context) == 0 then "disabled=\"disabled\"" else ""#-->>
<option value="0">$T('ssl_verify-disabled')</option>
<option value="1" selected>$T('ssl_verify-normal')</option>
<option value="2">$T('ssl_verify-strict')</option>
</select>
<span class="desc">$T('explain-ssl_verify').replace('. ', '.<br/>')</span>
</div>
<div class="field-pair advanced-settings">
<label class="config" for="send_group">$T('srv-send_group')</label>
<input type="checkbox" name="send_group" id="send_group" value="1" />
<span class="desc">$T('srv-explain-send_group')</span>
</div>
<div class="field-pair advanced-settings">
<label class="config" for="optional">$T('srv-optional')</label>
<input type="checkbox" name="optional" id="optional" value="1" />
<span class="desc">$T('explain-optional')</span>
</div>
<div class="field-pair advanced-settings">
<label class="config" for="categories">$T('srv-categories')</label>
<select name="categories" id="categories" multiple>
<!--#for $cat in $cats#-->
<option value="$cat" <!--#if $cat == "Default"#-->selected<!--#end if#-->>
<!--#if $cat == "Default" then $T('Default') else $cat#-->
</option>
<!--#end for#-->
</select>
<span class="desc">$T('srv-explain-categories')</span>
</div>
<div class="field-pair advanced-settings">
<label class="config" for="displayname">$T('srv-displayname')</label>
<input type="text" name="displayname" id="displayname" />
</div>
<div class="field-pair advanced-settings">
<label class="config" for="notes">$T('srv-notes')</label>
<textarea name="notes" id="notes" rows="3" cols="50"></textarea>
</div>
<div class="field-pair">
<button class="btn btn-default"><span class="glyphicon glyphicon-plus"></span> $T('button-addServer')</button>
<button class="btn btn-default advancedButton"><span class="glyphicon glyphicon-cog"></span> $T('button-advanced')</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 -->
</form>
<!--#set $prio_colors = ["#59cc33", "#3366cc","#7f33cc", "#cc33a6", "#cc3333"] #-->
<!--#set $cur_prio_color = -1 #-->
<!--#set $last_prio = -1 #-->
<!--#for $cur, $server in enumerate($servers) #-->
<form action="saveServer" method="post" class="fullform" autocomplete="off" novalidate>
<input type="hidden" name="session" value="$session" />
<input type="hidden" name="server" value="$server['name']" />
<div class="section <!--#if int($server['enable']) == 0 then 'server-disabled' else ""#-->">
<div class="col2 <!--#if int($server['enable']) == 0 then 'server-disabled' else ""#-->">
<h3>$server['displayname'] <a href="$helpuri$help_uri" target="_blank"><span class="glyphicon glyphicon-question-sign"></span></a></h3>
<!--#if int($server['enable']) != 0 #-->
<!--#if $last_prio != $server['priority'] and $cur_prio_color+1 < len($prio_colors) #-->
<!--#set $cur_prio_color = $cur_prio_color+1 #-->
<!--#set $last_prio = $server['priority'] #-->
<!--#end if#-->
<span class="label label-primary" style="background-color: $prio_colors[$cur_prio_color]">$server['priority']</span>
<span class="label label-primary" style="background-color: $prio_colors[$cur_prio_color]">$T('srv-priority'):</span>
<!--#end if#-->
<table><tr>
<td><input type="checkbox" class="toggleServerCheckbox" id="enable_$cur" name="$server['name']" value="1" <!--#if int($server['enable']) != 0 then 'checked="checked"' else ""#--> /></td>
<td><label for="enable_$cur">$T('enabled')</label></td>
</tr></table>
<button type="button" class="btn btn-default showserver"><span class="glyphicon glyphicon-pencil"></span> $T('showDetails')</button>
<button type="button" class="btn btn-default clrServer"><span class="glyphicon glyphicon-remove"></span> $T('button-clrServer')</button>
</div><!-- /col2 -->
<div class="col1" style="display:none;">
<input type="hidden" name="enable" id="enable$cur" value="$int($server['enable'])" />
<fieldset>
<div class="field-pair">
<label class="config" for="host$cur">$T('srv-host')</label>
<input type="text" name="host" id="host$cur" value="$server['host']" />
</div>
<div class="field-pair">
<label class="config" for="port$cur">$T('srv-port')</label>
<input type="number" name="port" id="port$cur" value="$server['port']" size="8" />
</div>
<div class="field-pair <!--#if int($have_ssl) == 0 then "disabled" else ""#-->">
<label class="config" for="ssl$cur">$T('srv-ssl')</label>
<input type="checkbox" name="ssl" id="ssl$cur" value="1" <!--#if int($server['ssl']) != 0 and int($have_ssl) == 1 then 'checked="checked"' else ""#--> <!--#if int($have_ssl) == 0 then "disabled=\"disabled\"" else ""#--> />
<span class="desc">$T('explain-ssl')</span>
</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" />
</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" />
</div>
<div class="field-pair">
<label class="config" for="connections$cur">$T('srv-connections')</label>
<input type="number" name="connections" id="connections$cur" value="$server['connections']" min="0" max="100" />
</div>
<div class="field-pair">
<label class="config" for="priority$cur">$T('srv-priority')</label>
<input type="number" name="priority" id="priority$cur" value="$server['priority']" min="0" max="100" /> <i>$T('explain-svrprio')</i>
</div>
<div class="field-pair advanced-settings">
<label class="config" for="retention$cur">$T('srv-retention')</label>
<input type="number" name="retention" id="retention$cur" value="$server['retention']" min="0" /> <i>$T('days')</i>
</div>
<div class="field-pair advanced-settings">
<label class="config" for="timeout$cur">$T('srv-timeout')</label>
<input type="number" name="timeout" id="timeout$cur" value="$server['timeout']" min="30" /> <i>$T('seconds')</i>
</div>
<div class="field-pair <!--#if int($have_ssl_context) == 0 then "disabled" else ""#--> advanced-settings">
<label class="config" for="ssl_verify$cur">$T('opt-ssl_verify')</label>
<select name="ssl_verify" id="ssl_verify$cur" <!--#if int($have_ssl_context) == 0 then "disabled=\"disabled\"" else ""#-->>
<option value="0" <!--#if $server['ssl_verify'] == 0 then 'selected="selected"' else ""#--> >$T('ssl_verify-disabled')</option>
<option value="1" <!--#if $server['ssl_verify'] == 1 then 'selected="selected"' else ""#--> >$T('ssl_verify-normal')</option>
<option value="2" <!--#if $server['ssl_verify'] == 2 then 'selected="selected"' else ""#--> >$T('ssl_verify-strict')</option>
</select>
<span class="desc">$T('explain-ssl_verify').replace('. ', '.<br/>')</span>
</div>
<div class="field-pair advanced-settings">
<label class="config" for="optional$cur">$T('srv-optional')</label>
<input type="checkbox" name="optional" id="optional$cur" value="1" <!--#if int($server['optional']) != 0 then 'checked="checked"' else ""#--> />
<span class="desc">$T('explain-optional')</span>
</div>
<div class="field-pair advanced-settings">
<label class="config" for="send_group$cur">$T('srv-send_group')</label>
<input type="checkbox" name="send_group" id="send_group$cur" value="1" <!--#if int($server['send_group']) != 0 then 'checked="checked"' else ""#--> />
<span class="desc">$T('srv-explain-send_group')</span>
</div>
<div class="field-pair advanced-settings">
<label class="config" for="categories$cur">$T('srv-categories')</label>
<select name="categories" id="categories$cur" multiple>
<!--#for $cat in $cats#-->
<option value="$cat" <!--#if $cat in $server['categories'] then 'selected' else ""#-->>
<!--#if $cat == "Default" then $T('Default') else $cat#-->
</option>
<!--#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 advanced-settings">
<label class="config" for="displayname$cur">$T('srv-displayname')</label>
<input type="text" name="displayname" id="displayname$cur" value="$server['displayname']" />
</div>
<div class="field-pair advanced-settings">
<label class="config" for="notes$cur">$T('srv-notes')</label>
<textarea name="notes" id="notes$cur" rows="3" cols="50">$server['notes']</textarea>
</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 advancedButton"><span class="glyphicon glyphicon-cog"></span> $T('button-advanced')</button>
<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;">
<!--#if 'amounts' in $server#-->
<b>$T('srv-bandwidth'):</b><br/>
$T('total'): $(server['amounts'][0])B<br/>
$T('today'): $(server['amounts'][3])B<br/>
$T('thisWeek'): $(server['amounts'][2])B<br/>
$T('thisMonth'): $(server['amounts'][1])B
<!--#end if#-->
</div>
</div><!-- /section -->
</form>
<!--#end for#-->
<div class="col1">
<form action="addServer" method="post" autocomplete="off" onsubmit="removeObfuscation();">
<input type="hidden" name="session" value="$session" />
<fieldset>
<div class="field-pair">
<label class="config" for="enable">$T('srv-enable')</label>
<input type="checkbox" name="enable" id="enable" value="1" checked="checked" />
<span class="desc">$T('srv-enable')</span>
</div>
<div class="field-pair">
<label class="config" for="host">$T('srv-host')</label>
<input type="text" name="host" id="host" required />
</div>
<div class="field-pair">
<label class="config" for="port">$T('srv-port')</label>
<input type="number" name="port" id="port" size="8" value="119" min="0" />
</div>
<div class="field-pair">
<label class="config" for="ssl">$T('srv-ssl')</label>
<input type="checkbox" name="ssl" id="ssl" value="1" />
<span class="desc">$T('explain-ssl')</span>
</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" />
</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" />
</div>
<div class="field-pair">
<label class="config" for="connections">$T('srv-connections')</label>
<input type="number" name="connections" id="connections" min="1" max="100" value="8" required />
</div>
<div class="field-pair">
<label class="config" for="priority">$T('srv-priority')</label>
<input type="number" name="priority" id="priority" min="0" max="99" /> <i>$T('explain-svrprio')</i>
</div>
<div class="field-pair advanced-settings">
<label class="config" for="retention">$T('srv-retention')</label>
<input type="number" name="retention" id="retention" min="0" /> <i>$T('days')</i>
</div>
<div class="field-pair advanced-settings">
<label class="config" for="timeout">$T('srv-timeout')</label>
<input type="number" name="timeout" id="timeout" min="20" max="240" /> <i>$T('seconds')</i>
</div>
<div class="field-pair <!--#if int($have_ssl_context) == 0 then "disabled" else ""#--> advanced-settings">
<label class="config" for="ssl_verify">$T('opt-ssl_verify')</label>
<select name="ssl_verify" id="ssl_verify" <!--#if int($have_ssl_context) == 0 then "disabled=\"disabled\"" else ""#-->>
<option value="2" selected>$T('ssl_verify-strict')</option>
<option value="1">$T('ssl_verify-normal')</option>
<option value="0">$T('ssl_verify-disabled')</option>
</select>
<span class="desc">$T('explain-ssl_verify').replace('. ', '.<br/>')</span>
</div>
<div class="field-pair advanced-settings">
<label class="config" for="ssl_ciphers">$T('opt-ssl_ciphers')</label>
<input type="text" name="ssl_ciphers" id="ssl_ciphers" />
<span class="desc">$T('explain-ssl_ciphers') <br>$T('readwiki')
<a href="${helpuri}advanced/ssl-ciphers" target="_blank">${helpuri}advanced/ssl-ciphers</a></span>
</div>
<div class="field-pair advanced-settings">
<label class="config" for="send_group">$T('srv-send_group')</label>
<input type="checkbox" name="send_group" id="send_group" value="1" />
<span class="desc">$T('srv-explain-send_group')</span>
</div>
<div class="field-pair advanced-settings">
<label class="config" for="optional">$T('srv-optional')</label>
<input type="checkbox" name="optional" id="optional" value="1" />
<span class="desc">$T('explain-optional')</span>
</div>
<div class="field-pair advanced-settings">
<label class="config" for="displayname">$T('srv-displayname')</label>
<input type="text" name="displayname" id="displayname" />
</div>
<div class="field-pair advanced-settings">
<label class="config" for="notes">$T('srv-notes')</label>
<textarea name="notes" id="notes" rows="3" cols="50"></textarea>
</div>
<div class="field-pair no-field-pair-bg">
<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>
</form>
</div>
</div>
</div><!-- /colmask -->
<!--#set $prio_colors = ["#59cc33", "#3366cc","#7f33cc", "#cc33a6", "#cc3333"] #-->
<!--#set $cur_prio_color = -1 #-->
<!--#set $last_prio = -1 #-->
<!--#for $cur, $server in enumerate($servers) #-->
<form action="saveServer" method="post" class="fullform" autocomplete="off">
<input type="hidden" name="session" value="$session" />
<input type="hidden" name="server" value="$server['name']" />
<input type="hidden" id="ajax" name="ajax" value=1 />
<div class="section <!--#if int($server['enable']) == 0 then 'server-disabled' else ""#-->">
<div class="col2 <!--#if int($server['enable']) == 0 then 'server-disabled' else ""#-->">
<h3>$server['displayname'] <a href="$helpuri$help_uri" target="_blank"><span class="glyphicon glyphicon-question-sign"></span></a></h3>
<!--#if int($server['enable']) != 0 #-->
<!--#if $last_prio != $server['priority'] and $cur_prio_color+1 < len($prio_colors) #-->
<!--#set $cur_prio_color = $cur_prio_color+1 #-->
<!--#set $last_prio = $server['priority'] #-->
<!--#end if#-->
<span class="label label-primary" style="background-color: $prio_colors[$cur_prio_color]">$server['priority']</span>
<span class="label label-primary" style="background-color: $prio_colors[$cur_prio_color]">$T('srv-priority'):</span>
<!--#end if#-->
<table>
<tr>
<td><input type="checkbox" class="toggleServerCheckbox" id="enable_$cur" name="$server['name']" value="1" <!--#if int($server['enable']) != 0 then 'checked="checked"' else ""#--> /></td>
<td><label for="enable_$cur">$T('enabled')</label></td>
</tr>
</table>
<button type="button" class="btn btn-default showserver"><span class="glyphicon glyphicon-pencil"></span> $T('showDetails')</button>
<button type="button" class="btn btn-default clrServer"><span class="glyphicon glyphicon-remove"></span> $T('button-clrServer')</button>
</div>
<div class="col1" style="display:none;">
<input type="hidden" name="enable" id="enable$cur" value="$int($server['enable'])" />
<fieldset>
<div class="field-pair">
<label class="config" for="host$cur">$T('srv-host')</label>
<input type="text" name="host" id="host$cur" value="$server['host']" required />
</div>
<div class="field-pair">
<label class="config" for="port$cur">$T('srv-port')</label>
<input type="number" name="port" id="port$cur" value="$server['port']" size="8" min="0" required />
</div>
<div class="field-pair">
<label class="config" for="ssl$cur">$T('srv-ssl')</label>
<input type="checkbox" name="ssl" id="ssl$cur" value="1" <!--#if int($server['ssl']) != 0 then 'checked="checked"' else ""#--> />
<span class="desc">$T('explain-ssl')</span>
</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" />
</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" />
</div>
<div class="field-pair">
<label class="config" for="connections$cur">$T('srv-connections')</label>
<input type="number" name="connections" id="connections$cur" value="$server['connections']" min="1" max="100" required />
</div>
<div class="field-pair">
<label class="config" for="priority$cur">$T('srv-priority')</label>
<input type="number" name="priority" id="priority$cur" value="$server['priority']" min="0" max="99" required /> <i>$T('explain-svrprio')</i>
</div>
<div class="field-pair advanced-settings">
<label class="config" for="retention$cur">$T('srv-retention')</label>
<input type="number" name="retention" id="retention$cur" value="$server['retention']" min="0" required /> <i>$T('days')</i>
</div>
<div class="field-pair advanced-settings">
<label class="config" for="timeout$cur">$T('srv-timeout')</label>
<input type="number" name="timeout" id="timeout$cur" value="$server['timeout']" min="20" max="240" required /> <i>$T('seconds')</i>
</div>
<div class="field-pair <!--#if int($have_ssl_context) == 0 then "disabled" else ""#--> advanced-settings">
<label class="config" for="ssl_verify$cur">$T('opt-ssl_verify')</label>
<select name="ssl_verify" id="ssl_verify$cur" <!--#if int($have_ssl_context) == 0 then "disabled=\"disabled\"" else ""#-->>
<option value="2" <!--#if $server['ssl_verify'] == 2 then 'selected="selected"' else ""#--> >$T('ssl_verify-strict')</option>
<option value="1" <!--#if $server['ssl_verify'] == 1 then 'selected="selected"' else ""#--> >$T('ssl_verify-normal')</option>
<option value="0" <!--#if $server['ssl_verify'] == 0 then 'selected="selected"' else ""#--> >$T('ssl_verify-disabled')</option>
</select>
<span class="desc">$T('explain-ssl_verify').replace('. ', '.<br/>')</span>
</div>
<div class="field-pair advanced-settings">
<label class="config" for="ssl_ciphers">$T('opt-ssl_ciphers')</label>
<input type="text" name="ssl_ciphers" id="ssl_ciphers" value="$server['ssl_ciphers']" />
<span class="desc">$T('explain-ssl_ciphers') <br>$T('readwiki')
<a href="${helpuri}advanced/ssl-ciphers" target="_blank">${helpuri}advanced/ssl-ciphers</a></span>
</div>
<div class="field-pair advanced-settings">
<label class="config" for="optional$cur">$T('srv-optional')</label>
<input type="checkbox" name="optional" id="optional$cur" value="1" <!--#if int($server['optional']) != 0 then 'checked="checked"' else ""#--> />
<span class="desc">$T('explain-optional')</span>
</div>
<div class="field-pair advanced-settings">
<label class="config" for="send_group$cur">$T('srv-send_group')</label>
<input type="checkbox" name="send_group" id="send_group$cur" value="1" <!--#if int($server['send_group']) != 0 then 'checked="checked"' else ""#--> />
<span class="desc">$T('srv-explain-send_group')</span>
</div>
<div class="field-pair advanced-settings">
<label class="config" for="displayname$cur">$T('srv-displayname')</label>
<input type="text" name="displayname" id="displayname$cur" value="$server['displayname']" />
</div>
<div class="field-pair advanced-settings">
<label class="config" for="notes$cur">$T('srv-notes')</label>
<textarea name="notes" id="notes$cur" rows="3" cols="50">$server['notes']</textarea>
</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 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>
<div class="col1" style="display:block;">
<!--#if 'amounts' in $server#-->
<div class="server-amounts-text">
<b>$T('srv-bandwidth'):</b><br/>
$T('total'): $(server['amounts'][0])B<br/>
$T('today'): $(server['amounts'][3])B<br/>
$T('thisWeek'): $(server['amounts'][2])B<br/>
$T('thisMonth'): $(server['amounts'][1])B<br/>
<span id="server-data-label-${cur}"></span>: <span id="server-data-value-${cur}"></span>
</div>
<div class="server-chart" data-serverid="${cur}"s>
<div id="server-chart-${cur}" class="ct-chart"></div>
</div>
<script type="text/javascript">
// Server data
serverData[${cur}] = <!--#echo json.dumps($server['amounts'][4])#-->
</script>
<!--#end if#-->
</div>
</div>
</form>
<!--#end for#-->
</div>
<script type="text/javascript" src="${root}staticcfg/js/chartist.min.js"></script>
<script type="text/javascript" src="${root}staticcfg/js/filesize.min.js"></script>
<script type="text/javascript">
\$(document).ready(function(){
// Exception when change of priority, reload
\$('input[name="priority"], input[name="displayname"]').on('change', function() {
\$('.fullform').submit(function() {
// Skip the fancy stuff, just submit
this.submit()
})
})
/**
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() && \$(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()
// Standardize chart options
var chartOptions = {
fullWidth: true,
showArea: true,
axisX: {
labelOffset: {
x: -5
},
showGrid: false
},
axisY: {
labelOffset: {
y: 7
},
scaleMinSpace: 30
},
chartPadding: {
top: 9,
bottom: 0,
left: 30,
right: 20
}
}
\$('select[name="categories"]').on('change', checkServerCats)
checkServerCats()
function showCharts() {
// This month
var theMonth = \$('#chart-selector').val()
var thisDay = new Date()
// What month are we doing?
var inputDate = new Date(theMonth+'-01')
var baseDate = new Date(inputDate.getUTCFullYear(), inputDate.getUTCMonth(), 1)
var maxDaysInMonth = new Date(baseDate.getFullYear(), baseDate.getMonth()+1, 0).getDate()
// Set the new maximum
chartOptions.axisY.high = \$('#chart-selector :selected').data('max');
chartOptions.axisY.low = 0
// For each chart
\$('.server-chart').each(function(i, elemn) {
var server_id = \$(elemn).data('serverid')
// Fill the data array
var data = {
labels: [],
series: [[]]
};
var totalThisMonth = 0
for(var i = 1; i < maxDaysInMonth+1; i++) {
// Add X-label
if(i % 3 == 1) {
data['labels'].push(i)
} else {
data['labels'].push(NaN)
}
// Get formatted date
baseDate.setDate(i)
var dateCheck = toFormattedDate(baseDate)
// Add data if we have it
if(dateCheck in serverData[server_id]) {
data['series'][0].push(serverData[server_id][dateCheck])
totalThisMonth += serverData[server_id][dateCheck]
} else if(thisDay.getYear() == baseDate.getYear() && thisDay.getMonth() == baseDate.getMonth() && thisDay.getDate() < i) {
data['series'][0].push(NaN)
} else {
data['series'][0].push(0)
}
}
// Update the text value
\$('#server-data-label-' + server_id).text(\$('#chart-selector :selected').text())
\$('#server-data-value-' + server_id).text(filesize(totalThisMonth, {round: 1}))
// Show the chart
chart = new Chartist.Line('#server-chart-'+server_id, data, chartOptions);
chart.on('created', function(context) {
// Make sure to add this as the first child so it's at the bottom
context.svg.elem('rect', {
x: context.chartRect.x1,
y: context.chartRect.y2-1,
width: context.chartRect.width(),
height: context.chartRect.height()+2,
fill: 'none',
stroke: '#B9B9B9',
'stroke-width': '1px'
}, '', context.svg, true)
\$('#server-chart-'+server_id+' .ct-label.ct-vertical').each(function(index, elmn) {
elmn.innerHTML = filesize(elmn.innerHTML, {round: 1}).replace(' ','')
})
});
})
}
// Need to mitigate timezone effects!
function toFormattedDate(date) {
var local = new Date(date);
local.setMinutes(date.getMinutes() - date.getTimezoneOffset());
return local.toJSON().slice(0, 10);
}
/**
Click events
When finished loading
**/
\$('.showserver').click(function () {
if(\$(this).parent().hasClass('server-disabled')) {
\$(this).parent().parent().toggleClass('server-disabled')
}
\$(this).parent().next().toggle();
\$(this).parent().next().next().toggle();
if (\$(this).attr("value") == "$T('showDetails')") {
\$(this).attr("value", "$T('hideDetails')");
} else {
\$(this).attr("value", "$T('showDetails')");
}
});
\$(document).ready(function(){
// Exception when change of priority, reload
\$('input[name="priority"], input[name="displayname"]').on('change', function() {
\$('.fullform').submit(function() {
// No ajax this time
\$('input[name="ajax"]').val('')
// Skip the fancy stuff, just submit
this.submit()
})
})
\$('#addServerButton').click(function(){
\$('#addServer').hide();
\$('#addServerContent').show();
});
/**
Update charts when changed
**/
\$('#chart-selector').on('change', function(elemn) {
showCharts()
\$('[name="ssl"]').click(function() {
// Use CSS transitions to do some highlighting
var portBox = \$(this).parent().parent().find('[name="port"]')
if(this.checked) {
// Enabled SSL change port when not already a custom port
if(portBox.val() == '119') {
portBox.val('563')
portBox.addClass('port-highlight')
// Lets us leave (needs to be called after the change event)
setTimeout(function() {
formWasSubmitted = true;
formHasChanged = false;
}, 100)
})
// And on page-load
showCharts()
/**
Click events
**/
\$('.showserver').click(function () {
if(\$(this).parent().hasClass('server-disabled')) {
\$(this).parent().parent().toggleClass('server-disabled')
}
} else {
// Remove SSL port
if(portBox.val() == '563') {
portBox.val('119')
portBox.addClass('port-highlight')
}
}
setTimeout(function() { portBox.removeClass('port-highlight') }, 2000)
})
\$('.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')
\$.ajax({
type: "POST",
url: "../../tapi",
data: "mode=config&output=json&name=test_server&" + \$(this).parents('form:first').serialize()
}).then(function(data) {
// Let's replace the link
msg = data.value.message.replace('https://sabnzbd.org/certificate-errors', '<a href="https://sabnzbd.org/certificate-errors" class="alert-link" target="_blank">https://sabnzbd.org/certificate-errors</a>')
// Fill the box and enable the button
resultBox.removeClass('alert-success alert-danger').show()
resultBox.html(msg)
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> ')
\$(this).parent().next().toggle();
\$(this).parent().next().next().toggle();
if (\$(this).text().indexOf("$T('showDetails')") > 0) {
\$(this).html(\$(this).html().replace("$T('showDetails')", "$T('hideDetails')"));
} else {
resultBox.addClass('alert-danger')
resultBox.prepend('<span class="glyphicon glyphicon-exclamation-sign"></span> ')
\$(this).html(\$(this).html().replace("$T('hideDetails')", "$T('showDetails')"));
}
// Add coloring
addRowColor()
});
\$('#addServerButton').click(function(){
\$('#addServerContent').show();
// Add coloring
addRowColor()
});
\$('[name="ssl"]').click(function() {
// Use CSS transitions to do some highlighting
var portBox = \$(this).parent().parent().find('[name="port"]')
if(this.checked) {
// Enabled SSL change port when not already a custom port
if(portBox.val() == '119') {
portBox.val('563')
portBox.addClass('port-highlight')
}
} else {
// Remove SSL port
if(portBox.val() == '563') {
portBox.val('119')
portBox.addClass('port-highlight')
}
}
setTimeout(function() { portBox.removeClass('port-highlight') }, 2000)
})
// Testing servers
\$('.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')
\$.ajax({
type: "POST",
url: "../../tapi",
data: "mode=config&output=json&name=test_server&" + \$(this).parents('form:first').serialize()
}).then(function(data) {
// Let's replace the link
msg = data.value.message.replace('https://sabnzbd.org/certificate-errors', '<a href="https://sabnzbd.org/certificate-errors" class="alert-link" target="_blank">https://sabnzbd.org/certificate-errors</a>')
msg = msg.replace('-', '<br>')
// Fill the box and enable the button
resultBox.removeClass('alert-success alert-danger').show()
resultBox.html(msg)
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> ')
}
});
});
\$('.delServer').click(function(){
if( confirm("$T('Plush-confirm')") ) {
\$(this).parents('form:first').attr('action','delServer').submit();
// Let us leave!
formWasSubmitted = true;
formHasChanged = false;
setTimeout(function() { location.reload(); }, 500)
}
return false;
});
\$('.clrServer').click(function(){
if( confirm("$T('Plush-confirm')") ) {
\$(this).parents('form:first').attr('action','clrServer').submit();
// Let us leave!
formWasSubmitted = true;
formHasChanged = false;
setTimeout(function() { location.reload(); }, 500)
}
return false;
});
\$('.toggleServerCheckbox').click(function(){
var whichServer = \$(this).attr("name");
\$.ajax({
type: "POST",
url: "toggleServer",
data: {server: whichServer, session: "$session" }
}).done(function() {
// Let us leave!
formWasSubmitted = true;
formHasChanged = false;
setTimeout(function() { location.reload(); }, 100)
});
});
});
\$('.delServer').click(function(){
if( confirm("$T('Plush-confirm')") ) {
\$(this).parents('form:first').attr('action','delServer').submit();
// Let us leave!
formWasSubmitted = true;
formHasChanged = false;
setTimeout(function() { location.reload(); }, 500)
}
return false;
});
\$('.clrServer').click(function(){
if( confirm("$T('Plush-confirm')") ) {
\$(this).parents('form:first').attr('action','clrServer').submit();
// Let us leave!
formWasSubmitted = true;
formHasChanged = false;
setTimeout(function() { location.reload(); }, 500)
}
return false;
});
\$('.toggleServerCheckbox').click(function(){
var whichServer = \$(this).attr("name");
\$.ajax({
type: "POST",
url: "toggleServer",
data: {server: whichServer, session: "$session" }
}).done(function() {
// Let us leave!
formWasSubmitted = true;
formHasChanged = false;
setTimeout(function() { location.reload(); }, 100)
});
});
});
</script>
<!--#include $webdir + "/_inc_footer_uc.tmpl"#-->

View File

@@ -1,5 +1,5 @@
<!--#set global $pane="Sorting"#-->
<!--#set global $help_uri="configuration/1.2/sorting"#-->
<!--#set global $help_uri="configuration/2.3/sorting"#-->
<!--#include $webdir + "/_inc_header_uc.tmpl"#-->
<div class="colmask">
@@ -12,7 +12,7 @@
<p>
<b>$T('affectedCat')</b><br/>
<select name="tv_cat" multiple="multiple" class="multiple_cats">
<!--#for $ct in $cat_list#-->
<!--#for $ct in $categories#-->
<option value="$ct" <!--#if $ct in $tv_categories then 'selected="selected"' else ""#--> >$Tspec($ct)</option>
<!--#end for#-->
</select>
@@ -40,6 +40,8 @@
<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')" />
<br>
<input type="button" class="btn btn-default" onclick="tvSet('%dn.%ext')" value="$T('button-FileLikeFolder')" />
</div>
</div>
<div id="previewtv" class="example">
@@ -51,7 +53,7 @@
<label class="config">$T('sortResult')</label> <span class="desc path" id="previewtv-result">&nbsp;</span>
</div>
</div>
<div class="field-pair">
<div class="field-pair no-field-pair-bg">
<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>
<table id="Key1" class="Key">
@@ -139,9 +141,9 @@
<td>$T("sort-File")</td>
</tr>
<tr>
<td class="align-right"><b>$T('orgDirname'):</b></td>
<td class="align-right"><b>$T('orgJobname'):</b></td>
<td>%dn</td>
<td>$T("sort-Folder")</td>
<td></td>
</tr>
<tr>
<td class="align-right"><b>$T('lowercase'):</b></td>
@@ -165,7 +167,7 @@
<p>
<b>$T('affectedCat')</b><br/>
<select name="movie_cat" multiple="multiple" class="multiple_cats">
<!--#for $ct in $cat_list#-->
<!--#for $ct in $categories#-->
<option value="$ct" <!--#if $ct in $movie_categories then 'selected="selected"' else ""#--> >$Tspec($ct)</option>
<!--#end for#-->
</select>
@@ -198,7 +200,8 @@
<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" class="btn btn-default" onclick="movieSet('%0decade/%title (%y).%ext',' CD%1');movieExtraFolder(true)" value="$T('decade')" />
<input type="button" class="btn btn-default" onclick="movieSet('%dn.%ext')" value="$T('button-FileLikeFolder')" />
</div>
</div>
<div id="previewmovie" class="example">
@@ -210,7 +213,7 @@
<label class="config">$T('sortResult')</label> <span class="desc path" id="previewmovie-result">&nbsp;</span>
</div>
</div>
<div class="field-pair">
<div class="field-pair no-field-pair-bg">
<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>
<table id="Key2" class="Key">
@@ -263,9 +266,9 @@
<td>$T('sort-File')</td>
</tr>
<tr>
<td class="align-right"><b>$T('orgDirname'):</b></td>
<td class="align-right"><b>$T('orgJobname'):</b></td>
<td>%dn</td>
<td>$T("sort-Folder")</td>
<td></td>
</tr>
<tr>
<td class="align-right"><b>$T('lowercase'):</b></td>
@@ -303,7 +306,7 @@
<p>
<b>$T('affectedCat')</b><br/>
<select name="date_cat" multiple="multiple" class="multiple_cats">
<!--#for $ct in $cat_list#-->
<!--#for $ct in $categories#-->
<option value="$ct" <!--#if $ct in $date_categories then 'selected="selected"' else ""#--> >$Tspec($ct)</option>
<!--#end for#-->
</select>
@@ -328,7 +331,9 @@
<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')" />
<br>
<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" class="btn btn-default" onclick="dateSet('%dn.%ext')" value="$T('button-FileLikeFolder')" />
</div>
</div>
<div id="previewdate" class="example">
@@ -340,7 +345,7 @@
<label class="config">$T('sortResult')</label> <span class="desc path" id="previewdate-result">&nbsp;</span>
</div>
</div>
<div class="field-pair">
<div class="field-pair no-field-pair-bg">
<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>
<table id="Key3" class="Key">
@@ -407,6 +412,11 @@
<td>%fn</td>
<td>$T('sort-File')</td>
</tr>
<tr>
<td class="align-right"><b>$T('orgJobname'):</b></td>
<td>%dn</td>
<td></td>
</tr>
<tr>
<td class="align-right"><b>$T('lowercase'):</b></td>
<td>{$T('TEXT')}</td>
@@ -487,7 +497,7 @@
\$.ajax({
type: "GET",
url: "../../tapi",
data: {mode:'eval_sort', value: 'generic', name: \$('#moviesamplename').val(), title: \$moviesortstring, movieextra: \$('#movieextra').val(), apikey: '$session', output: 'json' },
data: {mode:'eval_sort', value: 'movie', name: \$('#moviesamplename').val(), title: \$moviesortstring, movieextra: \$('#movieextra').val(), apikey: '$session', output: 'json' },
success: function(data){
\$('#previewmovie-result').removeClass("loading failure").html(data.result);
},

View File

@@ -1,5 +1,5 @@
<!--#set global $pane="Special"#-->
<!--#set global $help_uri="configuration/1.2/special"#-->
<!--#set global $help_uri="configuration/2.3/special"#-->
<!--#include $webdir + "/_inc_header_uc.tmpl"#-->
<div class="colmask">

View File

@@ -1,11 +1,16 @@
<!--#set global $pane="Switches"#-->
<!--#set global $help_uri="configuration/1.2/switches"#-->
<!--#set global $help_uri="configuration/2.3/switches"#-->
<!--#include $webdir + "/_inc_header_uc.tmpl"#-->
<div class="colmask">
<div class="padding alt section">
<label for="advanced-settings-button" class="form-control advanced-button ">
<input type="checkbox" id="advanced-settings-button" name="advanced-settings-button"> $T('button-advanced')
</label>
</div>
<form action="saveSwitches" method="post" name="fullform" class="fullform" autocomplete="off">
<input type="hidden" id="session" name="session" value="$session" />
<div class="section">
<div class="section advanced-settings">
<div class="col2">
<h3>$T('swtag-server') <a href="$helpuri$help_uri#toc1" target="_blank"><span class="glyphicon glyphicon-question-sign"></span></a></h3>
</div><!-- /col2 -->
@@ -20,23 +25,11 @@
</select>
<span class="desc">$T('explain-load_balancing')</span>
</div>
<div class="field-pair <!--#if int($have_ssl) == 0 then "disabled" else ""#-->">
<label class="config" for="ssl_ciphers">$T('opt-ssl_ciphers')</label>
<input type="text" name="ssl_ciphers" id="ssl_ciphers" value="$ssl_ciphers"<!--#if int($have_ssl) == 0 then "disabled=\"disabled\"" else ""#--> />
<span class="desc">$T('explain-ssl_ciphers') <br>$T('readwiki')
<a href="${helpuri}advanced/ssl-ciphers" target="_blank">${helpuri}advanced/ssl-ciphers</a></span>
</div>
<div class="field-pair">
<label class="config" for="max_art_tries">$T('opt-max_art_tries')</label>
<input type="number" name="max_art_tries" id="max_art_tries" value="$max_art_tries" min="2" max="2000" />
<span class="desc">$T('explain-max_art_tries')</span>
</div>
<div class="field-pair">
<label class="config" for="max_art_opt">$T('opt-max_art_opt')</label>
<input type="checkbox" name="max_art_opt" id="max_art_opt" value="1" <!--#if int($max_art_opt) > 0 then 'checked="checked"' else ""#--> />
<span class="desc">$T('explain-max_art_opt')</span>
</div>
<div class="field-pair">
<label class="config" for="auto_disconnect">$T('opt-auto_disconnect')</label>
<input type="checkbox" name="auto_disconnect" id="auto_disconnect" value="1" <!--#if int($auto_disconnect) > 0 then 'checked="checked"' else ""#--> />
@@ -58,7 +51,7 @@
<div class="field-pair">
<label class="config" for="pre_script">$T('opt-pre_script')</label>
<select name="pre_script" id="pre_script">
<!--#for $sc in $script_list#-->
<!--#for $sc in $scripts#-->
<!--#if $sc.lower() == $pre_script.lower()#-->
<option value="$sc" selected="selected">$Tspec($sc)</option>
<!--#else#-->
@@ -73,12 +66,12 @@
<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">
<div class="field-pair advanced-settings">
<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 ""#--> />
<span class="desc">$T('explain-top_only')</span>
</div>
<div class="field-pair">
<div class="field-pair advanced-settings">
<label class="config" for="pre_check">$T('opt-pre_check')</label>
<input type="checkbox" name="pre_check" id="pre_check" value="1" <!--#if int($pre_check) > 0 then 'checked="checked"' else ""#--> />
<span class="desc">$T('explain-pre_check')</span>
@@ -92,6 +85,7 @@
<label class="config" for="no_dupes">$T('opt-no_dupes')</label>
<select name="no_dupes" id="no_dupes">
<option value="0" <!--#if int($no_dupes) == 0 then 'selected="selected"' else ""#--> >$T('nodupes-off')</option>
<option value="4" <!--#if int($no_dupes) == 4 then 'selected="selected"' else ""#--> >$T('nodupes-tag')</option>
<option value="2" <!--#if int($no_dupes) == 2 then 'selected="selected"' else ""#--> >$T('nodupes-pause')</option>
<option value="3" <!--#if int($no_dupes) == 3 then 'selected="selected"' else ""#--> >$T('nodupes-fail')</option>
<option value="1" <!--#if int($no_dupes) == 1 then 'selected="selected"' else ""#--> >$T('nodupes-ignore')</option>
@@ -102,12 +96,18 @@
<label class="config" for="no_series_dupes">$T('opt-no_series_dupes')</label>
<select name="no_series_dupes" id="no_series_dupes">
<option value="0" <!--#if int($no_series_dupes) == 0 then 'selected="selected"' else ""#--> >$T('nodupes-off')</option>
<option value="4" <!--#if int($no_series_dupes) == 4 then 'selected="selected"' else ""#--> >$T('nodupes-tag')</option>
<option value="2" <!--#if int($no_series_dupes) == 2 then 'selected="selected"' else ""#--> >$T('nodupes-pause')</option>
<option value="3" <!--#if int($no_series_dupes) == 3 then 'selected="selected"' else ""#--> >$T('nodupes-fail')</option>
<option value="1" <!--#if int($no_series_dupes) == 1 then 'selected="selected"' else ""#--> >$T('nodupes-ignore')</option>
</select>
<span class="desc">$T('explain-no_series_dupes')</span>
</div>
<div class="field-pair advanced-settings">
<label class="config" for="series_propercheck">$T('opt-series_propercheck')</label>
<input type="checkbox" name="series_propercheck" id="series_propercheck" value="1" <!--#if int($series_propercheck) > 0 then 'checked="checked"' else ""#--> />
<span class="desc">$T('explain-series_propercheck')</span>
</div>
<div class="field-pair">
<label class="config" for="pause_on_pwrar">$T('opt-pause_on_pwrar')</label>
<select name="pause_on_pwrar" id="pause_on_pwrar">
@@ -117,6 +117,11 @@
</select>
<span class="desc">$T('explain-pause_on_pwrar')</span>
</div>
<div class="field-pair">
<label class="config" for="unwanted_extensions">$T('opt-unwanted_extensions')</label>
<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="action_on_unwanted_extensions">$T('opt-action_on_unwanted_extensions')</label>
<select name="action_on_unwanted_extensions" id="action_on_unwanted_extensions">
@@ -126,16 +131,16 @@
</select>
<span class="desc">$T('explain-action_on_unwanted_extensions')</span>
</div>
<div class="field-pair">
<label class="config" for="unwanted_extensions">$T('opt-unwanted_extensions')</label>
<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">
<div class="field-pair advanced-settings">
<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 ""#--> />
<span class="desc">$T('explain-auto_sort')</span>
</div>
<div class="field-pair">
<label class="config" for="direct_unpack">$T('opt-direct_unpack')</label>
<input type="checkbox" name="direct_unpack" id="direct_unpack" value="1" <!--#if int($direct_unpack) > 0 then 'checked="checked"' else ""#--> />
<span class="desc">$T('explain-direct_unpack').replace('. ', '.<br/>')</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>
@@ -154,85 +159,97 @@
<input type="checkbox" name="pause_on_post_processing" id="pause_on_post_processing" value="1" <!--#if int($pause_on_post_processing) > 0 then 'checked="checked"' else ""#--> />
<span class="desc">$T('explain-pause_on_post_processing')</span>
</div>
<div class="field-pair">
<div class="field-pair advanced-settings">
<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>
</div>
<div class="field-pair">
<label class="config" for="quick_check">$T('opt-quick_check')</label>
<input type="checkbox" name="quick_check" id="quick_check" value="1" <!--#if int($quick_check) > 0 then 'checked="checked"' else ""#--> />
<span class="desc">$T('explain-quick_check')</span>
</div>
<!--#if $have_multicore#-->
<div class="field-pair">
<label class="config" for="par2_multicore">$T('opt-par2_multicore')</label>
<input type="checkbox" name="par2_multicore" id="par2_multicore" value="1" <!--#if int($par2_multicore) > 0 then 'checked="checked"' else ""#--> />
<span class="desc">$T('explain-par2_multicore')</span>
</div>
<!--#end if#-->
<div class="field-pair">
<label class="config" for="par_option">$T('opt-par_option')</label>
<input type="text" name="par_option" id="par_option" value="$par_option" />
<span class="desc">$T('explain-par_option')</span>
</div>
<div class="field-pair">
<label class="config" for="sfv_check">$T('opt-sfv_check')</label>
<input type="checkbox" name="sfv_check" id="sfv_check" value="1" <!--#if int($sfv_check) > 0 then 'checked="checked"' else ""#--> />
<span class="desc">$T('explain-sfv_check')</span>
</div>
<div class="field-pair">
<label class="config" for="safe_postproc">$T('opt-safe_postproc')</label>
<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">
<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 ""#--> />
<span class="desc">$T('explain-enable_recursive')</span>
</div>
<div class="field-pair">
<label class="config" for="flat_unpack">$T('opt-flat_unpack')</label>
<input type="checkbox" name="flat_unpack" id="flat_unpack" value="1" <!--#if int($flat_unpack) > 0 then 'checked="checked"' else ""#--> />
<span class="desc">$T('explain-flat_unpack')</span>
</div>
<div class="field-pair">
<label class="config" for="overwrite_files">$T('opt-overwrite_files')</label>
<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="script_can_fail">$T('opt-script_can_fail')</label>
<input type="checkbox" name="script_can_fail" id="script_can_fail" value="1" <!--#if int($script_can_fail) > 0 then 'checked="checked"' else ""#--> />
<span class="desc">$T('explain-script_can_fail')</span>
</div>
<div class="field-pair">
<label class="config" for="new_nzb_on_failure">$T('opt-new_nzb_on_failure')</label>
<input type="checkbox" name="new_nzb_on_failure" id="new_nzb_on_failure" value="1" <!--#if int($new_nzb_on_failure) > 0 then 'checked="checked"' else ""#--> />
<span class="desc">$T('explain-new_nzb_on_failure')</span>
</div>
<!--#if not $nt#-->
<div class="field-pair <!--#if not $have_nice then "disabled" else "" #-->">
<div class="field-pair advanced-settings <!--#if not $have_nice then "disabled" else "" #-->">
<label class="config" for="nice">$T('opt-nice')</label>
<input type="text" name="nice" id="nice" value="$nice" <!--#if not $have_nice then 'readonly="readonly" disabled="disabled"' else "" #--> />
<span class="desc">$T('explain-nice')</span>
</div>
<div class="field-pair <!--#if not $have_ionice then "disabled" else "" #-->">
<div class="field-pair advanced-settings <!--#if not $have_ionice then "disabled" else "" #-->">
<label class="config" for="ionice">$T('opt-ionice')</label>
<input type="text" name="ionice" id="ionice" value="$ionice" <!--#if not $have_ionice then 'readonly="readonly" disabled="disabled"' else "" #--> />
<span class="desc">$T('explain-ionice')</span>
</div>
<!--#else#-->
<div class="field-pair advanced-settings">
<label class="config" for="win_process_prio">$T('opt-win_process_prio')</label>
<select name="win_process_prio" id="win_process_prio">
<option value="4" <!--#if int($win_process_prio) == 4 then 'selected="selected"' else ""#-->>$T('win_process_prio-high')</option>
<option value="3" <!--#if int($win_process_prio) == 3 then 'selected="selected"' else ""#-->>$T('win_process_prio-normal')</option>
<option value="2" <!--#if int($win_process_prio) == 2 then 'selected="selected"' else ""#-->>$T('win_process_prio-low')</option>
<option value="1" <!--#if int($win_process_prio) == 1 then 'selected="selected"' else ""#-->>$T('win_process_prio-idle')</option>
</select>
<span class="desc">$T('explain-win_process_prio')</span>
</div>
<!--#end if#-->
<div class="field-pair advanced-settings">
<label class="config" for="par_option">$T('opt-par_option')</label>
<input type="text" name="par_option" id="par_option" value="$par_option" />
<span class="desc">$T('explain-par_option')</span>
</div>
<div class="field-pair advanced-settings">
<label class="config" for="sfv_check">$T('opt-sfv_check')</label>
<input type="checkbox" name="sfv_check" id="sfv_check" value="1" <!--#if int($sfv_check) > 0 then 'checked="checked"' else ""#--> />
<span class="desc">$T('explain-sfv_check')</span>
</div>
<div class="field-pair advanced-settings">
<label class="config" for="safe_postproc">$T('opt-safe_postproc')</label>
<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 advanced-settings">
<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 ""#--> />
<span class="desc">$T('explain-enable_recursive')</span>
</div>
<div class="field-pair advanced-settings">
<label class="config" for="flat_unpack">$T('opt-flat_unpack')</label>
<input type="checkbox" name="flat_unpack" id="flat_unpack" value="1" <!--#if int($flat_unpack) > 0 then 'checked="checked"' else ""#--> />
<span class="desc">$T('explain-flat_unpack')</span>
</div>
<div class="field-pair advanced-settings">
<label class="config" for="script_can_fail">$T('opt-script_can_fail')</label>
<input type="checkbox" name="script_can_fail" id="script_can_fail" value="1" <!--#if int($script_can_fail) > 0 then 'checked="checked"' else ""#--> />
<span class="desc">$T('explain-script_can_fail')</span>
</div>
<div class="field-pair advanced-settings">
<label class="config" for="new_nzb_on_failure">$T('opt-new_nzb_on_failure')</label>
<input type="checkbox" name="new_nzb_on_failure" id="new_nzb_on_failure" value="1" <!--#if int($new_nzb_on_failure) > 0 then 'checked="checked"' else ""#--> />
<span class="desc">$T('explain-new_nzb_on_failure')</span>
</div>
<div class="field-pair">
<label class="config" for="ignore_samples">$T('opt-ignore_samples')</label>
<input type="checkbox" name="ignore_samples" id="ignore_samples" value="1" <!--#if int($ignore_samples) > 0 then 'checked="checked"' else ""#--> />
<span class="desc">$T('explain-ignore_samples') $T('igsam-del').</span>
</div>
<div class="field-pair advanced-settings">
<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 ""#--> />
<span class="desc">$T('explain-enable_meta').replace('. ', '.<br/>')</span>
</div>
<div class="field-pair">
<label class="config" for="cleanup_list">$T('opt-cleanup_list')</label>
<input type="text" name="cleanup_list" id="cleanup_list" value="$cleanup_list"/>
<span class="desc">$T('explain-cleanup_list')</span>
</div>
<div class="field-pair">
<label class="config" for="history_retention_select">$T('opt-history_retention')</label>
<input type="hidden" name="history_retention" id="history_retention" value="$history_retention">
<select name="history_retention_select" id="history_retention_select">
<option value="0">$T('history_retention-all')</option>
<option value="n">$T('history_retention-number')</option>
<option value="d">$T('history_retention-days')</option>
<option value="-1">$T('history_retention-none')</option>
</select>
<input type="number" id="history_retention_number" name="history_retention_number" min="1">
<span class="desc">$T('explain-history_retention').replace('. ', '.<br/>')</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>
@@ -240,7 +257,7 @@
</fieldset>
</div><!-- /col1 -->
</div><!-- /section -->
<div class="section">
<div class="section advanced-settings">
<div class="col2">
<h3>$T('swtag-naming') <a href="$helpuri$help_uri#toc4" target="_blank"><span class="glyphicon glyphicon-question-sign"></span></a></h3>
</div><!-- /col2 -->
@@ -249,7 +266,7 @@
<div class="field-pair">
<label class="config" for="folder_rename">$T('opt-folder_rename')</label>
<input type="checkbox" name="folder_rename" id="folder_rename" value="1" <!--#if int($folder_rename) > 0 then 'checked="checked"' else ""#--> />
<span class="desc">$T('explain-folder_rename')</span>
<span class="desc">$T('explain-folder_rename').replace('. ', '.<br/>')</span>
</div>
<div class="field-pair">
<label class="config" for="replace_spaces">$T('opt-replace_spaces')</label>
@@ -261,11 +278,6 @@
<input type="checkbox" name="replace_dots" id="replace_dots" value="1" <!--#if int($replace_dots) > 0 then 'checked="checked"' else ""#--> />
<span class="desc">$T('explain-replace_dots')</span>
</div>
<div class="field-pair">
<label class="config" for="replace_illegal">$T('opt-replace_illegal')</label>
<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>
@@ -273,11 +285,6 @@
<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 ""#--> />
<span class="desc">$T('explain-enable_meta')</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>
@@ -331,16 +338,7 @@
<div class="field-pair">
<label class="config" for="rating_enable">$T('opt-rating_enable')</label>
<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" />
<span class="desc">$T('explain-rating_enable').replace('. ', '.<br/>')</span>
</div>
<div class="field-pair">
<label class="config" for="rating_api_key">$T('opt-rating_api_key')</label>
@@ -474,6 +472,53 @@
}
});
\$('#history_retention_select, #history_retention_number').on('change', updateHistoryRetention)
function updateHistoryRetention() {
var retention_setting = \$('#history_retention')
var retention_select = \$('#history_retention_select').val()
var retention_number = \$('#history_retention_number')
// Keep all or keep none
if(retention_select == "0" || retention_select == "-1") {
retention_number.hide()
retention_number.val('')
retention_number.attr('placeholder', '')
retention_setting.val(retention_select)
} else {
retention_number.show()
// Days or number?
if(retention_select.indexOf("d") !== -1) {
retention_number.attr('placeholder', '$T('days').capitalize()')
if(retention_number.val()) {
retention_setting.val(retention_number.val() + 'd')
} else if(parseInt(retention_setting.val()) > 0) {
retention_number.val(parseInt(retention_setting.val()))
}
} else {
retention_number.attr('placeholder', '$T('history_retention-limit')')
if(retention_number.val()) {
retention_setting.val(retention_number.val())
} else if(parseInt(retention_setting.val()) > 0) {
retention_number.val(parseInt(retention_setting.val()))
}
}
}
}
// Set the history-retention settig
var retention_setting_value = \$('#history_retention').val()
if(parseInt(retention_setting_value) > 0) {
// Days or number?
if(retention_setting_value.indexOf("d") !== -1) {
\$('#history_retention_select').val("d")
} else {
\$('#history_retention_select').val("n")
}
\$('#history_retention_number').val(parseInt(retention_setting_value))
} else {
// Keep all or keep none
\$('#history_retention_select').val(retention_setting_value)
\$('#history_retention_number').hide()
}
\$('.restoreDefaults').click(function(e) {
// Get section name
var sectionName = \$(this).parents('.section').find('.col2 h3').text().trim()

View File

@@ -1,9 +1,9 @@
<html lang="$active_lang">
<head>
<title>SABnzbd</title>
<title>SABnzbd - $T('login')</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no, maximum-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" />
@@ -16,7 +16,7 @@
<link rel="stylesheet" type="text/css" href="../staticcfg/bootstrap/css/bootstrap.min.css?v=$version" />
<link rel="stylesheet" type="text/css" href="../staticcfg/css/login.css?v=$version" />
<script type="text/javascript" src="../staticcfg/js/jquery-3.1.1.min.js?v=$version"></script>
<script type="text/javascript" src="../staticcfg/js/jquery-3.2.1.min.js?v=$version"></script>
<script type="text/javascript" src="../staticcfg/bootstrap/js/bootstrap.min.js?v=$version"></script>
</head>
<html>

View File

File diff suppressed because one or more lines are too long

View File

@@ -4,16 +4,13 @@ body {
}
#logo {
display: block;
margin: auto;
margin-top: 3px;
margin: 3px auto auto;
}
#content {
color: #000;
padding: 15px 20px 20px;
padding: 65px 20px 20px;
font-size: 13px;
padding-top: 65px;
padding-bottom: 20px;
}
.colmask {
z-index: 20;
@@ -23,16 +20,32 @@ body {
overflow: visible;
border: 1px solid #dfdede;
background-color: #FFF;
border: 1px solid rgba(0, 0, 0, 0.2);
width: 100%
}
.section, .Servers form, #addFeed, #addFeedContent {
.advanced-button {
float: right;
width: auto;
margin: 0 4px 0px 0px;
font-size: 13px;
color: inherit;
height: auto;
padding-bottom: 5px;
}
#advanced-settings-button {
float: left;
margin: 2px 7px 0px 0px;
}
.advanced-buttonSeperator {
width: 1px;
height: 34px;
background-color: #dfdede;
float: right;
margin: 0px 10px;
}
.section, #addFeed, #addFeedContent {
border-bottom: 1px solid #dfdede;
overflow: auto;
}
.section:last-child, .Servers form:last-child {
border: 0;
}
.col2 {
z-index: 10;
float: left;
@@ -137,7 +150,8 @@ input[type="checkbox"]+.desc {
font-style: italic;
padding: 0 1px;
}
.col2 p {
.col2 p,
.col2-cats {
font-size: 12px;
color: #666;
margin: 1em 0;
@@ -149,13 +163,13 @@ input[type="checkbox"]+.desc {
overflow: hidden;
min-width: 555px;
}
.field-pair:nth-child(odd),
.Key tr:nth-child(odd),
.tab-pane tr:nth-child(odd),
.even {
background-color: #F8F8F8;
}
.field-pair:last-child {
.field-pair:last-child,
.no-field-pair-bg {
background-color: transparent;
}
.alt,
@@ -297,7 +311,7 @@ tr.separator {
visibility: hidden;
}
.Categories form.sorting-row:nth-child(even) tr {
.Categories form.sorting-row:nth-child(2n-1) tr {
background-color: #F8F8F8;
}
@@ -512,7 +526,7 @@ tr.separator {
}
#filebrowser_modal .checkbox {
float: left;
margin: 8px 5px 0x;
margin: 8px 5px 0px;
}
#filebrowser_modal .checkbox input {
margin-top: 1px;
@@ -559,6 +573,7 @@ h2.activeRSS {
float: left;
margin: 0 6px 0 2px;
text-align: center;
color: black !important;
}
.source-icon span {
top: -3px;
@@ -583,8 +598,7 @@ h2.activeRSS {
padding-top: .4em;
}
#subscriptions .chk {
padding: 5px;
padding-top: 8px;
padding: 8px 5px 5px;
vertical-align: middle;
}
#subscriptions .title {
@@ -618,7 +632,6 @@ h2.activeRSS {
padding: 0 0 .5em;
}
.feed-row div {
padding-right: 10px;
overflow:hidden;
white-space: nowrap;
text-overflow: ellipsis;
@@ -757,7 +770,6 @@ input[type=radio] {
input[type="button"],
input[type="submit"] {
color: #333;
background-color: #fff;
display:inline-block;
padding:6px 12px;
margin-bottom: 0;
@@ -768,7 +780,7 @@ input[type="submit"] {
white-space:nowrap;
vertical-align:middle;
cursor:pointer;
background-image:none;
background: #fff none;
border:1px solid #ccc;
height: 34px;
}
@@ -865,11 +877,17 @@ input[type="checkbox"] {
max-width: 150px !important;
}
.Scheduling input[type="checkbox"] {
.Scheduling form[action="addSchedule"] input[type="checkbox"] {
margin-top: 0px;
margin-left: -20px;
}
.Scheduling form[action="delSchedule"] input[type="checkbox"] {
position: initial;
float: left;
margin: 9px 10px 0px 5px;
}
.navbar .container {
padding-right: 0;
}
@@ -980,13 +998,57 @@ input[type="checkbox"] {
}
.Servers .col2.server-disabled .label {
color: ##777 !important;
color: #777 !important;
}
.Servers .col2 .label:nth-child(2) {
opacity: 0.7;
}
.Servers .server-amounts-text {
width: 20%;
float: left;
}
.Servers .server-chart {
float: right;
width: calc(100% - 250px - 25%);
text-align: center;
position: relative;
}
.Servers .ct-series-a .ct-line {
stroke: #666666;
}
.Servers .ct-series-a .ct-point {
stroke: #666666;
stroke-width: 4px;
}
.Servers .ct-series-a .ct-area {
fill: #666666
}
.Servers .ct-label {
font-size: 1em;
color: black;
}
.Servers .chart-selector-container {
float: right;
}
.Servers .chart-selector-container .glyphicon {
font-size: 1.3em;
padding-right: 4px;
top: 5px;
}
.Servers .ct-grid.ct-vertical:first-of-type {
display: none;
}
.advanced-settings {
display: none;
}
@@ -997,9 +1059,7 @@ input[type="checkbox"] {
.Servers .col2 label,
.Email .col2 label {
margin: 0;
margin-left: 4px;
margin-top: 2px;
margin: 2px 0 0 4px;
cursor: pointer;
}
@@ -1012,7 +1072,6 @@ input[type="checkbox"] {
display: none;
}
.alert-no-category,
.alert-translate {
display: none;
margin: 5px 0px 0px;
@@ -1028,6 +1087,13 @@ input[type="checkbox"] {
color: #666;
}
.host-warning {
color: #F0AD4E;
margin-left: 13px;
top: 5px;
font-size: 1.2em;
}
.fileBrowser .glyphicon {
margin-right: 2px;
top: 1px;
@@ -1069,6 +1135,7 @@ input[type="checkbox"] {
}
.value-and-select select {
min-width: 30px;
margin-top: 1px;
}
.dotOne, .dotTwo, .dotThree {
@@ -1112,7 +1179,7 @@ input[type="checkbox"] {
.navbar-nav {
/* For extra wide languages like Polish */
margin-right: -50px;
margin-right: -150px;
}
}
@@ -1164,6 +1231,17 @@ input[type="checkbox"] {
.Servers .col2 button:first-of-type {
margin-bottom: 0;
}
.Servers .server-chart,
.Servers .chart-selector-container,
.Servers .advanced-buttonSeperator {
display: none;
}
.Servers .server-amounts-text {
padding: 0px 15px 10px;
width: inherit;
}
}
@media screen and (max-width: 768px) {
@@ -1258,9 +1336,7 @@ input[type="checkbox"] {
}
.desc {
margin: 0;
margin-left: 3px;
margin-top: 2px;
margin: 2px 0 0 3px;
padding: 0 !important;
}

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 982 B

View File

@@ -0,0 +1,36 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
width="608.000000pt" height="608.000000pt" viewBox="0 0 608.000000 608.000000"
preserveAspectRatio="xMidYMid meet">
<g transform="translate(0.000000,608.000000) scale(0.100000,-0.100000)"
fill="#000000" stroke="none">
<path d="M1295 5818 c-46 -17 -81 -45 -107 -86 l-23 -37 -5 -1160 -5 -1160
-386 -5 -386 -5 -40 -28 c-80 -56 -113 -173 -70 -257 9 -19 91 -113 182 -209
91 -96 165 -177 165 -180 -1 -3 -18 -18 -39 -32 -21 -15 -48 -46 -60 -71 -21
-43 -21 -54 -21 -514 0 -441 1 -472 19 -507 18 -36 18 -38 0 -74 -17 -33 -19
-61 -19 -236 0 -222 6 -245 74 -305 60 -53 76 -54 851 -50 706 3 710 3 751 25
26 13 45 18 51 12 5 -5 32 -17 60 -27 46 -15 77 -44 336 -317 157 -165 300
-309 319 -320 24 -14 51 -20 98 -20 47 0 74 6 98 20 19 11 162 155 319 320
200 211 295 304 321 315 20 8 46 20 56 26 15 9 26 7 55 -10 36 -21 39 -21 781
-21 l745 0 45 25 c24 14 58 45 75 68 l30 44 3 746 2 746 -25 51 c-14 29 -40
63 -60 77 l-35 25 170 179 c94 98 178 195 188 214 41 84 9 202 -71 257 l-40
28 -386 5 -386 5 -5 1162 -5 1161 -30 43 c-16 23 -48 52 -70 63 l-40 21 -1725
2 c-1202 1 -1734 -2 -1755 -9z m3215 -1488 l0 -1080 -284 0 -284 0 -53 -28
c-39 -20 -62 -41 -84 -77 l-30 -48 -3 -199 -4 -198 -736 0 c-703 0 -739 -1
-779 -19 -41 -19 -42 -19 -79 0 -36 18 -64 19 -510 19 l-472 0 -122 128 -122
127 239 5 c258 5 269 8 325 67 61 64 58 -6 58 1243 l0 1140 1470 0 1470 0 0
-1080z m32 -1562 l3 -273 408 -3 407 -2 0 -690 0 -690 -690 0 -690 0 0 965 0
965 280 0 280 0 2 -272z m558 155 c-19 -21 -75 -80 -125 -130 l-90 -93 -67 0
-68 0 0 130 0 130 192 0 192 0 -34 -37z m-3010 -573 l0 -140 -410 0 -410 0 0
-130 0 -130 408 -2 407 -3 0 -415 0 -415 -692 -3 -693 -2 0 145 0 145 410 0
410 0 0 130 0 130 -410 0 -410 0 0 415 0 415 695 0 695 0 0 -140z m1630 -550
l0 -690 -690 0 -690 0 0 420 0 420 410 0 410 0 0 130 0 130 -410 0 -410 0 0
140 0 140 690 0 690 0 0 -690z m-611 -967 c-35 -38 -66 -68 -69 -68 -3 0 -34
30 -69 68 l-63 67 132 0 132 0 -63 -67z"/>
<path d="M4547 2203 c-4 -3 -7 -186 -7 -405 l0 -398 130 0 130 0 0 405 0 405
-123 0 c-68 0 -127 -3 -130 -7z"/>
<path d="M2902 1533 l3 -128 128 -3 127 -3 0 131 0 130 -130 0 -131 0 3 -127z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,6 @@
/*
2017 Jason Mulligan <jason.mulligan@avoidwork.com>
@version 3.5.11
*/
"use strict";!function(i){function e(i){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=[],d=0,r=void 0,a=void 0,s=void 0,f=void 0,u=void 0,l=void 0,v=void 0,B=void 0,c=void 0,p=void 0,y=void 0,m=void 0,x=void 0,g=void 0;if(isNaN(i))throw new Error("Invalid arguments");return s=!0===e.bits,y=!0===e.unix,a=e.base||2,p=void 0!==e.round?e.round:y?1:2,m=void 0!==e.spacer?e.spacer:y?"":" ",g=e.symbols||e.suffixes||{},x=2===a?e.standard||"jedec":"jedec",c=e.output||"string",u=!0===e.fullform,l=e.fullforms instanceof Array?e.fullforms:[],r=void 0!==e.exponent?e.exponent:-1,B=Number(i),v=B<0,f=a>2?1e3:1024,v&&(B=-B),(-1===r||isNaN(r))&&(r=Math.floor(Math.log(B)/Math.log(f)))<0&&(r=0),r>8&&(r=8),0===B?(n[0]=0,n[1]=y?"":t[x][s?"bits":"bytes"][r]):(d=B/(2===a?Math.pow(2,10*r):Math.pow(1e3,r)),s&&(d*=8)>=f&&r<8&&(d/=f,r++),n[0]=Number(d.toFixed(r>0?p:0)),n[1]=10===a&&1===r?s?"kb":"kB":t[x][s?"bits":"bytes"][r],y&&(n[1]="jedec"===x?n[1].charAt(0):r>0?n[1].replace(/B$/,""):n[1],o.test(n[1])&&(n[0]=Math.floor(n[0]),n[1]=""))),v&&(n[0]=-n[0]),n[1]=g[n[1]]||n[1],"array"===c?n:"exponent"===c?r:"object"===c?{value:n[0],suffix:n[1],symbol:n[1]}:(u&&(n[1]=l[r]?l[r]:b[x][r]+(s?"bit":"byte")+(1===n[0]?"":"s")),n.join(m))}var o=/^(b|B)$/,t={iec:{bits:["b","Kib","Mib","Gib","Tib","Pib","Eib","Zib","Yib"],bytes:["B","KiB","MiB","GiB","TiB","PiB","EiB","ZiB","YiB"]},jedec:{bits:["b","Kb","Mb","Gb","Tb","Pb","Eb","Zb","Yb"],bytes:["B","KB","MB","GB","TB","PB","EB","ZB","YB"]}},b={iec:["","kibi","mebi","gibi","tebi","pebi","exbi","zebi","yobi"],jedec:["","kilo","mega","giga","tera","peta","exa","zetta","yotta"]};e.partial=function(i){return function(o){return e(o,i)}},"undefined"!=typeof exports?module.exports=e:"function"==typeof define&&define.amd?define(function(){return e}):i.filesize=e}("undefined"!=typeof window?window:global);
//# sourceMappingURL=filesize.min.js.map

View File

File diff suppressed because one or more lines are too long

View File

File diff suppressed because one or more lines are too long

View File

File diff suppressed because one or more lines are too long

View File

@@ -47,9 +47,9 @@
<!-- ko if: historyStatus.has_rating -->
<div class="dropdown history-ratings">
<a href="#" class="name-ratings hover-button" data-toggle="dropdown" onclick="keepOpen(this)">
<span class="glyphicon glyphicon-facetime-video"></span> <span data-bind="text: historyStatus.rating_avg_video"></span>
<span class="glyphicon glyphicon-volume-up"></span> <span data-bind="text: historyStatus.rating_avg_audio"></span>
<a href="#" class="name-icons hover-button" data-toggle="dropdown" onclick="keepOpen(this)">
<span class="glyphicon glyphicon-thumbs-up"></span> <span data-bind="text: historyStatus.rating_avg_vote_up"></span>
<span class="glyphicon glyphicon-thumbs-down"></span> <span data-bind="text: historyStatus.rating_avg_vote_down"></span>
</a>
<ul class="dropdown-menu history-ratings-menu">
<li>
@@ -170,6 +170,10 @@
<div class="col-sm-2">$T('category')</div>
<div class="col-sm-10" data-bind="text: historyStatus.category() == '*' ? glitterTranslate.defaultText : historyStatus.category"></div>
</div>
<div class="row" data-bind="visible: historyStatus.password">
<div class="col-sm-2">$T('srv-password')</div>
<div class="col-sm-10" data-bind="text: historyStatus.password"></div>
</div>
<div class="row">
<div class="col-sm-2">$T('msg-path')</div>
<div class="col-sm-10" data-bind="text: historyStatus.storage() == '' ? historyStatus.path : historyStatus.storage"></div>
@@ -206,6 +210,7 @@
</ul>
<div class="multioperations-selector" id="history-options">
<a href="#" class="hover-button" title="$T('link-retryAll')" data-tooltip="true" data-placement="left" data-bind="click: history.retryAllFailed"><span class="glyphicon glyphicon-repeat"></span></a>
<a href="#" class="hover-button" title="$T('showAllHis') / $T('showFailedHis')" data-tooltip="true" data-placement="left" data-bind="click: history.toggleShowFailed, css: { 'history-options-show-failed': history.showFailed }"><span class="glyphicon glyphicon-exclamation-sign"></span></a>
<a href="#modal-purge-history" class="hover-button" title="$T('purgeHist')" data-toggle="modal" data-tooltip="true" data-placement="left"><span class="glyphicon glyphicon-trash"></span></a>
</div>

View File

@@ -89,6 +89,7 @@
</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>
<li><a href="https://sabnzbd.org/donate" target="_blank"><span class="glyphicon glyphicon-heart"></span> $T('menu-donate')</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#--><li><a href="#" data-bind="click: doQueueAction" data-mode="reset_quota">$T('link-resetQuota')</a></li><!--#end if#-->
@@ -101,7 +102,7 @@
<li class="divider"></li>
<li class="dropdown-header"><span class="glyphicon glyphicon-off"></span> $T('Glitter-onFinish'):</li>
<li>
<select data-bind="value: onQueueFinish" class="form-control">
<select data-bind="value: onQueueFinish, event: { change: setOnQueueFinish }" class="form-control">
<option value=""></option>
<optgroup label="$T('eoq-actions')">
<option value="shutdown_program">$T('shutdownSab')</option>
@@ -111,9 +112,11 @@
<option value="hibernate_pc">$T('hibernatePc')</option>
<!--#end if#-->
</optgroup>
<optgroup label="$T('eoq-scripts')">
<optgroup label="$T('eoq-scripts')" data-bind="visible: queue.scriptsList().length > 1">
<!-- ko foreach: queue.scriptsList -->
<option data-bind="text: \$data, attr: { value: 'script_'+\$data } " ></option>
<!-- ko if: \$data != glitterTranslate.noneText -->
<option data-bind="text: \$data, attr: { value: 'script_'+\$data } " ></option>
<!-- /ko -->
<!-- /ko -->
</optgroup>
</select>

View File

@@ -18,7 +18,7 @@
<tr>
<td class="table-messages-label">
<span class="label" data-bind="css: 'label-' + css, text: type"></span>
<strong data-bind="text: date, attr: { 'data-timestamp': timestamp }"></strong>
<strong data-bind="text: displayDateTime(timestamp, \$parent.dateFormat(), 'X'), attr: { 'data-timestamp': timestamp }"></strong>
<span class="queue-message-text" data-bind="html: text"></span>
</td>
</tr>

View File

@@ -59,15 +59,9 @@
<div id="feedback-slider-inner">
<p><strong>If anything is not working as expected, or could be improved, let us know!</strong></p>
<p><strong>If you encounter an error, please include the log file (click on <span class="glyphicon glyphicon-wrench"></span> ) when contacting us.</strong></p>
<h4>General</h4>
<span class="glyphicon glyphicon-home"></span> <a href="http://forums.sabnzbd.org/" target="_blank">SABnzbd Forum</a><br />
<span class="glyphicon glyphicon-home"></span> <a href="https://forums.sabnzbd.org/viewforum.php?f=11" target="_blank">SABnzbd Forum</a><br />
<span class="glyphicon glyphicon-plane"></span> <a href="https://github.com/sabnzbd/sabnzbd/" target="_blank">SABnzbd on Github</a><br />
<span class="glyphicon glyphicon-globe"></span> <a href="https://translations.launchpad.net/sabnzbd" target="_blank">Translations of SABnzbd</a><br />
<span class="glyphicon glyphicon-envelope"></span> <a href="mailto:bugs@sabnzbd.org?body=Version:%20$version%20Skin:%20Glitter">Email bugs@sabnzbd.org</a>
<h4>Interface (Glitter)</h4>
<span class="glyphicon glyphicon-home"></span> <a href="http://forums.sabnzbd.org/viewtopic.php?f=5&amp;t=18880" target="_blank">Glitter at SABnzbd Forum</a><br />
<span class="glyphicon glyphicon-envelope"></span> <a href="mailto:safihre@sabnzbd.org?body=Version:%20$version">Email safihre@sabnzbd.org</a>
</div>
</div>
@@ -112,42 +106,41 @@
<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 class="col-sm-6">
<span data-bind="text: cacheSize"></span> (<span data-bind="text: cacheArticles"></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">
<div class="col-sm-6" data-bind="visible: hasPerformanceInfo">
<span data-bind="text: statusInfo.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>
<a href="#" data-bind="click: testDiskSpeed" 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, attr: { 'data-original-title': statusInfo.cpumodel }" data-tooltip="true"></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 class="col-sm-6 col-loading" data-bind="visible: !hasPerformanceInfo()">$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">
<div class="col-sm-6" data-bind="visible: hasPerformanceInfo">
<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, attr: { 'data-original-title': statusInfo.downloaddir }" data-tooltip="true"></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 class="col-sm-6 col-loading" data-bind="visible: !hasPerformanceInfo()">$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">
<div class="col-sm-6" data-bind="visible: hasPerformanceInfo">
<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, attr: { 'data-original-title': statusInfo.completedir }" data-tooltip="true"></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 class="col-sm-6 col-loading" data-bind="visible: !hasPerformanceInfo()">$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">
<div class="col-sm-6">
<a href="#" data-bind="click: forceDisconnect" class="btn btn-default "><span class="glyphicon glyphicon-minus-sign"></span> $T('link-forceDisc')</a>
<a href="#" data-bind="click: forceDisconnect" class="btn btn-default" data-tooltip="true" data-placement="top" title="$T('explain-forceDisc')"><span class="glyphicon glyphicon-minus-sign"></span> $T('link-forceDisc')</a>
</div>
<div class="col-sm-6">
<a href="#" data-bind="click: repairQueue" data-tooltip="true" data-placement="top" title="$T('explain-Repair').replace('<br>',' ').replace('<br />',' ')" class="btn btn-default">
@@ -157,7 +150,9 @@
</div>
<div class="row options-function-box">
<div class="col-sm-6">
<a href="./status/showlog?session=$session" target="_blank" class="btn btn-default"><span class="glyphicon glyphicon-file"></span> $T('link-showLog')</a>
<a href="./status/showlog?session=$session" target="_blank" class="btn btn-default" data-tooltip="true" data-placement="top" title="$T('Glitter-logText')">
<span class="glyphicon glyphicon-file"></span> $T('link-showLog')
</a>
</div>
<div class="col-sm-6">
<div class="input-group" data-tooltip="true" data-placement="top" title="$T('logging')">
@@ -180,12 +175,11 @@
</div>
<div data-bind="foreach: statusInfo.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>
<span class="label label-warning" data-bind="visible: serveroptional">$T('optional').capitalize()</span>
</div>
</div>
<div class="row">
@@ -208,7 +202,7 @@
<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>
<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>
@@ -526,10 +520,14 @@
<div class="progress-bar progress-bar-info" data-bind="attr: { 'style': 'width: '+percentage()+'; background-color: ' + \$parent.filelist.currentItem.progressColor() + ';' }">
<input type="checkbox" data-bind="attr: { 'name' : nzf_id }, disable: !canselect(), click : \$parent.filelist.checkSelectRange" title="$T('Glitter-multiSelect')" />
<strong data-bind="text: percentage"></strong>
<span>
<div class="fileDetails">
<span data-bind="truncatedTextCenter: filename"></span>
<div class="fileControls">
<a href="#" data-bind="click: \$parent.filelist.moveButton" class="hover-button buttonMoveToTop" title="$T('Glitter-top')"><span class="glyphicon glyphicon-chevron-up"></span></a>
<a href="#" data-bind="click: \$parent.filelist.moveButton" class="hover-button buttonMoveToBottom" title="$T('Glitter-bottom')"><span class="glyphicon glyphicon-chevron-down"></span></a>
</div>
<small>(<span data-bind="text: file_age"></span> - <span data-bind="text: mb"></span> MB)</small>
</span>
</div>
</div>
</div>
</td>
@@ -622,7 +620,7 @@
</tr>
<tr>
<td><strong>$T('menu-forums'):</strong></td>
<td><a href="http://forums.sabnzbd.org/" target="_blank">http://forums.sabnzbd.org/</a></td>
<td><a href="https://forums.sabnzbd.org/" target="_blank">https://forums.sabnzbd.org/</a></td>
</tr>
<tr>
<td><strong>GitHub:</strong></td>
@@ -630,12 +628,12 @@
</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>
<td><a href="https://sabnzbd.org/live-chat" target="_blank">https://sabnzbd.org/live-chat</a></td>
</tr>
</tbody>
</table>
<hr/>
<p><small>Copyright (C) 2008-2017, The SABnzbd Team &lt;team@sabnzbd.org&gt;<br/>$T('yourRights') </small></p>
<p><small>Copyright (C) 2007-2018, The SABnzbd Team &lt;team@sabnzbd.org&gt;<br/>$T('yourRights') </small></p>
</div>
</div>
</div>
@@ -653,7 +651,6 @@
<button type="button" class="btn btn-danger" data-bind="click: history.emptyHistory" data-action="history-purgeremove-failed"><span class="glyphicon glyphicon-floppy-remove"></span> $T('purgeFailed-Files')</button><hr />
<button type="button" class="btn btn-danger" data-bind="click: history.emptyHistory" data-action="history-purge-completed"><span class="glyphicon glyphicon-floppy-saved"></span> $T('purgeCompl')</button><hr />
<button type="button" class="btn btn-danger" data-bind="click: history.emptyHistory" data-action="history-purge-page"><span class="glyphicon glyphicon-check"></span> $T('purgePage') <span class="label label-default" data-bind="text: history.historyItems().length"></span></button>
</div>
</div>
</div>

View File

@@ -39,6 +39,11 @@
<span class="glyphicon glyphicon-hdd"></span> <span data-bind="text: diskSpaceLeft2"></span>B $T('Glitter-free')
</div>
<!-- /ko -->
<div class="info-container-box-sorting" style="display: none" data-bind="visible: refreshRate() > 2">
<a href="#" data-bind="click: refresh">
<span class="glyphicon glyphicon-repeat" data-tooltip="true" data-placement="left" title="$T('Glitter-refresh')"></span>
</a>
</div>
<div class="info-container-box-sorting dropdown" data-bind="visible: hasQueue()">
<a href="#" data-toggle="dropdown">
<span class="glyphicon glyphicon-sort-by-alphabet" data-tooltip="true" data-placement="left" title="$T('cmenu-sorting')"></span>
@@ -83,31 +88,30 @@
<tr class="queue-item">
<td>
<a href="#" data-bind="click: pauseToggle, attr: { 'title': pausedStatus() ? '$T('link-resume')' : '$T('link-pause')' }">
<span class="hover-button glyphicon" data-bind="css: { 'glyphicon-play': pausedStatus(), 'glyphicon-pause': !pausedStatus() }"></span>
<span class="hover-button glyphicon" data-bind="css: queueIcon"></span>
</a>
</td>
<td class="name">
<div class="row-wrap-text" data-bind="visible: !editingName()">
<span data-bind="text: name, attr: { 'title': name() }"></span>
<div class="row-wrap-text" data-bind="visible: !editingName(), css: { 'direct-unpack-text': direct_unpack }">
<span data-bind="text: name, attr: { 'title': name_title }"></span>
<!-- ko if: password() -->
<small class="queue-item-password">
<span class="glyphicon glyphicon-lock"></span>
<span data-bind="text: password"></span>
</small>
<!-- /ko -->
<!-- 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>
<span class="glyphicon glyphicon-volume-up"></span> <span data-bind="text: rating_avg_audio"></span>
</div>
<!-- /ko -->
</div>
<form data-bind="submit: editingNameSubmit">
<input type="text" data-bind="value: nameForEdit, visible: editingName(), hasfocus: editingName" />
</form>
<div class="name-options" data-bind="visible: !editingName()">
<a href="#" data-bind="click: editName, css: { disabled: isGrabbing() }" class="hover-button"><span class="glyphicon glyphicon-pencil"></span></a>
<a href="#" data-bind="click: showFiles, css: { disabled: isGrabbing() }" class="hover-button" title="$T('nzoDetails') - $T('srv-password')"><span class="glyphicon glyphicon-folder-open"></span></a>
<div class="name-icons direct-unpack hover-button" data-bind="visible: direct_unpack">
<span class="glyphicon glyphicon-compressed"></span> <span data-bind="text: direct_unpack"></span>
</div>
<div class="name-options" data-bind="visible: !editingName(), css: { disabled: isGrabbing() }">
<a href="#" data-bind="click: \$parent.queue.moveButton" class="hover-button buttonMoveToTop" title="$T('Glitter-top')"><span class="glyphicon glyphicon-chevron-up"></span></a>
<a href="#" data-bind="click: \$parent.queue.moveButton" class="hover-button buttonMoveToBottom" title="$T('Glitter-bottom')"><span class="glyphicon glyphicon-chevron-down"></span></a>
<a href="#" data-bind="click: editName" class="hover-button" title="$T('Glitter-rename')"><span class="glyphicon glyphicon-pencil"></span></a>
<a href="#" data-bind="click: showFiles" class="hover-button" title="$T('nzoDetails') - $T('srv-password')"><span class="glyphicon glyphicon-folder-open"></span></a>
<small data-bind="text: avg_age"></small>
</div>
</td>
@@ -117,8 +121,8 @@
</td>
<td class="progress-indicator">
<div class="progress">
<div class="progress-bar progress-bar-info" data-bind="attr: { 'style': 'width: ' + percentageRounded() + '; background-color: ' + progressColor() + ';' }">
<strong data-bind="text: percentageRounded"></strong>
<div class="progress-bar progress-bar-info" data-bind="attr: { 'style': 'width: ' + percentage() + '%; background-color: ' + progressColor() + ';' }">
<strong data-bind="text: percentage() + '%'"></strong>
<i data-bind="text: missingText"></i>
</div>
<span data-bind="text: progressText"></span>
@@ -129,8 +133,8 @@
<label data-bind="visible: parent.isMultiEditing()">
<input type="checkbox" name="multiedit" title="$T('Glitter-multiSelect')" data-bind="click: parent.addMultiEdit, attr: { 'id': 'multiedit_' + id } " />
</label>
<!-- ko if: !isGrabbing() -->
<div class="dropdown" data-bind="visible: !isGrabbing() && !parent.isMultiEditing()">
<!-- ko if: !isGrabbing() -->
<div class="dropdown" data-bind="visible: !parent.isMultiEditing()">
<a href="#" data-toggle="dropdown" data-bind="click: toggleDropdown">
<span class="caret"></span>
</a>
@@ -140,7 +144,7 @@
<span class="glyphicon glyphicon-tag"></span>
<select name="Category" class="form-control" data-bind="options: parent.categoriesList, optionsValue: 'catValue', optionsText: 'catText', value: category, event: { change: changeCat }"></select>
</li>
<!-- ko if: (status() != 'Fetching') -->
<!-- ko if: !isFetchingBlocks -->
<li title="$T('priority')" data-tooltip="true" data-placement="left">
<span class="glyphicon glyphicon-sort-by-attributes-alt"></span>
<select name="Priority" class="form-control" data-bind="options: parent.priorityOptions, optionsValue: 'value', optionsText: 'name', value: priority, event: { change: changePriority }"></select>
@@ -152,7 +156,7 @@
</li>
<li title="$T('eoq-scripts')" data-tooltip="true" data-placement="left">
<span class="glyphicon glyphicon-flash"></span>
<select name="Post-processing" class="form-control" data-bind="options: parent.scriptsList, value: script, event: { change: changeScript }, enable: (parent.scriptsList().length > 0)"></select>
<select name="Post-processing" class="form-control" data-bind="options: parent.scriptsList, value: script, event: { change: changeScript }, enable: (parent.scriptsList().length > 1)"></select>
</li>
</ul>
<!-- /ko -->

View File

@@ -16,7 +16,7 @@
<title data-bind="text: 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="viewport" content="width=device-width, initial-scale=1, user-scalable=no, maximum-scale=1" />
<meta name="mobile-web-app-capable" content="yes" />
<meta name="application-name" content="SABnzbd">
<meta name="apple-mobile-web-app-capable" content="yes" />
@@ -30,13 +30,14 @@
<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="mask-icon" href="./staticcfg/ico/safari-pinned-tab.svg" color="#383F45">
<link rel="shortcut icon" type="image/ico" href="./staticcfg/ico/favicon.ico?v=$version" 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" />
<link rel="stylesheet" type="text/css" href="./static/stylesheets/glitter.mobile.css?p=$pid" media="all and (max-width: 768px)" />
<link rel="stylesheet" type="text/css" href="./static/bootstrap/css/bootstrap.min.css?v=$version" />
<link rel="stylesheet" type="text/css" href="./static/stylesheets/glitter.css?v=$version" />
<link rel="stylesheet" type="text/css" href="./static/stylesheets/glitter.mobile.css?v=$version" media="all and (max-width: 768px)" />
<!--#if $color_scheme not in ('Default', '') #-->
<link rel="stylesheet" type="text/css" href="./static/stylesheets/colorschemes/${color_scheme}.css?p=$pid"/>
<link rel="stylesheet" type="text/css" href="./static/stylesheets/colorschemes/${color_scheme}.css?v=$version"/>
<!--#end if#-->
<!-- Make translations available in scripts -->
@@ -46,7 +47,7 @@
var sabSpeedHistory = [$bytespersec_list];
var newRelease = "$new_release";
var newReleaseUrl = "$new_rel_url";
var glitterIsBeta = ("$version".search(/Alpa|Beta|x/)) > 0;
var glitterIsBeta = ("$version".search(/[develop|Alpha|Beta|RC]/)) > 0;
var glitterPreLoadQueue = $preload_queue;
var glitterPreLoadHistory = $preload_history;
@@ -58,10 +59,12 @@
glitterTranslate.pauseFor = "$T('pauseFor')"
glitterTranslate.minutes = "$T('mins')"
glitterTranslate.shutdown = "$T('shutdownOK?')";
glitterTranslate.restart = "$T('explain-Restart')".replace(/<br \/>/g, "\n");
glitterTranslate.repair = "$T('explain-Repair')".replace(/<br \/>/g, "\n");
glitterTranslate.restart = "$T('explain-Restart') $T('explain-needNewLogin')".replace(/\<br(\s*\/|)\>/g, '\n');
glitterTranslate.repair = "$T('explain-Repair')".replace(/<br \/>/g, "\n").replace(/&quot;/g,'"');
glitterTranslate.removeDown = "$T('Glitter-confirmClearDownloads')";
glitterTranslate.removeDow1 = "$T('Glitter-confirmClear1Download')";
glitterTranslate.retryAll = "$T('link-retryAll')?";
glitterTranslate.fetch = "$T('Glitter-fetch')";
glitterTranslate.encrypted = "$T('Glitter-encrypted')";
glitterTranslate.duplicate = "$T('Glitter-duplicate')";
glitterTranslate.tooLarge = "$T('Glitter-tooLarge')";
@@ -84,6 +87,7 @@
glitterTranslate.moreText = "$T('Glitter-more')";
glitterTranslate.status = [];
glitterTranslate.status['DirectUnpack'] = "$T('opt-direct_unpack')";
glitterTranslate.status['Completed'] = "$T('post-Completed')";
glitterTranslate.status['Failed'] = "$T('post-Failed')";
glitterTranslate.status['Queued'] = "$T('post-Queued')";
@@ -102,7 +106,7 @@
glitterTranslate.status['Script'] = "$T('stage-script')";
glitterTranslate.status['Source'] = "$T('stage-source')";
glitterTranslate.status['Servers'] = "$T('stage-servers')";
glitterTranslate.status['INFO'] = "$T('log-info')".replace('+ ', '').toUpperCase();
glitterTranslate.status['INFO'] = "$T('log-info')".replace('+', '').toUpperCase();
glitterTranslate.status['WARNING'] = "$T('Glitter-warning')";
glitterTranslate.status['ERROR'] = "$T('Glitter-error')";
@@ -125,7 +129,7 @@
<!--#include raw $webdir + "/static/javascripts/jquery.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/moment.min.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"#-->

View File

@@ -128,13 +128,13 @@ function displayDateTime(inDate, outFormat, inFormat) {
if(inDate == '') {
var theMoment = moment()
} else {
var theMoment = moment(inDate, inFormat)
var theMoment = moment.utc(inDate, inFormat)
}
// Special format or regular format?
if(outFormat == 'fromNow') {
return theMoment.fromNow()
} else {
return theMoment.format(outFormat)
return theMoment.local().format(outFormat)
}
}

View File

@@ -35,6 +35,30 @@ function Fileslisting(parent) {
})
}
// Move to top and bottom buttons
self.moveButton = function (item,event) {
var targetRow, sourceRow, tbody;
sourceRow = $(event.currentTarget).parents("tr").filter(":first");
tbody = sourceRow.parents("tbody").filter(":first");
ko.utils.domData.set(sourceRow[0], "ko_sourceIndex", ko.utils.arrayIndexOf(sourceRow.parent().children(), sourceRow[0]));
sourceRow = sourceRow.detach();
if ($(event.currentTarget).is(".buttonMoveToTop")) {
// we are moving to the top
targetRow = tbody.children(".files-done").filter(":last");
} else {
//we are moving to the bottom
targetRow = tbody.children(".files-sortable").filter(":last");
}
if(targetRow.length < 1 ){
// we found an edge case and need to do something special
targetRow = tbody.children(".files-sortable").filter(":first");
sourceRow.insertBefore(targetRow[0]);
} else {
sourceRow.insertAfter($(targetRow[0]));
}
tbody.sortable('option', 'update').call(tbody[0],null, { item: sourceRow });
};
// Trigger update
self.triggerUpdate = function() {
// Call API
@@ -55,15 +79,10 @@ function Fileslisting(parent) {
$.each(response.files, function(index, slot) {
// Existing or updating?
var existingItem = ko.utils.arrayFirst(self.fileItems(), function(i) {
return i.filename() == slot.filename;
return i.nzf_id() == slot.nzf_id;
});
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 {
@@ -202,9 +221,9 @@ function FileslistingModel(parent, data) {
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.canselect = ko.observable(data.status != "finished" && data.status != "queued");
self.isdone = ko.observable(data.status == "finished");
self.percentage = ko.observable(self.isdone() ? fixPercentages(100) : fixPercentages((100 - (data.mbleft / data.mb * 100)).toFixed(0)));
// Update internally
self.updateFromData = function(data) {
@@ -212,9 +231,10 @@ function FileslistingModel(parent, data) {
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.canselect(data.status != "finished" && data.status != "queued")
self.isdone(data.status == "finished")
// Data is given in MB, would always show 0% for small files even if completed
self.percentage(self.isdone() ? fixPercentages(100) : fixPercentages((100 - (data.mbleft / data.mb * 100)).toFixed(0)))
}
}

View File

@@ -169,7 +169,6 @@ function HistoryListModel(parent) {
// 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())
@@ -177,7 +176,20 @@ function HistoryListModel(parent) {
$('#history-options a').tooltip('hide')
// Force refresh
self.parent.refresh(true)
}
// Retry all failed
self.retryAllFailed = function(data, event) {
// Ask to be sure
if(confirm(glitterTranslate.retryAll)) {
// Send the command
callAPI({
mode: 'retry_all'
}).then(function() {
// Force refresh
self.parent.refresh(true)
})
}
}
// Empty history options
@@ -297,11 +309,11 @@ function HistoryModel(parent, data) {
self.processingDownload = ko.pureComputed(function() {
var status = self.status();
// When we can cancel
if (status === 'Extracting' || status === 'Verifying' || status == 'Repairing') {
if (status === 'Extracting' || status === 'Verifying' || status == 'Repairing' || status === 'Running') {
return 2
}
// These cannot be cancelled
if(status === 'Moving' || status === 'Running') {
if(status === 'Moving') {
return 1
}
return false;
@@ -328,16 +340,14 @@ function HistoryModel(parent, data) {
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'
}
try {
// Extract the Download section
var downloadLog = ko.utils.arrayFirst(self.historyStatus.stage_log(), function(item) {
return item.name() == 'Download'
});
// Extract the speed
return downloadLog.actions()[0].match(/(\S*\s\S+)(?=<br\/>)/)[0]
} catch(err) { }
}
return;
case 'category':
@@ -356,10 +366,17 @@ function HistoryModel(parent, data) {
return displayDateTime(self.completed(), parent.parent.dateFormat(), 'X')
});
// Subscribe to retryEvent so we can load the password
self.canRetry.subscribe(function() {
self.updateAllHistory = true;
})
// Re-try button
self.retry = function() {
// Set JOB-id
$('#modal-retry-job input[name="retry_job_id"]').val(self.nzo_id)
// Set password
$('#retry_job_password').val(self.historyStatus.password())
// Open modal
$('#modal-retry-job').modal("show")
};

View File

@@ -12,7 +12,7 @@ function ViewModel() {
self.isRestarting = ko.observable(false);
self.useGlobalOptions = ko.observable(true).extend({ persist: 'useGlobalOptions' });
self.refreshRate = ko.observable(1).extend({ persist: 'pageRefreshRate' });
self.dateFormat = ko.observable('DD/MM/YYYY HH:mm').extend({ persist: 'pageDateFormat' });
self.dateFormat = ko.observable('fromNow').extend({ persist: 'pageDateFormat' });
self.displayTabbed = ko.observable().extend({ persist: 'displayTabbed' });
self.displayCompact = ko.observable(false).extend({ persist: 'displayCompact' });
self.confirmDeleteQueue = ko.observable(true).extend({ persist: 'confirmDeleteQueue' });
@@ -40,6 +40,8 @@ function ViewModel() {
self.quotaLimit = ko.observable();
self.quotaLimitLeft = ko.observable();
self.systemLoad = ko.observable();
self.cacheSize = ko.observable();
self.cacheArticles = ko.observable();
self.nrWarnings = ko.observable(0);
self.allWarnings = ko.observableArray([]);
self.allMessages = ko.observableArray([]);
@@ -48,7 +50,7 @@ function ViewModel() {
// Statusinfo container
self.hasStatusInfo = ko.observable(false);
self.hasDiskStatusInfo = ko.observable(false);
self.hasPerformanceInfo = ko.observable(false);
self.statusInfo = {};
self.statusInfo.folders = ko.observableArray([]);
self.statusInfo.servers = ko.observableArray([]);
@@ -59,8 +61,6 @@ function ViewModel() {
self.statusInfo.pystone = ko.observable();
self.statusInfo.cpumodel = ko.observable();
self.statusInfo.loglevel = ko.observable();
self.statusInfo.cache_size = ko.observable();
self.statusInfo.cache_art = ko.observable();
self.statusInfo.downloaddir = ko.observable();
self.statusInfo.downloaddirspeed = ko.observable();
self.statusInfo.completedir = ko.observable();
@@ -173,8 +173,8 @@ function ViewModel() {
}
// Did we exceed the space?
self.diskSpaceExceeded1(parseInt(response.queue.mbleft)/1024 > parseInt(response.queue.diskspace1))
self.diskSpaceExceeded2(parseInt(response.queue.mbleft)/1024 > parseInt(response.queue.diskspace2))
self.diskSpaceExceeded1(parseInt(response.queue.mbleft)/1024 > parseFloat(response.queue.diskspace1))
self.diskSpaceExceeded2(parseInt(response.queue.mbleft)/1024 > parseFloat(response.queue.diskspace2))
// Quota
self.quotaLimit(response.queue.quota)
@@ -183,6 +183,10 @@ function ViewModel() {
// System load
self.systemLoad(response.queue.loadavg)
// Cache
self.cacheSize(response.queue.cache_size)
self.cacheArticles(response.queue.cache_art)
// Warnings (new warnings will trigger an update of allMessages)
self.nrWarnings(response.queue.have_warnings)
@@ -327,6 +331,13 @@ function ViewModel() {
// Split title & speed
var dataSplit = data.split('|||');
// Maybe the result is actually the login page?
if(dataSplit[0].substring(0, 11) === '<html lang=') {
// Redirect
document.location = document.location
return
}
// Set title
self.title(dataSplit[0]);
@@ -467,6 +478,12 @@ function ViewModel() {
return;
}
// Fix DateJS bug it has some strange problem with the current day-of-month + 1
// Removing the space makes DateJS work properly
newValue = newValue.replace(/\s*h|\s*m|\s*d/g, function(match) {
return match.trim()
});
// Parse
var pauseParsed = Date.parse(newValue);
@@ -519,6 +536,7 @@ function ViewModel() {
callAPI({
mode: 'warnings'
}).then(function(response) {
// Reset it all
self.allWarnings.removeAll();
if(response) {
@@ -527,20 +545,16 @@ function ViewModel() {
// Go over all warnings and add
$.each(response.warnings, function(index, warning) {
// Split warning into parts
var warningSplit = warning.split(/\n/);
// Reformat CSS label and date
// Replaces spaces by non-breakable spaces and newlines with br's
var warningData = {
index: index,
type: glitterTranslate.status[warningSplit[1]].slice(0, -1),
text: warningSplit.slice(2).join('<br/>').replace(/ /g, '\u00A0'), // Recombine if multiple lines
date: displayDateTime(warningSplit[0], self.dateFormat(), 'YYYY-MM-DD HH:mm'),
timestamp: moment(warningSplit[0], 'YYYY-MM-DD HH:mm').unix(),
css: (warningSplit[1] == "ERROR" ? "danger" : warningSplit[1] == "WARNING" ? "warning" : "info"),
type: glitterTranslate.status[warning.type].slice(0, -1),
text: convertHTMLtoText(warning.text).replace(/ /g, '\u00A0').replace(/(?:\r\n|\r|\n)/g, '<br />'),
timestamp: warning.time,
css: (warning.type == "ERROR" ? "danger" : warning.type == "WARNING" ? "warning" : "info"),
clear: self.clearWarnings
};
self.allWarnings.push(warningData)
})
}
@@ -589,7 +603,7 @@ function ViewModel() {
}
// Shutdown options
self.onQueueFinish.subscribe(function(newValue) {
self.setOnQueueFinish = function(model, event) {
// Ignore updates before the page is done
if(!self.hasStatusInfo()) return;
@@ -597,9 +611,12 @@ function ViewModel() {
callAPI({
mode: 'queue',
name: 'change_complete_action',
value: newValue
value: $(event.target).val()
})
})
// Top stop blinking while the API is calling
self.onQueueFinish($(event.target).val())
}
// Use global settings or device-specific?
self.useGlobalOptions.subscribe(function(newValue) {
@@ -749,8 +766,6 @@ function ViewModel() {
callAPI({ mode: 'fullstatus', skip_dashboard: (!statusFullRefresh)*1 }).then(function(data) {
// Update basic
self.statusInfo.loglevel(data.status.loglevel)
self.statusInfo.cache_art(data.status.cache_art)
self.statusInfo.cache_size(data.status.cache_size)
self.statusInfo.folders(data.status.folders)
// Update the full set
@@ -766,19 +781,32 @@ function ViewModel() {
self.statusInfo.publicipv4(data.status.publicipv4)
self.statusInfo.ipv6(data.status.ipv6 || glitterTranslate.noneText)
// Loaded disk info
self.hasDiskStatusInfo(true)
self.hasPerformanceInfo(true)
}
// Update the servers
if(self.statusInfo.servers().length == 0) {
if(self.statusInfo.servers().length != data.status.servers.length) {
// Only now we can subscribe to the log-level-changes! (only at start)
if(self.statusInfo.servers().length == 0) {
self.statusInfo.loglevel.subscribe(function(newValue) {
// Update log-level
callSpecialAPI('./status/change_loglevel/', {
loglevel: newValue
});
})
}
// Empty them, in case of update
self.statusInfo.servers([])
// Initial add
$.each(data.status.servers, function() {
self.statusInfo.servers.push({
'servername': this.servername,
'serveroptional': this.serveroptional,
'serverpriority': this.serverpriority,
'servertotalconn': this.servertotalconn,
'serverssl': this.serverssl,
'servername': ko.observable(this.servername),
'serveroptional': ko.observable(this.serveroptional),
'serverpriority': ko.observable(this.serverpriority),
'servertotalconn': ko.observable(this.servertotalconn),
'serverssl': ko.observable(this.serverssl),
'serversslinfo': ko.observable(this.serversslinfo),
'serveractiveconn': ko.observable(this.serveractiveconn),
'servererror': ko.observable(this.servererror),
@@ -786,23 +814,20 @@ function ViewModel() {
'serverconnections': ko.observableArray(this.serverconnections)
})
})
// Only now we can subscribe to the log-level-changes!
self.statusInfo.loglevel.subscribe(function(newValue) {
// Update log-level
callSpecialAPI('./status/change_loglevel/', {
loglevel: newValue
});
})
} else {
// Update
$.each(data.status.servers, function(index) {
var activeServer = self.statusInfo.servers()[index];
activeServer.serveractiveconn(this.serveractiveconn)
activeServer.servererror(this.servererror)
activeServer.serveractive(this.serveractive)
activeServer.servername(this.servername),
activeServer.serveroptional(this.serveroptional),
activeServer.serverpriority(this.serverpriority),
activeServer.servertotalconn(this.servertotalconn),
activeServer.serverssl(this.serverssl),
activeServer.serversslinfo(this.serversslinfo),
activeServer.serveractiveconn(this.serveractiveconn),
activeServer.servererror(this.servererror),
activeServer.serveractive(this.serveractive),
activeServer.serverconnections(this.serverconnections)
activeServer.serversslinfo(this.serversslinfo)
})
}
@@ -816,7 +841,7 @@ function ViewModel() {
// Do a disk-speedtest
self.testDiskSpeed = function(item, event) {
self.hasDiskStatusInfo(false)
self.hasPerformanceInfo(false)
// Run it and then display it
callSpecialAPI('./status/dashrefresh/').then(function() {

View File

@@ -148,7 +148,6 @@ function QueueListModel(parent) {
// See what the actual index is of the queue-object
// This way we can see how we move up and down independent of pagination
var itemReplaced = self.queueItems()[event.targetIndex+corTerm];
callAPI({
mode: "switch",
value: itemMoved.id,
@@ -156,6 +155,25 @@ function QueueListModel(parent) {
}).then(self.parent.refresh);
};
// Move button clicked
self.moveButton = function(event,ui) {
var itemMoved = event;
var targetIndex;
if($(ui.currentTarget).is(".buttonMoveToTop")){
//we want to move to the top
targetIndex = 0;
} else {
// we want to move to the bottom
targetIndex = self.totalItems() - 1;
}
callAPI({
mode: "switch",
value: itemMoved.id,
value2: targetIndex
}).then(self.parent.refresh);
}
// Save pagination state
self.paginationLimit.subscribe(function(newValue) {
// Save in config if global
@@ -171,7 +189,7 @@ function QueueListModel(parent) {
// Do we show search box. So it doesn't dissapear when nothing is found
self.hasQueueSearch = ko.pureComputed(function() {
return (self.pagination.hasPagination() || self.searchTerm())
return (self.pagination.hasPagination() || self.searchTerm() || (self.parent.hasQueue() && self.isMultiEditing()))
})
// Searching in queue (rate-limited in decleration)
@@ -460,11 +478,14 @@ function QueueModel(parent, data) {
self.password = ko.observable(data.password);
self.index = ko.observable(data.index);
self.status = ko.observable(data.status);
self.isGrabbing = ko.observable(data.status == 'Grabbing')
self.isGrabbing = ko.observable(data.status == 'Grabbing' || data.avg_age == '-')
self.isFetchingBlocks = data.status == 'Fetching' || data.priority == 'Repair' // No need to update
self.totalMB = ko.observable(parseFloat(data.mb));
self.remainingMB = ko.observable(parseFloat(data.mbleft));
self.remainingMB = ko.observable(parseFloat(data.mbleft))
self.missingMB = ko.observable(parseFloat(data.mbmissing))
self.percentage = ko.observable(parseInt(data.percentage))
self.avg_age = ko.observable(data.avg_age)
self.missing = ko.observable(data.missing)
self.direct_unpack = ko.observable(data.direct_unpack)
self.category = ko.observable(data.cat);
self.priority = ko.observable(parent.priorityName[data.priority]);
self.script = ko.observable(data.script);
@@ -476,8 +497,6 @@ function QueueModel(parent, data) {
self.nameForEdit = ko.observable();
self.editingName = ko.observable(false);
self.hasDropdown = ko.observable(false);
self.rating_avg_video = ko.observable(false)
self.rating_avg_audio = ko.observable(false)
// Color of the progress bar
self.progressColor = ko.computed(function() {
@@ -485,8 +504,8 @@ function QueueModel(parent, data) {
if(self.status() == 'Checking') {
return '#58A9FA'
}
// Check for missing data, the value is arbitrary!
if(self.missing() > 50) {
// Check for missing data, the value is arbitrary! (2%)
if(self.missingMB()/self.totalMB() > 0.02) {
return '#F8A34E'
}
// Set to grey, only when not Force download
@@ -497,22 +516,23 @@ function QueueModel(parent, data) {
return '';
});
// MB's and percentages
self.downloadedMB = ko.computed(function() {
return(self.totalMB() - self.remainingMB()).toFixed(0);
});
self.percentageRounded = ko.pureComputed(function() {
return fixPercentages(((self.downloadedMB() / self.totalMB()) * 100).toFixed(2))
})
// MB's
self.progressText = ko.pureComputed(function() {
return self.downloadedMB() + " MB / " + (self.totalMB() * 1).toFixed(0) + " MB";
return (self.totalMB() - self.remainingMB()).toFixed(0) + " MB / " + (self.totalMB() * 1).toFixed(0) + " MB";
})
// Texts
self.missingText= ko.pureComputed(function() {
// Check for missing data, the value is arbitrary!
if(self.missing() > 50) {
return self.missing() + ' ' + glitterTranslate.misingArt
self.name_title = ko.pureComputed(function() {
// When hovering over the job
if(self.direct_unpack()) {
return self.name() + ' - ' + glitterTranslate.status['DirectUnpack'] + ': ' + self.direct_unpack()
}
return self.name()
})
self.missingText = ko.pureComputed(function() {
// Check for missing data, the value is arbitrary! (1%)
if(self.missingMB()/self.totalMB() > 0.01) {
return self.missingMB().toFixed(0) + ' MB ' + glitterTranslate.misingArt
}
return;
})
@@ -521,6 +541,10 @@ function QueueModel(parent, data) {
if(self.status() == 'Checking') {
return glitterTranslate.checking
}
// Grabbing
if(self.status() == 'Grabbing') {
return glitterTranslate.fetch
}
// Pausing status
if((self.parent.parent.downloadsPaused() && self.priority() != 2) || self.pausedStatus()) {
return glitterTranslate.paused;
@@ -529,6 +553,18 @@ function QueueModel(parent, data) {
return rewriteTime(self.timeLeft());
});
// Icon to better show force-priority
self.queueIcon = ko.computed(function() {
// Force comes first
if(self.priority() == 2) {
return 'glyphicon-forward'
}
if(self.pausedStatus()) {
return 'glyphicon-play'
}
return 'glyphicon-pause'
})
// Extra queue column
self.extraText = ko.pureComputed(function() {
// Picked anything?
@@ -561,23 +597,19 @@ function QueueModel(parent, data) {
self.password(data.password);
self.index(data.index);
self.status(data.status)
self.isGrabbing(data.status == 'Grabbing')
self.isGrabbing(data.status == 'Grabbing' || data.avg_age == '-')
self.totalMB(parseFloat(data.mb));
self.remainingMB(parseFloat(data.mbleft));
self.missingMB(parseFloat(data.mbmissing))
self.percentage(parseInt(data.percentage))
self.avg_age(data.avg_age)
self.missing(data.missing)
self.direct_unpack(data.direct_unpack)
self.category(data.cat);
self.priority(parent.priorityName[data.priority]);
self.script(data.script);
self.unpackopts(parseInt(data.unpackopts)) // UnpackOpts fails if not parseInt'd!
self.pausedStatus(data.status == 'Paused');
self.timeLeft(data.timeleft);
// If exists, otherwise false
if(data.rating_avg_video !== undefined) {
self.rating_avg_video(data.rating_avg_video === 0 ? '-' : data.rating_avg_video);
self.rating_avg_audio(data.rating_avg_audio === 0 ? '-' : data.rating_avg_audio);
}
};
// Pause individual download
@@ -652,7 +684,7 @@ function QueueModel(parent, data) {
}
self.changeScript = function(item) {
// Not on empty handlers
if(!item.script()) return;
if(!item.script() || parent.scriptsList().length <= 1) return;
callAPI({
mode: 'change_script',
value: item.id,
@@ -668,7 +700,7 @@ function QueueModel(parent, data) {
}
self.changePriority = function(item, event) {
// Not if we are fetching extra blocks for repair!
if(item.status() == 'Fetching') return
if(item.isFetchingBlocks) return
callAPI({
mode: 'queue',
name: 'priority',

View File

@@ -1,7 +1,13 @@
// Peity jQuery plugin version 3.2.0
// (c) 2015 Ben Pickles
// Peity jQuery plugin version 3.2.1
// (c) 2016 Ben Pickles
//
// http://benpickles.github.io/peity
//
// Released under MIT license.
!function(t,i,e,n){var r=t.fn.peity=function(i,e){return l&&this.each(function(){var n=t(this),s=n.data("_peity");s?(i&&(s.type=i),t.extend(s.opts,e)):(s=new a(n,i,t.extend({},r.defaults[i],n.data("peity"),e)),n.change(function(){s.draw()}).data("_peity",s)),s.draw()}),this},a=function(t,i,e){this.$el=t,this.type=i,this.opts=e},s=a.prototype,h=s.svgElement=function(e,n){return t(i.createElementNS("http://www.w3.org/2000/svg",e)).attr(n)},l="createElementNS"in i&&h("svg",{})[0].createSVGRect;s.draw=function(){var t=this.opts;r.graphers[this.type].call(this,t),t.after&&t.after.call(this,t)},s.fill=function(){var i=this.opts.fill;return t.isFunction(i)?i:function(t,e){return i[e%i.length]}},s.prepare=function(t,i){return this.$svg||this.$el.hide().after(this.$svg=h("svg",{"class":"peity"})),this.$svg.empty().data("peity",this).attr({height:i,width:t})},s.values=function(){if(this.opts.values){var i=this.opts.values;return this.opts.values="",i}return t.map(this.$el.text().split(this.opts.delimiter),function(t){return parseFloat(t)})},r.defaults={},r.graphers={},r.register=function(t,i,e){this.defaults[t]=i,this.graphers[t]=e},r.register("pie",{fill:["#ff9900","#fff4dd","#ffc66e"],radius:8},function(i){if(!i.delimiter){var n=this.$el.text().match(/[^0-9\.]/);i.delimiter=n?n[0]:","}var r=t.map(this.values(),function(t){return t>0?t:0});if("/"==i.delimiter){var a=r[0],s=r[1];r=[a,e.max(0,s-a)]}for(var l=0,p=r.length,o=0;p>l;l++)o+=r[l];o||(p=2,o=1,r=[0,1]);var f=2*i.radius,c=this.prepare(i.width||f,i.height||f),u=c.width(),d=c.height(),g=u/2,v=d/2,m=e.min(g,v),y=i.innerRadius;"donut"!=this.type||y||(y=.5*m);var w=e.PI,x=this.fill(),k=this.scale=function(t,i){var n=t/o*w*2-w/2;return[i*e.cos(n)+g,i*e.sin(n)+v]},$=0;for(l=0;p>l;l++){var j,A=r[l],E=A/o;if(0!=E){if(1==E)if(y){var F=g-.01,M=v-m,S=v-y;j=h("path",{d:["M",g,M,"A",m,m,0,1,1,F,M,"L",F,S,"A",y,y,0,1,0,g,S].join(" ")})}else j=h("circle",{cx:g,cy:v,r:m});else{var L=$+A,N=["M"].concat(k($,m),"A",m,m,0,E>.5?1:0,1,k(L,m),"L");y?N=N.concat(k(L,y),"A",y,y,0,E>.5?1:0,0,k($,y)):N.push(g,v),$+=A,j=h("path",{d:N.join(" ")})}j.attr("fill",x.call(this,A,l,r)),c.append(j)}}}),r.register("donut",t.extend(!0,{},r.defaults.pie),function(t){r.graphers.pie.call(this,t)}),r.register("line",{delimiter:",",fill:"#c6d9fd",height:16,min:0,stroke:"#4d89f9",strokeWidth:1,width:32},function(t){var i=this.values();1==i.length&&i.push(i[0]);for(var r=e.max.apply(e,t.max==n?i:i.concat(t.max)),a=e.min.apply(e,t.min==n?i:i.concat(t.min)),s=this.prepare(t.width,t.height),l=t.strokeWidth,p=s.width(),o=s.height()-l,f=r-a,c=this.x=function(t){return t*(p/(i.length-1))},u=this.y=function(t){var i=o;return f&&(i-=(t-a)/f*o),i+l/2},d=u(e.max(a,0)),g=[0,d],v=0;v<i.length;v++)g.push(c(v),u(i[v]));g.push(p,d),t.fill&&s.append(h("polygon",{fill:t.fill,points:g.join(" ")})),l&&s.append(h("polyline",{fill:"none",points:g.slice(2,g.length-2).join(" "),stroke:t.stroke,"stroke-width":l,"stroke-linecap":"square"}))}),r.register("bar",{delimiter:",",fill:["#4D89F9"],height:16,min:0,padding:.1,width:32},function(t){for(var i=this.values(),r=e.max.apply(e,t.max==n?i:i.concat(t.max)),a=e.min.apply(e,t.min==n?i:i.concat(t.min)),s=this.prepare(t.width,t.height),l=s.width(),p=s.height(),o=r-a,f=t.padding,c=this.fill(),u=this.x=function(t){return t*l/i.length},d=this.y=function(t){return p-(o?(t-a)/o*p:1)},g=0;g<i.length;g++){var v,m=u(g+f),y=u(g+1-f)-m,w=i[g],x=d(w),k=x,$=x;o?0>w?k=d(e.min(r,0)):$=d(e.max(a,0)):v=1,v=$-k,0==v&&(v=1,r>0&&o&&k--),s.append(h("rect",{fill:c.call(this,w,g,i),x:m,y:k,width:y,height:v}))}})}(jQuery,document,Math);
(function(k,w,h,v){var d=k.fn.peity=function(a,b){y&&this.each(function(){var e=k(this),c=e.data("_peity");c?(a&&(c.type=a),k.extend(c.opts,b)):(c=new x(e,a,k.extend({},d.defaults[a],e.data("peity"),b)),e.change(function(){c.draw()}).data("_peity",c));c.draw()});return this},x=function(a,b,e){this.$el=a;this.type=b;this.opts=e},o=x.prototype,q=o.svgElement=function(a,b){return k(w.createElementNS("http://www.w3.org/2000/svg",a)).attr(b)},y="createElementNS"in w&&q("svg",{})[0].createSVGRect;o.draw=
function(){var a=this.opts;d.graphers[this.type].call(this,a);a.after&&a.after.call(this,a)};o.fill=function(){var a=this.opts.fill;return k.isFunction(a)?a:function(b,e){return a[e%a.length]}};o.prepare=function(a,b){this.$svg||this.$el.hide().after(this.$svg=q("svg",{"class":"peity"}));return this.$svg.empty().data("peity",this).attr({height:b,width:a})};o.values=function(){return k.map(this.$el.text().split(this.opts.delimiter),function(a){return parseFloat(a)})};d.defaults={};d.graphers={};d.register=
function(a,b,e){this.defaults[a]=b;this.graphers[a]=e};d.register("pie",{fill:["#ff9900","#fff4dd","#ffc66e"],radius:8},function(a){if(!a.delimiter){var b=this.$el.text().match(/[^0-9\.]/);a.delimiter=b?b[0]:","}b=k.map(this.values(),function(a){return 0<a?a:0});if("/"==a.delimiter)var e=b[0],b=[e,h.max(0,b[1]-e)];for(var c=0,e=b.length,t=0;c<e;c++)t+=b[c];t||(e=2,t=1,b=[0,1]);var l=2*a.radius,l=this.prepare(a.width||l,a.height||l),c=l.width(),f=l.height(),j=c/2,d=f/2,f=h.min(j,d),a=a.innerRadius;
"donut"==this.type&&!a&&(a=0.5*f);for(var r=h.PI,s=this.fill(),g=this.scale=function(a,b){var c=a/t*r*2-r/2;return[b*h.cos(c)+j,b*h.sin(c)+d]},m=0,c=0;c<e;c++){var u=b[c],i=u/t;if(0!=i){if(1==i)if(a)var i=j-0.01,p=d-f,n=d-a,i=q("path",{d:["M",j,p,"A",f,f,0,1,1,i,p,"L",i,n,"A",a,a,0,1,0,j,n].join(" ")});else i=q("circle",{cx:j,cy:d,r:f});else p=m+u,n=["M"].concat(g(m,f),"A",f,f,0,0.5<i?1:0,1,g(p,f),"L"),a?n=n.concat(g(p,a),"A",a,a,0,0.5<i?1:0,0,g(m,a)):n.push(j,d),m+=u,i=q("path",{d:n.join(" ")});
i.attr("fill",s.call(this,u,c,b));l.append(i)}}});d.register("donut",k.extend(!0,{},d.defaults.pie),function(a){d.graphers.pie.call(this,a)});d.register("line",{delimiter:",",fill:"#c6d9fd",height:16,min:0,stroke:"#4d89f9",strokeWidth:1,width:32},function(a){var b=this.values();1==b.length&&b.push(b[0]);for(var e=h.max.apply(h,a.max==v?b:b.concat(a.max)),c=h.min.apply(h,a.min==v?b:b.concat(a.min)),d=this.prepare(a.width,a.height),l=a.strokeWidth,f=d.width(),j=d.height()-l,k=e-c,e=this.x=function(a){return a*
(f/(b.length-1))},r=this.y=function(a){var b=j;k&&(b-=(a-c)/k*j);return b+l/2},s=r(h.max(c,0)),g=[0,s],m=0;m<b.length;m++)g.push(e(m),r(b[m]));g.push(f,s);a.fill&&d.append(q("polygon",{fill:a.fill,points:g.join(" ")}));l&&d.append(q("polyline",{fill:"none",points:g.slice(2,g.length-2).join(" "),stroke:a.stroke,"stroke-width":l,"stroke-linecap":"square"}))});d.register("bar",{delimiter:",",fill:["#4D89F9"],height:16,min:0,padding:0.1,width:32},function(a){for(var b=this.values(),e=h.max.apply(h,a.max==
v?b:b.concat(a.max)),c=h.min.apply(h,a.min==v?b:b.concat(a.min)),d=this.prepare(a.width,a.height),l=d.width(),f=d.height(),j=e-c,a=a.padding,k=this.fill(),r=this.x=function(a){return a*l/b.length},s=this.y=function(a){return f-(j?(a-c)/j*f:1)},g=0;g<b.length;g++){var m=r(g+a),u=r(g+1-a)-m,i=b[g],p=s(i),n=p,o;j?0>i?n=s(h.min(e,0)):p=s(h.max(c,0)):o=1;o=p-n;0==o&&(o=1,0<e&&j&&n--);d.append(q("rect",{fill:k.call(this,i,g,b),x:m,y:n,width:u,height:o}))}})})(jQuery,document,Math);

View File

@@ -1,123 +1,124 @@
/*!
* Knockout JavaScript library v3.4.0
* (c) Steven Sanderson - http://knockoutjs.com/
* Knockout JavaScript library v3.4.2
* (c) The Knockout.js team - http://knockoutjs.com/
* License: MIT (http://www.opensource.org/licenses/mit-license.php)
*/
(function() {(function(n){var x=this||(0,eval)("this"),u=x.document,M=x.navigator,v=x.jQuery,F=x.JSON;(function(n){"function"===typeof define&&define.amd?define(["exports","require"],n):"object"===typeof exports&&"object"===typeof module?n(module.exports||exports):n(x.ko={})})(function(N,O){function J(a,c){return null===a||typeof a in T?a===c:!1}function U(b,c){var d;return function(){d||(d=a.a.setTimeout(function(){d=n;b()},c))}}function V(b,c){var d;return function(){clearTimeout(d);d=a.a.setTimeout(b,c)}}function W(a,
c){c&&c!==I?"beforeChange"===c?this.Kb(a):this.Ha(a,c):this.Lb(a)}function X(a,c){null!==c&&c.k&&c.k()}function Y(a,c){var d=this.Hc,e=d[s];e.R||(this.lb&&this.Ma[c]?(d.Pb(c,a,this.Ma[c]),this.Ma[c]=null,--this.lb):e.r[c]||d.Pb(c,a,e.s?{ia:a}:d.uc(a)))}function K(b,c,d,e){a.d[b]={init:function(b,g,k,l,m){var h,r;a.m(function(){var q=a.a.c(g()),p=!d!==!q,A=!r;if(A||c||p!==h)A&&a.va.Aa()&&(r=a.a.ua(a.f.childNodes(b),!0)),p?(A||a.f.da(b,a.a.ua(r)),a.eb(e?e(m,q):m,b)):a.f.xa(b),h=p},null,{i:b});return{controlsDescendantBindings:!0}}};
a.h.ta[b]=!1;a.f.Z[b]=!0}var a="undefined"!==typeof N?N:{};a.b=function(b,c){for(var d=b.split("."),e=a,f=0;f<d.length-1;f++)e=e[d[f]];e[d[d.length-1]]=c};a.G=function(a,c,d){a[c]=d};a.version="3.4.0";a.b("version",a.version);a.options={deferUpdates:!1,useOnlyNativeEvents:!1};a.a=function(){function b(a,b){for(var c in a)a.hasOwnProperty(c)&&b(c,a[c])}function c(a,b){if(b)for(var c in b)b.hasOwnProperty(c)&&(a[c]=b[c]);return a}function d(a,b){a.__proto__=b;return a}function e(b,c,d,e){var h=b[c].match(r)||
[];a.a.q(d.match(r),function(b){a.a.pa(h,b,e)});b[c]=h.join(" ")}var f={__proto__:[]}instanceof Array,g="function"===typeof Symbol,k={},l={};k[M&&/Firefox\/2/i.test(M.userAgent)?"KeyboardEvent":"UIEvents"]=["keyup","keydown","keypress"];k.MouseEvents="click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave".split(" ");b(k,function(a,b){if(b.length)for(var c=0,d=b.length;c<d;c++)l[b[c]]=a});var m={propertychange:!0},h=u&&function(){for(var a=3,b=u.createElement("div"),c=
b.getElementsByTagName("i");b.innerHTML="\x3c!--[if gt IE "+ ++a+"]><i></i><![endif]--\x3e",c[0];);return 4<a?a:n}(),r=/\S+/g;return{cc:["authenticity_token",/^__RequestVerificationToken(_.*)?$/],q:function(a,b){for(var c=0,d=a.length;c<d;c++)b(a[c],c)},o:function(a,b){if("function"==typeof Array.prototype.indexOf)return Array.prototype.indexOf.call(a,b);for(var c=0,d=a.length;c<d;c++)if(a[c]===b)return c;return-1},Sb:function(a,b,c){for(var d=0,e=a.length;d<e;d++)if(b.call(c,a[d],d))return a[d];
return null},La:function(b,c){var d=a.a.o(b,c);0<d?b.splice(d,1):0===d&&b.shift()},Tb:function(b){b=b||[];for(var c=[],d=0,e=b.length;d<e;d++)0>a.a.o(c,b[d])&&c.push(b[d]);return c},fb:function(a,b){a=a||[];for(var c=[],d=0,e=a.length;d<e;d++)c.push(b(a[d],d));return c},Ka:function(a,b){a=a||[];for(var c=[],d=0,e=a.length;d<e;d++)b(a[d],d)&&c.push(a[d]);return c},ra:function(a,b){if(b instanceof Array)a.push.apply(a,b);else for(var c=0,d=b.length;c<d;c++)a.push(b[c]);return a},pa:function(b,c,d){var e=
a.a.o(a.a.zb(b),c);0>e?d&&b.push(c):d||b.splice(e,1)},ka:f,extend:c,Xa:d,Ya:f?d:c,D:b,Ca:function(a,b){if(!a)return a;var c={},d;for(d in a)a.hasOwnProperty(d)&&(c[d]=b(a[d],d,a));return c},ob:function(b){for(;b.firstChild;)a.removeNode(b.firstChild)},jc:function(b){b=a.a.V(b);for(var c=(b[0]&&b[0].ownerDocument||u).createElement("div"),d=0,e=b.length;d<e;d++)c.appendChild(a.$(b[d]));return c},ua:function(b,c){for(var d=0,e=b.length,h=[];d<e;d++){var m=b[d].cloneNode(!0);h.push(c?a.$(m):m)}return h},
da:function(b,c){a.a.ob(b);if(c)for(var d=0,e=c.length;d<e;d++)b.appendChild(c[d])},qc:function(b,c){var d=b.nodeType?[b]:b;if(0<d.length){for(var e=d[0],h=e.parentNode,m=0,l=c.length;m<l;m++)h.insertBefore(c[m],e);m=0;for(l=d.length;m<l;m++)a.removeNode(d[m])}},za:function(a,b){if(a.length){for(b=8===b.nodeType&&b.parentNode||b;a.length&&a[0].parentNode!==b;)a.splice(0,1);for(;1<a.length&&a[a.length-1].parentNode!==b;)a.length--;if(1<a.length){var c=a[0],d=a[a.length-1];for(a.length=0;c!==d;)a.push(c),
c=c.nextSibling;a.push(d)}}return a},sc:function(a,b){7>h?a.setAttribute("selected",b):a.selected=b},$a:function(a){return null===a||a===n?"":a.trim?a.trim():a.toString().replace(/^[\s\xa0]+|[\s\xa0]+$/g,"")},nd:function(a,b){a=a||"";return b.length>a.length?!1:a.substring(0,b.length)===b},Mc:function(a,b){if(a===b)return!0;if(11===a.nodeType)return!1;if(b.contains)return b.contains(3===a.nodeType?a.parentNode:a);if(b.compareDocumentPosition)return 16==(b.compareDocumentPosition(a)&16);for(;a&&a!=
b;)a=a.parentNode;return!!a},nb:function(b){return a.a.Mc(b,b.ownerDocument.documentElement)},Qb:function(b){return!!a.a.Sb(b,a.a.nb)},A:function(a){return a&&a.tagName&&a.tagName.toLowerCase()},Wb:function(b){return a.onError?function(){try{return b.apply(this,arguments)}catch(c){throw a.onError&&a.onError(c),c;}}:b},setTimeout:function(b,c){return setTimeout(a.a.Wb(b),c)},$b:function(b){setTimeout(function(){a.onError&&a.onError(b);throw b;},0)},p:function(b,c,d){var e=a.a.Wb(d);d=h&&m[c];if(a.options.useOnlyNativeEvents||
d||!v)if(d||"function"!=typeof b.addEventListener)if("undefined"!=typeof b.attachEvent){var l=function(a){e.call(b,a)},f="on"+c;b.attachEvent(f,l);a.a.F.oa(b,function(){b.detachEvent(f,l)})}else throw Error("Browser doesn't support addEventListener or attachEvent");else b.addEventListener(c,e,!1);else v(b).bind(c,e)},Da:function(b,c){if(!b||!b.nodeType)throw Error("element must be a DOM node when calling triggerEvent");var d;"input"===a.a.A(b)&&b.type&&"click"==c.toLowerCase()?(d=b.type,d="checkbox"==
d||"radio"==d):d=!1;if(a.options.useOnlyNativeEvents||!v||d)if("function"==typeof u.createEvent)if("function"==typeof b.dispatchEvent)d=u.createEvent(l[c]||"HTMLEvents"),d.initEvent(c,!0,!0,x,0,0,0,0,0,!1,!1,!1,!1,0,b),b.dispatchEvent(d);else throw Error("The supplied element doesn't support dispatchEvent");else if(d&&b.click)b.click();else if("undefined"!=typeof b.fireEvent)b.fireEvent("on"+c);else throw Error("Browser doesn't support triggering events");else v(b).trigger(c)},c:function(b){return a.H(b)?
b():b},zb:function(b){return a.H(b)?b.t():b},bb:function(b,c,d){var h;c&&("object"===typeof b.classList?(h=b.classList[d?"add":"remove"],a.a.q(c.match(r),function(a){h.call(b.classList,a)})):"string"===typeof b.className.baseVal?e(b.className,"baseVal",c,d):e(b,"className",c,d))},Za:function(b,c){var d=a.a.c(c);if(null===d||d===n)d="";var e=a.f.firstChild(b);!e||3!=e.nodeType||a.f.nextSibling(e)?a.f.da(b,[b.ownerDocument.createTextNode(d)]):e.data=d;a.a.Rc(b)},rc:function(a,b){a.name=b;if(7>=h)try{a.mergeAttributes(u.createElement("<input name='"+
a.name+"'/>"),!1)}catch(c){}},Rc:function(a){9<=h&&(a=1==a.nodeType?a:a.parentNode,a.style&&(a.style.zoom=a.style.zoom))},Nc:function(a){if(h){var b=a.style.width;a.style.width=0;a.style.width=b}},hd:function(b,c){b=a.a.c(b);c=a.a.c(c);for(var d=[],e=b;e<=c;e++)d.push(e);return d},V:function(a){for(var b=[],c=0,d=a.length;c<d;c++)b.push(a[c]);return b},Yb:function(a){return g?Symbol(a):a},rd:6===h,sd:7===h,C:h,ec:function(b,c){for(var d=a.a.V(b.getElementsByTagName("input")).concat(a.a.V(b.getElementsByTagName("textarea"))),
e="string"==typeof c?function(a){return a.name===c}:function(a){return c.test(a.name)},h=[],m=d.length-1;0<=m;m--)e(d[m])&&h.push(d[m]);return h},ed:function(b){return"string"==typeof b&&(b=a.a.$a(b))?F&&F.parse?F.parse(b):(new Function("return "+b))():null},Eb:function(b,c,d){if(!F||!F.stringify)throw Error("Cannot find JSON.stringify(). Some browsers (e.g., IE < 8) don't support it natively, but you can overcome this by adding a script reference to json2.js, downloadable from http://www.json.org/json2.js");
return F.stringify(a.a.c(b),c,d)},fd:function(c,d,e){e=e||{};var h=e.params||{},m=e.includeFields||this.cc,l=c;if("object"==typeof c&&"form"===a.a.A(c))for(var l=c.action,f=m.length-1;0<=f;f--)for(var g=a.a.ec(c,m[f]),k=g.length-1;0<=k;k--)h[g[k].name]=g[k].value;d=a.a.c(d);var r=u.createElement("form");r.style.display="none";r.action=l;r.method="post";for(var n in d)c=u.createElement("input"),c.type="hidden",c.name=n,c.value=a.a.Eb(a.a.c(d[n])),r.appendChild(c);b(h,function(a,b){var c=u.createElement("input");
c.type="hidden";c.name=a;c.value=b;r.appendChild(c)});u.body.appendChild(r);e.submitter?e.submitter(r):r.submit();setTimeout(function(){r.parentNode.removeChild(r)},0)}}}();a.b("utils",a.a);a.b("utils.arrayForEach",a.a.q);a.b("utils.arrayFirst",a.a.Sb);a.b("utils.arrayFilter",a.a.Ka);a.b("utils.arrayGetDistinctValues",a.a.Tb);a.b("utils.arrayIndexOf",a.a.o);a.b("utils.arrayMap",a.a.fb);a.b("utils.arrayPushAll",a.a.ra);a.b("utils.arrayRemoveItem",a.a.La);a.b("utils.extend",a.a.extend);a.b("utils.fieldsIncludedWithJsonPost",
a.a.cc);a.b("utils.getFormFields",a.a.ec);a.b("utils.peekObservable",a.a.zb);a.b("utils.postJson",a.a.fd);a.b("utils.parseJson",a.a.ed);a.b("utils.registerEventHandler",a.a.p);a.b("utils.stringifyJson",a.a.Eb);a.b("utils.range",a.a.hd);a.b("utils.toggleDomNodeCssClass",a.a.bb);a.b("utils.triggerEvent",a.a.Da);a.b("utils.unwrapObservable",a.a.c);a.b("utils.objectForEach",a.a.D);a.b("utils.addOrRemoveItem",a.a.pa);a.b("utils.setTextContent",a.a.Za);a.b("unwrap",a.a.c);Function.prototype.bind||(Function.prototype.bind=
function(a){var c=this;if(1===arguments.length)return function(){return c.apply(a,arguments)};var d=Array.prototype.slice.call(arguments,1);return function(){var e=d.slice(0);e.push.apply(e,arguments);return c.apply(a,e)}});a.a.e=new function(){function a(b,g){var k=b[d];if(!k||"null"===k||!e[k]){if(!g)return n;k=b[d]="ko"+c++;e[k]={}}return e[k]}var c=0,d="__ko__"+(new Date).getTime(),e={};return{get:function(c,d){var e=a(c,!1);return e===n?n:e[d]},set:function(c,d,e){if(e!==n||a(c,!1)!==n)a(c,!0)[d]=
e},clear:function(a){var b=a[d];return b?(delete e[b],a[d]=null,!0):!1},I:function(){return c++ +d}}};a.b("utils.domData",a.a.e);a.b("utils.domData.clear",a.a.e.clear);a.a.F=new function(){function b(b,c){var e=a.a.e.get(b,d);e===n&&c&&(e=[],a.a.e.set(b,d,e));return e}function c(d){var e=b(d,!1);if(e)for(var e=e.slice(0),l=0;l<e.length;l++)e[l](d);a.a.e.clear(d);a.a.F.cleanExternalData(d);if(f[d.nodeType])for(e=d.firstChild;d=e;)e=d.nextSibling,8===d.nodeType&&c(d)}var d=a.a.e.I(),e={1:!0,8:!0,9:!0},
f={1:!0,9:!0};return{oa:function(a,c){if("function"!=typeof c)throw Error("Callback must be a function");b(a,!0).push(c)},pc:function(c,e){var l=b(c,!1);l&&(a.a.La(l,e),0==l.length&&a.a.e.set(c,d,n))},$:function(b){if(e[b.nodeType]&&(c(b),f[b.nodeType])){var d=[];a.a.ra(d,b.getElementsByTagName("*"));for(var l=0,m=d.length;l<m;l++)c(d[l])}return b},removeNode:function(b){a.$(b);b.parentNode&&b.parentNode.removeChild(b)},cleanExternalData:function(a){v&&"function"==typeof v.cleanData&&v.cleanData([a])}}};
a.$=a.a.F.$;a.removeNode=a.a.F.removeNode;a.b("cleanNode",a.$);a.b("removeNode",a.removeNode);a.b("utils.domNodeDisposal",a.a.F);a.b("utils.domNodeDisposal.addDisposeCallback",a.a.F.oa);a.b("utils.domNodeDisposal.removeDisposeCallback",a.a.F.pc);(function(){var b=[0,"",""],c=[1,"<table>","</table>"],d=[3,"<table><tbody><tr>","</tr></tbody></table>"],e=[1,"<select multiple='multiple'>","</select>"],f={thead:c,tbody:c,tfoot:c,tr:[2,"<table><tbody>","</tbody></table>"],td:d,th:d,option:e,optgroup:e},
g=8>=a.a.C;a.a.ma=function(c,d){var e;if(v)if(v.parseHTML)e=v.parseHTML(c,d)||[];else{if((e=v.clean([c],d))&&e[0]){for(var h=e[0];h.parentNode&&11!==h.parentNode.nodeType;)h=h.parentNode;h.parentNode&&h.parentNode.removeChild(h)}}else{(e=d)||(e=u);var h=e.parentWindow||e.defaultView||x,r=a.a.$a(c).toLowerCase(),q=e.createElement("div"),p;p=(r=r.match(/^<([a-z]+)[ >]/))&&f[r[1]]||b;r=p[0];p="ignored<div>"+p[1]+c+p[2]+"</div>";"function"==typeof h.innerShiv?q.appendChild(h.innerShiv(p)):(g&&e.appendChild(q),
q.innerHTML=p,g&&q.parentNode.removeChild(q));for(;r--;)q=q.lastChild;e=a.a.V(q.lastChild.childNodes)}return e};a.a.Cb=function(b,c){a.a.ob(b);c=a.a.c(c);if(null!==c&&c!==n)if("string"!=typeof c&&(c=c.toString()),v)v(b).html(c);else for(var d=a.a.ma(c,b.ownerDocument),e=0;e<d.length;e++)b.appendChild(d[e])}})();a.b("utils.parseHtmlFragment",a.a.ma);a.b("utils.setHtml",a.a.Cb);a.M=function(){function b(c,e){if(c)if(8==c.nodeType){var f=a.M.lc(c.nodeValue);null!=f&&e.push({Lc:c,cd:f})}else if(1==c.nodeType)for(var f=
0,g=c.childNodes,k=g.length;f<k;f++)b(g[f],e)}var c={};return{wb:function(a){if("function"!=typeof a)throw Error("You can only pass a function to ko.memoization.memoize()");var b=(4294967296*(1+Math.random())|0).toString(16).substring(1)+(4294967296*(1+Math.random())|0).toString(16).substring(1);c[b]=a;return"\x3c!--[ko_memo:"+b+"]--\x3e"},xc:function(a,b){var f=c[a];if(f===n)throw Error("Couldn't find any memo with ID "+a+". Perhaps it's already been unmemoized.");try{return f.apply(null,b||[]),
!0}finally{delete c[a]}},yc:function(c,e){var f=[];b(c,f);for(var g=0,k=f.length;g<k;g++){var l=f[g].Lc,m=[l];e&&a.a.ra(m,e);a.M.xc(f[g].cd,m);l.nodeValue="";l.parentNode&&l.parentNode.removeChild(l)}},lc:function(a){return(a=a.match(/^\[ko_memo\:(.*?)\]$/))?a[1]:null}}}();a.b("memoization",a.M);a.b("memoization.memoize",a.M.wb);a.b("memoization.unmemoize",a.M.xc);a.b("memoization.parseMemoText",a.M.lc);a.b("memoization.unmemoizeDomNodeAndDescendants",a.M.yc);a.Y=function(){function b(){if(e)for(var b=
e,c=0,m;g<e;)if(m=d[g++]){if(g>b){if(5E3<=++c){g=e;a.a.$b(Error("'Too much recursion' after processing "+c+" task groups."));break}b=e}try{m()}catch(h){a.a.$b(h)}}}function c(){b();g=e=d.length=0}var d=[],e=0,f=1,g=0;return{scheduler:x.MutationObserver?function(a){var b=u.createElement("div");(new MutationObserver(a)).observe(b,{attributes:!0});return function(){b.classList.toggle("foo")}}(c):u&&"onreadystatechange"in u.createElement("script")?function(a){var b=u.createElement("script");b.onreadystatechange=
function(){b.onreadystatechange=null;u.documentElement.removeChild(b);b=null;a()};u.documentElement.appendChild(b)}:function(a){setTimeout(a,0)},Wa:function(b){e||a.Y.scheduler(c);d[e++]=b;return f++},cancel:function(a){a-=f-e;a>=g&&a<e&&(d[a]=null)},resetForTesting:function(){var a=e-g;g=e=d.length=0;return a},md:b}}();a.b("tasks",a.Y);a.b("tasks.schedule",a.Y.Wa);a.b("tasks.runEarly",a.Y.md);a.ya={throttle:function(b,c){b.throttleEvaluation=c;var d=null;return a.B({read:b,write:function(e){clearTimeout(d);
d=a.a.setTimeout(function(){b(e)},c)}})},rateLimit:function(a,c){var d,e,f;"number"==typeof c?d=c:(d=c.timeout,e=c.method);a.cb=!1;f="notifyWhenChangesStop"==e?V:U;a.Ta(function(a){return f(a,d)})},deferred:function(b,c){if(!0!==c)throw Error("The 'deferred' extender only accepts the value 'true', because it is not supported to turn deferral off once enabled.");b.cb||(b.cb=!0,b.Ta(function(c){var e;return function(){a.Y.cancel(e);e=a.Y.Wa(c);b.notifySubscribers(n,"dirty")}}))},notify:function(a,c){a.equalityComparer=
"always"==c?null:J}};var T={undefined:1,"boolean":1,number:1,string:1};a.b("extenders",a.ya);a.vc=function(b,c,d){this.ia=b;this.gb=c;this.Kc=d;this.R=!1;a.G(this,"dispose",this.k)};a.vc.prototype.k=function(){this.R=!0;this.Kc()};a.J=function(){a.a.Ya(this,D);D.rb(this)};var I="change",D={rb:function(a){a.K={};a.Nb=1},X:function(b,c,d){var e=this;d=d||I;var f=new a.vc(e,c?b.bind(c):b,function(){a.a.La(e.K[d],f);e.Ia&&e.Ia(d)});e.sa&&e.sa(d);e.K[d]||(e.K[d]=[]);e.K[d].push(f);return f},notifySubscribers:function(b,
c){c=c||I;c===I&&this.zc();if(this.Pa(c))try{a.l.Ub();for(var d=this.K[c].slice(0),e=0,f;f=d[e];++e)f.R||f.gb(b)}finally{a.l.end()}},Na:function(){return this.Nb},Uc:function(a){return this.Na()!==a},zc:function(){++this.Nb},Ta:function(b){var c=this,d=a.H(c),e,f,g;c.Ha||(c.Ha=c.notifySubscribers,c.notifySubscribers=W);var k=b(function(){c.Mb=!1;d&&g===c&&(g=c());e=!1;c.tb(f,g)&&c.Ha(f=g)});c.Lb=function(a){c.Mb=e=!0;g=a;k()};c.Kb=function(a){e||(f=a,c.Ha(a,"beforeChange"))}},Pa:function(a){return this.K[a]&&
this.K[a].length},Sc:function(b){if(b)return this.K[b]&&this.K[b].length||0;var c=0;a.a.D(this.K,function(a,b){"dirty"!==a&&(c+=b.length)});return c},tb:function(a,c){return!this.equalityComparer||!this.equalityComparer(a,c)},extend:function(b){var c=this;b&&a.a.D(b,function(b,e){var f=a.ya[b];"function"==typeof f&&(c=f(c,e)||c)});return c}};a.G(D,"subscribe",D.X);a.G(D,"extend",D.extend);a.G(D,"getSubscriptionsCount",D.Sc);a.a.ka&&a.a.Xa(D,Function.prototype);a.J.fn=D;a.hc=function(a){return null!=
a&&"function"==typeof a.X&&"function"==typeof a.notifySubscribers};a.b("subscribable",a.J);a.b("isSubscribable",a.hc);a.va=a.l=function(){function b(a){d.push(e);e=a}function c(){e=d.pop()}var d=[],e,f=0;return{Ub:b,end:c,oc:function(b){if(e){if(!a.hc(b))throw Error("Only subscribable things can act as dependencies");e.gb.call(e.Gc,b,b.Cc||(b.Cc=++f))}},w:function(a,d,e){try{return b(),a.apply(d,e||[])}finally{c()}},Aa:function(){if(e)return e.m.Aa()},Sa:function(){if(e)return e.Sa}}}();a.b("computedContext",
a.va);a.b("computedContext.getDependenciesCount",a.va.Aa);a.b("computedContext.isInitial",a.va.Sa);a.b("ignoreDependencies",a.qd=a.l.w);var E=a.a.Yb("_latestValue");a.N=function(b){function c(){if(0<arguments.length)return c.tb(c[E],arguments[0])&&(c.ga(),c[E]=arguments[0],c.fa()),this;a.l.oc(c);return c[E]}c[E]=b;a.a.ka||a.a.extend(c,a.J.fn);a.J.fn.rb(c);a.a.Ya(c,B);a.options.deferUpdates&&a.ya.deferred(c,!0);return c};var B={equalityComparer:J,t:function(){return this[E]},fa:function(){this.notifySubscribers(this[E])},
ga:function(){this.notifySubscribers(this[E],"beforeChange")}};a.a.ka&&a.a.Xa(B,a.J.fn);var H=a.N.gd="__ko_proto__";B[H]=a.N;a.Oa=function(b,c){return null===b||b===n||b[H]===n?!1:b[H]===c?!0:a.Oa(b[H],c)};a.H=function(b){return a.Oa(b,a.N)};a.Ba=function(b){return"function"==typeof b&&b[H]===a.N||"function"==typeof b&&b[H]===a.B&&b.Vc?!0:!1};a.b("observable",a.N);a.b("isObservable",a.H);a.b("isWriteableObservable",a.Ba);a.b("isWritableObservable",a.Ba);a.b("observable.fn",B);a.G(B,"peek",B.t);a.G(B,
"valueHasMutated",B.fa);a.G(B,"valueWillMutate",B.ga);a.la=function(b){b=b||[];if("object"!=typeof b||!("length"in b))throw Error("The argument passed when initializing an observable array must be an array, or null, or undefined.");b=a.N(b);a.a.Ya(b,a.la.fn);return b.extend({trackArrayChanges:!0})};a.la.fn={remove:function(b){for(var c=this.t(),d=[],e="function"!=typeof b||a.H(b)?function(a){return a===b}:b,f=0;f<c.length;f++){var g=c[f];e(g)&&(0===d.length&&this.ga(),d.push(g),c.splice(f,1),f--)}d.length&&
this.fa();return d},removeAll:function(b){if(b===n){var c=this.t(),d=c.slice(0);this.ga();c.splice(0,c.length);this.fa();return d}return b?this.remove(function(c){return 0<=a.a.o(b,c)}):[]},destroy:function(b){var c=this.t(),d="function"!=typeof b||a.H(b)?function(a){return a===b}:b;this.ga();for(var e=c.length-1;0<=e;e--)d(c[e])&&(c[e]._destroy=!0);this.fa()},destroyAll:function(b){return b===n?this.destroy(function(){return!0}):b?this.destroy(function(c){return 0<=a.a.o(b,c)}):[]},indexOf:function(b){var c=
this();return a.a.o(c,b)},replace:function(a,c){var d=this.indexOf(a);0<=d&&(this.ga(),this.t()[d]=c,this.fa())}};a.a.ka&&a.a.Xa(a.la.fn,a.N.fn);a.a.q("pop push reverse shift sort splice unshift".split(" "),function(b){a.la.fn[b]=function(){var a=this.t();this.ga();this.Vb(a,b,arguments);var d=a[b].apply(a,arguments);this.fa();return d===a?this:d}});a.a.q(["slice"],function(b){a.la.fn[b]=function(){var a=this();return a[b].apply(a,arguments)}});a.b("observableArray",a.la);a.ya.trackArrayChanges=function(b,
c){function d(){if(!e){e=!0;var c=b.notifySubscribers;b.notifySubscribers=function(a,b){b&&b!==I||++k;return c.apply(this,arguments)};var d=[].concat(b.t()||[]);f=null;g=b.X(function(c){c=[].concat(c||[]);if(b.Pa("arrayChange")){var e;if(!f||1<k)f=a.a.ib(d,c,b.hb);e=f}d=c;f=null;k=0;e&&e.length&&b.notifySubscribers(e,"arrayChange")})}}b.hb={};c&&"object"==typeof c&&a.a.extend(b.hb,c);b.hb.sparse=!0;if(!b.Vb){var e=!1,f=null,g,k=0,l=b.sa,m=b.Ia;b.sa=function(a){l&&l.call(b,a);"arrayChange"===a&&d()};
b.Ia=function(a){m&&m.call(b,a);"arrayChange"!==a||b.Pa("arrayChange")||(g.k(),e=!1)};b.Vb=function(b,c,d){function m(a,b,c){return l[l.length]={status:a,value:b,index:c}}if(e&&!k){var l=[],g=b.length,t=d.length,G=0;switch(c){case "push":G=g;case "unshift":for(c=0;c<t;c++)m("added",d[c],G+c);break;case "pop":G=g-1;case "shift":g&&m("deleted",b[G],G);break;case "splice":c=Math.min(Math.max(0,0>d[0]?g+d[0]:d[0]),g);for(var g=1===t?g:Math.min(c+(d[1]||0),g),t=c+t-2,G=Math.max(g,t),P=[],n=[],Q=2;c<G;++c,
++Q)c<g&&n.push(m("deleted",b[c],c)),c<t&&P.push(m("added",d[Q],c));a.a.dc(n,P);break;default:return}f=l}}}};var s=a.a.Yb("_state");a.m=a.B=function(b,c,d){function e(){if(0<arguments.length){if("function"===typeof f)f.apply(g.pb,arguments);else throw Error("Cannot write a value to a ko.computed unless you specify a 'write' option. If you wish to read the current value, don't pass any parameters.");return this}a.l.oc(e);(g.S||g.s&&e.Qa())&&e.aa();return g.T}"object"===typeof b?d=b:(d=d||{},b&&(d.read=
b));if("function"!=typeof d.read)throw Error("Pass a function that returns the value of the ko.computed");var f=d.write,g={T:n,S:!0,Ra:!1,Fb:!1,R:!1,Va:!1,s:!1,jd:d.read,pb:c||d.owner,i:d.disposeWhenNodeIsRemoved||d.i||null,wa:d.disposeWhen||d.wa,mb:null,r:{},L:0,bc:null};e[s]=g;e.Vc="function"===typeof f;a.a.ka||a.a.extend(e,a.J.fn);a.J.fn.rb(e);a.a.Ya(e,z);d.pure?(g.Va=!0,g.s=!0,a.a.extend(e,$)):d.deferEvaluation&&a.a.extend(e,aa);a.options.deferUpdates&&a.ya.deferred(e,!0);g.i&&(g.Fb=!0,g.i.nodeType||
(g.i=null));g.s||d.deferEvaluation||e.aa();g.i&&e.ba()&&a.a.F.oa(g.i,g.mb=function(){e.k()});return e};var z={equalityComparer:J,Aa:function(){return this[s].L},Pb:function(a,c,d){if(this[s].Va&&c===this)throw Error("A 'pure' computed must not be called recursively");this[s].r[a]=d;d.Ga=this[s].L++;d.na=c.Na()},Qa:function(){var a,c,d=this[s].r;for(a in d)if(d.hasOwnProperty(a)&&(c=d[a],c.ia.Uc(c.na)))return!0},bd:function(){this.Fa&&!this[s].Ra&&this.Fa()},ba:function(){return this[s].S||0<this[s].L},
ld:function(){this.Mb||this.ac()},uc:function(a){if(a.cb&&!this[s].i){var c=a.X(this.bd,this,"dirty"),d=a.X(this.ld,this);return{ia:a,k:function(){c.k();d.k()}}}return a.X(this.ac,this)},ac:function(){var b=this,c=b.throttleEvaluation;c&&0<=c?(clearTimeout(this[s].bc),this[s].bc=a.a.setTimeout(function(){b.aa(!0)},c)):b.Fa?b.Fa():b.aa(!0)},aa:function(b){var c=this[s],d=c.wa;if(!c.Ra&&!c.R){if(c.i&&!a.a.nb(c.i)||d&&d()){if(!c.Fb){this.k();return}}else c.Fb=!1;c.Ra=!0;try{this.Qc(b)}finally{c.Ra=!1}c.L||
this.k()}},Qc:function(b){var c=this[s],d=c.Va?n:!c.L,e={Hc:this,Ma:c.r,lb:c.L};a.l.Ub({Gc:e,gb:Y,m:this,Sa:d});c.r={};c.L=0;e=this.Pc(c,e);this.tb(c.T,e)&&(c.s||this.notifySubscribers(c.T,"beforeChange"),c.T=e,c.s?this.zc():b&&this.notifySubscribers(c.T));d&&this.notifySubscribers(c.T,"awake")},Pc:function(b,c){try{var d=b.jd;return b.pb?d.call(b.pb):d()}finally{a.l.end(),c.lb&&!b.s&&a.a.D(c.Ma,X),b.S=!1}},t:function(){var a=this[s];(a.S&&!a.L||a.s&&this.Qa())&&this.aa();return a.T},Ta:function(b){a.J.fn.Ta.call(this,
b);this.Fa=function(){this.Kb(this[s].T);this[s].S=!0;this.Lb(this)}},k:function(){var b=this[s];!b.s&&b.r&&a.a.D(b.r,function(a,b){b.k&&b.k()});b.i&&b.mb&&a.a.F.pc(b.i,b.mb);b.r=null;b.L=0;b.R=!0;b.S=!1;b.s=!1;b.i=null}},$={sa:function(b){var c=this,d=c[s];if(!d.R&&d.s&&"change"==b){d.s=!1;if(d.S||c.Qa())d.r=null,d.L=0,d.S=!0,c.aa();else{var e=[];a.a.D(d.r,function(a,b){e[b.Ga]=a});a.a.q(e,function(a,b){var e=d.r[a],l=c.uc(e.ia);l.Ga=b;l.na=e.na;d.r[a]=l})}d.R||c.notifySubscribers(d.T,"awake")}},
Ia:function(b){var c=this[s];c.R||"change"!=b||this.Pa("change")||(a.a.D(c.r,function(a,b){b.k&&(c.r[a]={ia:b.ia,Ga:b.Ga,na:b.na},b.k())}),c.s=!0,this.notifySubscribers(n,"asleep"))},Na:function(){var b=this[s];b.s&&(b.S||this.Qa())&&this.aa();return a.J.fn.Na.call(this)}},aa={sa:function(a){"change"!=a&&"beforeChange"!=a||this.t()}};a.a.ka&&a.a.Xa(z,a.J.fn);var R=a.N.gd;a.m[R]=a.N;z[R]=a.m;a.Xc=function(b){return a.Oa(b,a.m)};a.Yc=function(b){return a.Oa(b,a.m)&&b[s]&&b[s].Va};a.b("computed",a.m);
a.b("dependentObservable",a.m);a.b("isComputed",a.Xc);a.b("isPureComputed",a.Yc);a.b("computed.fn",z);a.G(z,"peek",z.t);a.G(z,"dispose",z.k);a.G(z,"isActive",z.ba);a.G(z,"getDependenciesCount",z.Aa);a.nc=function(b,c){if("function"===typeof b)return a.m(b,c,{pure:!0});b=a.a.extend({},b);b.pure=!0;return a.m(b,c)};a.b("pureComputed",a.nc);(function(){function b(a,f,g){g=g||new d;a=f(a);if("object"!=typeof a||null===a||a===n||a instanceof RegExp||a instanceof Date||a instanceof String||a instanceof
Number||a instanceof Boolean)return a;var k=a instanceof Array?[]:{};g.save(a,k);c(a,function(c){var d=f(a[c]);switch(typeof d){case "boolean":case "number":case "string":case "function":k[c]=d;break;case "object":case "undefined":var h=g.get(d);k[c]=h!==n?h:b(d,f,g)}});return k}function c(a,b){if(a instanceof Array){for(var c=0;c<a.length;c++)b(c);"function"==typeof a.toJSON&&b("toJSON")}else for(c in a)b(c)}function d(){this.keys=[];this.Ib=[]}a.wc=function(c){if(0==arguments.length)throw Error("When calling ko.toJS, pass the object you want to convert.");
return b(c,function(b){for(var c=0;a.H(b)&&10>c;c++)b=b();return b})};a.toJSON=function(b,c,d){b=a.wc(b);return a.a.Eb(b,c,d)};d.prototype={save:function(b,c){var d=a.a.o(this.keys,b);0<=d?this.Ib[d]=c:(this.keys.push(b),this.Ib.push(c))},get:function(b){b=a.a.o(this.keys,b);return 0<=b?this.Ib[b]:n}}})();a.b("toJS",a.wc);a.b("toJSON",a.toJSON);(function(){a.j={u:function(b){switch(a.a.A(b)){case "option":return!0===b.__ko__hasDomDataOptionValue__?a.a.e.get(b,a.d.options.xb):7>=a.a.C?b.getAttributeNode("value")&&
b.getAttributeNode("value").specified?b.value:b.text:b.value;case "select":return 0<=b.selectedIndex?a.j.u(b.options[b.selectedIndex]):n;default:return b.value}},ha:function(b,c,d){switch(a.a.A(b)){case "option":switch(typeof c){case "string":a.a.e.set(b,a.d.options.xb,n);"__ko__hasDomDataOptionValue__"in b&&delete b.__ko__hasDomDataOptionValue__;b.value=c;break;default:a.a.e.set(b,a.d.options.xb,c),b.__ko__hasDomDataOptionValue__=!0,b.value="number"===typeof c?c:""}break;case "select":if(""===c||
null===c)c=n;for(var e=-1,f=0,g=b.options.length,k;f<g;++f)if(k=a.j.u(b.options[f]),k==c||""==k&&c===n){e=f;break}if(d||0<=e||c===n&&1<b.size)b.selectedIndex=e;break;default:if(null===c||c===n)c="";b.value=c}}}})();a.b("selectExtensions",a.j);a.b("selectExtensions.readValue",a.j.u);a.b("selectExtensions.writeValue",a.j.ha);a.h=function(){function b(b){b=a.a.$a(b);123===b.charCodeAt(0)&&(b=b.slice(1,-1));var c=[],d=b.match(e),r,k=[],p=0;if(d){d.push(",");for(var A=0,y;y=d[A];++A){var t=y.charCodeAt(0);
if(44===t){if(0>=p){c.push(r&&k.length?{key:r,value:k.join("")}:{unknown:r||k.join("")});r=p=0;k=[];continue}}else if(58===t){if(!p&&!r&&1===k.length){r=k.pop();continue}}else 47===t&&A&&1<y.length?(t=d[A-1].match(f))&&!g[t[0]]&&(b=b.substr(b.indexOf(y)+1),d=b.match(e),d.push(","),A=-1,y="/"):40===t||123===t||91===t?++p:41===t||125===t||93===t?--p:r||k.length||34!==t&&39!==t||(y=y.slice(1,-1));k.push(y)}}return c}var c=["true","false","null","undefined"],d=/^(?:[$_a-z][$\w]*|(.+)(\.\s*[$_a-z][$\w]*|\[.+\]))$/i,
e=RegExp("\"(?:[^\"\\\\]|\\\\.)*\"|'(?:[^'\\\\]|\\\\.)*'|/(?:[^/\\\\]|\\\\.)*/w*|[^\\s:,/][^,\"'{}()/:[\\]]*[^\\s,\"'{}()/:[\\]]|[^\\s]","g"),f=/[\])"'A-Za-z0-9_$]+$/,g={"in":1,"return":1,"typeof":1},k={};return{ta:[],ea:k,yb:b,Ua:function(e,m){function h(b,e){var m;if(!A){var l=a.getBindingHandler(b);if(l&&l.preprocess&&!(e=l.preprocess(e,b,h)))return;if(l=k[b])m=e,0<=a.a.o(c,m)?m=!1:(l=m.match(d),m=null===l?!1:l[1]?"Object("+l[1]+")"+l[2]:m),l=m;l&&g.push("'"+b+"':function(_z){"+m+"=_z}")}p&&(e=
"function(){return "+e+" }");f.push("'"+b+"':"+e)}m=m||{};var f=[],g=[],p=m.valueAccessors,A=m.bindingParams,y="string"===typeof e?b(e):e;a.a.q(y,function(a){h(a.key||a.unknown,a.value)});g.length&&h("_ko_property_writers","{"+g.join(",")+" }");return f.join(",")},ad:function(a,b){for(var c=0;c<a.length;c++)if(a[c].key==b)return!0;return!1},Ea:function(b,c,d,e,f){if(b&&a.H(b))!a.Ba(b)||f&&b.t()===e||b(e);else if((b=c.get("_ko_property_writers"))&&b[d])b[d](e)}}}();a.b("expressionRewriting",a.h);a.b("expressionRewriting.bindingRewriteValidators",
a.h.ta);a.b("expressionRewriting.parseObjectLiteral",a.h.yb);a.b("expressionRewriting.preProcessBindings",a.h.Ua);a.b("expressionRewriting._twoWayBindings",a.h.ea);a.b("jsonExpressionRewriting",a.h);a.b("jsonExpressionRewriting.insertPropertyAccessorsIntoJson",a.h.Ua);(function(){function b(a){return 8==a.nodeType&&g.test(f?a.text:a.nodeValue)}function c(a){return 8==a.nodeType&&k.test(f?a.text:a.nodeValue)}function d(a,d){for(var e=a,f=1,l=[];e=e.nextSibling;){if(c(e)&&(f--,0===f))return l;l.push(e);
b(e)&&f++}if(!d)throw Error("Cannot find closing comment tag to match: "+a.nodeValue);return null}function e(a,b){var c=d(a,b);return c?0<c.length?c[c.length-1].nextSibling:a.nextSibling:null}var f=u&&"\x3c!--test--\x3e"===u.createComment("test").text,g=f?/^\x3c!--\s*ko(?:\s+([\s\S]+))?\s*--\x3e$/:/^\s*ko(?:\s+([\s\S]+))?\s*$/,k=f?/^\x3c!--\s*\/ko\s*--\x3e$/:/^\s*\/ko\s*$/,l={ul:!0,ol:!0};a.f={Z:{},childNodes:function(a){return b(a)?d(a):a.childNodes},xa:function(c){if(b(c)){c=a.f.childNodes(c);for(var d=
0,e=c.length;d<e;d++)a.removeNode(c[d])}else a.a.ob(c)},da:function(c,d){if(b(c)){a.f.xa(c);for(var e=c.nextSibling,f=0,l=d.length;f<l;f++)e.parentNode.insertBefore(d[f],e)}else a.a.da(c,d)},mc:function(a,c){b(a)?a.parentNode.insertBefore(c,a.nextSibling):a.firstChild?a.insertBefore(c,a.firstChild):a.appendChild(c)},gc:function(c,d,e){e?b(c)?c.parentNode.insertBefore(d,e.nextSibling):e.nextSibling?c.insertBefore(d,e.nextSibling):c.appendChild(d):a.f.mc(c,d)},firstChild:function(a){return b(a)?!a.nextSibling||
c(a.nextSibling)?null:a.nextSibling:a.firstChild},nextSibling:function(a){b(a)&&(a=e(a));return a.nextSibling&&c(a.nextSibling)?null:a.nextSibling},Tc:b,pd:function(a){return(a=(f?a.text:a.nodeValue).match(g))?a[1]:null},kc:function(d){if(l[a.a.A(d)]){var h=d.firstChild;if(h){do if(1===h.nodeType){var f;f=h.firstChild;var g=null;if(f){do if(g)g.push(f);else if(b(f)){var k=e(f,!0);k?f=k:g=[f]}else c(f)&&(g=[f]);while(f=f.nextSibling)}if(f=g)for(g=h.nextSibling,k=0;k<f.length;k++)g?d.insertBefore(f[k],
g):d.appendChild(f[k])}while(h=h.nextSibling)}}}}})();a.b("virtualElements",a.f);a.b("virtualElements.allowedBindings",a.f.Z);a.b("virtualElements.emptyNode",a.f.xa);a.b("virtualElements.insertAfter",a.f.gc);a.b("virtualElements.prepend",a.f.mc);a.b("virtualElements.setDomNodeChildren",a.f.da);(function(){a.Q=function(){this.Fc={}};a.a.extend(a.Q.prototype,{nodeHasBindings:function(b){switch(b.nodeType){case 1:return null!=b.getAttribute("data-bind")||a.g.getComponentNameForNode(b);case 8:return a.f.Tc(b);
default:return!1}},getBindings:function(b,c){var d=this.getBindingsString(b,c),d=d?this.parseBindingsString(d,c,b):null;return a.g.Ob(d,b,c,!1)},getBindingAccessors:function(b,c){var d=this.getBindingsString(b,c),d=d?this.parseBindingsString(d,c,b,{valueAccessors:!0}):null;return a.g.Ob(d,b,c,!0)},getBindingsString:function(b){switch(b.nodeType){case 1:return b.getAttribute("data-bind");case 8:return a.f.pd(b);default:return null}},parseBindingsString:function(b,c,d,e){try{var f=this.Fc,g=b+(e&&e.valueAccessors||
""),k;if(!(k=f[g])){var l,m="with($context){with($data||{}){return{"+a.h.Ua(b,e)+"}}}";l=new Function("$context","$element",m);k=f[g]=l}return k(c,d)}catch(h){throw h.message="Unable to parse bindings.\nBindings value: "+b+"\nMessage: "+h.message,h;}}});a.Q.instance=new a.Q})();a.b("bindingProvider",a.Q);(function(){function b(a){return function(){return a}}function c(a){return a()}function d(b){return a.a.Ca(a.l.w(b),function(a,c){return function(){return b()[c]}})}function e(c,e,h){return"function"===
typeof c?d(c.bind(null,e,h)):a.a.Ca(c,b)}function f(a,b){return d(this.getBindings.bind(this,a,b))}function g(b,c,d){var e,h=a.f.firstChild(c),f=a.Q.instance,m=f.preprocessNode;if(m){for(;e=h;)h=a.f.nextSibling(e),m.call(f,e);h=a.f.firstChild(c)}for(;e=h;)h=a.f.nextSibling(e),k(b,e,d)}function k(b,c,d){var e=!0,h=1===c.nodeType;h&&a.f.kc(c);if(h&&d||a.Q.instance.nodeHasBindings(c))e=m(c,null,b,d).shouldBindDescendants;e&&!r[a.a.A(c)]&&g(b,c,!h)}function l(b){var c=[],d={},e=[];a.a.D(b,function Z(h){if(!d[h]){var f=
a.getBindingHandler(h);f&&(f.after&&(e.push(h),a.a.q(f.after,function(c){if(b[c]){if(-1!==a.a.o(e,c))throw Error("Cannot combine the following bindings, because they have a cyclic dependency: "+e.join(", "));Z(c)}}),e.length--),c.push({key:h,fc:f}));d[h]=!0}});return c}function m(b,d,e,h){var m=a.a.e.get(b,q);if(!d){if(m)throw Error("You cannot apply bindings multiple times to the same element.");a.a.e.set(b,q,!0)}!m&&h&&a.tc(b,e);var g;if(d&&"function"!==typeof d)g=d;else{var k=a.Q.instance,r=k.getBindingAccessors||
f,p=a.B(function(){(g=d?d(e,b):r.call(k,b,e))&&e.P&&e.P();return g},null,{i:b});g&&p.ba()||(p=null)}var u;if(g){var v=p?function(a){return function(){return c(p()[a])}}:function(a){return g[a]},s=function(){return a.a.Ca(p?p():g,c)};s.get=function(a){return g[a]&&c(v(a))};s.has=function(a){return a in g};h=l(g);a.a.q(h,function(c){var d=c.fc.init,h=c.fc.update,f=c.key;if(8===b.nodeType&&!a.f.Z[f])throw Error("The binding '"+f+"' cannot be used with virtual elements");try{"function"==typeof d&&a.l.w(function(){var a=
d(b,v(f),s,e.$data,e);if(a&&a.controlsDescendantBindings){if(u!==n)throw Error("Multiple bindings ("+u+" and "+f+") are trying to control descendant bindings of the same element. You cannot use these bindings together on the same element.");u=f}}),"function"==typeof h&&a.B(function(){h(b,v(f),s,e.$data,e)},null,{i:b})}catch(m){throw m.message='Unable to process binding "'+f+": "+g[f]+'"\nMessage: '+m.message,m;}})}return{shouldBindDescendants:u===n}}function h(b){return b&&b instanceof a.U?b:new a.U(b)}
a.d={};var r={script:!0,textarea:!0,template:!0};a.getBindingHandler=function(b){return a.d[b]};a.U=function(b,c,d,e){var h=this,f="function"==typeof b&&!a.H(b),m,g=a.B(function(){var m=f?b():b,l=a.a.c(m);c?(c.P&&c.P(),a.a.extend(h,c),g&&(h.P=g)):(h.$parents=[],h.$root=l,h.ko=a);h.$rawData=m;h.$data=l;d&&(h[d]=l);e&&e(h,c,l);return h.$data},null,{wa:function(){return m&&!a.a.Qb(m)},i:!0});g.ba()&&(h.P=g,g.equalityComparer=null,m=[],g.Ac=function(b){m.push(b);a.a.F.oa(b,function(b){a.a.La(m,b);m.length||
(g.k(),h.P=g=n)})})};a.U.prototype.createChildContext=function(b,c,d){return new a.U(b,this,c,function(a,b){a.$parentContext=b;a.$parent=b.$data;a.$parents=(b.$parents||[]).slice(0);a.$parents.unshift(a.$parent);d&&d(a)})};a.U.prototype.extend=function(b){return new a.U(this.P||this.$data,this,null,function(c,d){c.$rawData=d.$rawData;a.a.extend(c,"function"==typeof b?b():b)})};var q=a.a.e.I(),p=a.a.e.I();a.tc=function(b,c){if(2==arguments.length)a.a.e.set(b,p,c),c.P&&c.P.Ac(b);else return a.a.e.get(b,
p)};a.Ja=function(b,c,d){1===b.nodeType&&a.f.kc(b);return m(b,c,h(d),!0)};a.Dc=function(b,c,d){d=h(d);return a.Ja(b,e(c,d,b),d)};a.eb=function(a,b){1!==b.nodeType&&8!==b.nodeType||g(h(a),b,!0)};a.Rb=function(a,b){!v&&x.jQuery&&(v=x.jQuery);if(b&&1!==b.nodeType&&8!==b.nodeType)throw Error("ko.applyBindings: first parameter should be your view model; second parameter should be a DOM node");b=b||x.document.body;k(h(a),b,!0)};a.kb=function(b){switch(b.nodeType){case 1:case 8:var c=a.tc(b);if(c)return c;
if(b.parentNode)return a.kb(b.parentNode)}return n};a.Jc=function(b){return(b=a.kb(b))?b.$data:n};a.b("bindingHandlers",a.d);a.b("applyBindings",a.Rb);a.b("applyBindingsToDescendants",a.eb);a.b("applyBindingAccessorsToNode",a.Ja);a.b("applyBindingsToNode",a.Dc);a.b("contextFor",a.kb);a.b("dataFor",a.Jc)})();(function(b){function c(c,e){var m=f.hasOwnProperty(c)?f[c]:b,h;m?m.X(e):(m=f[c]=new a.J,m.X(e),d(c,function(b,d){var e=!(!d||!d.synchronous);g[c]={definition:b,Zc:e};delete f[c];h||e?m.notifySubscribers(b):
a.Y.Wa(function(){m.notifySubscribers(b)})}),h=!0)}function d(a,b){e("getConfig",[a],function(c){c?e("loadComponent",[a,c],function(a){b(a,c)}):b(null,null)})}function e(c,d,f,h){h||(h=a.g.loaders.slice(0));var g=h.shift();if(g){var q=g[c];if(q){var p=!1;if(q.apply(g,d.concat(function(a){p?f(null):null!==a?f(a):e(c,d,f,h)}))!==b&&(p=!0,!g.suppressLoaderExceptions))throw Error("Component loaders must supply values by invoking the callback, not by returning values synchronously.");}else e(c,d,f,h)}else f(null)}
var f={},g={};a.g={get:function(d,e){var f=g.hasOwnProperty(d)?g[d]:b;f?f.Zc?a.l.w(function(){e(f.definition)}):a.Y.Wa(function(){e(f.definition)}):c(d,e)},Xb:function(a){delete g[a]},Jb:e};a.g.loaders=[];a.b("components",a.g);a.b("components.get",a.g.get);a.b("components.clearCachedDefinition",a.g.Xb)})();(function(){function b(b,c,d,e){function g(){0===--y&&e(k)}var k={},y=2,t=d.template;d=d.viewModel;t?f(c,t,function(c){a.g.Jb("loadTemplate",[b,c],function(a){k.template=a;g()})}):g();d?f(c,d,function(c){a.g.Jb("loadViewModel",
[b,c],function(a){k[l]=a;g()})}):g()}function c(a,b,d){if("function"===typeof b)d(function(a){return new b(a)});else if("function"===typeof b[l])d(b[l]);else if("instance"in b){var e=b.instance;d(function(){return e})}else"viewModel"in b?c(a,b.viewModel,d):a("Unknown viewModel value: "+b)}function d(b){switch(a.a.A(b)){case "script":return a.a.ma(b.text);case "textarea":return a.a.ma(b.value);case "template":if(e(b.content))return a.a.ua(b.content.childNodes)}return a.a.ua(b.childNodes)}function e(a){return x.DocumentFragment?
a instanceof DocumentFragment:a&&11===a.nodeType}function f(a,b,c){"string"===typeof b.require?O||x.require?(O||x.require)([b.require],c):a("Uses require, but no AMD loader is present"):c(b)}function g(a){return function(b){throw Error("Component '"+a+"': "+b);}}var k={};a.g.register=function(b,c){if(!c)throw Error("Invalid configuration for "+b);if(a.g.ub(b))throw Error("Component "+b+" is already registered");k[b]=c};a.g.ub=function(a){return k.hasOwnProperty(a)};a.g.od=function(b){delete k[b];
a.g.Xb(b)};a.g.Zb={getConfig:function(a,b){b(k.hasOwnProperty(a)?k[a]:null)},loadComponent:function(a,c,d){var e=g(a);f(e,c,function(c){b(a,e,c,d)})},loadTemplate:function(b,c,f){b=g(b);if("string"===typeof c)f(a.a.ma(c));else if(c instanceof Array)f(c);else if(e(c))f(a.a.V(c.childNodes));else if(c.element)if(c=c.element,x.HTMLElement?c instanceof HTMLElement:c&&c.tagName&&1===c.nodeType)f(d(c));else if("string"===typeof c){var l=u.getElementById(c);l?f(d(l)):b("Cannot find element with ID "+c)}else b("Unknown element type: "+
c);else b("Unknown template value: "+c)},loadViewModel:function(a,b,d){c(g(a),b,d)}};var l="createViewModel";a.b("components.register",a.g.register);a.b("components.isRegistered",a.g.ub);a.b("components.unregister",a.g.od);a.b("components.defaultLoader",a.g.Zb);a.g.loaders.push(a.g.Zb);a.g.Bc=k})();(function(){function b(b,e){var f=b.getAttribute("params");if(f){var f=c.parseBindingsString(f,e,b,{valueAccessors:!0,bindingParams:!0}),f=a.a.Ca(f,function(c){return a.m(c,null,{i:b})}),g=a.a.Ca(f,function(c){var e=
c.t();return c.ba()?a.m({read:function(){return a.a.c(c())},write:a.Ba(e)&&function(a){c()(a)},i:b}):e});g.hasOwnProperty("$raw")||(g.$raw=f);return g}return{$raw:{}}}a.g.getComponentNameForNode=function(b){var c=a.a.A(b);if(a.g.ub(c)&&(-1!=c.indexOf("-")||"[object HTMLUnknownElement]"==""+b||8>=a.a.C&&b.tagName===c))return c};a.g.Ob=function(c,e,f,g){if(1===e.nodeType){var k=a.g.getComponentNameForNode(e);if(k){c=c||{};if(c.component)throw Error('Cannot use the "component" binding on a custom element matching a component');
var l={name:k,params:b(e,f)};c.component=g?function(){return l}:l}}return c};var c=new a.Q;9>a.a.C&&(a.g.register=function(a){return function(b){u.createElement(b);return a.apply(this,arguments)}}(a.g.register),u.createDocumentFragment=function(b){return function(){var c=b(),f=a.g.Bc,g;for(g in f)f.hasOwnProperty(g)&&c.createElement(g);return c}}(u.createDocumentFragment))})();(function(b){function c(b,c,d){c=c.template;if(!c)throw Error("Component '"+b+"' has no template");b=a.a.ua(c);a.f.da(d,b)}
function d(a,b,c,d){var e=a.createViewModel;return e?e.call(a,d,{element:b,templateNodes:c}):d}var e=0;a.d.component={init:function(f,g,k,l,m){function h(){var a=r&&r.dispose;"function"===typeof a&&a.call(r);q=r=null}var r,q,p=a.a.V(a.f.childNodes(f));a.a.F.oa(f,h);a.m(function(){var l=a.a.c(g()),k,t;"string"===typeof l?k=l:(k=a.a.c(l.name),t=a.a.c(l.params));if(!k)throw Error("No component name specified");var n=q=++e;a.g.get(k,function(e){if(q===n){h();if(!e)throw Error("Unknown component '"+k+
"'");c(k,e,f);var g=d(e,f,p,t);e=m.createChildContext(g,b,function(a){a.$component=g;a.$componentTemplateNodes=p});r=g;a.eb(e,f)}})},null,{i:f});return{controlsDescendantBindings:!0}}};a.f.Z.component=!0})();var S={"class":"className","for":"htmlFor"};a.d.attr={update:function(b,c){var d=a.a.c(c())||{};a.a.D(d,function(c,d){d=a.a.c(d);var g=!1===d||null===d||d===n;g&&b.removeAttribute(c);8>=a.a.C&&c in S?(c=S[c],g?b.removeAttribute(c):b[c]=d):g||b.setAttribute(c,d.toString());"name"===c&&a.a.rc(b,
g?"":d.toString())})}};(function(){a.d.checked={after:["value","attr"],init:function(b,c,d){function e(){var e=b.checked,f=p?g():e;if(!a.va.Sa()&&(!l||e)){var m=a.l.w(c);if(h){var k=r?m.t():m;q!==f?(e&&(a.a.pa(k,f,!0),a.a.pa(k,q,!1)),q=f):a.a.pa(k,f,e);r&&a.Ba(m)&&m(k)}else a.h.Ea(m,d,"checked",f,!0)}}function f(){var d=a.a.c(c());b.checked=h?0<=a.a.o(d,g()):k?d:g()===d}var g=a.nc(function(){return d.has("checkedValue")?a.a.c(d.get("checkedValue")):d.has("value")?a.a.c(d.get("value")):b.value}),k=
"checkbox"==b.type,l="radio"==b.type;if(k||l){var m=c(),h=k&&a.a.c(m)instanceof Array,r=!(h&&m.push&&m.splice),q=h?g():n,p=l||h;l&&!b.name&&a.d.uniqueName.init(b,function(){return!0});a.m(e,null,{i:b});a.a.p(b,"click",e);a.m(f,null,{i:b});m=n}}};a.h.ea.checked=!0;a.d.checkedValue={update:function(b,c){b.value=a.a.c(c())}}})();a.d.css={update:function(b,c){var d=a.a.c(c());null!==d&&"object"==typeof d?a.a.D(d,function(c,d){d=a.a.c(d);a.a.bb(b,c,d)}):(d=a.a.$a(String(d||"")),a.a.bb(b,b.__ko__cssValue,
!1),b.__ko__cssValue=d,a.a.bb(b,d,!0))}};a.d.enable={update:function(b,c){var d=a.a.c(c());d&&b.disabled?b.removeAttribute("disabled"):d||b.disabled||(b.disabled=!0)}};a.d.disable={update:function(b,c){a.d.enable.update(b,function(){return!a.a.c(c())})}};a.d.event={init:function(b,c,d,e,f){var g=c()||{};a.a.D(g,function(g){"string"==typeof g&&a.a.p(b,g,function(b){var m,h=c()[g];if(h){try{var r=a.a.V(arguments);e=f.$data;r.unshift(e);m=h.apply(e,r)}finally{!0!==m&&(b.preventDefault?b.preventDefault():
b.returnValue=!1)}!1===d.get(g+"Bubble")&&(b.cancelBubble=!0,b.stopPropagation&&b.stopPropagation())}})})}};a.d.foreach={ic:function(b){return function(){var c=b(),d=a.a.zb(c);if(!d||"number"==typeof d.length)return{foreach:c,templateEngine:a.W.sb};a.a.c(c);return{foreach:d.data,as:d.as,includeDestroyed:d.includeDestroyed,afterAdd:d.afterAdd,beforeRemove:d.beforeRemove,afterRender:d.afterRender,beforeMove:d.beforeMove,afterMove:d.afterMove,templateEngine:a.W.sb}}},init:function(b,c){return a.d.template.init(b,
a.d.foreach.ic(c))},update:function(b,c,d,e,f){return a.d.template.update(b,a.d.foreach.ic(c),d,e,f)}};a.h.ta.foreach=!1;a.f.Z.foreach=!0;a.d.hasfocus={init:function(b,c,d){function e(e){b.__ko_hasfocusUpdating=!0;var f=b.ownerDocument;if("activeElement"in f){var g;try{g=f.activeElement}catch(h){g=f.body}e=g===b}f=c();a.h.Ea(f,d,"hasfocus",e,!0);b.__ko_hasfocusLastValue=e;b.__ko_hasfocusUpdating=!1}var f=e.bind(null,!0),g=e.bind(null,!1);a.a.p(b,"focus",f);a.a.p(b,"focusin",f);a.a.p(b,"blur",g);a.a.p(b,
"focusout",g)},update:function(b,c){var d=!!a.a.c(c());b.__ko_hasfocusUpdating||b.__ko_hasfocusLastValue===d||(d?b.focus():b.blur(),!d&&b.__ko_hasfocusLastValue&&b.ownerDocument.body.focus(),a.l.w(a.a.Da,null,[b,d?"focusin":"focusout"]))}};a.h.ea.hasfocus=!0;a.d.hasFocus=a.d.hasfocus;a.h.ea.hasFocus=!0;a.d.html={init:function(){return{controlsDescendantBindings:!0}},update:function(b,c){a.a.Cb(b,c())}};K("if");K("ifnot",!1,!0);K("with",!0,!1,function(a,c){return a.createChildContext(c)});var L={};
a.d.options={init:function(b){if("select"!==a.a.A(b))throw Error("options binding applies only to SELECT elements");for(;0<b.length;)b.remove(0);return{controlsDescendantBindings:!0}},update:function(b,c,d){function e(){return a.a.Ka(b.options,function(a){return a.selected})}function f(a,b,c){var d=typeof b;return"function"==d?b(a):"string"==d?a[b]:c}function g(c,e){if(A&&h)a.j.ha(b,a.a.c(d.get("value")),!0);else if(p.length){var f=0<=a.a.o(p,a.j.u(e[0]));a.a.sc(e[0],f);A&&!f&&a.l.w(a.a.Da,null,[b,
"change"])}}var k=b.multiple,l=0!=b.length&&k?b.scrollTop:null,m=a.a.c(c()),h=d.get("valueAllowUnset")&&d.has("value"),r=d.get("optionsIncludeDestroyed");c={};var q,p=[];h||(k?p=a.a.fb(e(),a.j.u):0<=b.selectedIndex&&p.push(a.j.u(b.options[b.selectedIndex])));m&&("undefined"==typeof m.length&&(m=[m]),q=a.a.Ka(m,function(b){return r||b===n||null===b||!a.a.c(b._destroy)}),d.has("optionsCaption")&&(m=a.a.c(d.get("optionsCaption")),null!==m&&m!==n&&q.unshift(L)));var A=!1;c.beforeRemove=function(a){b.removeChild(a)};
m=g;d.has("optionsAfterRender")&&"function"==typeof d.get("optionsAfterRender")&&(m=function(b,c){g(0,c);a.l.w(d.get("optionsAfterRender"),null,[c[0],b!==L?b:n])});a.a.Bb(b,q,function(c,e,g){g.length&&(p=!h&&g[0].selected?[a.j.u(g[0])]:[],A=!0);e=b.ownerDocument.createElement("option");c===L?(a.a.Za(e,d.get("optionsCaption")),a.j.ha(e,n)):(g=f(c,d.get("optionsValue"),c),a.j.ha(e,a.a.c(g)),c=f(c,d.get("optionsText"),g),a.a.Za(e,c));return[e]},c,m);a.l.w(function(){h?a.j.ha(b,a.a.c(d.get("value")),
!0):(k?p.length&&e().length<p.length:p.length&&0<=b.selectedIndex?a.j.u(b.options[b.selectedIndex])!==p[0]:p.length||0<=b.selectedIndex)&&a.a.Da(b,"change")});a.a.Nc(b);l&&20<Math.abs(l-b.scrollTop)&&(b.scrollTop=l)}};a.d.options.xb=a.a.e.I();a.d.selectedOptions={after:["options","foreach"],init:function(b,c,d){a.a.p(b,"change",function(){var e=c(),f=[];a.a.q(b.getElementsByTagName("option"),function(b){b.selected&&f.push(a.j.u(b))});a.h.Ea(e,d,"selectedOptions",f)})},update:function(b,c){if("select"!=
a.a.A(b))throw Error("values binding applies only to SELECT elements");var d=a.a.c(c()),e=b.scrollTop;d&&"number"==typeof d.length&&a.a.q(b.getElementsByTagName("option"),function(b){var c=0<=a.a.o(d,a.j.u(b));b.selected!=c&&a.a.sc(b,c)});b.scrollTop=e}};a.h.ea.selectedOptions=!0;a.d.style={update:function(b,c){var d=a.a.c(c()||{});a.a.D(d,function(c,d){d=a.a.c(d);if(null===d||d===n||!1===d)d="";b.style[c]=d})}};a.d.submit={init:function(b,c,d,e,f){if("function"!=typeof c())throw Error("The value for a submit binding must be a function");
a.a.p(b,"submit",function(a){var d,e=c();try{d=e.call(f.$data,b)}finally{!0!==d&&(a.preventDefault?a.preventDefault():a.returnValue=!1)}})}};a.d.text={init:function(){return{controlsDescendantBindings:!0}},update:function(b,c){a.a.Za(b,c())}};a.f.Z.text=!0;(function(){if(x&&x.navigator)var b=function(a){if(a)return parseFloat(a[1])},c=x.opera&&x.opera.version&&parseInt(x.opera.version()),d=x.navigator.userAgent,e=b(d.match(/^(?:(?!chrome).)*version\/([^ ]*) safari/i)),f=b(d.match(/Firefox\/([^ ]*)/));
if(10>a.a.C)var g=a.a.e.I(),k=a.a.e.I(),l=function(b){var c=this.activeElement;(c=c&&a.a.e.get(c,k))&&c(b)},m=function(b,c){var d=b.ownerDocument;a.a.e.get(d,g)||(a.a.e.set(d,g,!0),a.a.p(d,"selectionchange",l));a.a.e.set(b,k,c)};a.d.textInput={init:function(b,d,g){function l(c,d){a.a.p(b,c,d)}function k(){var c=a.a.c(d());if(null===c||c===n)c="";v!==n&&c===v?a.a.setTimeout(k,4):b.value!==c&&(u=c,b.value=c)}function y(){s||(v=b.value,s=a.a.setTimeout(t,4))}function t(){clearTimeout(s);v=s=n;var c=
b.value;u!==c&&(u=c,a.h.Ea(d(),g,"textInput",c))}var u=b.value,s,v,x=9==a.a.C?y:t;10>a.a.C?(l("propertychange",function(a){"value"===a.propertyName&&x(a)}),8==a.a.C&&(l("keyup",t),l("keydown",t)),8<=a.a.C&&(m(b,x),l("dragend",y))):(l("input",t),5>e&&"textarea"===a.a.A(b)?(l("keydown",y),l("paste",y),l("cut",y)):11>c?l("keydown",y):4>f&&(l("DOMAutoComplete",t),l("dragdrop",t),l("drop",t)));l("change",t);a.m(k,null,{i:b})}};a.h.ea.textInput=!0;a.d.textinput={preprocess:function(a,b,c){c("textInput",
a)}}})();a.d.uniqueName={init:function(b,c){if(c()){var d="ko_unique_"+ ++a.d.uniqueName.Ic;a.a.rc(b,d)}}};a.d.uniqueName.Ic=0;a.d.value={after:["options","foreach"],init:function(b,c,d){if("input"!=b.tagName.toLowerCase()||"checkbox"!=b.type&&"radio"!=b.type){var e=["change"],f=d.get("valueUpdate"),g=!1,k=null;f&&("string"==typeof f&&(f=[f]),a.a.ra(e,f),e=a.a.Tb(e));var l=function(){k=null;g=!1;var e=c(),f=a.j.u(b);a.h.Ea(e,d,"value",f)};!a.a.C||"input"!=b.tagName.toLowerCase()||"text"!=b.type||
"off"==b.autocomplete||b.form&&"off"==b.form.autocomplete||-1!=a.a.o(e,"propertychange")||(a.a.p(b,"propertychange",function(){g=!0}),a.a.p(b,"focus",function(){g=!1}),a.a.p(b,"blur",function(){g&&l()}));a.a.q(e,function(c){var d=l;a.a.nd(c,"after")&&(d=function(){k=a.j.u(b);a.a.setTimeout(l,0)},c=c.substring(5));a.a.p(b,c,d)});var m=function(){var e=a.a.c(c()),f=a.j.u(b);if(null!==k&&e===k)a.a.setTimeout(m,0);else if(e!==f)if("select"===a.a.A(b)){var g=d.get("valueAllowUnset"),f=function(){a.j.ha(b,
e,g)};f();g||e===a.j.u(b)?a.a.setTimeout(f,0):a.l.w(a.a.Da,null,[b,"change"])}else a.j.ha(b,e)};a.m(m,null,{i:b})}else a.Ja(b,{checkedValue:c})},update:function(){}};a.h.ea.value=!0;a.d.visible={update:function(b,c){var d=a.a.c(c()),e="none"!=b.style.display;d&&!e?b.style.display="":!d&&e&&(b.style.display="none")}};(function(b){a.d[b]={init:function(c,d,e,f,g){return a.d.event.init.call(this,c,function(){var a={};a[b]=d();return a},e,f,g)}}})("click");a.O=function(){};a.O.prototype.renderTemplateSource=
function(){throw Error("Override renderTemplateSource");};a.O.prototype.createJavaScriptEvaluatorBlock=function(){throw Error("Override createJavaScriptEvaluatorBlock");};a.O.prototype.makeTemplateSource=function(b,c){if("string"==typeof b){c=c||u;var d=c.getElementById(b);if(!d)throw Error("Cannot find template with ID "+b);return new a.v.n(d)}if(1==b.nodeType||8==b.nodeType)return new a.v.qa(b);throw Error("Unknown template type: "+b);};a.O.prototype.renderTemplate=function(a,c,d,e){a=this.makeTemplateSource(a,
e);return this.renderTemplateSource(a,c,d,e)};a.O.prototype.isTemplateRewritten=function(a,c){return!1===this.allowTemplateRewriting?!0:this.makeTemplateSource(a,c).data("isRewritten")};a.O.prototype.rewriteTemplate=function(a,c,d){a=this.makeTemplateSource(a,d);c=c(a.text());a.text(c);a.data("isRewritten",!0)};a.b("templateEngine",a.O);a.Gb=function(){function b(b,c,d,k){b=a.h.yb(b);for(var l=a.h.ta,m=0;m<b.length;m++){var h=b[m].key;if(l.hasOwnProperty(h)){var r=l[h];if("function"===typeof r){if(h=
r(b[m].value))throw Error(h);}else if(!r)throw Error("This template engine does not support the '"+h+"' binding within its templates");}}d="ko.__tr_ambtns(function($context,$element){return(function(){return{ "+a.h.Ua(b,{valueAccessors:!0})+" } })()},'"+d.toLowerCase()+"')";return k.createJavaScriptEvaluatorBlock(d)+c}var c=/(<([a-z]+\d*)(?:\s+(?!data-bind\s*=\s*)[a-z0-9\-]+(?:=(?:\"[^\"]*\"|\'[^\']*\'|[^>]*))?)*\s+)data-bind\s*=\s*(["'])([\s\S]*?)\3/gi,d=/\x3c!--\s*ko\b\s*([\s\S]*?)\s*--\x3e/g;return{Oc:function(b,
c,d){c.isTemplateRewritten(b,d)||c.rewriteTemplate(b,function(b){return a.Gb.dd(b,c)},d)},dd:function(a,f){return a.replace(c,function(a,c,d,e,h){return b(h,c,d,f)}).replace(d,function(a,c){return b(c,"\x3c!-- ko --\x3e","#comment",f)})},Ec:function(b,c){return a.M.wb(function(d,k){var l=d.nextSibling;l&&l.nodeName.toLowerCase()===c&&a.Ja(l,b,k)})}}}();a.b("__tr_ambtns",a.Gb.Ec);(function(){a.v={};a.v.n=function(b){if(this.n=b){var c=a.a.A(b);this.ab="script"===c?1:"textarea"===c?2:"template"==c&&
b.content&&11===b.content.nodeType?3:4}};a.v.n.prototype.text=function(){var b=1===this.ab?"text":2===this.ab?"value":"innerHTML";if(0==arguments.length)return this.n[b];var c=arguments[0];"innerHTML"===b?a.a.Cb(this.n,c):this.n[b]=c};var b=a.a.e.I()+"_";a.v.n.prototype.data=function(c){if(1===arguments.length)return a.a.e.get(this.n,b+c);a.a.e.set(this.n,b+c,arguments[1])};var c=a.a.e.I();a.v.n.prototype.nodes=function(){var b=this.n;if(0==arguments.length)return(a.a.e.get(b,c)||{}).jb||(3===this.ab?
b.content:4===this.ab?b:n);a.a.e.set(b,c,{jb:arguments[0]})};a.v.qa=function(a){this.n=a};a.v.qa.prototype=new a.v.n;a.v.qa.prototype.text=function(){if(0==arguments.length){var b=a.a.e.get(this.n,c)||{};b.Hb===n&&b.jb&&(b.Hb=b.jb.innerHTML);return b.Hb}a.a.e.set(this.n,c,{Hb:arguments[0]})};a.b("templateSources",a.v);a.b("templateSources.domElement",a.v.n);a.b("templateSources.anonymousTemplate",a.v.qa)})();(function(){function b(b,c,d){var e;for(c=a.f.nextSibling(c);b&&(e=b)!==c;)b=a.f.nextSibling(e),
d(e,b)}function c(c,d){if(c.length){var e=c[0],f=c[c.length-1],g=e.parentNode,k=a.Q.instance,n=k.preprocessNode;if(n){b(e,f,function(a,b){var c=a.previousSibling,d=n.call(k,a);d&&(a===e&&(e=d[0]||b),a===f&&(f=d[d.length-1]||c))});c.length=0;if(!e)return;e===f?c.push(e):(c.push(e,f),a.a.za(c,g))}b(e,f,function(b){1!==b.nodeType&&8!==b.nodeType||a.Rb(d,b)});b(e,f,function(b){1!==b.nodeType&&8!==b.nodeType||a.M.yc(b,[d])});a.a.za(c,g)}}function d(a){return a.nodeType?a:0<a.length?a[0]:null}function e(b,
e,f,k,q){q=q||{};var p=(b&&d(b)||f||{}).ownerDocument,n=q.templateEngine||g;a.Gb.Oc(f,n,p);f=n.renderTemplate(f,k,q,p);if("number"!=typeof f.length||0<f.length&&"number"!=typeof f[0].nodeType)throw Error("Template engine must return an array of DOM nodes");p=!1;switch(e){case "replaceChildren":a.f.da(b,f);p=!0;break;case "replaceNode":a.a.qc(b,f);p=!0;break;case "ignoreTargetNode":break;default:throw Error("Unknown renderMode: "+e);}p&&(c(f,k),q.afterRender&&a.l.w(q.afterRender,null,[f,k.$data]));
return f}function f(b,c,d){return a.H(b)?b():"function"===typeof b?b(c,d):b}var g;a.Db=function(b){if(b!=n&&!(b instanceof a.O))throw Error("templateEngine must inherit from ko.templateEngine");g=b};a.Ab=function(b,c,h,k,q){h=h||{};if((h.templateEngine||g)==n)throw Error("Set a template engine before calling renderTemplate");q=q||"replaceChildren";if(k){var p=d(k);return a.B(function(){var g=c&&c instanceof a.U?c:new a.U(a.a.c(c)),n=f(b,g.$data,g),g=e(k,q,n,g,h);"replaceNode"==q&&(k=g,p=d(k))},null,
{wa:function(){return!p||!a.a.nb(p)},i:p&&"replaceNode"==q?p.parentNode:p})}return a.M.wb(function(d){a.Ab(b,c,h,d,"replaceNode")})};a.kd=function(b,d,g,k,q){function p(a,b){c(b,s);g.afterRender&&g.afterRender(b,a);s=null}function u(a,c){s=q.createChildContext(a,g.as,function(a){a.$index=c});var d=f(b,a,s);return e(null,"ignoreTargetNode",d,s,g)}var s;return a.B(function(){var b=a.a.c(d)||[];"undefined"==typeof b.length&&(b=[b]);b=a.a.Ka(b,function(b){return g.includeDestroyed||b===n||null===b||!a.a.c(b._destroy)});
a.l.w(a.a.Bb,null,[k,b,u,g,p])},null,{i:k})};var k=a.a.e.I();a.d.template={init:function(b,c){var d=a.a.c(c());if("string"==typeof d||d.name)a.f.xa(b);else{if("nodes"in d){if(d=d.nodes||[],a.H(d))throw Error('The "nodes" option must be a plain, non-observable array.');}else d=a.f.childNodes(b);d=a.a.jc(d);(new a.v.qa(b)).nodes(d)}return{controlsDescendantBindings:!0}},update:function(b,c,d,e,f){var g=c(),s;c=a.a.c(g);d=!0;e=null;"string"==typeof c?c={}:(g=c.name,"if"in c&&(d=a.a.c(c["if"])),d&&"ifnot"in
c&&(d=!a.a.c(c.ifnot)),s=a.a.c(c.data));"foreach"in c?e=a.kd(g||b,d&&c.foreach||[],c,b,f):d?(f="data"in c?f.createChildContext(s,c.as):f,e=a.Ab(g||b,f,c,b)):a.f.xa(b);f=e;(s=a.a.e.get(b,k))&&"function"==typeof s.k&&s.k();a.a.e.set(b,k,f&&f.ba()?f:n)}};a.h.ta.template=function(b){b=a.h.yb(b);return 1==b.length&&b[0].unknown||a.h.ad(b,"name")?null:"This template engine does not support anonymous templates nested within its templates"};a.f.Z.template=!0})();a.b("setTemplateEngine",a.Db);a.b("renderTemplate",
a.Ab);a.a.dc=function(a,c,d){if(a.length&&c.length){var e,f,g,k,l;for(e=f=0;(!d||e<d)&&(k=a[f]);++f){for(g=0;l=c[g];++g)if(k.value===l.value){k.moved=l.index;l.moved=k.index;c.splice(g,1);e=g=0;break}e+=g}}};a.a.ib=function(){function b(b,d,e,f,g){var k=Math.min,l=Math.max,m=[],h,n=b.length,q,p=d.length,s=p-n||1,u=n+p+1,t,v,x;for(h=0;h<=n;h++)for(v=t,m.push(t=[]),x=k(p,h+s),q=l(0,h-1);q<=x;q++)t[q]=q?h?b[h-1]===d[q-1]?v[q-1]:k(v[q]||u,t[q-1]||u)+1:q+1:h+1;k=[];l=[];s=[];h=n;for(q=p;h||q;)p=m[h][q]-
1,q&&p===m[h][q-1]?l.push(k[k.length]={status:e,value:d[--q],index:q}):h&&p===m[h-1][q]?s.push(k[k.length]={status:f,value:b[--h],index:h}):(--q,--h,g.sparse||k.push({status:"retained",value:d[q]}));a.a.dc(s,l,!g.dontLimitMoves&&10*n);return k.reverse()}return function(a,d,e){e="boolean"===typeof e?{dontLimitMoves:e}:e||{};a=a||[];d=d||[];return a.length<d.length?b(a,d,"added","deleted",e):b(d,a,"deleted","added",e)}}();a.b("utils.compareArrays",a.a.ib);(function(){function b(b,c,d,k,l){var m=[],
h=a.B(function(){var h=c(d,l,a.a.za(m,b))||[];0<m.length&&(a.a.qc(m,h),k&&a.l.w(k,null,[d,h,l]));m.length=0;a.a.ra(m,h)},null,{i:b,wa:function(){return!a.a.Qb(m)}});return{ca:m,B:h.ba()?h:n}}var c=a.a.e.I(),d=a.a.e.I();a.a.Bb=function(e,f,g,k,l){function m(b,c){w=q[c];v!==c&&(D[b]=w);w.qb(v++);a.a.za(w.ca,e);u.push(w);z.push(w)}function h(b,c){if(b)for(var d=0,e=c.length;d<e;d++)c[d]&&a.a.q(c[d].ca,function(a){b(a,d,c[d].ja)})}f=f||[];k=k||{};var r=a.a.e.get(e,c)===n,q=a.a.e.get(e,c)||[],p=a.a.fb(q,
function(a){return a.ja}),s=a.a.ib(p,f,k.dontLimitMoves),u=[],t=0,v=0,x=[],z=[];f=[];for(var D=[],p=[],w,C=0,B,E;B=s[C];C++)switch(E=B.moved,B.status){case "deleted":E===n&&(w=q[t],w.B&&(w.B.k(),w.B=n),a.a.za(w.ca,e).length&&(k.beforeRemove&&(u.push(w),z.push(w),w.ja===d?w=null:f[C]=w),w&&x.push.apply(x,w.ca)));t++;break;case "retained":m(C,t++);break;case "added":E!==n?m(C,E):(w={ja:B.value,qb:a.N(v++)},u.push(w),z.push(w),r||(p[C]=w))}a.a.e.set(e,c,u);h(k.beforeMove,D);a.a.q(x,k.beforeRemove?a.$:
a.removeNode);for(var C=0,r=a.f.firstChild(e),F;w=z[C];C++){w.ca||a.a.extend(w,b(e,g,w.ja,l,w.qb));for(t=0;s=w.ca[t];r=s.nextSibling,F=s,t++)s!==r&&a.f.gc(e,s,F);!w.Wc&&l&&(l(w.ja,w.ca,w.qb),w.Wc=!0)}h(k.beforeRemove,f);for(C=0;C<f.length;++C)f[C]&&(f[C].ja=d);h(k.afterMove,D);h(k.afterAdd,p)}})();a.b("utils.setDomNodeChildrenFromArrayMapping",a.a.Bb);a.W=function(){this.allowTemplateRewriting=!1};a.W.prototype=new a.O;a.W.prototype.renderTemplateSource=function(b,c,d,e){if(c=(9>a.a.C?0:b.nodes)?
b.nodes():null)return a.a.V(c.cloneNode(!0).childNodes);b=b.text();return a.a.ma(b,e)};a.W.sb=new a.W;a.Db(a.W.sb);a.b("nativeTemplateEngine",a.W);(function(){a.vb=function(){var a=this.$c=function(){if(!v||!v.tmpl)return 0;try{if(0<=v.tmpl.tag.tmpl.open.toString().indexOf("__"))return 2}catch(a){}return 1}();this.renderTemplateSource=function(b,e,f,g){g=g||u;f=f||{};if(2>a)throw Error("Your version of jQuery.tmpl is too old. Please upgrade to jQuery.tmpl 1.0.0pre or later.");var k=b.data("precompiled");
k||(k=b.text()||"",k=v.template(null,"{{ko_with $item.koBindingContext}}"+k+"{{/ko_with}}"),b.data("precompiled",k));b=[e.$data];e=v.extend({koBindingContext:e},f.templateOptions);e=v.tmpl(k,b,e);e.appendTo(g.createElement("div"));v.fragments={};return e};this.createJavaScriptEvaluatorBlock=function(a){return"{{ko_code ((function() { return "+a+" })()) }}"};this.addTemplate=function(a,b){u.write("<script type='text/html' id='"+a+"'>"+b+"\x3c/script>")};0<a&&(v.tmpl.tag.ko_code={open:"__.push($1 || '');"},
v.tmpl.tag.ko_with={open:"with($1) {",close:"} "})};a.vb.prototype=new a.O;var b=new a.vb;0<b.$c&&a.Db(b);a.b("jqueryTmplTemplateEngine",a.vb)})()})})();})();
(function() {(function(n){var x=this||(0,eval)("this"),t=x.document,M=x.navigator,u=x.jQuery,H=x.JSON;(function(n){"function"===typeof define&&define.amd?define(["exports","require"],n):"object"===typeof exports&&"object"===typeof module?n(module.exports||exports):n(x.ko={})})(function(N,O){function J(a,c){return null===a||typeof a in R?a===c:!1}function S(b,c){var d;return function(){d||(d=a.a.setTimeout(function(){d=n;b()},c))}}function T(b,c){var d;return function(){clearTimeout(d);d=a.a.setTimeout(b,c)}}function U(a,
c){c&&c!==E?"beforeChange"===c?this.Ob(a):this.Ja(a,c):this.Pb(a)}function V(a,c){null!==c&&c.k&&c.k()}function W(a,c){var d=this.Mc,e=d[s];e.T||(this.ob&&this.Oa[c]?(d.Sb(c,a,this.Oa[c]),this.Oa[c]=null,--this.ob):e.s[c]||d.Sb(c,a,e.t?{$:a}:d.yc(a)),a.Ha&&a.Hc())}function K(b,c,d,e){a.d[b]={init:function(b,g,h,l,m){var k,r;a.m(function(){var q=g(),p=a.a.c(q),p=!d!==!p,A=!r;if(A||c||p!==k)A&&a.xa.Ca()&&(r=a.a.wa(a.f.childNodes(b),!0)),p?(A||a.f.fa(b,a.a.wa(r)),a.hb(e?e(m,q):m,b)):a.f.za(b),k=p},null,
{i:b});return{controlsDescendantBindings:!0}}};a.h.va[b]=!1;a.f.aa[b]=!0}var a="undefined"!==typeof N?N:{};a.b=function(b,c){for(var d=b.split("."),e=a,f=0;f<d.length-1;f++)e=e[d[f]];e[d[d.length-1]]=c};a.H=function(a,c,d){a[c]=d};a.version="3.4.2";a.b("version",a.version);a.options={deferUpdates:!1,useOnlyNativeEvents:!1};a.a=function(){function b(a,b){for(var c in a)a.hasOwnProperty(c)&&b(c,a[c])}function c(a,b){if(b)for(var c in b)b.hasOwnProperty(c)&&(a[c]=b[c]);return a}function d(a,b){a.__proto__=
b;return a}function e(b,c,d,e){var m=b[c].match(r)||[];a.a.r(d.match(r),function(b){a.a.ra(m,b,e)});b[c]=m.join(" ")}var f={__proto__:[]}instanceof Array,g="function"===typeof Symbol,h={},l={};h[M&&/Firefox\/2/i.test(M.userAgent)?"KeyboardEvent":"UIEvents"]=["keyup","keydown","keypress"];h.MouseEvents="click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave".split(" ");b(h,function(a,b){if(b.length)for(var c=0,d=b.length;c<d;c++)l[b[c]]=a});var m={propertychange:!0},k=
t&&function(){for(var a=3,b=t.createElement("div"),c=b.getElementsByTagName("i");b.innerHTML="\x3c!--[if gt IE "+ ++a+"]><i></i><![endif]--\x3e",c[0];);return 4<a?a:n}(),r=/\S+/g;return{gc:["authenticity_token",/^__RequestVerificationToken(_.*)?$/],r:function(a,b){for(var c=0,d=a.length;c<d;c++)b(a[c],c)},o:function(a,b){if("function"==typeof Array.prototype.indexOf)return Array.prototype.indexOf.call(a,b);for(var c=0,d=a.length;c<d;c++)if(a[c]===b)return c;return-1},Vb:function(a,b,c){for(var d=
0,e=a.length;d<e;d++)if(b.call(c,a[d],d))return a[d];return null},Na:function(b,c){var d=a.a.o(b,c);0<d?b.splice(d,1):0===d&&b.shift()},Wb:function(b){b=b||[];for(var c=[],d=0,e=b.length;d<e;d++)0>a.a.o(c,b[d])&&c.push(b[d]);return c},ib:function(a,b){a=a||[];for(var c=[],d=0,e=a.length;d<e;d++)c.push(b(a[d],d));return c},Ma:function(a,b){a=a||[];for(var c=[],d=0,e=a.length;d<e;d++)b(a[d],d)&&c.push(a[d]);return c},ta:function(a,b){if(b instanceof Array)a.push.apply(a,b);else for(var c=0,d=b.length;c<
d;c++)a.push(b[c]);return a},ra:function(b,c,d){var e=a.a.o(a.a.Bb(b),c);0>e?d&&b.push(c):d||b.splice(e,1)},la:f,extend:c,$a:d,ab:f?d:c,D:b,Ea:function(a,b){if(!a)return a;var c={},d;for(d in a)a.hasOwnProperty(d)&&(c[d]=b(a[d],d,a));return c},rb:function(b){for(;b.firstChild;)a.removeNode(b.firstChild)},nc:function(b){b=a.a.W(b);for(var c=(b[0]&&b[0].ownerDocument||t).createElement("div"),d=0,e=b.length;d<e;d++)c.appendChild(a.ba(b[d]));return c},wa:function(b,c){for(var d=0,e=b.length,m=[];d<e;d++){var k=
b[d].cloneNode(!0);m.push(c?a.ba(k):k)}return m},fa:function(b,c){a.a.rb(b);if(c)for(var d=0,e=c.length;d<e;d++)b.appendChild(c[d])},uc:function(b,c){var d=b.nodeType?[b]:b;if(0<d.length){for(var e=d[0],m=e.parentNode,k=0,f=c.length;k<f;k++)m.insertBefore(c[k],e);k=0;for(f=d.length;k<f;k++)a.removeNode(d[k])}},Ba:function(a,b){if(a.length){for(b=8===b.nodeType&&b.parentNode||b;a.length&&a[0].parentNode!==b;)a.splice(0,1);for(;1<a.length&&a[a.length-1].parentNode!==b;)a.length--;if(1<a.length){var c=
a[0],d=a[a.length-1];for(a.length=0;c!==d;)a.push(c),c=c.nextSibling;a.push(d)}}return a},wc:function(a,b){7>k?a.setAttribute("selected",b):a.selected=b},cb:function(a){return null===a||a===n?"":a.trim?a.trim():a.toString().replace(/^[\s\xa0]+|[\s\xa0]+$/g,"")},sd:function(a,b){a=a||"";return b.length>a.length?!1:a.substring(0,b.length)===b},Rc:function(a,b){if(a===b)return!0;if(11===a.nodeType)return!1;if(b.contains)return b.contains(3===a.nodeType?a.parentNode:a);if(b.compareDocumentPosition)return 16==
(b.compareDocumentPosition(a)&16);for(;a&&a!=b;)a=a.parentNode;return!!a},qb:function(b){return a.a.Rc(b,b.ownerDocument.documentElement)},Tb:function(b){return!!a.a.Vb(b,a.a.qb)},A:function(a){return a&&a.tagName&&a.tagName.toLowerCase()},Zb:function(b){return a.onError?function(){try{return b.apply(this,arguments)}catch(c){throw a.onError&&a.onError(c),c;}}:b},setTimeout:function(b,c){return setTimeout(a.a.Zb(b),c)},dc:function(b){setTimeout(function(){a.onError&&a.onError(b);throw b;},0)},q:function(b,
c,d){var e=a.a.Zb(d);d=k&&m[c];if(a.options.useOnlyNativeEvents||d||!u)if(d||"function"!=typeof b.addEventListener)if("undefined"!=typeof b.attachEvent){var f=function(a){e.call(b,a)},l="on"+c;b.attachEvent(l,f);a.a.G.qa(b,function(){b.detachEvent(l,f)})}else throw Error("Browser doesn't support addEventListener or attachEvent");else b.addEventListener(c,e,!1);else u(b).bind(c,e)},Fa:function(b,c){if(!b||!b.nodeType)throw Error("element must be a DOM node when calling triggerEvent");var d;"input"===
a.a.A(b)&&b.type&&"click"==c.toLowerCase()?(d=b.type,d="checkbox"==d||"radio"==d):d=!1;if(a.options.useOnlyNativeEvents||!u||d)if("function"==typeof t.createEvent)if("function"==typeof b.dispatchEvent)d=t.createEvent(l[c]||"HTMLEvents"),d.initEvent(c,!0,!0,x,0,0,0,0,0,!1,!1,!1,!1,0,b),b.dispatchEvent(d);else throw Error("The supplied element doesn't support dispatchEvent");else if(d&&b.click)b.click();else if("undefined"!=typeof b.fireEvent)b.fireEvent("on"+c);else throw Error("Browser doesn't support triggering events");
else u(b).trigger(c)},c:function(b){return a.I(b)?b():b},Bb:function(b){return a.I(b)?b.p():b},fb:function(b,c,d){var k;c&&("object"===typeof b.classList?(k=b.classList[d?"add":"remove"],a.a.r(c.match(r),function(a){k.call(b.classList,a)})):"string"===typeof b.className.baseVal?e(b.className,"baseVal",c,d):e(b,"className",c,d))},bb:function(b,c){var d=a.a.c(c);if(null===d||d===n)d="";var e=a.f.firstChild(b);!e||3!=e.nodeType||a.f.nextSibling(e)?a.f.fa(b,[b.ownerDocument.createTextNode(d)]):e.data=
d;a.a.Wc(b)},vc:function(a,b){a.name=b;if(7>=k)try{a.mergeAttributes(t.createElement("<input name='"+a.name+"'/>"),!1)}catch(c){}},Wc:function(a){9<=k&&(a=1==a.nodeType?a:a.parentNode,a.style&&(a.style.zoom=a.style.zoom))},Sc:function(a){if(k){var b=a.style.width;a.style.width=0;a.style.width=b}},nd:function(b,c){b=a.a.c(b);c=a.a.c(c);for(var d=[],e=b;e<=c;e++)d.push(e);return d},W:function(a){for(var b=[],c=0,d=a.length;c<d;c++)b.push(a[c]);return b},bc:function(a){return g?Symbol(a):a},xd:6===k,
yd:7===k,C:k,ic:function(b,c){for(var d=a.a.W(b.getElementsByTagName("input")).concat(a.a.W(b.getElementsByTagName("textarea"))),e="string"==typeof c?function(a){return a.name===c}:function(a){return c.test(a.name)},k=[],m=d.length-1;0<=m;m--)e(d[m])&&k.push(d[m]);return k},kd:function(b){return"string"==typeof b&&(b=a.a.cb(b))?H&&H.parse?H.parse(b):(new Function("return "+b))():null},Gb:function(b,c,d){if(!H||!H.stringify)throw Error("Cannot find JSON.stringify(). Some browsers (e.g., IE < 8) don't support it natively, but you can overcome this by adding a script reference to json2.js, downloadable from http://www.json.org/json2.js");
return H.stringify(a.a.c(b),c,d)},ld:function(c,d,e){e=e||{};var k=e.params||{},m=e.includeFields||this.gc,f=c;if("object"==typeof c&&"form"===a.a.A(c))for(var f=c.action,l=m.length-1;0<=l;l--)for(var g=a.a.ic(c,m[l]),h=g.length-1;0<=h;h--)k[g[h].name]=g[h].value;d=a.a.c(d);var r=t.createElement("form");r.style.display="none";r.action=f;r.method="post";for(var n in d)c=t.createElement("input"),c.type="hidden",c.name=n,c.value=a.a.Gb(a.a.c(d[n])),r.appendChild(c);b(k,function(a,b){var c=t.createElement("input");
c.type="hidden";c.name=a;c.value=b;r.appendChild(c)});t.body.appendChild(r);e.submitter?e.submitter(r):r.submit();setTimeout(function(){r.parentNode.removeChild(r)},0)}}}();a.b("utils",a.a);a.b("utils.arrayForEach",a.a.r);a.b("utils.arrayFirst",a.a.Vb);a.b("utils.arrayFilter",a.a.Ma);a.b("utils.arrayGetDistinctValues",a.a.Wb);a.b("utils.arrayIndexOf",a.a.o);a.b("utils.arrayMap",a.a.ib);a.b("utils.arrayPushAll",a.a.ta);a.b("utils.arrayRemoveItem",a.a.Na);a.b("utils.extend",a.a.extend);a.b("utils.fieldsIncludedWithJsonPost",
a.a.gc);a.b("utils.getFormFields",a.a.ic);a.b("utils.peekObservable",a.a.Bb);a.b("utils.postJson",a.a.ld);a.b("utils.parseJson",a.a.kd);a.b("utils.registerEventHandler",a.a.q);a.b("utils.stringifyJson",a.a.Gb);a.b("utils.range",a.a.nd);a.b("utils.toggleDomNodeCssClass",a.a.fb);a.b("utils.triggerEvent",a.a.Fa);a.b("utils.unwrapObservable",a.a.c);a.b("utils.objectForEach",a.a.D);a.b("utils.addOrRemoveItem",a.a.ra);a.b("utils.setTextContent",a.a.bb);a.b("unwrap",a.a.c);Function.prototype.bind||(Function.prototype.bind=
function(a){var c=this;if(1===arguments.length)return function(){return c.apply(a,arguments)};var d=Array.prototype.slice.call(arguments,1);return function(){var e=d.slice(0);e.push.apply(e,arguments);return c.apply(a,e)}});a.a.e=new function(){function a(b,g){var h=b[d];if(!h||"null"===h||!e[h]){if(!g)return n;h=b[d]="ko"+c++;e[h]={}}return e[h]}var c=0,d="__ko__"+(new Date).getTime(),e={};return{get:function(c,d){var e=a(c,!1);return e===n?n:e[d]},set:function(c,d,e){if(e!==n||a(c,!1)!==n)a(c,!0)[d]=
e},clear:function(a){var b=a[d];return b?(delete e[b],a[d]=null,!0):!1},J:function(){return c++ +d}}};a.b("utils.domData",a.a.e);a.b("utils.domData.clear",a.a.e.clear);a.a.G=new function(){function b(b,c){var e=a.a.e.get(b,d);e===n&&c&&(e=[],a.a.e.set(b,d,e));return e}function c(d){var e=b(d,!1);if(e)for(var e=e.slice(0),l=0;l<e.length;l++)e[l](d);a.a.e.clear(d);a.a.G.cleanExternalData(d);if(f[d.nodeType])for(e=d.firstChild;d=e;)e=d.nextSibling,8===d.nodeType&&c(d)}var d=a.a.e.J(),e={1:!0,8:!0,9:!0},
f={1:!0,9:!0};return{qa:function(a,c){if("function"!=typeof c)throw Error("Callback must be a function");b(a,!0).push(c)},tc:function(c,e){var f=b(c,!1);f&&(a.a.Na(f,e),0==f.length&&a.a.e.set(c,d,n))},ba:function(b){if(e[b.nodeType]&&(c(b),f[b.nodeType])){var d=[];a.a.ta(d,b.getElementsByTagName("*"));for(var l=0,m=d.length;l<m;l++)c(d[l])}return b},removeNode:function(b){a.ba(b);b.parentNode&&b.parentNode.removeChild(b)},cleanExternalData:function(a){u&&"function"==typeof u.cleanData&&u.cleanData([a])}}};
a.ba=a.a.G.ba;a.removeNode=a.a.G.removeNode;a.b("cleanNode",a.ba);a.b("removeNode",a.removeNode);a.b("utils.domNodeDisposal",a.a.G);a.b("utils.domNodeDisposal.addDisposeCallback",a.a.G.qa);a.b("utils.domNodeDisposal.removeDisposeCallback",a.a.G.tc);(function(){var b=[0,"",""],c=[1,"<table>","</table>"],d=[3,"<table><tbody><tr>","</tr></tbody></table>"],e=[1,"<select multiple='multiple'>","</select>"],f={thead:c,tbody:c,tfoot:c,tr:[2,"<table><tbody>","</tbody></table>"],td:d,th:d,option:e,optgroup:e},
g=8>=a.a.C;a.a.na=function(c,d){var e;if(u)if(u.parseHTML)e=u.parseHTML(c,d)||[];else{if((e=u.clean([c],d))&&e[0]){for(var k=e[0];k.parentNode&&11!==k.parentNode.nodeType;)k=k.parentNode;k.parentNode&&k.parentNode.removeChild(k)}}else{(e=d)||(e=t);var k=e.parentWindow||e.defaultView||x,r=a.a.cb(c).toLowerCase(),q=e.createElement("div"),p;p=(r=r.match(/^<([a-z]+)[ >]/))&&f[r[1]]||b;r=p[0];p="ignored<div>"+p[1]+c+p[2]+"</div>";"function"==typeof k.innerShiv?q.appendChild(k.innerShiv(p)):(g&&e.appendChild(q),
q.innerHTML=p,g&&q.parentNode.removeChild(q));for(;r--;)q=q.lastChild;e=a.a.W(q.lastChild.childNodes)}return e};a.a.Eb=function(b,c){a.a.rb(b);c=a.a.c(c);if(null!==c&&c!==n)if("string"!=typeof c&&(c=c.toString()),u)u(b).html(c);else for(var d=a.a.na(c,b.ownerDocument),e=0;e<d.length;e++)b.appendChild(d[e])}})();a.b("utils.parseHtmlFragment",a.a.na);a.b("utils.setHtml",a.a.Eb);a.N=function(){function b(c,e){if(c)if(8==c.nodeType){var f=a.N.pc(c.nodeValue);null!=f&&e.push({Qc:c,hd:f})}else if(1==c.nodeType)for(var f=
0,g=c.childNodes,h=g.length;f<h;f++)b(g[f],e)}var c={};return{yb:function(a){if("function"!=typeof a)throw Error("You can only pass a function to ko.memoization.memoize()");var b=(4294967296*(1+Math.random())|0).toString(16).substring(1)+(4294967296*(1+Math.random())|0).toString(16).substring(1);c[b]=a;return"\x3c!--[ko_memo:"+b+"]--\x3e"},Bc:function(a,b){var f=c[a];if(f===n)throw Error("Couldn't find any memo with ID "+a+". Perhaps it's already been unmemoized.");try{return f.apply(null,b||[]),
!0}finally{delete c[a]}},Cc:function(c,e){var f=[];b(c,f);for(var g=0,h=f.length;g<h;g++){var l=f[g].Qc,m=[l];e&&a.a.ta(m,e);a.N.Bc(f[g].hd,m);l.nodeValue="";l.parentNode&&l.parentNode.removeChild(l)}},pc:function(a){return(a=a.match(/^\[ko_memo\:(.*?)\]$/))?a[1]:null}}}();a.b("memoization",a.N);a.b("memoization.memoize",a.N.yb);a.b("memoization.unmemoize",a.N.Bc);a.b("memoization.parseMemoText",a.N.pc);a.b("memoization.unmemoizeDomNodeAndDescendants",a.N.Cc);a.Z=function(){function b(){if(e)for(var b=
e,c=0,m;g<e;)if(m=d[g++]){if(g>b){if(5E3<=++c){g=e;a.a.dc(Error("'Too much recursion' after processing "+c+" task groups."));break}b=e}try{m()}catch(k){a.a.dc(k)}}}function c(){b();g=e=d.length=0}var d=[],e=0,f=1,g=0;return{scheduler:x.MutationObserver?function(a){var b=t.createElement("div");(new MutationObserver(a)).observe(b,{attributes:!0});return function(){b.classList.toggle("foo")}}(c):t&&"onreadystatechange"in t.createElement("script")?function(a){var b=t.createElement("script");b.onreadystatechange=
function(){b.onreadystatechange=null;t.documentElement.removeChild(b);b=null;a()};t.documentElement.appendChild(b)}:function(a){setTimeout(a,0)},Za:function(b){e||a.Z.scheduler(c);d[e++]=b;return f++},cancel:function(a){a-=f-e;a>=g&&a<e&&(d[a]=null)},resetForTesting:function(){var a=e-g;g=e=d.length=0;return a},rd:b}}();a.b("tasks",a.Z);a.b("tasks.schedule",a.Z.Za);a.b("tasks.runEarly",a.Z.rd);a.Aa={throttle:function(b,c){b.throttleEvaluation=c;var d=null;return a.B({read:b,write:function(e){clearTimeout(d);
d=a.a.setTimeout(function(){b(e)},c)}})},rateLimit:function(a,c){var d,e,f;"number"==typeof c?d=c:(d=c.timeout,e=c.method);a.gb=!1;f="notifyWhenChangesStop"==e?T:S;a.Wa(function(a){return f(a,d)})},deferred:function(b,c){if(!0!==c)throw Error("The 'deferred' extender only accepts the value 'true', because it is not supported to turn deferral off once enabled.");b.gb||(b.gb=!0,b.Wa(function(c){var e,f=!1;return function(){if(!f){a.Z.cancel(e);e=a.Z.Za(c);try{f=!0,b.notifySubscribers(n,"dirty")}finally{f=
!1}}}}))},notify:function(a,c){a.equalityComparer="always"==c?null:J}};var R={undefined:1,"boolean":1,number:1,string:1};a.b("extenders",a.Aa);a.zc=function(b,c,d){this.$=b;this.jb=c;this.Pc=d;this.T=!1;a.H(this,"dispose",this.k)};a.zc.prototype.k=function(){this.T=!0;this.Pc()};a.K=function(){a.a.ab(this,D);D.ub(this)};var E="change",D={ub:function(a){a.F={change:[]};a.Qb=1},Y:function(b,c,d){var e=this;d=d||E;var f=new a.zc(e,c?b.bind(c):b,function(){a.a.Na(e.F[d],f);e.Ka&&e.Ka(d)});e.ua&&e.ua(d);
e.F[d]||(e.F[d]=[]);e.F[d].push(f);return f},notifySubscribers:function(b,c){c=c||E;c===E&&this.Kb();if(this.Ra(c)){var d=c===E&&this.Fc||this.F[c].slice(0);try{a.l.Xb();for(var e=0,f;f=d[e];++e)f.T||f.jb(b)}finally{a.l.end()}}},Pa:function(){return this.Qb},Zc:function(a){return this.Pa()!==a},Kb:function(){++this.Qb},Wa:function(b){var c=this,d=a.I(c),e,f,g,h;c.Ja||(c.Ja=c.notifySubscribers,c.notifySubscribers=U);var l=b(function(){c.Ha=!1;d&&h===c&&(h=c.Mb?c.Mb():c());var a=f||c.Ua(g,h);f=e=!1;
a&&c.Ja(g=h)});c.Pb=function(a){c.Fc=c.F[E].slice(0);c.Ha=e=!0;h=a;l()};c.Ob=function(a){e||(g=a,c.Ja(a,"beforeChange"))};c.Hc=function(){c.Ua(g,c.p(!0))&&(f=!0)}},Ra:function(a){return this.F[a]&&this.F[a].length},Xc:function(b){if(b)return this.F[b]&&this.F[b].length||0;var c=0;a.a.D(this.F,function(a,b){"dirty"!==a&&(c+=b.length)});return c},Ua:function(a,c){return!this.equalityComparer||!this.equalityComparer(a,c)},extend:function(b){var c=this;b&&a.a.D(b,function(b,e){var f=a.Aa[b];"function"==
typeof f&&(c=f(c,e)||c)});return c}};a.H(D,"subscribe",D.Y);a.H(D,"extend",D.extend);a.H(D,"getSubscriptionsCount",D.Xc);a.a.la&&a.a.$a(D,Function.prototype);a.K.fn=D;a.lc=function(a){return null!=a&&"function"==typeof a.Y&&"function"==typeof a.notifySubscribers};a.b("subscribable",a.K);a.b("isSubscribable",a.lc);a.xa=a.l=function(){function b(a){d.push(e);e=a}function c(){e=d.pop()}var d=[],e,f=0;return{Xb:b,end:c,sc:function(b){if(e){if(!a.lc(b))throw Error("Only subscribable things can act as dependencies");
e.jb.call(e.Lc,b,b.Gc||(b.Gc=++f))}},w:function(a,d,e){try{return b(),a.apply(d,e||[])}finally{c()}},Ca:function(){if(e)return e.m.Ca()},Va:function(){if(e)return e.Va}}}();a.b("computedContext",a.xa);a.b("computedContext.getDependenciesCount",a.xa.Ca);a.b("computedContext.isInitial",a.xa.Va);a.b("ignoreDependencies",a.wd=a.l.w);var F=a.a.bc("_latestValue");a.O=function(b){function c(){if(0<arguments.length)return c.Ua(c[F],arguments[0])&&(c.ia(),c[F]=arguments[0],c.ha()),this;a.l.sc(c);return c[F]}
c[F]=b;a.a.la||a.a.extend(c,a.K.fn);a.K.fn.ub(c);a.a.ab(c,B);a.options.deferUpdates&&a.Aa.deferred(c,!0);return c};var B={equalityComparer:J,p:function(){return this[F]},ha:function(){this.notifySubscribers(this[F])},ia:function(){this.notifySubscribers(this[F],"beforeChange")}};a.a.la&&a.a.$a(B,a.K.fn);var I=a.O.md="__ko_proto__";B[I]=a.O;a.Qa=function(b,c){return null===b||b===n||b[I]===n?!1:b[I]===c?!0:a.Qa(b[I],c)};a.I=function(b){return a.Qa(b,a.O)};a.Da=function(b){return"function"==typeof b&&
b[I]===a.O||"function"==typeof b&&b[I]===a.B&&b.$c?!0:!1};a.b("observable",a.O);a.b("isObservable",a.I);a.b("isWriteableObservable",a.Da);a.b("isWritableObservable",a.Da);a.b("observable.fn",B);a.H(B,"peek",B.p);a.H(B,"valueHasMutated",B.ha);a.H(B,"valueWillMutate",B.ia);a.ma=function(b){b=b||[];if("object"!=typeof b||!("length"in b))throw Error("The argument passed when initializing an observable array must be an array, or null, or undefined.");b=a.O(b);a.a.ab(b,a.ma.fn);return b.extend({trackArrayChanges:!0})};
a.ma.fn={remove:function(b){for(var c=this.p(),d=[],e="function"!=typeof b||a.I(b)?function(a){return a===b}:b,f=0;f<c.length;f++){var g=c[f];e(g)&&(0===d.length&&this.ia(),d.push(g),c.splice(f,1),f--)}d.length&&this.ha();return d},removeAll:function(b){if(b===n){var c=this.p(),d=c.slice(0);this.ia();c.splice(0,c.length);this.ha();return d}return b?this.remove(function(c){return 0<=a.a.o(b,c)}):[]},destroy:function(b){var c=this.p(),d="function"!=typeof b||a.I(b)?function(a){return a===b}:b;this.ia();
for(var e=c.length-1;0<=e;e--)d(c[e])&&(c[e]._destroy=!0);this.ha()},destroyAll:function(b){return b===n?this.destroy(function(){return!0}):b?this.destroy(function(c){return 0<=a.a.o(b,c)}):[]},indexOf:function(b){var c=this();return a.a.o(c,b)},replace:function(a,c){var d=this.indexOf(a);0<=d&&(this.ia(),this.p()[d]=c,this.ha())}};a.a.la&&a.a.$a(a.ma.fn,a.O.fn);a.a.r("pop push reverse shift sort splice unshift".split(" "),function(b){a.ma.fn[b]=function(){var a=this.p();this.ia();this.Yb(a,b,arguments);
var d=a[b].apply(a,arguments);this.ha();return d===a?this:d}});a.a.r(["slice"],function(b){a.ma.fn[b]=function(){var a=this();return a[b].apply(a,arguments)}});a.b("observableArray",a.ma);a.Aa.trackArrayChanges=function(b,c){function d(){if(!e){e=!0;l=b.notifySubscribers;b.notifySubscribers=function(a,b){b&&b!==E||++h;return l.apply(this,arguments)};var c=[].concat(b.p()||[]);f=null;g=b.Y(function(d){d=[].concat(d||[]);if(b.Ra("arrayChange")){var e;if(!f||1<h)f=a.a.lb(c,d,b.kb);e=f}c=d;f=null;h=0;
e&&e.length&&b.notifySubscribers(e,"arrayChange")})}}b.kb={};c&&"object"==typeof c&&a.a.extend(b.kb,c);b.kb.sparse=!0;if(!b.Yb){var e=!1,f=null,g,h=0,l,m=b.ua,k=b.Ka;b.ua=function(a){m&&m.call(b,a);"arrayChange"===a&&d()};b.Ka=function(a){k&&k.call(b,a);"arrayChange"!==a||b.Ra("arrayChange")||(l&&(b.notifySubscribers=l,l=n),g.k(),e=!1)};b.Yb=function(b,c,d){function k(a,b,c){return m[m.length]={status:a,value:b,index:c}}if(e&&!h){var m=[],l=b.length,g=d.length,G=0;switch(c){case "push":G=l;case "unshift":for(c=
0;c<g;c++)k("added",d[c],G+c);break;case "pop":G=l-1;case "shift":l&&k("deleted",b[G],G);break;case "splice":c=Math.min(Math.max(0,0>d[0]?l+d[0]:d[0]),l);for(var l=1===g?l:Math.min(c+(d[1]||0),l),g=c+g-2,G=Math.max(l,g),n=[],s=[],w=2;c<G;++c,++w)c<l&&s.push(k("deleted",b[c],c)),c<g&&n.push(k("added",d[w],c));a.a.hc(s,n);break;default:return}f=m}}}};var s=a.a.bc("_state");a.m=a.B=function(b,c,d){function e(){if(0<arguments.length){if("function"===typeof f)f.apply(g.sb,arguments);else throw Error("Cannot write a value to a ko.computed unless you specify a 'write' option. If you wish to read the current value, don't pass any parameters.");
return this}a.l.sc(e);(g.V||g.t&&e.Sa())&&e.U();return g.M}"object"===typeof b?d=b:(d=d||{},b&&(d.read=b));if("function"!=typeof d.read)throw Error("Pass a function that returns the value of the ko.computed");var f=d.write,g={M:n,da:!0,V:!0,Ta:!1,Hb:!1,T:!1,Ya:!1,t:!1,od:d.read,sb:c||d.owner,i:d.disposeWhenNodeIsRemoved||d.i||null,ya:d.disposeWhen||d.ya,pb:null,s:{},L:0,fc:null};e[s]=g;e.$c="function"===typeof f;a.a.la||a.a.extend(e,a.K.fn);a.K.fn.ub(e);a.a.ab(e,z);d.pure?(g.Ya=!0,g.t=!0,a.a.extend(e,
Y)):d.deferEvaluation&&a.a.extend(e,Z);a.options.deferUpdates&&a.Aa.deferred(e,!0);g.i&&(g.Hb=!0,g.i.nodeType||(g.i=null));g.t||d.deferEvaluation||e.U();g.i&&e.ca()&&a.a.G.qa(g.i,g.pb=function(){e.k()});return e};var z={equalityComparer:J,Ca:function(){return this[s].L},Sb:function(a,c,d){if(this[s].Ya&&c===this)throw Error("A 'pure' computed must not be called recursively");this[s].s[a]=d;d.Ia=this[s].L++;d.pa=c.Pa()},Sa:function(){var a,c,d=this[s].s;for(a in d)if(d.hasOwnProperty(a)&&(c=d[a],this.oa&&
c.$.Ha||c.$.Zc(c.pa)))return!0},gd:function(){this.oa&&!this[s].Ta&&this.oa(!1)},ca:function(){var a=this[s];return a.V||0<a.L},qd:function(){this.Ha?this[s].V&&(this[s].da=!0):this.ec()},yc:function(a){if(a.gb&&!this[s].i){var c=a.Y(this.gd,this,"dirty"),d=a.Y(this.qd,this);return{$:a,k:function(){c.k();d.k()}}}return a.Y(this.ec,this)},ec:function(){var b=this,c=b.throttleEvaluation;c&&0<=c?(clearTimeout(this[s].fc),this[s].fc=a.a.setTimeout(function(){b.U(!0)},c)):b.oa?b.oa(!0):b.U(!0)},U:function(b){var c=
this[s],d=c.ya,e=!1;if(!c.Ta&&!c.T){if(c.i&&!a.a.qb(c.i)||d&&d()){if(!c.Hb){this.k();return}}else c.Hb=!1;c.Ta=!0;try{e=this.Vc(b)}finally{c.Ta=!1}c.L||this.k();return e}},Vc:function(b){var c=this[s],d=!1,e=c.Ya?n:!c.L,f={Mc:this,Oa:c.s,ob:c.L};a.l.Xb({Lc:f,jb:W,m:this,Va:e});c.s={};c.L=0;f=this.Uc(c,f);this.Ua(c.M,f)&&(c.t||this.notifySubscribers(c.M,"beforeChange"),c.M=f,c.t?this.Kb():b&&this.notifySubscribers(c.M),d=!0);e&&this.notifySubscribers(c.M,"awake");return d},Uc:function(b,c){try{var d=
b.od;return b.sb?d.call(b.sb):d()}finally{a.l.end(),c.ob&&!b.t&&a.a.D(c.Oa,V),b.da=b.V=!1}},p:function(a){var c=this[s];(c.V&&(a||!c.L)||c.t&&this.Sa())&&this.U();return c.M},Wa:function(b){a.K.fn.Wa.call(this,b);this.Mb=function(){this[s].da?this.U():this[s].V=!1;return this[s].M};this.oa=function(a){this.Ob(this[s].M);this[s].V=!0;a&&(this[s].da=!0);this.Pb(this)}},k:function(){var b=this[s];!b.t&&b.s&&a.a.D(b.s,function(a,b){b.k&&b.k()});b.i&&b.pb&&a.a.G.tc(b.i,b.pb);b.s=null;b.L=0;b.T=!0;b.da=
!1;b.V=!1;b.t=!1;b.i=null}},Y={ua:function(b){var c=this,d=c[s];if(!d.T&&d.t&&"change"==b){d.t=!1;if(d.da||c.Sa())d.s=null,d.L=0,c.U()&&c.Kb();else{var e=[];a.a.D(d.s,function(a,b){e[b.Ia]=a});a.a.r(e,function(a,b){var e=d.s[a],l=c.yc(e.$);l.Ia=b;l.pa=e.pa;d.s[a]=l})}d.T||c.notifySubscribers(d.M,"awake")}},Ka:function(b){var c=this[s];c.T||"change"!=b||this.Ra("change")||(a.a.D(c.s,function(a,b){b.k&&(c.s[a]={$:b.$,Ia:b.Ia,pa:b.pa},b.k())}),c.t=!0,this.notifySubscribers(n,"asleep"))},Pa:function(){var b=
this[s];b.t&&(b.da||this.Sa())&&this.U();return a.K.fn.Pa.call(this)}},Z={ua:function(a){"change"!=a&&"beforeChange"!=a||this.p()}};a.a.la&&a.a.$a(z,a.K.fn);var P=a.O.md;a.m[P]=a.O;z[P]=a.m;a.bd=function(b){return a.Qa(b,a.m)};a.cd=function(b){return a.Qa(b,a.m)&&b[s]&&b[s].Ya};a.b("computed",a.m);a.b("dependentObservable",a.m);a.b("isComputed",a.bd);a.b("isPureComputed",a.cd);a.b("computed.fn",z);a.H(z,"peek",z.p);a.H(z,"dispose",z.k);a.H(z,"isActive",z.ca);a.H(z,"getDependenciesCount",z.Ca);a.rc=
function(b,c){if("function"===typeof b)return a.m(b,c,{pure:!0});b=a.a.extend({},b);b.pure=!0;return a.m(b,c)};a.b("pureComputed",a.rc);(function(){function b(a,f,g){g=g||new d;a=f(a);if("object"!=typeof a||null===a||a===n||a instanceof RegExp||a instanceof Date||a instanceof String||a instanceof Number||a instanceof Boolean)return a;var h=a instanceof Array?[]:{};g.save(a,h);c(a,function(c){var d=f(a[c]);switch(typeof d){case "boolean":case "number":case "string":case "function":h[c]=d;break;case "object":case "undefined":var k=
g.get(d);h[c]=k!==n?k:b(d,f,g)}});return h}function c(a,b){if(a instanceof Array){for(var c=0;c<a.length;c++)b(c);"function"==typeof a.toJSON&&b("toJSON")}else for(c in a)b(c)}function d(){this.keys=[];this.Lb=[]}a.Ac=function(c){if(0==arguments.length)throw Error("When calling ko.toJS, pass the object you want to convert.");return b(c,function(b){for(var c=0;a.I(b)&&10>c;c++)b=b();return b})};a.toJSON=function(b,c,d){b=a.Ac(b);return a.a.Gb(b,c,d)};d.prototype={save:function(b,c){var d=a.a.o(this.keys,
b);0<=d?this.Lb[d]=c:(this.keys.push(b),this.Lb.push(c))},get:function(b){b=a.a.o(this.keys,b);return 0<=b?this.Lb[b]:n}}})();a.b("toJS",a.Ac);a.b("toJSON",a.toJSON);(function(){a.j={u:function(b){switch(a.a.A(b)){case "option":return!0===b.__ko__hasDomDataOptionValue__?a.a.e.get(b,a.d.options.zb):7>=a.a.C?b.getAttributeNode("value")&&b.getAttributeNode("value").specified?b.value:b.text:b.value;case "select":return 0<=b.selectedIndex?a.j.u(b.options[b.selectedIndex]):n;default:return b.value}},ja:function(b,
c,d){switch(a.a.A(b)){case "option":switch(typeof c){case "string":a.a.e.set(b,a.d.options.zb,n);"__ko__hasDomDataOptionValue__"in b&&delete b.__ko__hasDomDataOptionValue__;b.value=c;break;default:a.a.e.set(b,a.d.options.zb,c),b.__ko__hasDomDataOptionValue__=!0,b.value="number"===typeof c?c:""}break;case "select":if(""===c||null===c)c=n;for(var e=-1,f=0,g=b.options.length,h;f<g;++f)if(h=a.j.u(b.options[f]),h==c||""==h&&c===n){e=f;break}if(d||0<=e||c===n&&1<b.size)b.selectedIndex=e;break;default:if(null===
c||c===n)c="";b.value=c}}}})();a.b("selectExtensions",a.j);a.b("selectExtensions.readValue",a.j.u);a.b("selectExtensions.writeValue",a.j.ja);a.h=function(){function b(b){b=a.a.cb(b);123===b.charCodeAt(0)&&(b=b.slice(1,-1));var c=[],d=b.match(e),r,h=[],p=0;if(d){d.push(",");for(var A=0,y;y=d[A];++A){var v=y.charCodeAt(0);if(44===v){if(0>=p){c.push(r&&h.length?{key:r,value:h.join("")}:{unknown:r||h.join("")});r=p=0;h=[];continue}}else if(58===v){if(!p&&!r&&1===h.length){r=h.pop();continue}}else 47===
v&&A&&1<y.length?(v=d[A-1].match(f))&&!g[v[0]]&&(b=b.substr(b.indexOf(y)+1),d=b.match(e),d.push(","),A=-1,y="/"):40===v||123===v||91===v?++p:41===v||125===v||93===v?--p:r||h.length||34!==v&&39!==v||(y=y.slice(1,-1));h.push(y)}}return c}var c=["true","false","null","undefined"],d=/^(?:[$_a-z][$\w]*|(.+)(\.\s*[$_a-z][$\w]*|\[.+\]))$/i,e=RegExp("\"(?:[^\"\\\\]|\\\\.)*\"|'(?:[^'\\\\]|\\\\.)*'|/(?:[^/\\\\]|\\\\.)*/w*|[^\\s:,/][^,\"'{}()/:[\\]]*[^\\s,\"'{}()/:[\\]]|[^\\s]","g"),f=/[\])"'A-Za-z0-9_$]+$/,
g={"in":1,"return":1,"typeof":1},h={};return{va:[],ga:h,Ab:b,Xa:function(e,m){function k(b,e){var m;if(!A){var l=a.getBindingHandler(b);if(l&&l.preprocess&&!(e=l.preprocess(e,b,k)))return;if(l=h[b])m=e,0<=a.a.o(c,m)?m=!1:(l=m.match(d),m=null===l?!1:l[1]?"Object("+l[1]+")"+l[2]:m),l=m;l&&g.push("'"+b+"':function(_z){"+m+"=_z}")}p&&(e="function(){return "+e+" }");f.push("'"+b+"':"+e)}m=m||{};var f=[],g=[],p=m.valueAccessors,A=m.bindingParams,y="string"===typeof e?b(e):e;a.a.r(y,function(a){k(a.key||
a.unknown,a.value)});g.length&&k("_ko_property_writers","{"+g.join(",")+" }");return f.join(",")},fd:function(a,b){for(var c=0;c<a.length;c++)if(a[c].key==b)return!0;return!1},Ga:function(b,c,d,e,f){if(b&&a.I(b))!a.Da(b)||f&&b.p()===e||b(e);else if((b=c.get("_ko_property_writers"))&&b[d])b[d](e)}}}();a.b("expressionRewriting",a.h);a.b("expressionRewriting.bindingRewriteValidators",a.h.va);a.b("expressionRewriting.parseObjectLiteral",a.h.Ab);a.b("expressionRewriting.preProcessBindings",a.h.Xa);a.b("expressionRewriting._twoWayBindings",
a.h.ga);a.b("jsonExpressionRewriting",a.h);a.b("jsonExpressionRewriting.insertPropertyAccessorsIntoJson",a.h.Xa);(function(){function b(a){return 8==a.nodeType&&g.test(f?a.text:a.nodeValue)}function c(a){return 8==a.nodeType&&h.test(f?a.text:a.nodeValue)}function d(a,d){for(var e=a,f=1,l=[];e=e.nextSibling;){if(c(e)&&(f--,0===f))return l;l.push(e);b(e)&&f++}if(!d)throw Error("Cannot find closing comment tag to match: "+a.nodeValue);return null}function e(a,b){var c=d(a,b);return c?0<c.length?c[c.length-
1].nextSibling:a.nextSibling:null}var f=t&&"\x3c!--test--\x3e"===t.createComment("test").text,g=f?/^\x3c!--\s*ko(?:\s+([\s\S]+))?\s*--\x3e$/:/^\s*ko(?:\s+([\s\S]+))?\s*$/,h=f?/^\x3c!--\s*\/ko\s*--\x3e$/:/^\s*\/ko\s*$/,l={ul:!0,ol:!0};a.f={aa:{},childNodes:function(a){return b(a)?d(a):a.childNodes},za:function(c){if(b(c)){c=a.f.childNodes(c);for(var d=0,e=c.length;d<e;d++)a.removeNode(c[d])}else a.a.rb(c)},fa:function(c,d){if(b(c)){a.f.za(c);for(var e=c.nextSibling,f=0,l=d.length;f<l;f++)e.parentNode.insertBefore(d[f],
e)}else a.a.fa(c,d)},qc:function(a,c){b(a)?a.parentNode.insertBefore(c,a.nextSibling):a.firstChild?a.insertBefore(c,a.firstChild):a.appendChild(c)},kc:function(c,d,e){e?b(c)?c.parentNode.insertBefore(d,e.nextSibling):e.nextSibling?c.insertBefore(d,e.nextSibling):c.appendChild(d):a.f.qc(c,d)},firstChild:function(a){return b(a)?!a.nextSibling||c(a.nextSibling)?null:a.nextSibling:a.firstChild},nextSibling:function(a){b(a)&&(a=e(a));return a.nextSibling&&c(a.nextSibling)?null:a.nextSibling},Yc:b,vd:function(a){return(a=
(f?a.text:a.nodeValue).match(g))?a[1]:null},oc:function(d){if(l[a.a.A(d)]){var k=d.firstChild;if(k){do if(1===k.nodeType){var f;f=k.firstChild;var g=null;if(f){do if(g)g.push(f);else if(b(f)){var h=e(f,!0);h?f=h:g=[f]}else c(f)&&(g=[f]);while(f=f.nextSibling)}if(f=g)for(g=k.nextSibling,h=0;h<f.length;h++)g?d.insertBefore(f[h],g):d.appendChild(f[h])}while(k=k.nextSibling)}}}}})();a.b("virtualElements",a.f);a.b("virtualElements.allowedBindings",a.f.aa);a.b("virtualElements.emptyNode",a.f.za);a.b("virtualElements.insertAfter",
a.f.kc);a.b("virtualElements.prepend",a.f.qc);a.b("virtualElements.setDomNodeChildren",a.f.fa);(function(){a.S=function(){this.Kc={}};a.a.extend(a.S.prototype,{nodeHasBindings:function(b){switch(b.nodeType){case 1:return null!=b.getAttribute("data-bind")||a.g.getComponentNameForNode(b);case 8:return a.f.Yc(b);default:return!1}},getBindings:function(b,c){var d=this.getBindingsString(b,c),d=d?this.parseBindingsString(d,c,b):null;return a.g.Rb(d,b,c,!1)},getBindingAccessors:function(b,c){var d=this.getBindingsString(b,
c),d=d?this.parseBindingsString(d,c,b,{valueAccessors:!0}):null;return a.g.Rb(d,b,c,!0)},getBindingsString:function(b){switch(b.nodeType){case 1:return b.getAttribute("data-bind");case 8:return a.f.vd(b);default:return null}},parseBindingsString:function(b,c,d,e){try{var f=this.Kc,g=b+(e&&e.valueAccessors||""),h;if(!(h=f[g])){var l,m="with($context){with($data||{}){return{"+a.h.Xa(b,e)+"}}}";l=new Function("$context","$element",m);h=f[g]=l}return h(c,d)}catch(k){throw k.message="Unable to parse bindings.\nBindings value: "+
b+"\nMessage: "+k.message,k;}}});a.S.instance=new a.S})();a.b("bindingProvider",a.S);(function(){function b(a){return function(){return a}}function c(a){return a()}function d(b){return a.a.Ea(a.l.w(b),function(a,c){return function(){return b()[c]}})}function e(c,e,k){return"function"===typeof c?d(c.bind(null,e,k)):a.a.Ea(c,b)}function f(a,b){return d(this.getBindings.bind(this,a,b))}function g(b,c,d){var e,k=a.f.firstChild(c),f=a.S.instance,m=f.preprocessNode;if(m){for(;e=k;)k=a.f.nextSibling(e),
m.call(f,e);k=a.f.firstChild(c)}for(;e=k;)k=a.f.nextSibling(e),h(b,e,d)}function h(b,c,d){var e=!0,k=1===c.nodeType;k&&a.f.oc(c);if(k&&d||a.S.instance.nodeHasBindings(c))e=m(c,null,b,d).shouldBindDescendants;e&&!r[a.a.A(c)]&&g(b,c,!k)}function l(b){var c=[],d={},e=[];a.a.D(b,function X(k){if(!d[k]){var f=a.getBindingHandler(k);f&&(f.after&&(e.push(k),a.a.r(f.after,function(c){if(b[c]){if(-1!==a.a.o(e,c))throw Error("Cannot combine the following bindings, because they have a cyclic dependency: "+e.join(", "));
X(c)}}),e.length--),c.push({key:k,jc:f}));d[k]=!0}});return c}function m(b,d,e,k){var m=a.a.e.get(b,q);if(!d){if(m)throw Error("You cannot apply bindings multiple times to the same element.");a.a.e.set(b,q,!0)}!m&&k&&a.xc(b,e);var g;if(d&&"function"!==typeof d)g=d;else{var h=a.S.instance,r=h.getBindingAccessors||f,p=a.B(function(){(g=d?d(e,b):r.call(h,b,e))&&e.Q&&e.Q();return g},null,{i:b});g&&p.ca()||(p=null)}var s;if(g){var t=p?function(a){return function(){return c(p()[a])}}:function(a){return g[a]},
u=function(){return a.a.Ea(p?p():g,c)};u.get=function(a){return g[a]&&c(t(a))};u.has=function(a){return a in g};k=l(g);a.a.r(k,function(c){var d=c.jc.init,k=c.jc.update,f=c.key;if(8===b.nodeType&&!a.f.aa[f])throw Error("The binding '"+f+"' cannot be used with virtual elements");try{"function"==typeof d&&a.l.w(function(){var a=d(b,t(f),u,e.$data,e);if(a&&a.controlsDescendantBindings){if(s!==n)throw Error("Multiple bindings ("+s+" and "+f+") are trying to control descendant bindings of the same element. You cannot use these bindings together on the same element.");
s=f}}),"function"==typeof k&&a.B(function(){k(b,t(f),u,e.$data,e)},null,{i:b})}catch(m){throw m.message='Unable to process binding "'+f+": "+g[f]+'"\nMessage: '+m.message,m;}})}return{shouldBindDescendants:s===n}}function k(b){return b&&b instanceof a.R?b:new a.R(b)}a.d={};var r={script:!0,textarea:!0,template:!0};a.getBindingHandler=function(b){return a.d[b]};a.R=function(b,c,d,e,k){function f(){var k=g?b():b,m=a.a.c(k);c?(c.Q&&c.Q(),a.a.extend(l,c),l.Q=r):(l.$parents=[],l.$root=m,l.ko=a);l.$rawData=
k;l.$data=m;d&&(l[d]=m);e&&e(l,c,m);return l.$data}function m(){return h&&!a.a.Tb(h)}var l=this,g="function"==typeof b&&!a.I(b),h,r;k&&k.exportDependencies?f():(r=a.B(f,null,{ya:m,i:!0}),r.ca()&&(l.Q=r,r.equalityComparer=null,h=[],r.Dc=function(b){h.push(b);a.a.G.qa(b,function(b){a.a.Na(h,b);h.length||(r.k(),l.Q=r=n)})}))};a.R.prototype.createChildContext=function(b,c,d,e){return new a.R(b,this,c,function(a,b){a.$parentContext=b;a.$parent=b.$data;a.$parents=(b.$parents||[]).slice(0);a.$parents.unshift(a.$parent);
d&&d(a)},e)};a.R.prototype.extend=function(b){return new a.R(this.Q||this.$data,this,null,function(c,d){c.$rawData=d.$rawData;a.a.extend(c,"function"==typeof b?b():b)})};a.R.prototype.ac=function(a,b){return this.createChildContext(a,b,null,{exportDependencies:!0})};var q=a.a.e.J(),p=a.a.e.J();a.xc=function(b,c){if(2==arguments.length)a.a.e.set(b,p,c),c.Q&&c.Q.Dc(b);else return a.a.e.get(b,p)};a.La=function(b,c,d){1===b.nodeType&&a.f.oc(b);return m(b,c,k(d),!0)};a.Ic=function(b,c,d){d=k(d);return a.La(b,
e(c,d,b),d)};a.hb=function(a,b){1!==b.nodeType&&8!==b.nodeType||g(k(a),b,!0)};a.Ub=function(a,b){!u&&x.jQuery&&(u=x.jQuery);if(b&&1!==b.nodeType&&8!==b.nodeType)throw Error("ko.applyBindings: first parameter should be your view model; second parameter should be a DOM node");b=b||x.document.body;h(k(a),b,!0)};a.nb=function(b){switch(b.nodeType){case 1:case 8:var c=a.xc(b);if(c)return c;if(b.parentNode)return a.nb(b.parentNode)}return n};a.Oc=function(b){return(b=a.nb(b))?b.$data:n};a.b("bindingHandlers",
a.d);a.b("applyBindings",a.Ub);a.b("applyBindingsToDescendants",a.hb);a.b("applyBindingAccessorsToNode",a.La);a.b("applyBindingsToNode",a.Ic);a.b("contextFor",a.nb);a.b("dataFor",a.Oc)})();(function(b){function c(c,e){var m=f.hasOwnProperty(c)?f[c]:b,k;m?m.Y(e):(m=f[c]=new a.K,m.Y(e),d(c,function(b,d){var e=!(!d||!d.synchronous);g[c]={definition:b,dd:e};delete f[c];k||e?m.notifySubscribers(b):a.Z.Za(function(){m.notifySubscribers(b)})}),k=!0)}function d(a,b){e("getConfig",[a],function(c){c?e("loadComponent",
[a,c],function(a){b(a,c)}):b(null,null)})}function e(c,d,f,k){k||(k=a.g.loaders.slice(0));var g=k.shift();if(g){var q=g[c];if(q){var p=!1;if(q.apply(g,d.concat(function(a){p?f(null):null!==a?f(a):e(c,d,f,k)}))!==b&&(p=!0,!g.suppressLoaderExceptions))throw Error("Component loaders must supply values by invoking the callback, not by returning values synchronously.");}else e(c,d,f,k)}else f(null)}var f={},g={};a.g={get:function(d,e){var f=g.hasOwnProperty(d)?g[d]:b;f?f.dd?a.l.w(function(){e(f.definition)}):
a.Z.Za(function(){e(f.definition)}):c(d,e)},$b:function(a){delete g[a]},Nb:e};a.g.loaders=[];a.b("components",a.g);a.b("components.get",a.g.get);a.b("components.clearCachedDefinition",a.g.$b)})();(function(){function b(b,c,d,e){function g(){0===--y&&e(h)}var h={},y=2,v=d.template;d=d.viewModel;v?f(c,v,function(c){a.g.Nb("loadTemplate",[b,c],function(a){h.template=a;g()})}):g();d?f(c,d,function(c){a.g.Nb("loadViewModel",[b,c],function(a){h[l]=a;g()})}):g()}function c(a,b,d){if("function"===typeof b)d(function(a){return new b(a)});
else if("function"===typeof b[l])d(b[l]);else if("instance"in b){var e=b.instance;d(function(){return e})}else"viewModel"in b?c(a,b.viewModel,d):a("Unknown viewModel value: "+b)}function d(b){switch(a.a.A(b)){case "script":return a.a.na(b.text);case "textarea":return a.a.na(b.value);case "template":if(e(b.content))return a.a.wa(b.content.childNodes)}return a.a.wa(b.childNodes)}function e(a){return x.DocumentFragment?a instanceof DocumentFragment:a&&11===a.nodeType}function f(a,b,c){"string"===typeof b.require?
O||x.require?(O||x.require)([b.require],c):a("Uses require, but no AMD loader is present"):c(b)}function g(a){return function(b){throw Error("Component '"+a+"': "+b);}}var h={};a.g.register=function(b,c){if(!c)throw Error("Invalid configuration for "+b);if(a.g.wb(b))throw Error("Component "+b+" is already registered");h[b]=c};a.g.wb=function(a){return h.hasOwnProperty(a)};a.g.ud=function(b){delete h[b];a.g.$b(b)};a.g.cc={getConfig:function(a,b){b(h.hasOwnProperty(a)?h[a]:null)},loadComponent:function(a,
c,d){var e=g(a);f(e,c,function(c){b(a,e,c,d)})},loadTemplate:function(b,c,f){b=g(b);if("string"===typeof c)f(a.a.na(c));else if(c instanceof Array)f(c);else if(e(c))f(a.a.W(c.childNodes));else if(c.element)if(c=c.element,x.HTMLElement?c instanceof HTMLElement:c&&c.tagName&&1===c.nodeType)f(d(c));else if("string"===typeof c){var l=t.getElementById(c);l?f(d(l)):b("Cannot find element with ID "+c)}else b("Unknown element type: "+c);else b("Unknown template value: "+c)},loadViewModel:function(a,b,d){c(g(a),
b,d)}};var l="createViewModel";a.b("components.register",a.g.register);a.b("components.isRegistered",a.g.wb);a.b("components.unregister",a.g.ud);a.b("components.defaultLoader",a.g.cc);a.g.loaders.push(a.g.cc);a.g.Ec=h})();(function(){function b(b,e){var f=b.getAttribute("params");if(f){var f=c.parseBindingsString(f,e,b,{valueAccessors:!0,bindingParams:!0}),f=a.a.Ea(f,function(c){return a.m(c,null,{i:b})}),g=a.a.Ea(f,function(c){var e=c.p();return c.ca()?a.m({read:function(){return a.a.c(c())},write:a.Da(e)&&
function(a){c()(a)},i:b}):e});g.hasOwnProperty("$raw")||(g.$raw=f);return g}return{$raw:{}}}a.g.getComponentNameForNode=function(b){var c=a.a.A(b);if(a.g.wb(c)&&(-1!=c.indexOf("-")||"[object HTMLUnknownElement]"==""+b||8>=a.a.C&&b.tagName===c))return c};a.g.Rb=function(c,e,f,g){if(1===e.nodeType){var h=a.g.getComponentNameForNode(e);if(h){c=c||{};if(c.component)throw Error('Cannot use the "component" binding on a custom element matching a component');var l={name:h,params:b(e,f)};c.component=g?function(){return l}:
l}}return c};var c=new a.S;9>a.a.C&&(a.g.register=function(a){return function(b){t.createElement(b);return a.apply(this,arguments)}}(a.g.register),t.createDocumentFragment=function(b){return function(){var c=b(),f=a.g.Ec,g;for(g in f)f.hasOwnProperty(g)&&c.createElement(g);return c}}(t.createDocumentFragment))})();(function(b){function c(b,c,d){c=c.template;if(!c)throw Error("Component '"+b+"' has no template");b=a.a.wa(c);a.f.fa(d,b)}function d(a,b,c,d){var e=a.createViewModel;return e?e.call(a,
d,{element:b,templateNodes:c}):d}var e=0;a.d.component={init:function(f,g,h,l,m){function k(){var a=r&&r.dispose;"function"===typeof a&&a.call(r);q=r=null}var r,q,p=a.a.W(a.f.childNodes(f));a.a.G.qa(f,k);a.m(function(){var l=a.a.c(g()),h,v;"string"===typeof l?h=l:(h=a.a.c(l.name),v=a.a.c(l.params));if(!h)throw Error("No component name specified");var n=q=++e;a.g.get(h,function(e){if(q===n){k();if(!e)throw Error("Unknown component '"+h+"'");c(h,e,f);var l=d(e,f,p,v);e=m.createChildContext(l,b,function(a){a.$component=
l;a.$componentTemplateNodes=p});r=l;a.hb(e,f)}})},null,{i:f});return{controlsDescendantBindings:!0}}};a.f.aa.component=!0})();var Q={"class":"className","for":"htmlFor"};a.d.attr={update:function(b,c){var d=a.a.c(c())||{};a.a.D(d,function(c,d){d=a.a.c(d);var g=!1===d||null===d||d===n;g&&b.removeAttribute(c);8>=a.a.C&&c in Q?(c=Q[c],g?b.removeAttribute(c):b[c]=d):g||b.setAttribute(c,d.toString());"name"===c&&a.a.vc(b,g?"":d.toString())})}};(function(){a.d.checked={after:["value","attr"],init:function(b,
c,d){function e(){var e=b.checked,f=p?g():e;if(!a.xa.Va()&&(!l||e)){var h=a.l.w(c);if(k){var m=r?h.p():h;q!==f?(e&&(a.a.ra(m,f,!0),a.a.ra(m,q,!1)),q=f):a.a.ra(m,f,e);r&&a.Da(h)&&h(m)}else a.h.Ga(h,d,"checked",f,!0)}}function f(){var d=a.a.c(c());b.checked=k?0<=a.a.o(d,g()):h?d:g()===d}var g=a.rc(function(){return d.has("checkedValue")?a.a.c(d.get("checkedValue")):d.has("value")?a.a.c(d.get("value")):b.value}),h="checkbox"==b.type,l="radio"==b.type;if(h||l){var m=c(),k=h&&a.a.c(m)instanceof Array,
r=!(k&&m.push&&m.splice),q=k?g():n,p=l||k;l&&!b.name&&a.d.uniqueName.init(b,function(){return!0});a.m(e,null,{i:b});a.a.q(b,"click",e);a.m(f,null,{i:b});m=n}}};a.h.ga.checked=!0;a.d.checkedValue={update:function(b,c){b.value=a.a.c(c())}}})();a.d.css={update:function(b,c){var d=a.a.c(c());null!==d&&"object"==typeof d?a.a.D(d,function(c,d){d=a.a.c(d);a.a.fb(b,c,d)}):(d=a.a.cb(String(d||"")),a.a.fb(b,b.__ko__cssValue,!1),b.__ko__cssValue=d,a.a.fb(b,d,!0))}};a.d.enable={update:function(b,c){var d=a.a.c(c());
d&&b.disabled?b.removeAttribute("disabled"):d||b.disabled||(b.disabled=!0)}};a.d.disable={update:function(b,c){a.d.enable.update(b,function(){return!a.a.c(c())})}};a.d.event={init:function(b,c,d,e,f){var g=c()||{};a.a.D(g,function(g){"string"==typeof g&&a.a.q(b,g,function(b){var m,k=c()[g];if(k){try{var r=a.a.W(arguments);e=f.$data;r.unshift(e);m=k.apply(e,r)}finally{!0!==m&&(b.preventDefault?b.preventDefault():b.returnValue=!1)}!1===d.get(g+"Bubble")&&(b.cancelBubble=!0,b.stopPropagation&&b.stopPropagation())}})})}};
a.d.foreach={mc:function(b){return function(){var c=b(),d=a.a.Bb(c);if(!d||"number"==typeof d.length)return{foreach:c,templateEngine:a.X.vb};a.a.c(c);return{foreach:d.data,as:d.as,includeDestroyed:d.includeDestroyed,afterAdd:d.afterAdd,beforeRemove:d.beforeRemove,afterRender:d.afterRender,beforeMove:d.beforeMove,afterMove:d.afterMove,templateEngine:a.X.vb}}},init:function(b,c){return a.d.template.init(b,a.d.foreach.mc(c))},update:function(b,c,d,e,f){return a.d.template.update(b,a.d.foreach.mc(c),
d,e,f)}};a.h.va.foreach=!1;a.f.aa.foreach=!0;a.d.hasfocus={init:function(b,c,d){function e(e){b.__ko_hasfocusUpdating=!0;var f=b.ownerDocument;if("activeElement"in f){var g;try{g=f.activeElement}catch(k){g=f.body}e=g===b}f=c();a.h.Ga(f,d,"hasfocus",e,!0);b.__ko_hasfocusLastValue=e;b.__ko_hasfocusUpdating=!1}var f=e.bind(null,!0),g=e.bind(null,!1);a.a.q(b,"focus",f);a.a.q(b,"focusin",f);a.a.q(b,"blur",g);a.a.q(b,"focusout",g)},update:function(b,c){var d=!!a.a.c(c());b.__ko_hasfocusUpdating||b.__ko_hasfocusLastValue===
d||(d?b.focus():b.blur(),!d&&b.__ko_hasfocusLastValue&&b.ownerDocument.body.focus(),a.l.w(a.a.Fa,null,[b,d?"focusin":"focusout"]))}};a.h.ga.hasfocus=!0;a.d.hasFocus=a.d.hasfocus;a.h.ga.hasFocus=!0;a.d.html={init:function(){return{controlsDescendantBindings:!0}},update:function(b,c){a.a.Eb(b,c())}};K("if");K("ifnot",!1,!0);K("with",!0,!1,function(a,c){return a.ac(c)});var L={};a.d.options={init:function(b){if("select"!==a.a.A(b))throw Error("options binding applies only to SELECT elements");for(;0<
b.length;)b.remove(0);return{controlsDescendantBindings:!0}},update:function(b,c,d){function e(){return a.a.Ma(b.options,function(a){return a.selected})}function f(a,b,c){var d=typeof b;return"function"==d?b(a):"string"==d?a[b]:c}function g(c,e){if(A&&k)a.j.ja(b,a.a.c(d.get("value")),!0);else if(p.length){var f=0<=a.a.o(p,a.j.u(e[0]));a.a.wc(e[0],f);A&&!f&&a.l.w(a.a.Fa,null,[b,"change"])}}var h=b.multiple,l=0!=b.length&&h?b.scrollTop:null,m=a.a.c(c()),k=d.get("valueAllowUnset")&&d.has("value"),r=
d.get("optionsIncludeDestroyed");c={};var q,p=[];k||(h?p=a.a.ib(e(),a.j.u):0<=b.selectedIndex&&p.push(a.j.u(b.options[b.selectedIndex])));m&&("undefined"==typeof m.length&&(m=[m]),q=a.a.Ma(m,function(b){return r||b===n||null===b||!a.a.c(b._destroy)}),d.has("optionsCaption")&&(m=a.a.c(d.get("optionsCaption")),null!==m&&m!==n&&q.unshift(L)));var A=!1;c.beforeRemove=function(a){b.removeChild(a)};m=g;d.has("optionsAfterRender")&&"function"==typeof d.get("optionsAfterRender")&&(m=function(b,c){g(0,c);
a.l.w(d.get("optionsAfterRender"),null,[c[0],b!==L?b:n])});a.a.Db(b,q,function(c,e,g){g.length&&(p=!k&&g[0].selected?[a.j.u(g[0])]:[],A=!0);e=b.ownerDocument.createElement("option");c===L?(a.a.bb(e,d.get("optionsCaption")),a.j.ja(e,n)):(g=f(c,d.get("optionsValue"),c),a.j.ja(e,a.a.c(g)),c=f(c,d.get("optionsText"),g),a.a.bb(e,c));return[e]},c,m);a.l.w(function(){k?a.j.ja(b,a.a.c(d.get("value")),!0):(h?p.length&&e().length<p.length:p.length&&0<=b.selectedIndex?a.j.u(b.options[b.selectedIndex])!==p[0]:
p.length||0<=b.selectedIndex)&&a.a.Fa(b,"change")});a.a.Sc(b);l&&20<Math.abs(l-b.scrollTop)&&(b.scrollTop=l)}};a.d.options.zb=a.a.e.J();a.d.selectedOptions={after:["options","foreach"],init:function(b,c,d){a.a.q(b,"change",function(){var e=c(),f=[];a.a.r(b.getElementsByTagName("option"),function(b){b.selected&&f.push(a.j.u(b))});a.h.Ga(e,d,"selectedOptions",f)})},update:function(b,c){if("select"!=a.a.A(b))throw Error("values binding applies only to SELECT elements");var d=a.a.c(c()),e=b.scrollTop;
d&&"number"==typeof d.length&&a.a.r(b.getElementsByTagName("option"),function(b){var c=0<=a.a.o(d,a.j.u(b));b.selected!=c&&a.a.wc(b,c)});b.scrollTop=e}};a.h.ga.selectedOptions=!0;a.d.style={update:function(b,c){var d=a.a.c(c()||{});a.a.D(d,function(c,d){d=a.a.c(d);if(null===d||d===n||!1===d)d="";b.style[c]=d})}};a.d.submit={init:function(b,c,d,e,f){if("function"!=typeof c())throw Error("The value for a submit binding must be a function");a.a.q(b,"submit",function(a){var d,e=c();try{d=e.call(f.$data,
b)}finally{!0!==d&&(a.preventDefault?a.preventDefault():a.returnValue=!1)}})}};a.d.text={init:function(){return{controlsDescendantBindings:!0}},update:function(b,c){a.a.bb(b,c())}};a.f.aa.text=!0;(function(){if(x&&x.navigator)var b=function(a){if(a)return parseFloat(a[1])},c=x.opera&&x.opera.version&&parseInt(x.opera.version()),d=x.navigator.userAgent,e=b(d.match(/^(?:(?!chrome).)*version\/([^ ]*) safari/i)),f=b(d.match(/Firefox\/([^ ]*)/));if(10>a.a.C)var g=a.a.e.J(),h=a.a.e.J(),l=function(b){var c=
this.activeElement;(c=c&&a.a.e.get(c,h))&&c(b)},m=function(b,c){var d=b.ownerDocument;a.a.e.get(d,g)||(a.a.e.set(d,g,!0),a.a.q(d,"selectionchange",l));a.a.e.set(b,h,c)};a.d.textInput={init:function(b,d,g){function l(c,d){a.a.q(b,c,d)}function h(){var c=a.a.c(d());if(null===c||c===n)c="";u!==n&&c===u?a.a.setTimeout(h,4):b.value!==c&&(s=c,b.value=c)}function y(){t||(u=b.value,t=a.a.setTimeout(v,4))}function v(){clearTimeout(t);u=t=n;var c=b.value;s!==c&&(s=c,a.h.Ga(d(),g,"textInput",c))}var s=b.value,
t,u,x=9==a.a.C?y:v;10>a.a.C?(l("propertychange",function(a){"value"===a.propertyName&&x(a)}),8==a.a.C&&(l("keyup",v),l("keydown",v)),8<=a.a.C&&(m(b,x),l("dragend",y))):(l("input",v),5>e&&"textarea"===a.a.A(b)?(l("keydown",y),l("paste",y),l("cut",y)):11>c?l("keydown",y):4>f&&(l("DOMAutoComplete",v),l("dragdrop",v),l("drop",v)));l("change",v);a.m(h,null,{i:b})}};a.h.ga.textInput=!0;a.d.textinput={preprocess:function(a,b,c){c("textInput",a)}}})();a.d.uniqueName={init:function(b,c){if(c()){var d="ko_unique_"+
++a.d.uniqueName.Nc;a.a.vc(b,d)}}};a.d.uniqueName.Nc=0;a.d.value={after:["options","foreach"],init:function(b,c,d){if("input"!=b.tagName.toLowerCase()||"checkbox"!=b.type&&"radio"!=b.type){var e=["change"],f=d.get("valueUpdate"),g=!1,h=null;f&&("string"==typeof f&&(f=[f]),a.a.ta(e,f),e=a.a.Wb(e));var l=function(){h=null;g=!1;var e=c(),f=a.j.u(b);a.h.Ga(e,d,"value",f)};!a.a.C||"input"!=b.tagName.toLowerCase()||"text"!=b.type||"off"==b.autocomplete||b.form&&"off"==b.form.autocomplete||-1!=a.a.o(e,"propertychange")||
(a.a.q(b,"propertychange",function(){g=!0}),a.a.q(b,"focus",function(){g=!1}),a.a.q(b,"blur",function(){g&&l()}));a.a.r(e,function(c){var d=l;a.a.sd(c,"after")&&(d=function(){h=a.j.u(b);a.a.setTimeout(l,0)},c=c.substring(5));a.a.q(b,c,d)});var m=function(){var e=a.a.c(c()),f=a.j.u(b);if(null!==h&&e===h)a.a.setTimeout(m,0);else if(e!==f)if("select"===a.a.A(b)){var g=d.get("valueAllowUnset"),f=function(){a.j.ja(b,e,g)};f();g||e===a.j.u(b)?a.a.setTimeout(f,0):a.l.w(a.a.Fa,null,[b,"change"])}else a.j.ja(b,
e)};a.m(m,null,{i:b})}else a.La(b,{checkedValue:c})},update:function(){}};a.h.ga.value=!0;a.d.visible={update:function(b,c){var d=a.a.c(c()),e="none"!=b.style.display;d&&!e?b.style.display="":!d&&e&&(b.style.display="none")}};(function(b){a.d[b]={init:function(c,d,e,f,g){return a.d.event.init.call(this,c,function(){var a={};a[b]=d();return a},e,f,g)}}})("click");a.P=function(){};a.P.prototype.renderTemplateSource=function(){throw Error("Override renderTemplateSource");};a.P.prototype.createJavaScriptEvaluatorBlock=
function(){throw Error("Override createJavaScriptEvaluatorBlock");};a.P.prototype.makeTemplateSource=function(b,c){if("string"==typeof b){c=c||t;var d=c.getElementById(b);if(!d)throw Error("Cannot find template with ID "+b);return new a.v.n(d)}if(1==b.nodeType||8==b.nodeType)return new a.v.sa(b);throw Error("Unknown template type: "+b);};a.P.prototype.renderTemplate=function(a,c,d,e){a=this.makeTemplateSource(a,e);return this.renderTemplateSource(a,c,d,e)};a.P.prototype.isTemplateRewritten=function(a,
c){return!1===this.allowTemplateRewriting?!0:this.makeTemplateSource(a,c).data("isRewritten")};a.P.prototype.rewriteTemplate=function(a,c,d){a=this.makeTemplateSource(a,d);c=c(a.text());a.text(c);a.data("isRewritten",!0)};a.b("templateEngine",a.P);a.Ib=function(){function b(b,c,d,h){b=a.h.Ab(b);for(var l=a.h.va,m=0;m<b.length;m++){var k=b[m].key;if(l.hasOwnProperty(k)){var r=l[k];if("function"===typeof r){if(k=r(b[m].value))throw Error(k);}else if(!r)throw Error("This template engine does not support the '"+
k+"' binding within its templates");}}d="ko.__tr_ambtns(function($context,$element){return(function(){return{ "+a.h.Xa(b,{valueAccessors:!0})+" } })()},'"+d.toLowerCase()+"')";return h.createJavaScriptEvaluatorBlock(d)+c}var c=/(<([a-z]+\d*)(?:\s+(?!data-bind\s*=\s*)[a-z0-9\-]+(?:=(?:\"[^\"]*\"|\'[^\']*\'|[^>]*))?)*\s+)data-bind\s*=\s*(["'])([\s\S]*?)\3/gi,d=/\x3c!--\s*ko\b\s*([\s\S]*?)\s*--\x3e/g;return{Tc:function(b,c,d){c.isTemplateRewritten(b,d)||c.rewriteTemplate(b,function(b){return a.Ib.jd(b,
c)},d)},jd:function(a,f){return a.replace(c,function(a,c,d,e,k){return b(k,c,d,f)}).replace(d,function(a,c){return b(c,"\x3c!-- ko --\x3e","#comment",f)})},Jc:function(b,c){return a.N.yb(function(d,h){var l=d.nextSibling;l&&l.nodeName.toLowerCase()===c&&a.La(l,b,h)})}}}();a.b("__tr_ambtns",a.Ib.Jc);(function(){a.v={};a.v.n=function(b){if(this.n=b){var c=a.a.A(b);this.eb="script"===c?1:"textarea"===c?2:"template"==c&&b.content&&11===b.content.nodeType?3:4}};a.v.n.prototype.text=function(){var b=1===
this.eb?"text":2===this.eb?"value":"innerHTML";if(0==arguments.length)return this.n[b];var c=arguments[0];"innerHTML"===b?a.a.Eb(this.n,c):this.n[b]=c};var b=a.a.e.J()+"_";a.v.n.prototype.data=function(c){if(1===arguments.length)return a.a.e.get(this.n,b+c);a.a.e.set(this.n,b+c,arguments[1])};var c=a.a.e.J();a.v.n.prototype.nodes=function(){var b=this.n;if(0==arguments.length)return(a.a.e.get(b,c)||{}).mb||(3===this.eb?b.content:4===this.eb?b:n);a.a.e.set(b,c,{mb:arguments[0]})};a.v.sa=function(a){this.n=
a};a.v.sa.prototype=new a.v.n;a.v.sa.prototype.text=function(){if(0==arguments.length){var b=a.a.e.get(this.n,c)||{};b.Jb===n&&b.mb&&(b.Jb=b.mb.innerHTML);return b.Jb}a.a.e.set(this.n,c,{Jb:arguments[0]})};a.b("templateSources",a.v);a.b("templateSources.domElement",a.v.n);a.b("templateSources.anonymousTemplate",a.v.sa)})();(function(){function b(b,c,d){var e;for(c=a.f.nextSibling(c);b&&(e=b)!==c;)b=a.f.nextSibling(e),d(e,b)}function c(c,d){if(c.length){var e=c[0],f=c[c.length-1],g=e.parentNode,h=
a.S.instance,n=h.preprocessNode;if(n){b(e,f,function(a,b){var c=a.previousSibling,d=n.call(h,a);d&&(a===e&&(e=d[0]||b),a===f&&(f=d[d.length-1]||c))});c.length=0;if(!e)return;e===f?c.push(e):(c.push(e,f),a.a.Ba(c,g))}b(e,f,function(b){1!==b.nodeType&&8!==b.nodeType||a.Ub(d,b)});b(e,f,function(b){1!==b.nodeType&&8!==b.nodeType||a.N.Cc(b,[d])});a.a.Ba(c,g)}}function d(a){return a.nodeType?a:0<a.length?a[0]:null}function e(b,e,f,h,q){q=q||{};var p=(b&&d(b)||f||{}).ownerDocument,n=q.templateEngine||g;
a.Ib.Tc(f,n,p);f=n.renderTemplate(f,h,q,p);if("number"!=typeof f.length||0<f.length&&"number"!=typeof f[0].nodeType)throw Error("Template engine must return an array of DOM nodes");p=!1;switch(e){case "replaceChildren":a.f.fa(b,f);p=!0;break;case "replaceNode":a.a.uc(b,f);p=!0;break;case "ignoreTargetNode":break;default:throw Error("Unknown renderMode: "+e);}p&&(c(f,h),q.afterRender&&a.l.w(q.afterRender,null,[f,h.$data]));return f}function f(b,c,d){return a.I(b)?b():"function"===typeof b?b(c,d):b}
var g;a.Fb=function(b){if(b!=n&&!(b instanceof a.P))throw Error("templateEngine must inherit from ko.templateEngine");g=b};a.Cb=function(b,c,k,h,q){k=k||{};if((k.templateEngine||g)==n)throw Error("Set a template engine before calling renderTemplate");q=q||"replaceChildren";if(h){var p=d(h);return a.B(function(){var g=c&&c instanceof a.R?c:new a.R(c,null,null,null,{exportDependencies:!0}),n=f(b,g.$data,g),g=e(h,q,n,g,k);"replaceNode"==q&&(h=g,p=d(h))},null,{ya:function(){return!p||!a.a.qb(p)},i:p&&
"replaceNode"==q?p.parentNode:p})}return a.N.yb(function(d){a.Cb(b,c,k,d,"replaceNode")})};a.pd=function(b,d,g,h,q){function p(a,b){c(b,t);g.afterRender&&g.afterRender(b,a);t=null}function s(a,c){t=q.createChildContext(a,g.as,function(a){a.$index=c});var d=f(b,a,t);return e(null,"ignoreTargetNode",d,t,g)}var t;return a.B(function(){var b=a.a.c(d)||[];"undefined"==typeof b.length&&(b=[b]);b=a.a.Ma(b,function(b){return g.includeDestroyed||b===n||null===b||!a.a.c(b._destroy)});a.l.w(a.a.Db,null,[h,b,
s,g,p])},null,{i:h})};var h=a.a.e.J();a.d.template={init:function(b,c){var d=a.a.c(c());if("string"==typeof d||d.name)a.f.za(b);else{if("nodes"in d){if(d=d.nodes||[],a.I(d))throw Error('The "nodes" option must be a plain, non-observable array.');}else d=a.f.childNodes(b);d=a.a.nc(d);(new a.v.sa(b)).nodes(d)}return{controlsDescendantBindings:!0}},update:function(b,c,d,e,f){var g=c();c=a.a.c(g);d=!0;e=null;"string"==typeof c?c={}:(g=c.name,"if"in c&&(d=a.a.c(c["if"])),d&&"ifnot"in c&&(d=!a.a.c(c.ifnot)));
"foreach"in c?e=a.pd(g||b,d&&c.foreach||[],c,b,f):d?(f="data"in c?f.ac(c.data,c.as):f,e=a.Cb(g||b,f,c,b)):a.f.za(b);f=e;(c=a.a.e.get(b,h))&&"function"==typeof c.k&&c.k();a.a.e.set(b,h,f&&f.ca()?f:n)}};a.h.va.template=function(b){b=a.h.Ab(b);return 1==b.length&&b[0].unknown||a.h.fd(b,"name")?null:"This template engine does not support anonymous templates nested within its templates"};a.f.aa.template=!0})();a.b("setTemplateEngine",a.Fb);a.b("renderTemplate",a.Cb);a.a.hc=function(a,c,d){if(a.length&&
c.length){var e,f,g,h,l;for(e=f=0;(!d||e<d)&&(h=a[f]);++f){for(g=0;l=c[g];++g)if(h.value===l.value){h.moved=l.index;l.moved=h.index;c.splice(g,1);e=g=0;break}e+=g}}};a.a.lb=function(){function b(b,d,e,f,g){var h=Math.min,l=Math.max,m=[],k,n=b.length,q,p=d.length,s=p-n||1,t=n+p+1,v,u,x;for(k=0;k<=n;k++)for(u=v,m.push(v=[]),x=h(p,k+s),q=l(0,k-1);q<=x;q++)v[q]=q?k?b[k-1]===d[q-1]?u[q-1]:h(u[q]||t,v[q-1]||t)+1:q+1:k+1;h=[];l=[];s=[];k=n;for(q=p;k||q;)p=m[k][q]-1,q&&p===m[k][q-1]?l.push(h[h.length]={status:e,
value:d[--q],index:q}):k&&p===m[k-1][q]?s.push(h[h.length]={status:f,value:b[--k],index:k}):(--q,--k,g.sparse||h.push({status:"retained",value:d[q]}));a.a.hc(s,l,!g.dontLimitMoves&&10*n);return h.reverse()}return function(a,d,e){e="boolean"===typeof e?{dontLimitMoves:e}:e||{};a=a||[];d=d||[];return a.length<d.length?b(a,d,"added","deleted",e):b(d,a,"deleted","added",e)}}();a.b("utils.compareArrays",a.a.lb);(function(){function b(b,c,d,h,l){var m=[],k=a.B(function(){var k=c(d,l,a.a.Ba(m,b))||[];0<
m.length&&(a.a.uc(m,k),h&&a.l.w(h,null,[d,k,l]));m.length=0;a.a.ta(m,k)},null,{i:b,ya:function(){return!a.a.Tb(m)}});return{ea:m,B:k.ca()?k:n}}var c=a.a.e.J(),d=a.a.e.J();a.a.Db=function(e,f,g,h,l){function m(b,c){w=q[c];u!==c&&(D[b]=w);w.tb(u++);a.a.Ba(w.ea,e);t.push(w);z.push(w)}function k(b,c){if(b)for(var d=0,e=c.length;d<e;d++)c[d]&&a.a.r(c[d].ea,function(a){b(a,d,c[d].ka)})}f=f||[];h=h||{};var r=a.a.e.get(e,c)===n,q=a.a.e.get(e,c)||[],p=a.a.ib(q,function(a){return a.ka}),s=a.a.lb(p,f,h.dontLimitMoves),
t=[],v=0,u=0,x=[],z=[];f=[];for(var D=[],p=[],w,C=0,B,E;B=s[C];C++)switch(E=B.moved,B.status){case "deleted":E===n&&(w=q[v],w.B&&(w.B.k(),w.B=n),a.a.Ba(w.ea,e).length&&(h.beforeRemove&&(t.push(w),z.push(w),w.ka===d?w=null:f[C]=w),w&&x.push.apply(x,w.ea)));v++;break;case "retained":m(C,v++);break;case "added":E!==n?m(C,E):(w={ka:B.value,tb:a.O(u++)},t.push(w),z.push(w),r||(p[C]=w))}a.a.e.set(e,c,t);k(h.beforeMove,D);a.a.r(x,h.beforeRemove?a.ba:a.removeNode);for(var C=0,r=a.f.firstChild(e),F;w=z[C];C++){w.ea||
a.a.extend(w,b(e,g,w.ka,l,w.tb));for(v=0;s=w.ea[v];r=s.nextSibling,F=s,v++)s!==r&&a.f.kc(e,s,F);!w.ad&&l&&(l(w.ka,w.ea,w.tb),w.ad=!0)}k(h.beforeRemove,f);for(C=0;C<f.length;++C)f[C]&&(f[C].ka=d);k(h.afterMove,D);k(h.afterAdd,p)}})();a.b("utils.setDomNodeChildrenFromArrayMapping",a.a.Db);a.X=function(){this.allowTemplateRewriting=!1};a.X.prototype=new a.P;a.X.prototype.renderTemplateSource=function(b,c,d,e){if(c=(9>a.a.C?0:b.nodes)?b.nodes():null)return a.a.W(c.cloneNode(!0).childNodes);b=b.text();
return a.a.na(b,e)};a.X.vb=new a.X;a.Fb(a.X.vb);a.b("nativeTemplateEngine",a.X);(function(){a.xb=function(){var a=this.ed=function(){if(!u||!u.tmpl)return 0;try{if(0<=u.tmpl.tag.tmpl.open.toString().indexOf("__"))return 2}catch(a){}return 1}();this.renderTemplateSource=function(b,e,f,g){g=g||t;f=f||{};if(2>a)throw Error("Your version of jQuery.tmpl is too old. Please upgrade to jQuery.tmpl 1.0.0pre or later.");var h=b.data("precompiled");h||(h=b.text()||"",h=u.template(null,"{{ko_with $item.koBindingContext}}"+
h+"{{/ko_with}}"),b.data("precompiled",h));b=[e.$data];e=u.extend({koBindingContext:e},f.templateOptions);e=u.tmpl(h,b,e);e.appendTo(g.createElement("div"));u.fragments={};return e};this.createJavaScriptEvaluatorBlock=function(a){return"{{ko_code ((function() { return "+a+" })()) }}"};this.addTemplate=function(a,b){t.write("<script type='text/html' id='"+a+"'>"+b+"\x3c/script>")};0<a&&(u.tmpl.tag.ko_code={open:"__.push($1 || '');"},u.tmpl.tag.ko_with={open:"with($1) {",close:"} "})};a.xb.prototype=
new a.P;var b=new a.xb;0<b.ed&&a.Fb(b);a.b("jqueryTmplTemplateEngine",a.xb)})()})})();})();

View File

File diff suppressed because one or more lines are too long

View File

File diff suppressed because one or more lines are too long

View File

@@ -1,59 +1,60 @@
//! moment.js locale configuration
//! locale : danish (da)
//! locale : Danish [da]
//! author : Ulrik Nielsen : https://github.com/mrbase
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(require('../moment')) :
typeof define === 'function' && define.amd ? define(['moment'], factory) :
;(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined'
&& typeof require === 'function' ? factory(require('../moment')) :
typeof define === 'function' && define.amd ? define(['../moment'], factory) :
factory(global.moment)
}(this, function (moment) { 'use strict';
}(this, (function (moment) { 'use strict';
var da = moment.defineLocale('da', {
months : 'januar_februar_marts_april_maj_juni_juli_august_september_oktober_november_december'.split('_'),
monthsShort : 'jan_feb_mar_apr_maj_jun_jul_aug_sep_okt_nov_dec'.split('_'),
weekdays : 'søndag_mandag_tirsdag_onsdag_torsdag_fredag_lørdag'.split('_'),
weekdaysShort : 'søn_man_tir_ons_tor_fre_lør'.split('_'),
weekdaysMin : 'sø_ma_ti_on_to_fr_lø'.split('_'),
longDateFormat : {
LT : 'HH:mm',
LTS : 'HH:mm:ss',
L : 'DD/MM/YYYY',
LL : 'D. MMMM YYYY',
LLL : 'D. MMMM YYYY HH:mm',
LLLL : 'dddd [d.] D. MMMM YYYY HH:mm'
},
calendar : {
sameDay : '[I dag kl.] LT',
nextDay : '[I morgen kl.] LT',
nextWeek : 'dddd [kl.] LT',
lastDay : '[I går kl.] LT',
lastWeek : '[sidste] dddd [kl] LT',
sameElse : 'L'
},
relativeTime : {
future : 'om %s',
past : '%s siden',
s : 'få sekunder',
m : 'et minut',
mm : '%d minutter',
h : 'en time',
hh : '%d timer',
d : 'en dag',
dd : '%d dage',
M : 'en måned',
MM : '%d måneder',
y : 'et år',
yy : '%d år'
},
ordinalParse: /\d{1,2}\./,
ordinal : '%d.',
week : {
dow : 1, // Monday is the first day of the week.
doy : 4 // The week that contains Jan 4th is the first week of the year.
}
});
var da = moment.defineLocale('da', {
months : 'januar_februar_marts_april_maj_juni_juli_august_september_oktober_november_december'.split('_'),
monthsShort : 'jan_feb_mar_apr_maj_jun_jul_aug_sep_okt_nov_dec'.split('_'),
weekdays : 'søndag_mandag_tirsdag_onsdag_torsdag_fredag_lørdag'.split('_'),
weekdaysShort : 'søn_man_tir_ons_tor_fre_lør'.split('_'),
weekdaysMin : 'sø_ma_ti_on_to_fr_lø'.split('_'),
longDateFormat : {
LT : 'HH:mm',
LTS : 'HH:mm:ss',
L : 'DD.MM.YYYY',
LL : 'D. MMMM YYYY',
LLL : 'D. MMMM YYYY HH:mm',
LLLL : 'dddd [d.] D. MMMM YYYY [kl.] HH:mm'
},
calendar : {
sameDay : '[i dag kl.] LT',
nextDay : '[i morgen kl.] LT',
nextWeek : 'dddd [kl.] LT',
lastDay : '[i går kl.] LT',
lastWeek : '[i] dddd[s kl.] LT',
sameElse : 'L'
},
relativeTime : {
future : 'om %s',
past : '%s siden',
s : 'få sekunder',
m : 'et minut',
mm : '%d minutter',
h : 'en time',
hh : '%d timer',
d : 'en dag',
dd : '%d dage',
M : 'en måned',
MM : '%d måneder',
y : 'et år',
yy : '%d år'
},
dayOfMonthOrdinalParse: /\d{1,2}\./,
ordinal : '%d.',
week : {
dow : 1, // Monday is the first day of the week.
doy : 4 // The week that contains Jan 4th is the first week of the year.
}
});
return da;
return da;
}));
})));

View File

@@ -1,74 +1,78 @@
//! moment.js locale configuration
//! locale : german (de)
//! locale : German [de]
//! author : lluchs : https://github.com/lluchs
//! author: Menelion Elensúle: https://github.com/Oire
//! author : Mikolaj Dadela : https://github.com/mik01aj
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(require('../moment')) :
typeof define === 'function' && define.amd ? define(['moment'], factory) :
;(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined'
&& typeof require === 'function' ? factory(require('../moment')) :
typeof define === 'function' && define.amd ? define(['../moment'], factory) :
factory(global.moment)
}(this, function (moment) { 'use strict';
}(this, (function (moment) { 'use strict';
function processRelativeTime(number, withoutSuffix, key, isFuture) {
var format = {
'm': ['eine Minute', 'einer Minute'],
'h': ['eine Stunde', 'einer Stunde'],
'd': ['ein Tag', 'einem Tag'],
'dd': [number + ' Tage', number + ' Tagen'],
'M': ['ein Monat', 'einem Monat'],
'MM': [number + ' Monate', number + ' Monaten'],
'y': ['ein Jahr', 'einem Jahr'],
'yy': [number + ' Jahre', number + ' Jahren']
};
return withoutSuffix ? format[key][0] : format[key][1];
function processRelativeTime(number, withoutSuffix, key, isFuture) {
var format = {
'm': ['eine Minute', 'einer Minute'],
'h': ['eine Stunde', 'einer Stunde'],
'd': ['ein Tag', 'einem Tag'],
'dd': [number + ' Tage', number + ' Tagen'],
'M': ['ein Monat', 'einem Monat'],
'MM': [number + ' Monate', number + ' Monaten'],
'y': ['ein Jahr', 'einem Jahr'],
'yy': [number + ' Jahre', number + ' Jahren']
};
return withoutSuffix ? format[key][0] : format[key][1];
}
var de = moment.defineLocale('de', {
months : 'Januar_Februar_März_April_Mai_Juni_Juli_August_September_Oktober_November_Dezember'.split('_'),
monthsShort : 'Jan._Feb._März_Apr._Mai_Juni_Juli_Aug._Sep._Okt._Nov._Dez.'.split('_'),
monthsParseExact : true,
weekdays : 'Sonntag_Montag_Dienstag_Mittwoch_Donnerstag_Freitag_Samstag'.split('_'),
weekdaysShort : 'So._Mo._Di._Mi._Do._Fr._Sa.'.split('_'),
weekdaysMin : 'So_Mo_Di_Mi_Do_Fr_Sa'.split('_'),
weekdaysParseExact : true,
longDateFormat : {
LT: 'HH:mm',
LTS: 'HH:mm:ss',
L : 'DD.MM.YYYY',
LL : 'D. MMMM YYYY',
LLL : 'D. MMMM YYYY HH:mm',
LLLL : 'dddd, D. MMMM YYYY HH:mm'
},
calendar : {
sameDay: '[heute um] LT [Uhr]',
sameElse: 'L',
nextDay: '[morgen um] LT [Uhr]',
nextWeek: 'dddd [um] LT [Uhr]',
lastDay: '[gestern um] LT [Uhr]',
lastWeek: '[letzten] dddd [um] LT [Uhr]'
},
relativeTime : {
future : 'in %s',
past : 'vor %s',
s : 'ein paar Sekunden',
m : processRelativeTime,
mm : '%d Minuten',
h : processRelativeTime,
hh : '%d Stunden',
d : processRelativeTime,
dd : processRelativeTime,
M : processRelativeTime,
MM : processRelativeTime,
y : processRelativeTime,
yy : processRelativeTime
},
dayOfMonthOrdinalParse: /\d{1,2}\./,
ordinal : '%d.',
week : {
dow : 1, // Monday is the first day of the week.
doy : 4 // The week that contains Jan 4th is the first week of the year.
}
});
var de = moment.defineLocale('de', {
months : 'Januar_Februar_März_April_Mai_Juni_Juli_August_September_Oktober_November_Dezember'.split('_'),
monthsShort : 'Jan._Febr._Mrz._Apr._Mai_Jun._Jul._Aug._Sept._Okt._Nov._Dez.'.split('_'),
weekdays : 'Sonntag_Montag_Dienstag_Mittwoch_Donnerstag_Freitag_Samstag'.split('_'),
weekdaysShort : 'So._Mo._Di._Mi._Do._Fr._Sa.'.split('_'),
weekdaysMin : 'So_Mo_Di_Mi_Do_Fr_Sa'.split('_'),
longDateFormat : {
LT: 'HH:mm',
LTS: 'HH:mm:ss',
L : 'DD.MM.YYYY',
LL : 'D. MMMM YYYY',
LLL : 'D. MMMM YYYY HH:mm',
LLLL : 'dddd, D. MMMM YYYY HH:mm'
},
calendar : {
sameDay: '[Heute um] LT [Uhr]',
sameElse: 'L',
nextDay: '[Morgen um] LT [Uhr]',
nextWeek: 'dddd [um] LT [Uhr]',
lastDay: '[Gestern um] LT [Uhr]',
lastWeek: '[letzten] dddd [um] LT [Uhr]'
},
relativeTime : {
future : 'in %s',
past : 'vor %s',
s : 'ein paar Sekunden',
m : processRelativeTime,
mm : '%d Minuten',
h : processRelativeTime,
hh : '%d Stunden',
d : processRelativeTime,
dd : processRelativeTime,
M : processRelativeTime,
MM : processRelativeTime,
y : processRelativeTime,
yy : processRelativeTime
},
ordinalParse: /\d{1,2}\./,
ordinal : '%d.',
week : {
dow : 1, // Monday is the first day of the week.
doy : 4 // The week that contains Jan 4th is the first week of the year.
}
});
return de;
return de;
}));
})));

View File

@@ -1,78 +1,92 @@
//! moment.js locale configuration
//! locale : spanish (es)
//! locale : Spanish [es]
//! author : Julio Napurí : https://github.com/julionc
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(require('../moment')) :
typeof define === 'function' && define.amd ? define(['moment'], factory) :
;(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined'
&& typeof require === 'function' ? factory(require('../moment')) :
typeof define === 'function' && define.amd ? define(['../moment'], factory) :
factory(global.moment)
}(this, function (moment) { 'use strict';
}(this, (function (moment) { 'use strict';
var monthsShortDot = 'Ene._Feb._Mar._Abr._May._Jun._Jul._Ago._Sep._Oct._Nov._Dic.'.split('_'),
monthsShort = 'Ene_Feb_Mar_Abr_May_Jun_Jul_Ago_Sep_Oct_Nov_Dic'.split('_');
var monthsShortDot = 'ene._feb._mar._abr._may._jun._jul._ago._sep._oct._nov._dic.'.split('_');
var monthsShort = 'ene_feb_mar_abr_may_jun_jul_ago_sep_oct_nov_dic'.split('_');
var es = moment.defineLocale('es', {
months : 'Enero_Febrero_Marzo_Abril_Mayo_Junio_Julio_Agosto_Septiembre_Octubre_Noviembre_Diciembre'.split('_'),
monthsShort : function (m, format) {
if (/-MMM-/.test(format)) {
return monthsShort[m.month()];
} else {
return monthsShortDot[m.month()];
}
},
weekdays : 'Domingo_Lunes_Martes_Miércoles_Jueves_Viernes_Sábado'.split('_'),
weekdaysShort : 'Dom._Lun._Mar._Mié._Jue._Vie._Sáb.'.split('_'),
weekdaysMin : 'Do_Lu_Ma_Mi_Ju_Vi_Sá'.split('_'),
longDateFormat : {
LT : 'H:mm',
LTS : 'H:mm:ss',
L : 'DD/MM/YYYY',
LL : 'D [de] MMMM [de] YYYY',
LLL : 'D [de] MMMM [de] YYYY H:mm',
LLLL : 'dddd, D [de] MMMM [de] YYYY H:mm'
},
calendar : {
sameDay : function () {
return '[hoy a la' + ((this.hours() !== 1) ? 's' : '') + '] LT';
},
nextDay : function () {
return '[mañana a la' + ((this.hours() !== 1) ? 's' : '') + '] LT';
},
nextWeek : function () {
return 'dddd [a la' + ((this.hours() !== 1) ? 's' : '') + '] LT';
},
lastDay : function () {
return '[ayer a la' + ((this.hours() !== 1) ? 's' : '') + '] LT';
},
lastWeek : function () {
return '[el] dddd [pasado a la' + ((this.hours() !== 1) ? 's' : '') + '] LT';
},
sameElse : 'L'
},
relativeTime : {
future : 'en %s',
past : 'hace %s',
s : 'unos segundos',
m : 'un minuto',
mm : '%d minutos',
h : 'una hora',
hh : '%d horas',
d : 'un día',
dd : '%d días',
M : 'un mes',
MM : '%d meses',
y : 'un año',
yy : '%d años'
},
ordinalParse : /\d{1,2}º/,
ordinal : '%dº',
week : {
dow : 1, // Monday is the first day of the week.
doy : 4 // The week that contains Jan 4th is the first week of the year.
var monthsParse = [/^ene/i, /^feb/i, /^mar/i, /^abr/i, /^may/i, /^jun/i, /^jul/i, /^ago/i, /^sep/i, /^oct/i, /^nov/i, /^dic/i];
var monthsRegex = /^(enero|febrero|marzo|abril|mayo|junio|julio|agosto|septiembre|octubre|noviembre|diciembre|ene\.?|feb\.?|mar\.?|abr\.?|may\.?|jun\.?|jul\.?|ago\.?|sep\.?|oct\.?|nov\.?|dic\.?)/i;
var es = moment.defineLocale('es', {
months : 'enero_febrero_marzo_abril_mayo_junio_julio_agosto_septiembre_octubre_noviembre_diciembre'.split('_'),
monthsShort : function (m, format) {
if (!m) {
return monthsShortDot;
} else if (/-MMM-/.test(format)) {
return monthsShort[m.month()];
} else {
return monthsShortDot[m.month()];
}
});
},
monthsRegex : monthsRegex,
monthsShortRegex : monthsRegex,
monthsStrictRegex : /^(enero|febrero|marzo|abril|mayo|junio|julio|agosto|septiembre|octubre|noviembre|diciembre)/i,
monthsShortStrictRegex : /^(ene\.?|feb\.?|mar\.?|abr\.?|may\.?|jun\.?|jul\.?|ago\.?|sep\.?|oct\.?|nov\.?|dic\.?)/i,
monthsParse : monthsParse,
longMonthsParse : monthsParse,
shortMonthsParse : monthsParse,
weekdays : 'domingo_lunes_martes_miércoles_jueves_viernes_sábado'.split('_'),
weekdaysShort : 'dom._lun._mar._mié._jue._vie._sáb.'.split('_'),
weekdaysMin : 'do_lu_ma_mi_ju_vi_sá'.split('_'),
weekdaysParseExact : true,
longDateFormat : {
LT : 'H:mm',
LTS : 'H:mm:ss',
L : 'DD/MM/YYYY',
LL : 'D [de] MMMM [de] YYYY',
LLL : 'D [de] MMMM [de] YYYY H:mm',
LLLL : 'dddd, D [de] MMMM [de] YYYY H:mm'
},
calendar : {
sameDay : function () {
return '[hoy a la' + ((this.hours() !== 1) ? 's' : '') + '] LT';
},
nextDay : function () {
return '[mañana a la' + ((this.hours() !== 1) ? 's' : '') + '] LT';
},
nextWeek : function () {
return 'dddd [a la' + ((this.hours() !== 1) ? 's' : '') + '] LT';
},
lastDay : function () {
return '[ayer a la' + ((this.hours() !== 1) ? 's' : '') + '] LT';
},
lastWeek : function () {
return '[el] dddd [pasado a la' + ((this.hours() !== 1) ? 's' : '') + '] LT';
},
sameElse : 'L'
},
relativeTime : {
future : 'en %s',
past : 'hace %s',
s : 'unos segundos',
m : 'un minuto',
mm : '%d minutos',
h : 'una hora',
hh : '%d horas',
d : 'un día',
dd : '%d días',
M : 'un mes',
MM : '%d meses',
y : 'un año',
yy : '%d años'
},
dayOfMonthOrdinalParse : /\d{1,2}º/,
ordinal : '%dº',
week : {
dow : 1, // Monday is the first day of the week.
doy : 4 // The week that contains Jan 4th is the first week of the year.
}
});
return es;
return es;
}));
})));

View File

@@ -1,22 +1,23 @@
//! moment.js locale configuration
//! locale : finnish (fi)
//! locale : Finnish [fi]
//! author : Tarmo Aidantausta : https://github.com/bleadof
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(require('../moment')) :
typeof define === 'function' && define.amd ? define(['moment'], factory) :
;(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined'
&& typeof require === 'function' ? factory(require('../moment')) :
typeof define === 'function' && define.amd ? define(['../moment'], factory) :
factory(global.moment)
}(this, function (moment) { 'use strict';
}(this, (function (moment) { 'use strict';
var numbersPast = 'nolla yksi kaksi kolme neljä viisi kuusi seitsemän kahdeksan yhdeksän'.split(' '),
numbersFuture = [
'nolla', 'yhden', 'kahden', 'kolmen', 'neljän', 'viiden', 'kuuden',
numbersPast[7], numbersPast[8], numbersPast[9]
];
function translate(number, withoutSuffix, key, isFuture) {
var result = '';
switch (key) {
var numbersPast = 'nolla yksi kaksi kolme neljä viisi kuusi seitsemän kahdeksan yhdeksän'.split(' ');
var numbersFuture = [
'nolla', 'yhden', 'kahden', 'kolmen', 'neljän', 'viiden', 'kuuden',
numbersPast[7], numbersPast[8], numbersPast[9]
];
function translate(number, withoutSuffix, key, isFuture) {
var result = '';
switch (key) {
case 's':
return isFuture ? 'muutaman sekunnin' : 'muutama sekunti';
case 'm':
@@ -44,63 +45,63 @@
case 'yy':
result = isFuture ? 'vuoden' : 'vuotta';
break;
}
result = verbalNumber(number, isFuture) + ' ' + result;
return result;
}
function verbalNumber(number, isFuture) {
return number < 10 ? (isFuture ? numbersFuture[number] : numbersPast[number]) : number;
result = verbalNumber(number, isFuture) + ' ' + result;
return result;
}
function verbalNumber(number, isFuture) {
return number < 10 ? (isFuture ? numbersFuture[number] : numbersPast[number]) : number;
}
var fi = moment.defineLocale('fi', {
months : 'tammikuu_helmikuu_maaliskuu_huhtikuu_toukokuu_kesäkuu_heinäkuu_elokuu_syyskuu_lokakuu_marraskuu_joulukuu'.split('_'),
monthsShort : 'tammi_helmi_maalis_huhti_touko_kesä_heinä_elo_syys_loka_marras_joulu'.split('_'),
weekdays : 'sunnuntai_maanantai_tiistai_keskiviikko_torstai_perjantai_lauantai'.split('_'),
weekdaysShort : 'su_ma_ti_ke_to_pe_la'.split('_'),
weekdaysMin : 'su_ma_ti_ke_to_pe_la'.split('_'),
longDateFormat : {
LT : 'HH.mm',
LTS : 'HH.mm.ss',
L : 'DD.MM.YYYY',
LL : 'Do MMMM[ta] YYYY',
LLL : 'Do MMMM[ta] YYYY, [klo] HH.mm',
LLLL : 'dddd, Do MMMM[ta] YYYY, [klo] HH.mm',
l : 'D.M.YYYY',
ll : 'Do MMM YYYY',
lll : 'Do MMM YYYY, [klo] HH.mm',
llll : 'ddd, Do MMM YYYY, [klo] HH.mm'
},
calendar : {
sameDay : '[tänään] [klo] LT',
nextDay : '[huomenna] [klo] LT',
nextWeek : 'dddd [klo] LT',
lastDay : '[eilen] [klo] LT',
lastWeek : '[viime] dddd[na] [klo] LT',
sameElse : 'L'
},
relativeTime : {
future : '%s päästä',
past : '%s sitten',
s : translate,
m : translate,
mm : translate,
h : translate,
hh : translate,
d : translate,
dd : translate,
M : translate,
MM : translate,
y : translate,
yy : translate
},
dayOfMonthOrdinalParse: /\d{1,2}\./,
ordinal : '%d.',
week : {
dow : 1, // Monday is the first day of the week.
doy : 4 // The week that contains Jan 4th is the first week of the year.
}
});
var fi = moment.defineLocale('fi', {
months : 'tammikuu_helmikuu_maaliskuu_huhtikuu_toukokuu_kesäkuu_heinäkuu_elokuu_syyskuu_lokakuu_marraskuu_joulukuu'.split('_'),
monthsShort : 'tammi_helmi_maalis_huhti_touko_kesä_heinä_elo_syys_loka_marras_joulu'.split('_'),
weekdays : 'sunnuntai_maanantai_tiistai_keskiviikko_torstai_perjantai_lauantai'.split('_'),
weekdaysShort : 'su_ma_ti_ke_to_pe_la'.split('_'),
weekdaysMin : 'su_ma_ti_ke_to_pe_la'.split('_'),
longDateFormat : {
LT : 'HH.mm',
LTS : 'HH.mm.ss',
L : 'DD.MM.YYYY',
LL : 'Do MMMM[ta] YYYY',
LLL : 'Do MMMM[ta] YYYY, [klo] HH.mm',
LLLL : 'dddd, Do MMMM[ta] YYYY, [klo] HH.mm',
l : 'D.M.YYYY',
ll : 'Do MMM YYYY',
lll : 'Do MMM YYYY, [klo] HH.mm',
llll : 'ddd, Do MMM YYYY, [klo] HH.mm'
},
calendar : {
sameDay : '[tänään] [klo] LT',
nextDay : '[huomenna] [klo] LT',
nextWeek : 'dddd [klo] LT',
lastDay : '[eilen] [klo] LT',
lastWeek : '[viime] dddd[na] [klo] LT',
sameElse : 'L'
},
relativeTime : {
future : '%s päästä',
past : '%s sitten',
s : translate,
m : translate,
mm : translate,
h : translate,
hh : translate,
d : translate,
dd : translate,
M : translate,
MM : translate,
y : translate,
yy : translate
},
ordinalParse: /\d{1,2}\./,
ordinal : '%d.',
week : {
dow : 1, // Monday is the first day of the week.
doy : 4 // The week that contains Jan 4th is the first week of the year.
}
});
return fi;
return fi;
}));
})));

View File

@@ -1,61 +1,83 @@
//! moment.js locale configuration
//! locale : french (fr)
//! locale : French [fr]
//! author : John Fischer : https://github.com/jfroffice
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(require('../moment')) :
typeof define === 'function' && define.amd ? define(['moment'], factory) :
;(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined'
&& typeof require === 'function' ? factory(require('../moment')) :
typeof define === 'function' && define.amd ? define(['../moment'], factory) :
factory(global.moment)
}(this, function (moment) { 'use strict';
}(this, (function (moment) { 'use strict';
var fr = moment.defineLocale('fr', {
months : 'janvier_février_mars_avril_mai_juin_juillet_août_septembre_octobre_novembre_décembre'.split('_'),
monthsShort : 'janv._févr._mars_avr._mai_juin_juil._août_sept._oct._nov._déc.'.split('_'),
weekdays : 'dimanche_lundi_mardi_mercredi_jeudi_vendredi_samedi'.split('_'),
weekdaysShort : 'dim._lun._mar._mer._jeu._ven._sam.'.split('_'),
weekdaysMin : 'Di_Lu_Ma_Me_Je_Ve_Sa'.split('_'),
longDateFormat : {
LT : 'HH:mm',
LTS : 'HH:mm:ss',
L : 'DD/MM/YYYY',
LL : 'D MMMM YYYY',
LLL : 'D MMMM YYYY HH:mm',
LLLL : 'dddd D MMMM YYYY HH:mm'
},
calendar : {
sameDay: '[Aujourd\'hui à] LT',
nextDay: '[Demain à] LT',
nextWeek: 'dddd [à] LT',
lastDay: '[Hier à] LT',
lastWeek: 'dddd [dernier à] LT',
sameElse: 'L'
},
relativeTime : {
future : 'dans %s',
past : 'il y a %s',
s : 'quelques secondes',
m : 'une minute',
mm : '%d minutes',
h : 'une heure',
hh : '%d heures',
d : 'un jour',
dd : '%d jours',
M : 'un mois',
MM : '%d mois',
y : 'un an',
yy : '%d ans'
},
ordinalParse: /\d{1,2}(er|)/,
ordinal : function (number) {
return number + (number === 1 ? 'er' : '');
},
week : {
dow : 1, // Monday is the first day of the week.
doy : 4 // The week that contains Jan 4th is the first week of the year.
var fr = moment.defineLocale('fr', {
months : 'janvier_février_mars_avril_mai_juin_juillet_août_septembre_octobre_novembre_décembre'.split('_'),
monthsShort : 'janv._févr._mars_avr._mai_juin_juil._août_sept._oct._nov._déc.'.split('_'),
monthsParseExact : true,
weekdays : 'dimanche_lundi_mardi_mercredi_jeudi_vendredi_samedi'.split('_'),
weekdaysShort : 'dim._lun._mar._mer._jeu._ven._sam.'.split('_'),
weekdaysMin : 'Di_Lu_Ma_Me_Je_Ve_Sa'.split('_'),
weekdaysParseExact : true,
longDateFormat : {
LT : 'HH:mm',
LTS : 'HH:mm:ss',
L : 'DD/MM/YYYY',
LL : 'D MMMM YYYY',
LLL : 'D MMMM YYYY HH:mm',
LLLL : 'dddd D MMMM YYYY HH:mm'
},
calendar : {
sameDay : '[Aujourdhui à] LT',
nextDay : '[Demain à] LT',
nextWeek : 'dddd [à] LT',
lastDay : '[Hier à] LT',
lastWeek : 'dddd [dernier à] LT',
sameElse : 'L'
},
relativeTime : {
future : 'dans %s',
past : 'il y a %s',
s : 'quelques secondes',
m : 'une minute',
mm : '%d minutes',
h : 'une heure',
hh : '%d heures',
d : 'un jour',
dd : '%d jours',
M : 'un mois',
MM : '%d mois',
y : 'un an',
yy : '%d ans'
},
dayOfMonthOrdinalParse: /\d{1,2}(er|)/,
ordinal : function (number, period) {
switch (period) {
// TODO: Return 'e' when day of month > 1. Move this case inside
// block for masculine words below.
// See https://github.com/moment/moment/issues/3375
case 'D':
return number + (number === 1 ? 'er' : '');
// Words with masculine grammatical gender: mois, trimestre, jour
default:
case 'M':
case 'Q':
case 'DDD':
case 'd':
return number + (number === 1 ? 'er' : 'e');
// Words with feminine grammatical gender: semaine
case 'w':
case 'W':
return number + (number === 1 ? 're' : 'e');
}
});
},
week : {
dow : 1, // Monday is the first day of the week.
doy : 4 // The week that contains Jan 4th is the first week of the year.
}
});
return fr;
return fr;
}));
})));

View File

@@ -0,0 +1,99 @@
//! moment.js locale configuration
//! locale : Hebrew [he]
//! author : Tomer Cohen : https://github.com/tomer
//! author : Moshe Simantov : https://github.com/DevelopmentIL
//! author : Tal Ater : https://github.com/TalAter
;(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined'
&& typeof require === 'function' ? factory(require('../moment')) :
typeof define === 'function' && define.amd ? define(['../moment'], factory) :
factory(global.moment)
}(this, (function (moment) { 'use strict';
var he = moment.defineLocale('he', {
months : 'ינואר_פברואר_מרץ_אפריל_מאי_יוני_יוליוגוסט_ספטמבר_אוקטובר_נובמבר_דצמבר'.split('_'),
monthsShort : 'ינו׳_פבר׳_מרץ_אפר׳_מאי_יוני_יוליוג׳_ספט׳וק׳וב׳_דצמ׳'.split('_'),
weekdays : 'ראשון_שני_שלישי_רביעי_חמישיישי_שבת'.split('_'),
weekdaysShort : ׳׳׳׳׳_ו׳׳'.split('_'),
weekdaysMin : 'א_ב_ג_ד_ה_ו_ש'.split('_'),
longDateFormat : {
LT : 'HH:mm',
LTS : 'HH:mm:ss',
L : 'DD/MM/YYYY',
LL : 'D [ב]MMMM YYYY',
LLL : 'D [ב]MMMM YYYY HH:mm',
LLLL : 'dddd, D [ב]MMMM YYYY HH:mm',
l : 'D/M/YYYY',
ll : 'D MMM YYYY',
lll : 'D MMM YYYY HH:mm',
llll : 'ddd, D MMM YYYY HH:mm'
},
calendar : {
sameDay : '[היום ב־]LT',
nextDay : '[מחר ב־]LT',
nextWeek : 'dddd [בשעה] LT',
lastDay : '[אתמול ב־]LT',
lastWeek : '[ביום] dddd [האחרון בשעה] LT',
sameElse : 'L'
},
relativeTime : {
future : 'בעוד %s',
past : 'לפני %s',
s : 'מספר שניות',
m : 'דקה',
mm : '%d דקות',
h : 'שעה',
hh : function (number) {
if (number === 2) {
return 'שעתיים';
}
return number + ' שעות';
},
d : 'יום',
dd : function (number) {
if (number === 2) {
return 'יומיים';
}
return number + ' ימים';
},
M : 'חודש',
MM : function (number) {
if (number === 2) {
return 'חודשיים';
}
return number + ' חודשים';
},
y : 'שנה',
yy : function (number) {
if (number === 2) {
return 'שנתיים';
} else if (number % 10 === 0 && number !== 10) {
return number + ' שנה';
}
return number + ' שנים';
}
},
meridiemParse: /אחה"צ|לפנה"צ|אחרי הצהריים|לפני הצהריים|לפנות בוקר|בבוקר|בערב/i,
isPM : function (input) {
return /^(אחה"צ|אחרי הצהריים|בערב)$/.test(input);
},
meridiem : function (hour, minute, isLower) {
if (hour < 5) {
return 'לפנות בוקר';
} else if (hour < 10) {
return 'בבוקר';
} else if (hour < 12) {
return isLower ? 'לפנה"צ' : 'לפני הצהריים';
} else if (hour < 18) {
return isLower ? 'אחה"צ' : 'אחרי הצהריים';
} else {
return 'בערב';
}
}
});
return he;
})));

View File

@@ -1,60 +1,63 @@
//! moment.js locale configuration
//! locale : norwegian bokmål (nb)
//! locale : Norwegian Bokmål [nb]
//! authors : Espen Hovlandsdal : https://github.com/rexxars
//! Sigurd Gartmann : https://github.com/sigurdga
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(require('../moment')) :
typeof define === 'function' && define.amd ? define(['moment'], factory) :
;(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined'
&& typeof require === 'function' ? factory(require('../moment')) :
typeof define === 'function' && define.amd ? define(['../moment'], factory) :
factory(global.moment)
}(this, function (moment) { 'use strict';
}(this, (function (moment) { 'use strict';
var nb = moment.defineLocale('nb', {
months : 'januar_februar_mars_april_mai_juni_juli_august_september_oktober_november_desember'.split('_'),
monthsShort : 'jan_feb_mar_apr_mai_jun_jul_aug_sep_okt_nov_des'.split('_'),
weekdays : 'søndag_mandag_tirsdag_onsdag_torsdag_fredag_lørdag'.split('_'),
weekdaysShort : 'søn_man_tirs_ons_tors_fre_lør'.split('_'),
weekdaysMin : 'sø_ma_ti_on_to_fr_lø'.split('_'),
longDateFormat : {
LT : 'H.mm',
LTS : 'H.mm.ss',
L : 'DD.MM.YYYY',
LL : 'D. MMMM YYYY',
LLL : 'D. MMMM YYYY [kl.] H.mm',
LLLL : 'dddd D. MMMM YYYY [kl.] H.mm'
},
calendar : {
sameDay: '[i dag kl.] LT',
nextDay: '[i morgen kl.] LT',
nextWeek: 'dddd [kl.] LT',
lastDay: '[i går kl.] LT',
lastWeek: '[forrige] dddd [kl.] LT',
sameElse: 'L'
},
relativeTime : {
future : 'om %s',
past : 'for %s siden',
s : 'noen sekunder',
m : 'ett minutt',
mm : '%d minutter',
h : 'en time',
hh : '%d timer',
d : 'en dag',
dd : '%d dager',
M : 'en måned',
MM : '%d måneder',
y : 'ett år',
yy : '%d år'
},
ordinalParse: /\d{1,2}\./,
ordinal : '%d.',
week : {
dow : 1, // Monday is the first day of the week.
doy : 4 // The week that contains Jan 4th is the first week of the year.
}
});
var nb = moment.defineLocale('nb', {
months : 'januar_februar_mars_april_mai_juni_juli_august_september_oktober_november_desember'.split('_'),
monthsShort : 'jan._feb._mars_april_mai_juni_juli_aug._sep._okt._nov._des.'.split('_'),
monthsParseExact : true,
weekdays : 'søndag_mandag_tirsdag_onsdag_torsdag_fredag_lørdag'.split('_'),
weekdaysShort : 'sø._ma._ti._on._to._fr._lø.'.split('_'),
weekdaysMin : 'sø_ma_ti_on_to_fr_lø'.split('_'),
weekdaysParseExact : true,
longDateFormat : {
LT : 'HH:mm',
LTS : 'HH:mm:ss',
L : 'DD.MM.YYYY',
LL : 'D. MMMM YYYY',
LLL : 'D. MMMM YYYY [kl.] HH:mm',
LLLL : 'dddd D. MMMM YYYY [kl.] HH:mm'
},
calendar : {
sameDay: '[i dag kl.] LT',
nextDay: '[i morgen kl.] LT',
nextWeek: 'dddd [kl.] LT',
lastDay: '[i går kl.] LT',
lastWeek: '[forrige] dddd [kl.] LT',
sameElse: 'L'
},
relativeTime : {
future : 'om %s',
past : '%s siden',
s : 'noen sekunder',
m : 'ett minutt',
mm : '%d minutter',
h : 'en time',
hh : '%d timer',
d : 'en dag',
dd : '%d dager',
M : 'en måned',
MM : '%d måneder',
y : 'ett år',
yy : '%d år'
},
dayOfMonthOrdinalParse: /\d{1,2}\./,
ordinal : '%d.',
week : {
dow : 1, // Monday is the first day of the week.
doy : 4 // The week that contains Jan 4th is the first week of the year.
}
});
return nb;
return nb;
}));
})));

View File

@@ -1,70 +1,88 @@
//! moment.js locale configuration
//! locale : dutch (nl)
//! author : Joris Röling : https://github.com/jjupiter
//! locale : Dutch [nl]
//! author : Joris Röling : https://github.com/jorisroling
//! author : Jacob Middag : https://github.com/middagj
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(require('../moment')) :
typeof define === 'function' && define.amd ? define(['moment'], factory) :
;(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined'
&& typeof require === 'function' ? factory(require('../moment')) :
typeof define === 'function' && define.amd ? define(['../moment'], factory) :
factory(global.moment)
}(this, function (moment) { 'use strict';
}(this, (function (moment) { 'use strict';
var monthsShortWithDots = 'jan._feb._mrt._apr._mei_jun._jul._aug._sep._okt._nov._dec.'.split('_'),
monthsShortWithoutDots = 'jan_feb_mrt_apr_mei_jun_jul_aug_sep_okt_nov_dec'.split('_');
var monthsShortWithDots = 'jan._feb._mrt._apr._mei_jun._jul._aug._sep._okt._nov._dec.'.split('_');
var monthsShortWithoutDots = 'jan_feb_mrt_apr_mei_jun_jul_aug_sep_okt_nov_dec'.split('_');
var nl = moment.defineLocale('nl', {
months : 'januari_februari_maart_april_mei_juni_juli_augustus_september_oktober_november_december'.split('_'),
monthsShort : function (m, format) {
if (/-MMM-/.test(format)) {
return monthsShortWithoutDots[m.month()];
} else {
return monthsShortWithDots[m.month()];
}
},
weekdays : 'zondag_maandag_dinsdag_woensdag_donderdag_vrijdag_zaterdag'.split('_'),
weekdaysShort : 'zo._ma._di._wo._do._vr._za.'.split('_'),
weekdaysMin : 'Zo_Ma_Di_Wo_Do_Vr_Za'.split('_'),
longDateFormat : {
LT : 'HH:mm',
LTS : 'HH:mm:ss',
L : 'DD-MM-YYYY',
LL : 'D MMMM YYYY',
LLL : 'D MMMM YYYY HH:mm',
LLLL : 'dddd D MMMM YYYY HH:mm'
},
calendar : {
sameDay: '[vandaag om] LT',
nextDay: '[morgen om] LT',
nextWeek: 'dddd [om] LT',
lastDay: '[gisteren om] LT',
lastWeek: '[afgelopen] dddd [om] LT',
sameElse: 'L'
},
relativeTime : {
future : 'over %s',
past : '%s geleden',
s : 'een paar seconden',
m : 'één minuut',
mm : '%d minuten',
h : 'één uur',
hh : '%d uur',
d : 'één dag',
dd : '%d dagen',
M : 'één maand',
MM : '%d maanden',
y : 'één jaar',
yy : '%d jaar'
},
ordinalParse: /\d{1,2}(ste|de)/,
ordinal : function (number) {
return number + ((number === 1 || number === 8 || number >= 20) ? 'ste' : 'de');
},
week : {
dow : 1, // Monday is the first day of the week.
doy : 4 // The week that contains Jan 4th is the first week of the year.
var monthsParse = [/^jan/i, /^feb/i, /^maart|mrt.?$/i, /^apr/i, /^mei$/i, /^jun[i.]?$/i, /^jul[i.]?$/i, /^aug/i, /^sep/i, /^okt/i, /^nov/i, /^dec/i];
var monthsRegex = /^(januari|februari|maart|april|mei|april|ju[nl]i|augustus|september|oktober|november|december|jan\.?|feb\.?|mrt\.?|apr\.?|ju[nl]\.?|aug\.?|sep\.?|okt\.?|nov\.?|dec\.?)/i;
var nl = moment.defineLocale('nl', {
months : 'januari_februari_maart_april_mei_juni_juli_augustus_september_oktober_november_december'.split('_'),
monthsShort : function (m, format) {
if (!m) {
return monthsShortWithDots;
} else if (/-MMM-/.test(format)) {
return monthsShortWithoutDots[m.month()];
} else {
return monthsShortWithDots[m.month()];
}
});
},
return nl;
monthsRegex: monthsRegex,
monthsShortRegex: monthsRegex,
monthsStrictRegex: /^(januari|februari|maart|mei|ju[nl]i|april|augustus|september|oktober|november|december)/i,
monthsShortStrictRegex: /^(jan\.?|feb\.?|mrt\.?|apr\.?|mei|ju[nl]\.?|aug\.?|sep\.?|okt\.?|nov\.?|dec\.?)/i,
}));
monthsParse : monthsParse,
longMonthsParse : monthsParse,
shortMonthsParse : monthsParse,
weekdays : 'zondag_maandag_dinsdag_woensdag_donderdag_vrijdag_zaterdag'.split('_'),
weekdaysShort : 'zo._ma._di._wo._do._vr._za.'.split('_'),
weekdaysMin : 'zo_ma_di_wo_do_vr_za'.split('_'),
weekdaysParseExact : true,
longDateFormat : {
LT : 'HH:mm',
LTS : 'HH:mm:ss',
L : 'DD-MM-YYYY',
LL : 'D MMMM YYYY',
LLL : 'D MMMM YYYY HH:mm',
LLLL : 'dddd D MMMM YYYY HH:mm'
},
calendar : {
sameDay: '[vandaag om] LT',
nextDay: '[morgen om] LT',
nextWeek: 'dddd [om] LT',
lastDay: '[gisteren om] LT',
lastWeek: '[afgelopen] dddd [om] LT',
sameElse: 'L'
},
relativeTime : {
future : 'over %s',
past : '%s geleden',
s : 'een paar seconden',
m : 'één minuut',
mm : '%d minuten',
h : 'één uur',
hh : '%d uur',
d : 'één dag',
dd : '%d dagen',
M : 'één maand',
MM : '%d maanden',
y : 'één jaar',
yy : '%d jaar'
},
dayOfMonthOrdinalParse: /\d{1,2}(ste|de)/,
ordinal : function (number) {
return number + ((number === 1 || number === 8 || number >= 20) ? 'ste' : 'de');
},
week : {
dow : 1, // Monday is the first day of the week.
doy : 4 // The week that contains Jan 4th is the first week of the year.
}
});
return nl;
})));

View File

@@ -1,22 +1,23 @@
//! moment.js locale configuration
//! locale : polish (pl)
//! locale : Polish [pl]
//! author : Rafal Hirsz : https://github.com/evoL
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(require('../moment')) :
typeof define === 'function' && define.amd ? define(['moment'], factory) :
;(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined'
&& typeof require === 'function' ? factory(require('../moment')) :
typeof define === 'function' && define.amd ? define(['../moment'], factory) :
factory(global.moment)
}(this, function (moment) { 'use strict';
}(this, (function (moment) { 'use strict';
var monthsNominative = 'styczeń_luty_marzec_kwiecień_maj_czerwiec_lipiec_sierpień_wrzesień_październik_listopad_grudzień'.split('_'),
monthsSubjective = 'stycznia_lutego_marca_kwietnia_maja_czerwca_lipca_sierpnia_września_października_listopada_grudnia'.split('_');
function plural(n) {
return (n % 10 < 5) && (n % 10 > 1) && ((~~(n / 10) % 10) !== 1);
}
function translate(number, withoutSuffix, key) {
var result = number + ' ';
switch (key) {
var monthsNominative = 'styczeń_luty_marzec_kwiecień_maj_czerwiec_lipiec_sierpień_wrzesień_październik_listopad_grudzień'.split('_');
var monthsSubjective = 'stycznia_lutego_marca_kwietnia_maja_czerwca_lipca_sierpnia_września_października_listopada_grudnia'.split('_');
function plural(n) {
return (n % 10 < 5) && (n % 10 > 1) && ((~~(n / 10) % 10) !== 1);
}
function translate(number, withoutSuffix, key) {
var result = number + ' ';
switch (key) {
case 'm':
return withoutSuffix ? 'minuta' : 'minutę';
case 'mm':
@@ -29,41 +30,60 @@
return result + (plural(number) ? 'miesiące' : 'miesięcy');
case 'yy':
return result + (plural(number) ? 'lata' : 'lat');
}
}
}
var pl = moment.defineLocale('pl', {
months : function (momentToFormat, format) {
if (format === '') {
// Hack: if format empty we know this is used to generate
// RegExp by moment. Give then back both valid forms of months
// in RegExp ready format.
return '(' + monthsSubjective[momentToFormat.month()] + '|' + monthsNominative[momentToFormat.month()] + ')';
} else if (/D MMMM/.test(format)) {
return monthsSubjective[momentToFormat.month()];
} else {
return monthsNominative[momentToFormat.month()];
var pl = moment.defineLocale('pl', {
months : function (momentToFormat, format) {
if (!momentToFormat) {
return monthsNominative;
} else if (format === '') {
// Hack: if format empty we know this is used to generate
// RegExp by moment. Give then back both valid forms of months
// in RegExp ready format.
return '(' + monthsSubjective[momentToFormat.month()] + '|' + monthsNominative[momentToFormat.month()] + ')';
} else if (/D MMMM/.test(format)) {
return monthsSubjective[momentToFormat.month()];
} else {
return monthsNominative[momentToFormat.month()];
}
},
monthsShort : 'sty_lut_mar_kwi_maj_cze_lip_sie_wrz_paź_lis_gru'.split('_'),
weekdays : 'niedziela_poniedziałek_wtorek_środa_czwartek_piątek_sobota'.split('_'),
weekdaysShort : 'ndz_pon_wt_śr_czw_pt_sob'.split('_'),
weekdaysMin : 'Nd_Pn_Wt_Śr_Cz_Pt_So'.split('_'),
longDateFormat : {
LT : 'HH:mm',
LTS : 'HH:mm:ss',
L : 'DD.MM.YYYY',
LL : 'D MMMM YYYY',
LLL : 'D MMMM YYYY HH:mm',
LLLL : 'dddd, D MMMM YYYY HH:mm'
},
calendar : {
sameDay: '[Dziś o] LT',
nextDay: '[Jutro o] LT',
nextWeek: function () {
switch (this.day()) {
case 0:
return '[W niedzielę o] LT';
case 2:
return '[We wtorek o] LT';
case 3:
return '[W środę o] LT';
case 6:
return '[W sobotę o] LT';
default:
return '[W] dddd [o] LT';
}
},
monthsShort : 'sty_lut_mar_kwi_maj_cze_lip_sie_wrz_paź_lis_gru'.split('_'),
weekdays : 'niedziela_poniedziałek_wtorek_środa_czwartek_piątek_sobota'.split('_'),
weekdaysShort : 'nie_pon_wt_śr_czw_pt_sb'.split('_'),
weekdaysMin : 'N_Pn_Wt_Śr_Cz_Pt_So'.split('_'),
longDateFormat : {
LT : 'HH:mm',
LTS : 'HH:mm:ss',
L : 'DD.MM.YYYY',
LL : 'D MMMM YYYY',
LLL : 'D MMMM YYYY HH:mm',
LLLL : 'dddd, D MMMM YYYY HH:mm'
},
calendar : {
sameDay: '[Dziś o] LT',
nextDay: '[Jutro o] LT',
nextWeek: '[W] dddd [o] LT',
lastDay: '[Wczoraj o] LT',
lastWeek: function () {
switch (this.day()) {
lastDay: '[Wczoraj o] LT',
lastWeek: function () {
switch (this.day()) {
case 0:
return '[W zeszłą niedzielę o] LT';
case 3:
@@ -72,33 +92,33 @@
return '[W zeszłą sobotę o] LT';
default:
return '[W zeszły] dddd [o] LT';
}
},
sameElse: 'L'
}
},
relativeTime : {
future : 'za %s',
past : '%s temu',
s : 'kilka sekund',
m : translate,
mm : translate,
h : translate,
hh : translate,
d : '1 dzień',
dd : '%d dni',
M : 'miesiąc',
MM : translate,
y : 'rok',
yy : translate
},
ordinalParse: /\d{1,2}\./,
ordinal : '%d.',
week : {
dow : 1, // Monday is the first day of the week.
doy : 4 // The week that contains Jan 4th is the first week of the year.
}
});
sameElse: 'L'
},
relativeTime : {
future : 'za %s',
past : '%s temu',
s : 'kilka sekund',
m : translate,
mm : translate,
h : translate,
hh : translate,
d : '1 dzień',
dd : '%d dni',
M : 'miesiąc',
MM : translate,
y : 'rok',
yy : translate
},
dayOfMonthOrdinalParse: /\d{1,2}\./,
ordinal : '%d.',
week : {
dow : 1, // Monday is the first day of the week.
doy : 4 // The week that contains Jan 4th is the first week of the year.
}
});
return pl;
return pl;
}));
})));

View File

@@ -1,59 +1,62 @@
//! moment.js locale configuration
//! locale : brazilian portuguese (pt-br)
//! locale : Portuguese (Brazil) [pt-br]
//! author : Caio Ribeiro Pereira : https://github.com/caio-ribeiro-pereira
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(require('../moment')) :
typeof define === 'function' && define.amd ? define(['moment'], factory) :
;(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined'
&& typeof require === 'function' ? factory(require('../moment')) :
typeof define === 'function' && define.amd ? define(['../moment'], factory) :
factory(global.moment)
}(this, function (moment) { 'use strict';
}(this, (function (moment) { 'use strict';
var pt_br = moment.defineLocale('pt-br', {
months : 'Janeiro_Fevereiro_Março_Abril_Maio_Junho_Julho_Agosto_Setembro_Outubro_Novembro_Dezembro'.split('_'),
monthsShort : 'Jan_Fev_Mar_Abr_Mai_Jun_Jul_Ago_Set_Out_Nov_Dez'.split('_'),
weekdays : 'Domingo_Segunda-Feira_Terça-Feira_Quarta-Feira_Quinta-Feira_Sexta-Feira_Sábado'.split('_'),
weekdaysShort : 'Dom_Seg_Ter_Qua_Qui_Sex_Sáb'.split('_'),
weekdaysMin : 'Dom_2ª_3ª_4ª_5ª_6ª_Sáb'.split('_'),
longDateFormat : {
LT : 'HH:mm',
LTS : 'HH:mm:ss',
L : 'DD/MM/YYYY',
LL : 'D [de] MMMM [de] YYYY',
LLL : 'D [de] MMMM [de] YYYY [às] HH:mm',
LLLL : 'dddd, D [de] MMMM [de] YYYY [às] HH:mm'
var ptBr = moment.defineLocale('pt-br', {
months : 'janeiro_fevereiro_março_abril_maio_junho_julho_agosto_setembro_outubro_novembro_dezembro'.split('_'),
monthsShort : 'jan_fev_mar_abr_mai_jun_jul_ago_set_out_nov_dez'.split('_'),
weekdays : 'Domingo_Segunda-feira_Terça-feira_Quarta-feira_Quinta-feira_Sexta-feira_Sábado'.split('_'),
weekdaysShort : 'Dom_Seg_Ter_Qua_Qui_Sex_Sáb'.split('_'),
weekdaysMin : 'Do_2ª_3ª_4ª_5ª_6ª_Sá'.split('_'),
weekdaysParseExact : true,
longDateFormat : {
LT : 'HH:mm',
LTS : 'HH:mm:ss',
L : 'DD/MM/YYYY',
LL : 'D [de] MMMM [de] YYYY',
LLL : 'D [de] MMMM [de] YYYY [às] HH:mm',
LLLL : 'dddd, D [de] MMMM [de] YYYY [às] HH:mm'
},
calendar : {
sameDay: '[Hoje às] LT',
nextDay: '[Amanhã às] LT',
nextWeek: 'dddd [às] LT',
lastDay: '[Ontem às] LT',
lastWeek: function () {
return (this.day() === 0 || this.day() === 6) ?
'[Último] dddd [às] LT' : // Saturday + Sunday
'[Última] dddd [às] LT'; // Monday - Friday
},
calendar : {
sameDay: '[Hoje às] LT',
nextDay: '[Amanhã às] LT',
nextWeek: 'dddd [às] LT',
lastDay: '[Ontem às] LT',
lastWeek: function () {
return (this.day() === 0 || this.day() === 6) ?
'[Último] dddd [às] LT' : // Saturday + Sunday
'[Última] dddd [às] LT'; // Monday - Friday
},
sameElse: 'L'
},
relativeTime : {
future : 'em %s',
past : '%s atrás',
s : 'poucos segundos',
m : 'um minuto',
mm : '%d minutos',
h : 'uma hora',
hh : '%d horas',
d : 'um dia',
dd : '%d dias',
M : 'um mês',
MM : '%d meses',
y : 'um ano',
yy : '%d anos'
},
ordinalParse: /\d{1,2}º/,
ordinal : '%dº'
});
sameElse: 'L'
},
relativeTime : {
future : 'em %s',
past : '%s atrás',
s : 'poucos segundos',
ss : '%d segundos',
m : 'um minuto',
mm : '%d minutos',
h : 'uma hora',
hh : '%d horas',
d : 'um dia',
dd : '%d dias',
M : 'um s',
MM : '%d meses',
y : 'um ano',
yy : '%d anos'
},
dayOfMonthOrdinalParse: /\d{1,2}º/,
ordinal : '%dº'
});
return pt_br;
return ptBr;
}));
})));

View File

@@ -1,73 +1,75 @@
//! moment.js locale configuration
//! locale : romanian (ro)
//! locale : Romanian [ro]
//! author : Vlad Gurdiga : https://github.com/gurdiga
//! author : Valentin Agachi : https://github.com/avaly
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(require('../moment')) :
typeof define === 'function' && define.amd ? define(['moment'], factory) :
;(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined'
&& typeof require === 'function' ? factory(require('../moment')) :
typeof define === 'function' && define.amd ? define(['../moment'], factory) :
factory(global.moment)
}(this, function (moment) { 'use strict';
}(this, (function (moment) { 'use strict';
function relativeTimeWithPlural(number, withoutSuffix, key) {
var format = {
'mm': 'minute',
'hh': 'ore',
'dd': 'zile',
'MM': 'luni',
'yy': 'ani'
},
separator = ' ';
if (number % 100 >= 20 || (number >= 100 && number % 100 === 0)) {
separator = ' de ';
}
return number + separator + format[key];
function relativeTimeWithPlural(number, withoutSuffix, key) {
var format = {
'mm': 'minute',
'hh': 'ore',
'dd': 'zile',
'MM': 'luni',
'yy': 'ani'
},
separator = ' ';
if (number % 100 >= 20 || (number >= 100 && number % 100 === 0)) {
separator = ' de ';
}
return number + separator + format[key];
}
var ro = moment.defineLocale('ro', {
months : 'ianuarie_februarie_martie_aprilie_mai_iunie_iulie_august_septembrie_octombrie_noiembrie_decembrie'.split('_'),
monthsShort : 'ian._febr._mart._apr._mai_iun._iul._aug._sept._oct._nov._dec.'.split('_'),
weekdays : 'duminică_luni_marți_miercuri_joi_vineri_sâmbătă'.split('_'),
weekdaysShort : 'Dum_Lun_Mar_Mie_Joi_Vin_Sâm'.split('_'),
weekdaysMin : 'Du_Lu_Ma_Mi_Jo_Vi_Sâ'.split('_'),
longDateFormat : {
LT : 'H:mm',
LTS : 'H:mm:ss',
L : 'DD.MM.YYYY',
LL : 'D MMMM YYYY',
LLL : 'D MMMM YYYY H:mm',
LLLL : 'dddd, D MMMM YYYY H:mm'
},
calendar : {
sameDay: '[azi la] LT',
nextDay: '[mâine la] LT',
nextWeek: 'dddd [la] LT',
lastDay: '[ieri la] LT',
lastWeek: '[fosta] dddd [la] LT',
sameElse: 'L'
},
relativeTime : {
future : 'peste %s',
past : '%s în urmă',
s : 'câteva secunde',
m : 'un minut',
mm : relativeTimeWithPlural,
h : 'o oră',
hh : relativeTimeWithPlural,
d : 'o zi',
dd : relativeTimeWithPlural,
M : 'o lună',
MM : relativeTimeWithPlural,
y : 'un an',
yy : relativeTimeWithPlural
},
week : {
dow : 1, // Monday is the first day of the week.
doy : 7 // The week that contains Jan 1st is the first week of the year.
}
});
var ro = moment.defineLocale('ro', {
months : 'ianuarie_februarie_martie_aprilie_mai_iunie_iulie_august_septembrie_octombrie_noiembrie_decembrie'.split('_'),
monthsShort : 'ian._febr._mart._apr._mai_iun._iul._aug._sept._oct._nov._dec.'.split('_'),
monthsParseExact: true,
weekdays : 'duminică_luni_marți_miercuri_joi_vineri_sâmbătă'.split('_'),
weekdaysShort : 'Dum_Lun_Mar_Mie_Joi_Vin_Sâm'.split('_'),
weekdaysMin : 'Du_Lu_Ma_Mi_Jo_Vi_Sâ'.split('_'),
longDateFormat : {
LT : 'H:mm',
LTS : 'H:mm:ss',
L : 'DD.MM.YYYY',
LL : 'D MMMM YYYY',
LLL : 'D MMMM YYYY H:mm',
LLLL : 'dddd, D MMMM YYYY H:mm'
},
calendar : {
sameDay: '[azi la] LT',
nextDay: '[mâine la] LT',
nextWeek: 'dddd [la] LT',
lastDay: '[ieri la] LT',
lastWeek: '[fosta] dddd [la] LT',
sameElse: 'L'
},
relativeTime : {
future : 'peste %s',
past : '%s în urmă',
s : 'câteva secunde',
m : 'un minut',
mm : relativeTimeWithPlural,
h : 'o oră',
hh : relativeTimeWithPlural,
d : 'o zi',
dd : relativeTimeWithPlural,
M : 'o lună',
MM : relativeTimeWithPlural,
y : 'un an',
yy : relativeTimeWithPlural
},
week : {
dow : 1, // Monday is the first day of the week.
doy : 7 // The week that contains Jan 1st is the first week of the year.
}
});
return ro;
return ro;
}));
})));

View File

@@ -1,90 +1,110 @@
//! moment.js locale configuration
//! locale : russian (ru)
//! locale : Russian [ru]
//! author : Viktorminator : https://github.com/Viktorminator
//! Author : Menelion Elensúle : https://github.com/Oire
//! author : Коренберг Марк : https://github.com/socketpair
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(require('../moment')) :
typeof define === 'function' && define.amd ? define(['moment'], factory) :
;(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined'
&& typeof require === 'function' ? factory(require('../moment')) :
typeof define === 'function' && define.amd ? define(['../moment'], factory) :
factory(global.moment)
}(this, function (moment) { 'use strict';
}(this, (function (moment) { 'use strict';
function plural(word, num) {
var forms = word.split('_');
return num % 10 === 1 && num % 100 !== 11 ? forms[0] : (num % 10 >= 2 && num % 10 <= 4 && (num % 100 < 10 || num % 100 >= 20) ? forms[1] : forms[2]);
function plural(word, num) {
var forms = word.split('_');
return num % 10 === 1 && num % 100 !== 11 ? forms[0] : (num % 10 >= 2 && num % 10 <= 4 && (num % 100 < 10 || num % 100 >= 20) ? forms[1] : forms[2]);
}
function relativeTimeWithPlural(number, withoutSuffix, key) {
var format = {
'mm': withoutSuffix ? 'минута_минуты_минут' : 'минуту_минуты_минут',
'hh': асасаасов',
'dd': ень_дня_дней',
'MM': есяц_месяцаесяцев',
'yy': 'год_годает'
};
if (key === 'm') {
return withoutSuffix ? 'минута' : 'минуту';
}
function relativeTimeWithPlural(number, withoutSuffix, key) {
var format = {
'mm': withoutSuffix ? 'минута_минуты_минут' : 'минуту_минуты_минут',
'hh': асасаасов',
'dd': ень_дня_дней',
'MM': есяц_месяцаесяцев',
'yy': 'год_годает'
};
if (key === 'm') {
return withoutSuffix ? 'минута' : 'минуту';
}
else {
return number + ' ' + plural(format[key], +number);
}
}
function monthsCaseReplace(m, format) {
var months = {
'nominative': 'январь_февраль_март_апрель_май_июнь_июль_август_сентябрь_октябрь_ноябрь_декабрь'.split('_'),
'accusative': 'января_февраля_марта_апреля_мая_июня_июля_августа_сентября_октября_ноября_декабря'.split('_')
},
nounCase = (/D[oD]?(\[[^\[\]]*\]|\s+)+MMMM?/).test(format) ?
'accusative' :
'nominative';
return months[nounCase][m.month()];
}
function monthsShortCaseReplace(m, format) {
var monthsShort = {
'nominative': 'янв_фев_март_апрай_июнь_июль_авг_сен_окт_ноя_дек'.split('_'),
'accusative': 'янв_фев_мар_апрая_июня_июля_авг_сен_окт_ноя_дек'.split('_')
},
nounCase = (/D[oD]?(\[[^\[\]]*\]|\s+)+MMMM?/).test(format) ?
'accusative' :
'nominative';
return monthsShort[nounCase][m.month()];
}
function weekdaysCaseReplace(m, format) {
var weekdays = {
'nominative': оскресенье_понедельник_вторник_средаетверг_пятница_суббота'.split('_'),
'accusative': оскресенье_понедельник_вторник_средуетверг_пятницу_субботу'.split('_')
},
nounCase = (/\[ ?[Вв] ?(?:прошлую|следующую|эту)? ?\] ?dddd/).test(format) ?
'accusative' :
'nominative';
return weekdays[nounCase][m.day()];
else {
return number + ' ' + plural(format[key], +number);
}
}
var monthsParse = [/^янв/i, /^фев/i, /^мар/i, /^апр/i, /^ма[йя]/i, /^июн/i, /^июл/i, /^авг/i, /^сен/i, /^окт/i, /^ноя/i, /^дек/i];
var ru = moment.defineLocale('ru', {
months : monthsCaseReplace,
monthsShort : monthsShortCaseReplace,
weekdays : weekdaysCaseReplace,
weekdaysShort : с_пн_вт_ср_чт_пт_сб'.split('_'),
weekdaysMin : с_пн_вт_ср_чт_пт_сб'.split('_'),
monthsParse : [/^янв/i, /^фев/i, /^мар/i, /^апр/i, /^ма[й|я]/i, /^июн/i, /^июл/i, /^авг/i, /^сен/i, /^окт/i, /^ноя/i, /^дек/i],
longDateFormat : {
LT : 'HH:mm',
LTS : 'HH:mm:ss',
L : 'DD.MM.YYYY',
LL : 'D MMMM YYYY г.',
LLL : 'D MMMM YYYY г., HH:mm',
LLLL : 'dddd, D MMMM YYYY г., HH:mm'
// http://new.gramota.ru/spravka/rules/139-prop : § 103
// Сокращения месяцев: http://new.gramota.ru/spravka/buro/search-answer?s=242637
// CLDR data: http://www.unicode.org/cldr/charts/28/summary/ru.html#1753
var ru = moment.defineLocale('ru', {
months : {
format: 'января_февраля_марта_апреля_мая_июня_июля_августа_сентября_октября_ноября_декабря'.split('_'),
standalone: 'январь_февраль_март_апрель_май_июнь_июль_август_сентябрь_октябрь_ноябрь_декабрь'.split('_')
},
monthsShort : {
// по CLDR именно "июл." и "июн.", но какой смысл менять букву на точку ?
format: 'янв._февр._мар._апр._мая_июня_июля_авг._сент._окт._нояб._дек.'.split('_'),
standalone: 'янв._февр._март_апр._май_июнь_июль_авг._сент._окт._нояб._дек.'.split('_')
},
weekdays : {
standalone: оскресенье_понедельник_вторник_средаетверг_пятница_суббота'.split('_'),
format: оскресенье_понедельник_вторник_средуетверг_пятницу_субботу'.split('_'),
isFormat: /\[ ?[Вв] ?(?:прошлую|следующую|эту)? ?\] ?dddd/
},
weekdaysShort : с_пн_вт_ср_чт_пт_сб'.split('_'),
weekdaysMin : с_пн_вт_ср_чт_пт_сб'.split('_'),
monthsParse : monthsParse,
longMonthsParse : monthsParse,
shortMonthsParse : monthsParse,
// полные названия с падежами, по три буквы, для некоторых, по 4 буквы, сокращения с точкой и без точки
monthsRegex: /^(январ[ья]|янв\.?|феврал[ья]|февр?\.?|марта?|мар\.?|апрел[ья]|апр\.?|ма[йя]|июн[ья]|июн\.?|июл[ья]|июл\.?|августа?|авг\.?|сентябр[ья]|сент?\.?|октябр[ья]|окт\.?|ноябр[ья]|нояб?\.?|декабр[ья]|дек\.?)/i,
// копия предыдущего
monthsShortRegex: /^(январ[ья]|янв\.?|феврал[ья]|февр?\.?|марта?|мар\.?|апрел[ья]|апр\.?|ма[йя]|июн[ья]|июн\.?|июл[ья]|июл\.?|августа?|авг\.?|сентябр[ья]|сент?\.?|октябр[ья]|окт\.?|ноябр[ья]|нояб?\.?|декабр[ья]|дек\.?)/i,
// полные названия с падежами
monthsStrictRegex: /^(январ[яь]|феврал[яь]|марта?|апрел[яь]|ма[яй]|июн[яь]|июл[яь]|августа?|сентябр[яь]|октябр[яь]|ноябр[яь]|декабр[яь])/i,
// Выражение, которое соотвествует только сокращённым формам
monthsShortStrictRegex: /^(янв\.|февр?\.|мар[т.]|апр\.|ма[яй]|июн[ья.]|июл[ья.]|авг\.|сент?\.|окт\.|нояб?\.|дек\.)/i,
longDateFormat : {
LT : 'HH:mm',
LTS : 'HH:mm:ss',
L : 'DD.MM.YYYY',
LL : 'D MMMM YYYY г.',
LLL : 'D MMMM YYYY г., HH:mm',
LLLL : 'dddd, D MMMM YYYY г., HH:mm'
},
calendar : {
sameDay: '[Сегодня в] LT',
nextDay: '[Завтра в] LT',
lastDay: '[Вчера в] LT',
nextWeek: function (now) {
if (now.week() !== this.week()) {
switch (this.day()) {
case 0:
return '[В следующее] dddd [в] LT';
case 1:
case 2:
case 4:
return '[В следующий] dddd [в] LT';
case 3:
case 5:
case 6:
return '[В следующую] dddd [в] LT';
}
} else {
if (this.day() === 2) {
return '[Во] dddd [в] LT';
} else {
return '[В] dddd [в] LT';
}
}
},
calendar : {
sameDay: '[Сегодня в] LT',
nextDay: '[Завтра в] LT',
lastDay: '[Вчера в] LT',
nextWeek: function () {
return this.day() === 2 ? '[Во] dddd [в] LT' : '[В] dddd [в] LT';
},
lastWeek: function (now) {
if (now.week() !== this.week()) {
switch (this.day()) {
lastWeek: function (now) {
if (now.week() !== this.week()) {
switch (this.day()) {
case 0:
return '[В прошлое] dddd [в] LT';
case 1:
@@ -95,50 +115,50 @@
case 5:
case 6:
return '[В прошлую] dddd [в] LT';
}
} else {
if (this.day() === 2) {
return '[Во] dddd [в] LT';
} else {
return '[В] dddd [в] LT';
}
}
},
sameElse: 'L'
},
relativeTime : {
future : 'через %s',
past : '%s назад',
s : 'несколько секунд',
m : relativeTimeWithPlural,
mm : relativeTimeWithPlural,
h : 'час',
hh : relativeTimeWithPlural,
d : 'день',
dd : relativeTimeWithPlural,
M : 'месяц',
MM : relativeTimeWithPlural,
y : 'год',
yy : relativeTimeWithPlural
},
meridiemParse: /ночи|утра|дня|вечера/i,
isPM : function (input) {
return /^(дня|вечера)$/.test(input);
},
meridiem : function (hour, minute, isLower) {
if (hour < 4) {
return 'ночи';
} else if (hour < 12) {
return 'утра';
} else if (hour < 17) {
return 'дня';
} else {
return 'вечера';
if (this.day() === 2) {
return '[Во] dddd [в] LT';
} else {
return '[В] dddd [в] LT';
}
}
},
ordinalParse: /\d{1,2}-(й|го|я)/,
ordinal: function (number, period) {
switch (period) {
sameElse: 'L'
},
relativeTime : {
future : 'через %s',
past : '%s назад',
s : 'несколько секунд',
m : relativeTimeWithPlural,
mm : relativeTimeWithPlural,
h : 'час',
hh : relativeTimeWithPlural,
d : 'день',
dd : relativeTimeWithPlural,
M : 'месяц',
MM : relativeTimeWithPlural,
y : 'год',
yy : relativeTimeWithPlural
},
meridiemParse: /ночи|утра|дня|вечера/i,
isPM : function (input) {
return /^(дня|вечера)$/.test(input);
},
meridiem : function (hour, minute, isLower) {
if (hour < 4) {
return 'ночи';
} else if (hour < 12) {
return 'утра';
} else if (hour < 17) {
return 'дня';
} else {
return 'вечера';
}
},
dayOfMonthOrdinalParse: /\d{1,2}-(й|го|я)/,
ordinal: function (number, period) {
switch (period) {
case 'M':
case 'd':
case 'DDD':
@@ -150,14 +170,14 @@
return number + '-я';
default:
return number;
}
},
week : {
dow : 1, // Monday is the first day of the week.
doy : 7 // The week that contains Jan 1st is the first week of the year.
}
});
},
week : {
dow : 1, // Monday is the first day of the week.
doy : 4 // The week that contains Jan 4th is the first week of the year.
}
});
return ru;
return ru;
}));
})));

View File

@@ -1,107 +1,110 @@
//! moment.js locale configuration
//! locale : Serbian-cyrillic (sr-cyrl)
//! locale : Serbian [sr]
//! author : Milan Janačković<milanjanackovic@gmail.com> : https://github.com/milan-j
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(require('../moment')) :
typeof define === 'function' && define.amd ? define(['moment'], factory) :
;(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined'
&& typeof require === 'function' ? factory(require('../moment')) :
typeof define === 'function' && define.amd ? define(['../moment'], factory) :
factory(global.moment)
}(this, function (moment) { 'use strict';
}(this, (function (moment) { 'use strict';
var translator = {
words: { //Different grammatical cases
m: ['један минут', 'једне минуте'],
mm: ['минут', 'минуте', 'минута'],
h: ['један сат', 'једног сата'],
hh: ['сат', 'сата', 'сати'],
dd: ['дан', 'дана', 'дана'],
MM: ['месец', 'месеца', 'месеци'],
yy: ['година', 'године', 'година']
},
correctGrammaticalCase: function (number, wordKey) {
return number === 1 ? wordKey[0] : (number >= 2 && number <= 4 ? wordKey[1] : wordKey[2]);
},
translate: function (number, withoutSuffix, key) {
var wordKey = translator.words[key];
if (key.length === 1) {
return withoutSuffix ? wordKey[0] : wordKey[1];
} else {
return number + ' ' + translator.correctGrammaticalCase(number, wordKey);
}
var translator = {
words: { //Different grammatical cases
m: ['jedan minut', 'jedne minute'],
mm: ['minut', 'minute', 'minuta'],
h: ['jedan sat', 'jednog sata'],
hh: ['sat', 'sata', 'sati'],
dd: ['dan', 'dana', 'dana'],
MM: ['mesec', 'meseca', 'meseci'],
yy: ['godina', 'godine', 'godina']
},
correctGrammaticalCase: function (number, wordKey) {
return number === 1 ? wordKey[0] : (number >= 2 && number <= 4 ? wordKey[1] : wordKey[2]);
},
translate: function (number, withoutSuffix, key) {
var wordKey = translator.words[key];
if (key.length === 1) {
return withoutSuffix ? wordKey[0] : wordKey[1];
} else {
return number + ' ' + translator.correctGrammaticalCase(number, wordKey);
}
};
}
};
var sr_cyrl = moment.defineLocale('sr-cyrl', {
months: ['јануар', 'фебруар', 'март', 'април', 'мај', 'јун', 'јул', 'август', 'септембар', 'октобар', 'новембар', 'децембар'],
monthsShort: ['јан.', 'феб.', 'мар.', 'апр.', 'мај', 'јун', 'јул', 'авг.', 'сеп.', 'окт.', 'нов.', 'дец.'],
weekdays: ['недеља', 'понедељак', 'уторак', 'среда', 'четвртак', 'петак', 'субота'],
weekdaysShort: ['нед.', 'пон.', 'уто.', 'сре.', 'чет.', 'пет.', 'суб.'],
weekdaysMin: ['не', 'по', 'ут', 'ср', 'че', 'пе', 'су'],
longDateFormat: {
LT: 'H:mm',
LTS : 'H:mm:ss',
L: 'DD. MM. YYYY',
LL: 'D. MMMM YYYY',
LLL: 'D. MMMM YYYY H:mm',
LLLL: 'dddd, D. MMMM YYYY H:mm'
},
calendar: {
sameDay: '[данас у] LT',
nextDay: '[сутра у] LT',
nextWeek: function () {
switch (this.day()) {
var sr = moment.defineLocale('sr', {
months: 'januar_februar_mart_april_maj_jun_jul_avgust_septembar_oktobar_novembar_decembar'.split('_'),
monthsShort: 'jan._feb._mar._apr._maj_jun_jul_avg._sep._okt._nov._dec.'.split('_'),
monthsParseExact: true,
weekdays: 'nedelja_ponedeljak_utorak_sreda_četvrtak_petak_subota'.split('_'),
weekdaysShort: 'ned._pon._uto._sre._čet._pet._sub.'.split('_'),
weekdaysMin: 'ne_po_ut_sr_če_pe_su'.split('_'),
weekdaysParseExact : true,
longDateFormat: {
LT: 'H:mm',
LTS : 'H:mm:ss',
L: 'DD.MM.YYYY',
LL: 'D. MMMM YYYY',
LLL: 'D. MMMM YYYY H:mm',
LLLL: 'dddd, D. MMMM YYYY H:mm'
},
calendar: {
sameDay: '[danas u] LT',
nextDay: '[sutra u] LT',
nextWeek: function () {
switch (this.day()) {
case 0:
return '[у] [недељу] [у] LT';
return '[u] [nedelju] [u] LT';
case 3:
return '[у] [среду] [у] LT';
return '[u] [sredu] [u] LT';
case 6:
return '[у] [суботу] [у] LT';
return '[u] [subotu] [u] LT';
case 1:
case 2:
case 4:
case 5:
return '[у] dddd [у] LT';
}
},
lastDay : '[јуче у] LT',
lastWeek : function () {
var lastWeekDays = [
'[прошле] [недеље] [у] LT',
'[прошлог] [понедељка] [у] LT',
'[прошлог] [уторка] [у] LT',
'[прошле] [среде] [у] LT',
'[прошлог] [четвртка] [у] LT',
'[прошлог] [петка] [у] LT',
'[прошле] [суботе] [у] LT'
];
return lastWeekDays[this.day()];
},
sameElse : 'L'
return '[u] dddd [u] LT';
}
},
relativeTime : {
future : 'за %s',
past : 'пре %s',
s : 'неколико секунди',
m : translator.translate,
mm : translator.translate,
h : translator.translate,
hh : translator.translate,
d : 'дан',
dd : translator.translate,
M : 'месец',
MM : translator.translate,
y : 'годину',
yy : translator.translate
lastDay : '[juče u] LT',
lastWeek : function () {
var lastWeekDays = [
'[prošle] [nedelje] [u] LT',
'[prošlog] [ponedeljka] [u] LT',
'[prošlog] [utorka] [u] LT',
'[prošle] [srede] [u] LT',
'[prošlog] [četvrtka] [u] LT',
'[prošlog] [petka] [u] LT',
'[prošle] [subote] [u] LT'
];
return lastWeekDays[this.day()];
},
ordinalParse: /\d{1,2}\./,
ordinal : '%d.',
week : {
dow : 1, // Monday is the first day of the week.
doy : 7 // The week that contains Jan 1st is the first week of the year.
}
});
sameElse : 'L'
},
relativeTime : {
future : 'za %s',
past : 'pre %s',
s : 'nekoliko sekundi',
m : translator.translate,
mm : translator.translate,
h : translator.translate,
hh : translator.translate,
d : 'dan',
dd : translator.translate,
M : 'mesec',
MM : translator.translate,
y : 'godinu',
yy : translator.translate
},
dayOfMonthOrdinalParse: /\d{1,2}\./,
ordinal : '%d.',
week : {
dow : 1, // Monday is the first day of the week.
doy : 7 // The week that contains Jan 1st is the first week of the year.
}
});
return sr_cyrl;
return sr;
}));
})));

View File

@@ -1,66 +1,69 @@
//! moment.js locale configuration
//! locale : swedish (sv)
//! locale : Swedish [sv]
//! author : Jens Alm : https://github.com/ulmus
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(require('../moment')) :
typeof define === 'function' && define.amd ? define(['moment'], factory) :
;(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined'
&& typeof require === 'function' ? factory(require('../moment')) :
typeof define === 'function' && define.amd ? define(['../moment'], factory) :
factory(global.moment)
}(this, function (moment) { 'use strict';
}(this, (function (moment) { 'use strict';
var sv = moment.defineLocale('sv', {
months : 'januari_februari_mars_april_maj_juni_juli_augusti_september_oktober_november_december'.split('_'),
monthsShort : 'jan_feb_mar_apr_maj_jun_jul_aug_sep_okt_nov_dec'.split('_'),
weekdays : 'söndag_måndag_tisdag_onsdag_torsdag_fredag_lördag'.split('_'),
weekdaysShort : 'sön_mån_tis_ons_tor_fre_lör'.split('_'),
weekdaysMin : 'sö_må_ti_on_to_fr_lö'.split('_'),
longDateFormat : {
LT : 'HH:mm',
LTS : 'HH:mm:ss',
L : 'YYYY-MM-DD',
LL : 'D MMMM YYYY',
LLL : 'D MMMM YYYY HH:mm',
LLLL : 'dddd D MMMM YYYY HH:mm'
},
calendar : {
sameDay: '[Idag] LT',
nextDay: '[Imorgon] LT',
lastDay: '[Igår] LT',
nextWeek: '[På] dddd LT',
lastWeek: '[I] dddd[s] LT',
sameElse: 'L'
},
relativeTime : {
future : 'om %s',
past : 'för %s sedan',
s : 'några sekunder',
m : 'en minut',
mm : '%d minuter',
h : 'en timme',
hh : '%d timmar',
d : 'en dag',
dd : '%d dagar',
M : 'en månad',
MM : '%d månader',
y : 'ett år',
yy : '%d år'
},
ordinalParse: /\d{1,2}(e|a)/,
ordinal : function (number) {
var b = number % 10,
output = (~~(number % 100 / 10) === 1) ? 'e' :
(b === 1) ? 'a' :
(b === 2) ? 'a' :
(b === 3) ? 'e' : 'e';
return number + output;
},
week : {
dow : 1, // Monday is the first day of the week.
doy : 4 // The week that contains Jan 4th is the first week of the year.
}
});
var sv = moment.defineLocale('sv', {
months : 'januari_februari_mars_april_maj_juni_juli_augusti_september_oktober_november_december'.split('_'),
monthsShort : 'jan_feb_mar_apr_maj_jun_jul_aug_sep_okt_nov_dec'.split('_'),
weekdays : 'söndag_måndag_tisdag_onsdag_torsdag_fredag_lördag'.split('_'),
weekdaysShort : 'sön_mån_tis_ons_tor_fre_lör'.split('_'),
weekdaysMin : 'sö_må_ti_on_to_fr_lö'.split('_'),
longDateFormat : {
LT : 'HH:mm',
LTS : 'HH:mm:ss',
L : 'YYYY-MM-DD',
LL : 'D MMMM YYYY',
LLL : 'D MMMM YYYY [kl.] HH:mm',
LLLL : 'dddd D MMMM YYYY [kl.] HH:mm',
lll : 'D MMM YYYY HH:mm',
llll : 'ddd D MMM YYYY HH:mm'
},
calendar : {
sameDay: '[Idag] LT',
nextDay: '[Imorgon] LT',
lastDay: '[Igår] LT',
nextWeek: '[På] dddd LT',
lastWeek: '[I] dddd[s] LT',
sameElse: 'L'
},
relativeTime : {
future : 'om %s',
past : 'för %s sedan',
s : 'några sekunder',
m : 'en minut',
mm : '%d minuter',
h : 'en timme',
hh : '%d timmar',
d : 'en dag',
dd : '%d dagar',
M : 'en månad',
MM : '%d månader',
y : 'ett år',
yy : '%d år'
},
dayOfMonthOrdinalParse: /\d{1,2}(e|a)/,
ordinal : function (number) {
var b = number % 10,
output = (~~(number % 100 / 10) === 1) ? 'e' :
(b === 1) ? 'a' :
(b === 2) ? 'a' :
(b === 3) ? 'e' : 'e';
return number + output;
},
week : {
dow : 1, // Monday is the first day of the week.
doy : 4 // The week that contains Jan 4th is the first week of the year.
}
});
return sv;
return sv;
}));
})));

View File

@@ -1,91 +1,76 @@
//! moment.js locale configuration
//! locale : chinese (zh-cn)
//! locale : Chinese (China) [zh-cn]
//! author : suupic : https://github.com/suupic
//! author : Zeno Zeng : https://github.com/zenozeng
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(require('../moment')) :
typeof define === 'function' && define.amd ? define(['moment'], factory) :
;(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined'
&& typeof require === 'function' ? factory(require('../moment')) :
typeof define === 'function' && define.amd ? define(['../moment'], factory) :
factory(global.moment)
}(this, function (moment) { 'use strict';
}(this, (function (moment) { 'use strict';
var zh_cn = moment.defineLocale('zh-cn', {
months : '一月_二月_三月_四月_五月_六月_七月_八月_九月_十月_十一月_十二月'.split('_'),
monthsShort : '1月_2月_3月_4月_5月_6月_7月_8月_9月_10月_11月_12月'.split('_'),
weekdays : '星期日_星期一_星期二_星期三_星期四_星期五_星期六'.split('_'),
weekdaysShort : '周日_周一_周二_周三_周四_周五_周六'.split('_'),
weekdaysMin : '日_一_二_三_四_五_六'.split('_'),
longDateFormat : {
LT : 'Ah点mm',
LTS : 'Ah点m分s秒',
L : 'YYYY-MM-DD',
LL : 'YYYY年MMMD日',
LLL : 'YYYY年MMMD日Ah点mm分',
LLLL : 'YYYY年MMMD日ddddAh点mm分',
l : 'YYYY-MM-DD',
ll : 'YYYY年MMMD日',
lll : 'YYYY年MMMD日Ah点mm',
llll : 'YYYY年MMMD日ddddAh点mm'
},
meridiemParse: /凌晨|早上|上午|中午|下午|晚上/,
meridiemHour: function (hour, meridiem) {
if (hour === 12) {
hour = 0;
}
if (meridiem === '凌晨' || meridiem === '早上' ||
meridiem === '上午') {
return hour;
} else if (meridiem === '下午' || meridiem === '晚上') {
return hour + 12;
} else {
// '中午'
return hour >= 11 ? hour : hour + 12;
}
},
meridiem : function (hour, minute, isLower) {
var hm = hour * 100 + minute;
if (hm < 600) {
return '凌晨';
} else if (hm < 900) {
return '早上';
} else if (hm < 1130) {
return '上午';
} else if (hm < 1230) {
return '中午';
} else if (hm < 1800) {
return '下午';
} else {
return '晚上';
}
},
calendar : {
sameDay : function () {
return this.minutes() === 0 ? '[今天]Ah[点整]' : '[天]LT';
},
nextDay : function () {
return this.minutes() === 0 ? '[明天]Ah[点整]' : '[明天]LT';
},
lastDay : function () {
return this.minutes() === 0 ? '[昨天]Ah[点整]' : '[昨天]LT';
},
nextWeek : function () {
var startOfWeek, prefix;
startOfWeek = moment().startOf('week');
prefix = this.unix() - startOfWeek.unix() >= 7 * 24 * 3600 ? '[下]' : '[本]';
return this.minutes() === 0 ? prefix + 'dddAh点整' : prefix + 'dddAh点mm';
},
lastWeek : function () {
var startOfWeek, prefix;
startOfWeek = moment().startOf('week');
prefix = this.unix() < startOfWeek.unix() ? '[上]' : '[本]';
return this.minutes() === 0 ? prefix + 'dddAh点整' : prefix + 'dddAh点mm';
},
sameElse : 'LL'
},
ordinalParse: /\d{1,2}(日|月|周)/,
ordinal : function (number, period) {
switch (period) {
var zhCn = moment.defineLocale('zh-cn', {
months : '一月_二月_三月_四月_五月_六月_七月_八月_九月_十月_十一月_十二月'.split('_'),
monthsShort : '1月_2月_3月_4月_5月_6月_7月_8月_9月_10月_11月_12月'.split('_'),
weekdays : '星期日_星期一_星期二_星期三_星期四_星期五_星期六'.split('_'),
weekdaysShort : '周日_周一_周二_周三_周四_周五_周六'.split('_'),
weekdaysMin : '日_一_二_三_四_五_六'.split('_'),
longDateFormat : {
LT : 'HH:mm',
LTS : 'HH:mm:ss',
L : 'YYYYMMMD日',
LL : 'YYYY年MMMD日',
LLL : 'YYYY年MMMD日Ah点mm分',
LLLL : 'YYYY年MMMD日ddddAh点mm分',
l : 'YYYYMMMD日',
ll : 'YYYY年MMMD日',
lll : 'YYYY年MMMD日 HH:mm',
llll : 'YYYY年MMMD日dddd HH:mm'
},
meridiemParse: /凌晨|早上|上午|中午|下午|晚上/,
meridiemHour: function (hour, meridiem) {
if (hour === 12) {
hour = 0;
}
if (meridiem === '凌晨' || meridiem === '早上' ||
meridiem === '上午') {
return hour;
} else if (meridiem === '下午' || meridiem === '晚上') {
return hour + 12;
} else {
// '中午'
return hour >= 11 ? hour : hour + 12;
}
},
meridiem : function (hour, minute, isLower) {
var hm = hour * 100 + minute;
if (hm < 600) {
return '凌晨';
} else if (hm < 900) {
return '早上';
} else if (hm < 1130) {
return '上午';
} else if (hm < 1230) {
return '中午';
} else if (hm < 1800) {
return '下午';
} else {
return '晚上';
}
},
calendar : {
sameDay : '[今天]LT',
nextDay : '[天]LT',
nextWeek : '[下]ddddLT',
lastDay : '[昨天]LT',
lastWeek : '[上]ddddLT',
sameElse : 'L'
},
dayOfMonthOrdinalParse: /\d{1,2}(日|月|周)/,
ordinal : function (number, period) {
switch (period) {
case 'd':
case 'D':
case 'DDD':
@@ -97,30 +82,30 @@
return number + '周';
default:
return number;
}
},
relativeTime : {
future : '%s内',
past : '%s前',
s : '几秒',
m : '1 分钟',
mm : '%d 分钟',
h : '1 小时',
hh : '%d 小时',
d : '1 天',
dd : '%d 天',
M : '1 个月',
MM : '%d 个月',
y : '1 年',
yy : '%d 年'
},
week : {
// GB/T 7408-1994《数据元和交换格式·信息交换·日期和时间表示法》与ISO 8601:1988等效
dow : 1, // Monday is the first day of the week.
doy : 4 // The week that contains Jan 4th is the first week of the year.
}
});
},
relativeTime : {
future : '%s内',
past : '%s前',
s : '几秒',
m : '1 分钟',
mm : '%d 分钟',
h : '1 小时',
hh : '%d 小时',
d : '1 天',
dd : '%d 天',
M : '1 个月',
MM : '%d 个月',
y : '1 年',
yy : '%d 年'
},
week : {
// GB/T 7408-1994《数据元和交换格式·信息交换·日期和时间表示法》与ISO 8601:1988等效
dow : 1, // Monday is the first day of the week.
doy : 4 // The week that contains Jan 4th is the first week of the year.
}
});
return zh_cn;
return zhCn;
}));
})));

View File

@@ -50,7 +50,8 @@ legend,
color: white !important;
}
.hover-button {
.hover-button,
.fileControls a:hover {
opacity: 0.7;
}
@@ -81,7 +82,8 @@ legend,
.max-speed-input-clear,
.max-speed-input-clear:hover,
.nav-tabs>li>a:hover {
.nav-tabs>li>a:hover,
.fileControls a {
color: black;
}
@@ -109,6 +111,7 @@ legend,
color: #EBEBEB;
}
table,
.table-striped>tbody>tr:nth-child(even)>td,
.table>tbody>tr:nth-child(even)>td,
.table th,
@@ -156,7 +159,9 @@ select.form-control,
.retry-button, .retry-button-inactive,
.history-options-show-failed,
.queue-error-info,
.options-bad-status {
.options-bad-status,
.history-failed-download:hover .retry-button .glyphicon:before,
.retry-button:hover .glyphicon:before {
color: #F95151 !important;
}
@@ -175,10 +180,11 @@ tbody .caret {
color: #D6D6D6;
}
td.name .name-ratings span,
td.name .name-icons span,
.navbar-nav .open .dropdown-menu>li>a,
.dropdown-header,
#modal-help small,
.hover-button.glyphicon-forward,
pre {
color: #EBEBEB !important;
opacity: 1 !important;
@@ -203,6 +209,10 @@ hr {
border-bottom: none !important;
}
#modal-item-files .item-files-table {
border-bottom: 1px solid black;
}
.history-queue-swicher .nav-tabs>li>a,
.history-queue-swicher .nav-tabs>li.active>a {
border-bottom: none;
@@ -220,6 +230,11 @@ hr {
box-shadow: inset 1px 0px 1px rgba(0, 0, 0, 1);
}
.modal-content {
border-color: #727272;
border-top: none;
}
/* Placeholders - Will not work if grouped! */
::-webkit-input-placeholder {
color: #EBEBEB !important;

View File

@@ -52,8 +52,8 @@ h2 {
box-shadow: 0 2px 2px rgba(0, 0, 0, 0.1);
background-color: #ffffff;
border: 1px solid #cccccc;
margin-left: -150px;
margin-top: 4px;
margin-left: -220px;
margin-top: 5px;
}
.navbar-collapse.in .dropdown-menu a,
@@ -105,10 +105,7 @@ h2 {
.navbar-logo {
vertical-align: middle;
display: inline-block;
margin-right: 12px;
margin-left: 15px;
margin-top: 4px;
margin-bottom: -1px;
margin: 4px 12px -1px 15px;
}
.navbar-logo svg {
@@ -288,8 +285,7 @@ li.dropdown {
opacity: 0.9;
color: black;
z-index: 2000;
padding: 1em;
padding-top: 15%;
padding: 15% 1em 1em;
}
.main-filedrop.in span {
@@ -360,7 +356,7 @@ li.dropdown {
#feedback-slider:hover {
left: 0px;
height: 340px;
height: 200px;
opacity: 1;
}
@@ -404,7 +400,6 @@ thead {
tr th {
border-bottom: none !important;
visibility: hidden;
padding: 0px !important;
height: 0px;
}
@@ -527,15 +522,23 @@ tbody>tr>td:last-child {
}
.hover-button.glyphicon-play,
.hover-button.glyphicon-forward,
.hover-button.glyphicon-stop {
opacity: 1;
color: #474747;
}
.hover-button.disabled,
.hover-button.disabled:hover {
.hover-button.disabled:hover,
.name-options.disabled .hover-button,
.name-options.disabled .hover-button:hover {
cursor: not-allowed !important;
opacity: 0.1 !important;
pointer-events: none;
}
.name-options.disabled {
cursor: not-allowed !important;
}
.info-container {
@@ -617,6 +620,7 @@ td.name .row-wrap-text {
}
.queue-table td.name .name-options small,
.queue-table td.name .direct-unpack,
.queue-item-password {
opacity: 0.5;
}
@@ -625,9 +629,17 @@ td.name .row-wrap-text {
margin-left: 3px;
}
.queue-table td.name .direct-unpack-text {
max-width: calc(100% - 75px);
}
.queue-table td.name:hover .row-wrap-text {
max-width: calc(100% - 85px);
/* Change for each size! */
max-width: calc(100% - 125px);
/* Change for each size! */
}
.queue-table td.name:hover .direct-unpack {
display: none;
}
.queue-table td.name:hover .name-options {
@@ -648,19 +660,23 @@ td.name .row-wrap-text {
border: 1px solid #ccc;
}
td.name .name-ratings {
td.name .name-icons {
display: inline;
margin-left: 5px;
color: black !important;
text-decoration: none !important;
}
.queue-table td.name:hover .name-ratings {
display: none;
td.name .name-icons .glyphicon {
margin-left: 2px;
top: 2px;
font-weight: bold;
}
td.name .name-ratings .glyphicon {
margin-left: 2px;
.glyphicon-chevron-down,
.glyphicon-chevron-up {
top: 2px;
font-weight: bold;
}
tbody.no-downloads tr td {
@@ -701,8 +717,7 @@ td.delete .dropdown>a {
td.delete input[type="checkbox"],
.add-nzb-inputbox-options input[type="checkbox"]{
margin: 0;
margin-bottom: -2px;
margin: 0 0 -2px;
display: block;
}
@@ -752,8 +767,8 @@ tr.queue-item>td:first-child>a {
line-height: 24px;
overflow: visible;
margin-bottom: 0;
box-shadow: inset 0 0px 1px rgba(0, 0, 0, 0.3);
-webkit-box-shadow: inset 0 0px 1px rgba(0, 0, 0, 0.3);
box-shadow: inset 0 0px 2px rgba(0, 0, 0, 0.2);
-webkit-box-shadow: inset 0 0px 2px rgba(0, 0, 0, 0.2);
}
.progress>span,
@@ -769,6 +784,35 @@ tr.queue-item>td:first-child>a {
padding-right: 10px;
}
.item-files-table tr .fileControls{
float:right;
display:none;
}
.item-files-table tr.files-sortable:hover .fileControls{
float:right;
display:block;
margin-left:5px;
}
.progress .progress-bar .fileDetails {
display:inline;
text-align: left;
margin-left: 70px;
line-height: 25px;
position: absolute;
top: 0;
left: 0;
z-index: 2;
font-size: 12px;
color: #404040;
padding-right: 0px;
}
.progress .progress-bar .fileDetails>span {
float: left;
}
.progress strong {
font-size: 13px;
}
@@ -1035,7 +1079,7 @@ tr.queue-item>td:first-child>a {
opacity: 1;
}
.history-ratings .name-ratings {
.history-ratings .name-icons {
float: none !important;
}
@@ -1106,8 +1150,7 @@ tr.queue-item>td:first-child>a {
#history-options {
margin-top: 0;
margin-left: 10px;
padding: 0;
padding-left: 4px;
padding: 0 0 0 4px;
}
#history-options .hover-button {
@@ -1487,8 +1530,7 @@ tr.queue-item>td:first-child>a {
.add-nzb-inputbox span {
display: inline-block;
margin: 8px 2px 0px 5px;
margin-left: -20px;
margin: 8px 2px 0px -20px;
}
.btn-file {
@@ -1581,11 +1623,9 @@ input[name="nzbURL"] {
#modal-item-files .multioperations-selector {
clear: left;
margin: 0;
float: left;
padding: 5px 8px;
margin-bottom: 5px;
margin-right: 10px;
margin: 0 10px 5px 0;
border: 1px solid #cccccc;
}
@@ -1623,6 +1663,11 @@ input[name="nzbURL"] {
#modal-item-files .item-files-table .progress small {
color: #727272 !important;
margin-left: 5px;
}
#modal-item-files .item-files-table tr.files-sortable:hover .progress small {
display:none;
}
#modal-item-files .item-files-table td {
@@ -1810,10 +1855,9 @@ input[name="nzbURL"] {
}
@media screen and (max-width: 1200px) {
td.name .name-ratings {
td.name .name-icons {
margin-left: 0px;
margin-right: -5px;
display: block;
}
td.name .row-wrap-text {
@@ -1841,7 +1885,6 @@ input[name="nzbURL"] {
.history-queue-swicher .nav-tabs>li>a {
border-bottom: inherit;
}
}
@media screen and (min-width: 768px) {
@@ -1853,10 +1896,14 @@ input[name="nzbURL"] {
min-width: 715px;
}
.queue .sortable-placeholder td {
padding: 9px 0px 8px !important;
}
.queue-table .buttonMoveToBottom,
.queue-table .buttonMoveToTop {
display: inline;
}
}
@media screen and (min-height: 800px) {
@@ -1921,49 +1968,6 @@ input[name="nzbURL"] {
}
}
/***
SPECIAL FOR FIREFOX
It uses very high CPU for anything animated (Sep 2015)
Disable animations on progress-bar and make the History-'processing' a block animation
Can be removed if it's performance gets better in the future..
***/
@supports (-moz-transform: translate(0, 0)) {
.progress-bar {
transform: none !important;
animation: none !important;
transition: none !important;
}
@keyframes stretchdelay {
0%, 60% {
transform: scaleY(0.4);
}
61%, 100% {
transform: scaleY(1.0);
}
}
.processing-download > div {
animation: stretchdelay 2s infinite linear;
}
.processing-download .loader-bar-two {
animation-delay: 0.2s;
}
.processing-download .loader-bar-three {
animation-delay: 0.4s;
}
.processing-download .loader-bar-four {
animation-delay: 0.6s;
}
.queue-table td.name input {
margin-left: -2px;
}
}
/***
Bootstrap overwrites
@@ -2032,9 +2036,8 @@ a:focus {
right: 17px;
display: inline-block;
border-right: 6px solid transparent;
border-bottom: 6px solid #ccc;
border-bottom: 6px solid rgba(0, 0, 0, 0.2);
border-left: 6px solid transparent;
border-bottom-color: rgba(0, 0, 0, 0.2);
content: '';
}

View File

@@ -132,12 +132,21 @@ h2 {
max-width: calc(100% - 45px);
}
.queue-table .buttonMoveToBottom,
.queue-table .buttonMoveToTop {
display: none;
}
tr.queue-item>td:first-child>a {
margin-top: 3px;
}
.queue-table td.name .direct-unpack {
display: none;
}
.queue-table td.name .name-options {
display: block;
display: inline-block;
}
.queue-table td.name .name-options small {

View File

@@ -1,7 +1,7 @@
Plush for SABnzbd 0.6.x | Feb. 21 2010
assembled by pairofdimes - see LICENSE-CC.txt
http://forums.sabnzbd.org contributions welcome
https://forums.sabnzbd.org contributions welcome
======================
THANKS TO CONTRIBUTORS

View File

@@ -26,9 +26,10 @@
<link rel="apple-touch-icon" sizes="76x76" href="${path}staticcfg/ico/apple-touch-icon-76x76-precomposed.png" />
<link rel="apple-touch-icon" sizes="120x120" href="${path}staticcfg/ico/apple-touch-icon-120x120-precomposed.png" />
<link rel="apple-touch-icon" sizes="152x152" href="${path}staticcfg/ico/apple-touch-icon-152x152-precomposed.png" />
<link rel="apple-touch-icon" sizes="180x180" href="${path}staticcfg/ico/apple-touch-icon-180x180-precomposed.png" />
<link rel="apple-touch-icon" sizes="180x180" href="${path}staticcfg/ico/apple-touch-icon-180x180-precomposed.png" />
<link rel="apple-touch-icon" sizes="192x192" href="${path}staticcfg/ico/android-192x192.png" />
<link rel="mask-icon" href="${path}staticcfg/ico/safari-pinned-tab.svg" color="#383F45">
<script type="text/javascript" src="${path}static/javascripts/lib.js?$version"></script>
#if $pane=="Main"#
@@ -99,10 +100,9 @@
#if $loadavg#$T('ft-sysload'):&nbsp;<span id="loadavg">$loadavg</span>#end if#
</div>
<div id="nav_text_left">
<span id="warning_box"><b><a href="${path}status/#tabs-warnings" id="last_warning" title="#echo $last_warning.replace("\n"," ").replace('"',"'") #"><span id="have_warnings">$have_warnings</span> $T('warnings')</a></b></span>
<span id="warning_box"><b><a href="${path}status/#tabs-warnings" id="last_warning"><span id="have_warnings">$have_warnings</span> $T('warnings')</a></b></span>
#if $pane=="Main"#
#if $new_release#&sdot; <a href="$new_rel_url" id="new_release" target="_blank">$T('Plush-updateAvailable').replace(' ','&nbsp;')</a>#end if#
#if $warning#&sdot; <a id="warning_message">$warning.replace(' ','&nbsp;')</a>#end if#
#end if#
</div>
</div>

View File

@@ -23,8 +23,8 @@
<div id="help_modal">
<table>
<tr><td><strong>$T('menu-wiki'):</strong></td><td><a href="$helpuri$help_uri" target="_blank">$helpuri$help_uri</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>$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>
<tr><td><strong>$T('menu-forums'):</strong></td><td><a href="https://forums.sabnzbd.org/" target="_blank">https://forums.sabnzbd.org/</a></td></tr>
<tr><td><strong>$T('menu-irc'):</strong></td><td><a href="https://sabnzbd.org/live-chat.html" target="_blank">https://sabnzbd.org/live-chat.html</a></td></tr>
</table>
<div class="sabnzbd_logo main_sprite_container sprite_sabnzbdplus_logo"></div>
<p><strong>SABnzbd $T('version'):</strong> $version</p>
@@ -84,10 +84,10 @@ $T('Plush-containerWidth'):
<div id="add_nzb_hr"><hr></div>
<i>$T('pp'):</i>
<table id="add_nzb_pp">
#if $cat_list#
#if $categories#
<tr><td style="text-align:right">$T('category'):</td>
<td><select id="addID_cat" name="cat">
#for $ct in $cat_list#
#for $ct in $categories#
<option value="$ct">$Tspec($ct)</option>
#end for#
</select>
@@ -111,10 +111,10 @@ $T('Plush-containerWidth'):
<option value="3">$T('pp-delete')</option>
</select>
</td></tr>
#if $script_list#
#if $scripts#
<tr><td style="text-align:right">$T('script'):</td>
<td><select id="addID_script" name="script">
#for $sc in $script_list#
#for $sc in $scripts#
<option value="$sc">$Tspec($sc)</option>
#end for#
</select>
@@ -186,7 +186,7 @@ $T('Plush-containerWidth'):
<option value='www.newshosting.com'>NewsHosting</option>
<option value='www.readnews.com'>Readnews</option>
<option value='www.supernews.com'>SuperNews</option>
<option value='www.thundernews.com'>ThunderNews</option>
<option value='www.thundernews.com'>ThunderNews</option>
<option value='www.tweaknews.eu'>Tweaknews</option>
<option value='www.usenetserver.com'>UsenetServer</option>
<option value='www.xentech.net'>XenTech</option>

View File

@@ -46,9 +46,9 @@
<option value="hibernate_pc" <!--#if $finishaction == 'hibernate_pc' then 'selected' else ''#-->>$T('hibernatePc')</option>
<!--#end if#-->
</optgroup>
<!--#if $script_list#-->
<!--#if $scripts#-->
<optgroup label="$T('eoq-scripts')">
<!--#for $sc in $script_list#-->
<!--#for $sc in $scripts#-->
<!--#if $sc != 'None'#-->
<option value="script_$sc" <!--#if $finishaction == 'script_'+$sc then 'selected' else ''#-->>$sc</option>
<!--#end if#-->
@@ -85,7 +85,7 @@
<ul>
<li>
$T('Plush-maxSpeed'):&nbsp;&nbsp;
<input type="text" id="maxSpeed-option" size="4" />
<input type="text" id="maxSpeed-option" size="4" />
<select id="maxSpeed-label">
<option value="%">%</option>
<option value="K">KB/s</option>
@@ -136,10 +136,10 @@
<option value="pause">$T('sch-pause')</option>
<option value="resume">$T('sch-resume')</option>
</optgroup></select>
<!--#if $cat_list#-->
<!--#if $categories#-->
<select id="multi_cat"><optgroup label="$T('category')">
<option value="">$T('category')</option>
<!--#for $ct in $cat_list#-->
<!--#for $ct in $categories#-->
<option value="$ct">$Tspec($ct)</option>
<!--#end for#-->
</optgroup></select>
@@ -158,10 +158,10 @@
<option value="2">$T('pp-unpack')</option>
<option value="3">$T('pp-delete')</option>
</optgroup></select>
<!--#if $script_list#-->
<!--#if $scripts#-->
<select id="multi_script"><optgroup label="$T('script')">
<option value="">$T('script')</option>
<!--#for $sc in $script_list#-->
<!--#for $sc in $scripts#-->
<!--#if $sc != "Default"#--><option value="$sc">$Tspec($sc)</option><!--#end if#-->
<!--#end for#-->
</optgroup></select>

View File

@@ -15,9 +15,9 @@
<option value=$i <!--#if $i == $index then "selected" else ""#-->>$i</option>
<!--#end for#-->
</select>
<!--#if $cat_list#-->
<!--#if $categories#-->
<select name="cat"><optgroup label="$T('category')">
<!--#for $ct in $cat_list#-->
<!--#for $ct in $categories#-->
<option value="$ct" <!--#if $slot.cat.lower() == $ct.lower() then "selected" else ""#-->>$Tspec($ct)</option>
<!--#end for#-->
</optgroup></select>
@@ -35,9 +35,9 @@
<option value="2" <!--#if $slot.unpackopts == "2" then "selected" else ""#-->>$T('pp-unpack')</option>
<option value="3" <!--#if $slot.unpackopts == "3" then "selected" else ""#-->>$T('pp-delete')</option>
</optgroup></select>
<!--#if $script_list#-->
<!--#if $scripts#-->
<select name="script"><optgroup label="$T('script')">
<!--#for $sc in $script_list#-->
<!--#for $sc in $scripts#-->
<option value="$sc" <!--#if $slot.script.lower() == $sc.lower() then "selected" else ""#-->>$Tspec($sc)</option>
<!--#end for#-->
</optgroup></select>

View File

@@ -6,7 +6,7 @@
$.plush.queuenoofslots = $noofslots;
$.plush.SetQueueSpeedLimit('$speedlimit', '$speedlimit_abs');
$.plush.SetQueueFinishAction('$finishaction');
$.plush.SetWarnings($have_warnings,"<!--#echo $last_warning.replace("\n"," ").replace('"',"'").replace('\\','\\\\') #-->");
$.plush.SetWarnings($have_warnings,"");
$.plush.SetQueuePauseInfo(<!--#if $paused#-->true<!--#else#-->false<!--#end if#-->,'$pause_int');
$.plush.SetQueueETAStats("<!--#if float($kbpersec) > 1023 #-->$speed<!--#else#--><!--#echo "%.0f" % float($kbpersec)#--> K<!--#end if#-->",<!--#echo "%.0f" % float($kbpersec)#-->,'$timeleft','$T('eta'): $eta');
@@ -56,7 +56,7 @@
</td>
<td class="download-title" <!--#if $rating_enable#-->style="width:35%"<!--#end if#-->>
<a href="nzb/$slot.nzo_id/" title="$T('status'): $T('post-'+$slot.status)<br/>$T('nzo-age'): $slot.avg_age<br/><!--#if $slot.missing#-->$T('missingArt'): $slot.missing<!--#end if#-->">$slot.filename.replace('.', '.&#8203;').replace('_', '_&#8203;')<!--#if $slot.password#--> / $slot.password<!--#end if#--></a>
<a href="nzb/$slot.nzo_id/" title="$T('status'): $T('post-'+$slot.status)<br/>$T('nzo-age'): $slot.avg_age<br/><!--#if $slot.mbmissing!="0.00"#-->$T('missingArt'): $slot.mbmissing $T('MB')<!--#end if#-->">$slot.filename.replace('.', '.&#8203;').replace('_', '_&#8203;')<!--#if $slot.password#--> / $slot.password<!--#end if#--></a>
</td>
<!--#if $rating_enable#-->
@@ -75,7 +75,7 @@
<td></td>
<!--#end if#-->
<!--#end if#-->
<td>
<div class="main_sprite_container sprite_progressbar_bg">
<div class="main_sprite_container sprite_progress_done" style="background-position: -<!--#if $slot.mb == "0.00" then "120" else int(120 - 120.0 / 100.0 * int(100 - float($slot.mbleft) / float($slot.mb) * 100))#-->px -401px">
@@ -94,9 +94,9 @@
</td>
<td class="options nowrap">
<!--#if $cat_list#-->
<!--#if $categories#-->
<select class="change_cat"><optgroup label="$T('category')">
<!--#for $ct in $cat_list#-->
<!--#for $ct in $categories#-->
<option value="$ct" <!--#if $slot.cat.lower() == $ct.lower() then "selected" else ""#-->>$Tspec($ct)</option>
<!--#end for#-->
</optgroup></select>
@@ -114,9 +114,9 @@
<option value="2" <!--#if $slot.unpackopts == "2" then "selected " else ""#-->>$T('pp-unpack')</option>
<option value="3" <!--#if $slot.unpackopts == "3" then "selected " else ""#-->>$T('pp-delete')</option>
</optgroup></select>
<!--#if $script_list#-->
<!--#if $scripts#-->
<select class="change_script"><optgroup label="$T('script')">
<!--#for $sc in $script_list#-->
<!--#for $sc in $scripts#-->
<option value="$sc" <!--#if $slot.script == $sc then "selected" else ""#-->>$Tspec($sc)</option>
<!--#end for#-->
</optgroup></select>

View File

@@ -146,7 +146,7 @@ jQuery(function($){
$("#plush_options").colorbox({ inline:true, href:"#plush_options_modal", title:$("#plush_options").text(),
innerWidth:"375px", innerHeight:"350px", initialWidth:"375px", initialHeight:"350px", speed:0, opacity:0.7
});
// Save the type of speedlimit display
$('#maxSpeed-label').change(function() {
$.plush.speedLimitType = $(this).val();
@@ -159,19 +159,19 @@ jQuery(function($){
$('#maxSpeed-label').val($.plush.speedLimitType)
// Max Speed main menu input -- don't change value on refresh when focused
$("#maxSpeed-option").focus(function(){
$.plush.focusedOnSpeedChanger = true;
}).blur(function(){
$.plush.focusedOnSpeedChanger = false;
$("#maxSpeed-option").focus(function(){
$.plush.focusedOnSpeedChanger = true;
}).blur(function(){
$.plush.focusedOnSpeedChanger = false;
}).keyup(function (e) {
// Catch the enter
if (e.keyCode == 13) {
$("#maxSpeed-enable").click()
}
})
// Submit the new speedlimit
$("#maxSpeed-enable, #maxSpeed-disable").click( function(e) {
$("#maxSpeed-enable, #maxSpeed-disable").click( function(e) {
// Remove
if ($(e.target).attr('id')=="maxSpeed-disable") {
$('#maxSpeed-option').val('');
@@ -496,7 +496,7 @@ jQuery(function($){
};
// static-element tooltips
$('body').delegate('#pausefor_title, #last_warning, #time-left, #multi_delete, #explain-blockRefresh, #pause_resume, #hist_purge, #queueTable td.download-title a, #queueTable td.eta span, #queueTable td.options .icon_nzb_remove, #historyTable td.options .icon_nzb_remove, #historyTable td div.icon_history_verbose', 'mouseover mouseout mousemove', function(event) {
$('body').delegate('#pausefor_title, #time-left, #multi_delete, #explain-blockRefresh, #pause_resume, #hist_purge, #queueTable td.download-title a, #queueTable td.eta span, #queueTable td.options .icon_nzb_remove, #historyTable td.options .icon_nzb_remove, #historyTable td div.icon_history_verbose', 'mouseover mouseout mousemove', function(event) {
var link = this,
$link = $(this);
@@ -1095,7 +1095,7 @@ $("a","#multiops_inputs").click(function(e){
title:function(){return $(this).text().replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;");},
innerWidth:"80%", innerHeight:"300px", initialWidth:"80%", initialHeight:"300px", speed:0, opacity:0.7 });
// modal for reporting issues
// modal for reporting issues
$("#historyTable .modal-report").colorbox({ inline:true,
href: function(){return "#report-"+$(this).parent().parent().parent().attr('id');},
title:function(){return $(this).text();},
@@ -1252,7 +1252,7 @@ $.plush.histprevslots = $.plush.histnoofslots; // for the next refresh
$('#queue-pagination span').removeClass('loading'); // Remove spinner graphic from pagination
$('#manual_refresh_wrapper').removeClass('refreshing'); // Refresh state notification
},
error: function(xhr){
error: function(xhr){
// Only reason for a 404 error could be a login failure -> redirect
if(xhr.status == 404) {
document.location=document.location;
@@ -1320,7 +1320,7 @@ $.plush.histprevslots = $.plush.histnoofslots; // for the next refresh
$('#history .avg_rate').rateit({readonly: true, resetable: false, step: 0.5});
$('#history .avg_rate').each(function() { $(this).rateit('value', $(this).attr('value') / 2); });
$('#history .user_combo option').filter(function() {
return $(this).attr('value') == $(this).parent().parent().find('input.user_combo').attr('value');
return $(this).attr('value') == $(this).parent().parent().find('input.user_combo').attr('value');
}).attr('selected', true);
$('#history-pagination span').removeClass('loading'); // Remove spinner graphic from pagination
}
@@ -1342,11 +1342,11 @@ $.plush.histprevslots = $.plush.histnoofslots; // for the next refresh
// For switching using the select
if(!speedLimit) speedLimit = $.plush.speedLimit;
if(speedLimitAbs == undefined) speedLimitAbs = $.plush.speedLimitAbs;
// Save
// Save
$.plush.speedLimit = speedLimit;
$.plush.speedLimitAbs = speedLimitAbs;
// How do we format?
switch($.plush.speedLimitType) {
case '%':
@@ -1360,11 +1360,11 @@ $.plush.histprevslots = $.plush.histnoofslots; // for the next refresh
speedlimitDisplay = speedLimitAbs/1024/1024;
break;
}
// In case nothing and we make the displaying of the float more pretty
speedlimitDisplay = (isNaN(speedlimitDisplay) || speedlimitDisplay == '0') ? '' : speedlimitDisplay;
speedlimitDisplay = Math.round(speedlimitDisplay*10)/10;
// Update
if ($("#maxSpeed-option").val() != speedlimitDisplay && !$.plush.focusedOnSpeedChanger)
$("#maxSpeed-option").val(speedlimitDisplay);

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 112 B

After

Width:  |  Height:  |  Size: 78 B

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 726 B

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 932 B

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 256 B

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 508 B

After

Width:  |  Height:  |  Size: 405 B

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 980 B

After

Width:  |  Height:  |  Size: 618 B

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 454 B

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 854 B

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