Compare commits

...
318 Commits
Author SHA1 Message Date
Safihre 675d7714ca Skip symlink tests if creation is not possible
On Windows, creating symbolic links requires either an elevated shell or Developer Mode. This introduces a runtime check to verify if symlinks can be created. Tests that rely on symlink creation are now skipped if this check fails, preventing unnecessary test failures in environments without the required permissions.
2026-09-08 14:56:29 +02:00
Safihre bcd1585ea1 Set timeout for translations job
Prevents the workflow from running indefinitely.
2026-09-08 13:24:58 +02:00
Safihre 9d365facaa Import same_directory for path validation 2026-09-08 12:17:17 +02:00
Safihre 948500799a Skip filesystem link behavior tests on Windows
These tests rely on a specific behavior of '..' in paths involving symbolic links, which differs on Windows where '..' is collapsed before the filesystem resolves links.
2026-09-08 12:16:47 +02:00
mnightingale d8815ef983 Treat a dangling link as a taken name and refuse it as a write target 2026-09-08 11:19:38 +02:00
mnightingale ff537363c6 Sanitize each part before checking it for traversal or the admin folder 2026-09-08 11:19:37 +02:00
mnightingale b58a25df07 Pin the tarfile behaviour that contains members against links in the destination 2026-09-08 11:19:36 +02:00
mnightingale 1ce8faafeb Contain renames using the resolved paths so links cannot redirect them 2026-09-08 11:19:35 +02:00
mnightingale 7dbecb0a2d Skip links and other non-regular members when extracting tar files 2026-09-08 11:19:34 +02:00
Safihre 8343f182aa Pass Discord webhook secrets to release workflow 2026-09-08 11:14:07 +02:00
renovate[bot] d3f691e695 Update all dependencies (#3617)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-09-07 04:49:02 +00:00
Safihre d25d857fc4 Remove ISSUES.txt and refer to wiki for known issues 2026-09-04 11:47:44 +02:00
Safihre 64f342ca1d Use dedicated token for tools update workflow
Replaces `github.token` with `secrets.AUTOMATION_GITHUB_TOKEN` to provide consistent and appropriate permissions for automated tasks like creating pull requests.
2026-09-04 11:43:30 +02:00
Safihre ab3ba4a53a Add Discord integration for release notes
Posts release notes automatically to designated Discord webhooks, supporting separate channels for pre-release and stable builds.
2026-09-04 11:37:54 +02:00
github-actions[bot]andSABnzbd Automation 169a42b08c 7-Zip 26.03 (#3615)
Co-authored-by: SABnzbd Automation <bugs@sabnzbd.org>
2026-09-04 11:35:45 +02:00
Safihre 6cbe811d10 Restore checkboxes color 2026-09-04 09:03:26 +02:00
SABnzbd Automation 33ff6800d2 Update translatable texts
[skip ci]
2026-09-04 06:50:18 +00:00
mnightingale ec61683097 Downloader hot path improvements (#3604)
* Guard the speed counters with their own lock so updates are not lost

* Update the speed counters without taking the Downloader lock

* Skip the downloader sleep once the loop rather than the connection is the limit

* Remove unused variable
2026-09-04 08:49:35 +02:00
SABnzbd Automation 069f978e64 Update translatable texts
[skip ci]
2026-09-03 09:25:36 +00:00
Safihre c65ce5d5aa Add tooltip to explain search filters in queue and history
Provide users with guidance on advanced search capabilities, including filter options like `cat:`, `status:`, and `priority:`.
Closes #3558
2026-09-03 11:24:44 +02:00
mnightingale 443b34ce55 Check the speed limit on its own lock so a held back connection does not hold up the Downloader 2026-09-02 22:13:55 +02:00
mnightingale fbe07dfb7e Refresh the time after waiting so the statistics keep their interval 2026-09-02 22:11:44 +02:00
mnightingale 168622ea47 Remove the unused MultiAddQueue and use a plain queue 2026-09-02 22:10:34 +02:00
Safihre ab702860df Refactor color scheme implementation
Moves theme-specific styles into main CSS files for each interface,
leveraging `color-scheme` and `light-dark()` CSS functions. This
consolidates styling, reduces HTTP requests, and improves automatic
dark/light mode detection based on OS preferences.
2026-09-02 22:02:33 +02:00
mnightingale 4a999a1908 Fix macOS tray crashing when the notification center is unavailable 2026-09-01 20:05:34 +02:00
mnightingale 975c552a6a Safer open folder from macOS menu 2026-09-01 18:47:03 +02:00
mnightingale 20915cc626 Fix mobile dropdown menu styling 2026-08-31 17:44:22 +02:00
mnightingale 4fde6e490c Update jQuery UI to 1.14.2 2026-08-31 16:32:13 +02:00
mnightingale 58c3c13954 Update jQuery to 3.7.1 2026-08-31 16:32:13 +02:00
mnightingale 7b3d1d41ab Load one copy of jQuery, jQuery UI and Bootstrap instead of two 2026-08-31 16:32:13 +02:00
mnightingale cb1102d992 Match navbar heights between styles 2026-08-31 14:31:01 +02:00
mnightingale ecf5e27c04 Let the browser hold static files for good 2026-08-31 08:58:41 +02:00
mnightingale cd96dadcc3 Send the restart request below the configured URL base 2026-08-31 08:58:41 +02:00
mnightingale fb2660dbb9 Serve Glitter's scripts as files the browser can cache 2026-08-31 08:58:41 +02:00
renovate[bot] f9cb5b302b Update all dependencies 2026-08-31 08:56:46 +02:00
SABnzbd Automation 614b3faca6 Update translatable texts
[skip ci]
2026-08-30 18:59:36 +00:00
mnightingale e4162b093a Store the API connection info per config file so instances do not collide 2026-08-30 20:59:01 +02:00
mnightingale e1009e1e92 Install the browser and its system dependencies in one step 2026-08-30 17:01:15 +02:00
mnightingale f8781e1e35 Only resolve the hostname when the access list actually uses it 2026-08-30 17:01:15 +02:00
mnightingale be5d394ebd Tolerate the reload that deleting a server triggers 2026-08-30 17:01:15 +02:00
mnightingale 55285c5cf9 Skip the save button's artificial one second delay with Playwright's clock 2026-08-30 17:01:15 +02:00
mnightingale c3f6b2d582 Keep pytest-playwright out of the pytest session tavern nests inside a test 2026-08-30 17:01:15 +02:00
mnightingale 5088e14fa6 Drive the functional tests with Playwright instead of Selenium 2026-08-30 17:01:15 +02:00
mnightingale 139b4a52b6 Run test coroutines on a thread of their own when a loop is already running 2026-08-30 17:01:15 +02:00
mnightingale 7a852e8654 Pin h11, which uvicorn requires and selenium was supplying by accident 2026-08-30 17:01:15 +02:00
mnightingale fd9acd2ea8 Serve the urlgrabber tests from a local server instead of httpbin 2026-08-30 17:01:15 +02:00
SABnzbd Automation eb673d3956 Update translatable texts
[skip ci]
2026-08-30 08:11:49 +00:00
mnightingale f7513c05b9 Stop the nested tavern session collecting the xdist worker cache dirs (#3599) 2026-08-30 10:11:14 +02:00
Safihre e3bc15e8e6 Make Compact Great Again 2026-08-28 14:48:13 +02:00
Safihre b263c6cfe6 Replace anonymous session cookie with stable CSRF identity (#3596)
Previously, UI access for login-bypassed clients relied on issuing a stateless "anonymous session" cookie to enable CSRF protection. This commit removes that cookie mechanism.

Instead, when the login is bypassed (e.g., for local clients without credentials), the CSRF token is now bound to a stable, internal "anonymous" identity. This maintains robust CSRF protection for browser interactions without requiring an explicit session cookie, simplifying overall session management.
2026-08-28 11:19:54 +02:00
mnightingale d990a17424 Enable direct_decode by default, paced against what the disk can do (#3586)
* Watch whether the download directory keeps up with writes placed where they belong

* Default direct_decode on, refusing a sink while the destination is not keeping up

* Measure what the destination drains and report it as a rate

* Pace the downloader against the measured write rate rather than the cache level

* Handle SparseUnsupported and do not disable direct_write just because it's unsupported for a file

* Let sabctools carve the write totals into intervals instead of tracking them here

* sabctools 9.7.1
2026-08-27 16:19:43 +02:00
SABnzbd Automation 38c2a7bca7 Update translatable texts
[skip ci]
2026-08-27 12:40:32 +00:00
Safihre e2a4d9953b Improve NNTP error reporting with raw server messages
Instead of interpreting and translating common NNTP error messages (e.g., authentication failure, too many connections) into generic localized strings, this change passes the raw server response directly to the user. This provides more accurate and specific diagnostic information for troubleshooting server-related issues.
Closes #3563
2026-08-27 14:39:41 +02:00
Safihre 5696a667e7 Show in-progress status for server tests
Add an `alert-info` class to the result box while a server test is running,
providing visual feedback to the user that the process is ongoing.
2026-08-27 14:25:06 +02:00
SABnzbd Automation 0651dd7a3a Update translatable texts
[skip ci]
2026-08-27 11:05:45 +00:00
mnightingale b192a7eca4 Persistent sessions, CSRF tokens, and no apikey in the web interface (#3559)
* Add a session store that keeps web-UI logins in the admin folder

* Authenticate the web interface with session cookies and CSRF tokens

* Stop embedding the apikey in the web interface

* Cover the session, CSRF and apikey changes
2026-08-27 13:05:12 +02:00
SABnzbd Automation 75f7db398a Update translatable texts
[skip ci]
2026-08-27 09:55:59 +00:00
Safihre 90f092a1a5 Automate LinuxServer.io Docker image builds
Trigger the corresponding LinuxServer.io Docker image build (develop for
pre-releases, master for stable) after a SABnzbd release, ensuring the
Docker images are kept up-to-date.
2026-08-27 11:54:55 +02:00
mnightingale f498147189 Restrict mode-based API key logic to actual API routes
The `mode` parameter in API calls enables specific behaviors and key bypasses (e.g., 'version', 'auth', NZB-key). Previously, this logic was applied universally by `check_apikey`. This change ensures such `mode`-based logic is only honored when accessing the dedicated `/api` endpoint, preventing potential API key bypasses on other web UI routes.
2026-08-25 13:24:22 +02:00
mnightingale e36d989ee5 Sanitize PP-values 2026-08-25 13:24:13 +02:00
Safihre e0acbd75e2 Harden paths that could overwrite __ADMIN__ files
Prevent directory creation in `renamer` to overwrite `__ADMIN__`
Check the whole new path for `__ADMIN__` in `renamer`
Drop `__ADMIN__` from names that are allowed to have sub-directories
Resolve the path before checking it against `__ADMIN__`
2026-08-25 13:22:12 +02:00
Safihre 2ce13bdd9a Prevent directory traversal in Deobfuscate 2026-08-25 13:21:57 +02:00
Safihre 93546e5b7a Add SafeUnpickler to guard against pickle-attacks (#3585)
* Add SafeUnpickler to guard against pickle-attacks

* Harden pickle unpickler with explicit allowlist

The previous `SafeUnpickler` allowed any class from `sabnzbd.*` by wildcard, which could still enable deserialization attacks if a "gadget class" (e.g., with a malicious `__del__` method) was present within our own package.

This commit renames the class to `RestrictedUnpickler` and changes its logic to only allow classes explicitly defined in `_SAFE_GLOBALS`. This significantly enhances security by preventing the unpickling of any unlisted classes, including those from within `sabnzbd`. Adds `os.stat_result` and `sabnzbd.nzb.*` classes to the allowlist for compatibility.
2026-08-24 22:40:04 +02:00
mnightingale 5973e5b6ed Stop tests that create NZBs from racing (#3590) 2026-08-24 09:00:03 +02:00
SABnzbd Automation 0fe92487b6 Update translatable texts
[skip ci]
2026-08-24 05:38:32 +00:00
renovate[bot] 743ff89cff Update all dependencies (#3591)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-08-24 07:37:39 +02:00
SABnzbd Automation 43f22b579f Update translatable texts
[skip ci]
2026-08-21 12:48:13 +00:00
Safihre 817ea452ba Correct wording for Quota period size 2026-08-21 14:47:33 +02:00
SABnzbd Automation 8c21f47f4c Update translatable texts
[skip ci]
2026-08-21 09:08:57 +00:00
mnightingale 3813a810ed Write articles through sabctools.FileWriter, and optionally straight from the decoder (#3562)
* Use sabctools.FileWriter for article writes

* Keep the FileWriter open on the NzbFile, bounded by an LRU

* Stream decoded articles straight to their file

* Fail the article, not the connection, when a streaming write fails

* Pause on a disk error from a streamed write

A full disk cannot be fixed by fetching the article again, so mirror the assembler
and pause instead of spending its retries and failing the job as incomplete.

* Treat an exhausted quota as a full disk

* Use sabctools 9.7.0
2026-08-21 11:08:24 +02:00
github-actions[bot]andSABnzbd Automation 602464349f par2cmdline-turbo 1.5.0 (#3584)
Co-authored-by: SABnzbd Automation <bugs@sabnzbd.org>
2026-08-20 18:27:18 +02:00
Safihre bdd821233f Re-enable par2cmdline in tools-update workflow 2026-08-20 17:30:33 +02:00
mnightingale 611dfa74d2 Fix applying changes to RSS filters (#3582) 2026-08-20 09:55:26 +02:00
coopa11yandcoopa11y 21d639bf5f Announce news server test results to screen readers (#3566)
Co-authored-by: coopa11y <coopa11y@users.noreply.github.com>
2026-08-19 11:31:14 +02:00
coopa11yandcoopa11y 6d897a5f23 Announce notification test results to screen readers (#3567)
Co-authored-by: coopa11y <coopa11y@users.noreply.github.com>
2026-08-19 11:31:06 +02:00
mnightingale 537ac2e7a4 Tighten IPv6 and Host header handling (#3564)
* Refuse unbracketed IPv6 addresses in the Host header

* Bracket the IPv6 client address logged with warnings

* Decide the Secure cookie attribute from the request scope
2026-08-19 11:30:13 +02:00
mnightingale cb2c44cfc2 Unpack rar sets that par2 moved into a subfolder (#3553)
* Reproduce bug

* Let sanitize_filename keep par2 sub-directories

Par2 sets can store their files inside a folder, always using a forward slash as
separator no matter which platform created the set. We replaced that slash with an
underscore, so the name could never match what par2 called the file.

With allow_subdirs the separator is kept and every part is sanitized on its own.
The result stays local to the folder it is used in: empty parts, "." and ".." are
dropped, so neither a leading slash nor any amount of traversal can point outside.

* Write files into the sub-directory par2 names them in

The name from the par2 pack is what we already rename files to, but the separator
was stripped so the file landed flat with an underscore. Post-processing then had
to move it into place afterwards, which is how the volumes ended up somewhere the
unpacker did not look.

Assemble straight into the sub-directory instead. get_unique_filepath() creates the
folder and returns the name relative to the download folder, so nzf.filename now
holds that relative name. analyze_rar_filename() takes the basename, so a setname
still matches what rar_unpack() derives from a path with setname_from_path().

* Match existing files in sub-directories on retry

check_existing_files() listed the job folder without descending into it, so on a
retry the volumes par2 put in a folder of their own were never matched and got
downloaded again.

List the sub-directories too, skipping the admin folder, and match on the name
relative to the job folder. Rename bookkeeping is normalized to the platform
separator, par2 always reports a forward slash.

* Find rar sets in the job's sub-directories

* Wait for the direct unpacker to finish

* Update nzf.setname and nzf.vol after deobfuscating names

* Fix double extraction

* Ruff
2026-08-19 11:13:39 +02:00
mnightingale adde7b961c Fix rss "Clear Downloaded" did not appear to do anything (#3569) 2026-08-18 12:30:38 +02:00
mnightingale 9639d651f4 Don't let uvicorn logging pollute the log and notifications (#3579) 2026-08-18 12:26:26 +02:00
mnightingale f481496e5d Fix rss clearing at startup and downloaded items still in the feed (#3576)
* Only purge unconfigured rss feeds at startup

* Only perform remove obsolete actions if the feed was read

* Update seen_at so downloaded items are not removed while still in the feed

* Increase hardcoded retention to 7 days
2026-08-18 11:46:51 +02:00
renovate[bot]andSafihre e0bf9218bb Update all dependencies (#3575)
* Update all dependencies

* Add feedparser-sgmllib

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Safihre <safihre@sabnzbd.org>
2026-08-18 10:01:04 +02:00
Safihre 19b597d21b Add static cache busting for Config Auto colorscheme 2026-08-18 08:50:08 +02:00
mnightingale 86a2025eca Use root relative URLs (#3572) 2026-08-17 21:57:20 +02:00
SABnzbd Automation 0996998ed9 Update translatable texts
[skip ci]
2026-08-17 18:58:34 +00:00
mnightingale 583617f0a9 Fix submission of server forms when priority, displayname, or notes changes (#3578) 2026-08-17 20:57:42 +02:00
mnightingale d45a1d8bf0 Fix pytest-xdist failure due to selenium driver in class global (#3573)
* Fix pytest-xdist failure due to selenium driver in class global

* ruff
2026-08-16 22:41:03 +02:00
SABnzbd Automation 2e99fd6fd0 Update translatable texts
[skip ci]
2026-08-16 13:47:18 +00:00
mnightingale cf26260910 Fix display of downloaded rss history (#3570) 2026-08-16 15:46:28 +02:00
mnightingale 59218a687d Fix race in TestAddingNZBsClean and share the NZB-adding helpers (#3571)
Jobs are listed in the history output as soon as they enter the
post-processing queue, and a stopped job has its status set to Failed at
the very start of process_job. The duplicate alternative is only released
near the end of it, so waiting for the job to show up in the history as
failed does not guarantee the alternative was already released. The gap
is tiny when running serially, but wide enough to fail the test under the
load of pytest-xdist. Wait for the label to be dropped instead.

Also move the helpers shared by both adding-NZB test classes into a base
class in testhelper, so the clean tests no longer have to import the
other test module to copy its methods.
2026-08-16 15:46:05 +02:00
Safihre 61df709a04 Add static cache busting for Auto colorscheme
Yes ugly, but it works for major updates
2026-08-12 10:55:18 +02:00
Safihre 1fcada54f9 Fetch full history for Claude AI review workflow
The Claude AI code action requires the complete Git history to properly diff pull requests against their base branches.
2026-08-11 13:31:29 +01:00
SABnzbd Automation 289b91a3de Update translatable texts
[skip ci]
2026-08-11 12:29:08 +00:00
mnightingale 35d5355f49 Replace CherryPy with Uvicorn and Starlette (#3550)
* Migrate web interface from CherryPy to Uvicorn/Starlette

Squashed rebase of feature/uvicorn (34 commits) onto develop, reconciled
with ~3 months of intervening develop changes.

Replaces the CherryPy webserver and request handling with Uvicorn/Starlette
across the API, web interface, RSS, config pages and related modules.

Reconciliation with develop during the rebase:
- api.py: kept develop's security/behaviour fixes (orphan path-traversal
  guard, expanded log redaction incl. host_whitelist and
  remote_label_replacement, get_dconfig single-return, get_retryable_jobs,
  connections default, translated NNTP test errors) on top of the Starlette
  request/response rewrite.
- interface.py: ported the RSS route handlers to develop's DB-backed
  RSSRepository API (process_feed, rss_repository / find_job_by_url /
  clear_feed / clear_downloaded / flag_downloaded).
- misc.py: kept develop's hachoir-based get_media_duration.
- requirements.txt: dropped the CherryPy stack, adopted develop's newer pins.

Also applied ruff --fix (PEP 604 unions, builtin generics) to align with
develop's lint config.

Verified: ruff check, black --check, and the affected test suites
(9413 passed, 1 skipped) all pass.

* Update starlette/uvicorn versions

* Fix race issues in global rss state

* Fix test race in server shutdown

The uvicorn migration turned /shutdown (and the shutdown API) into fire-and-forget: it spawned shutdown_program() in a background thread and replied immediately, whereas develop ran it synchronously and only replied once halt() had persisted all state. Because the module-scoped test teardown doesn't wait for the process to exit, the next module's clean_cache_dir wiped the shared cache dir (and reused the fixed port) while the previous instance was still saving state and holding the port — producing the three intermittent failures (deleted sabnzbd.log → "File log disabled or not found"; un-persisted [sorters] → KeyError; stale instance → missing wizard .quoteBlock).

* Fix robots and description, add favicon

* Remove remains of http basic auth

* Setup Starlette once configuration is available, fix static file relative cwd and url_base config

* abort_and_show_error when webserver fails to start

* Guard stopping webserver that never started

* Delegate XFF handling to ProxyHeadersMiddleware

* Merged params at request.state.params instead of modifying private apis

* Both shutdown routes share implementation and do not block event loop

* Run sync handlers via run_in_threadpool and facilitate eventual migration to async

* Pool database connections

* Online backup of database due to WAL changes

* Fix exception on None request.client (test clients or unix sockets)

* Fix flakey tests due to process not fully shutting down

* Restore X-Frame-Options behaviour via middleware

* Fix set_config_default with multiple keywords

* Remove broken logging call

* Restore api logging functionality

* Cache-Control: no-store

* Login only via POST

* Remove 401 (basic-auth) and add 404 handling via redirect

* Fix crash when shutdown not an int

* Use BaseRedirectResponse helper

* Remove trailing slashes from wizard routes

* URL helper, absolute URLs everywhere, fixes issues with nested navigation

* Fix scheduler adding multiple daysofweek

* Restore CherryPy api behaviour merging body with query params (body wins)

* Clearer documentation of get_request_params and request_params

* First stage supporting gradual api async

* Fix rss ajax consuming flash

* Restore access log functionality

* Hostname check in middleware

* Request logging in middleware

* Param parsing in middleware

* Security checks in middleware

* secured_expose is now purely route registration

* Lookup api handler once per request

* Fix flakey alert dialogs

* Trigger restart via BackgroundTask

* Restore CherryPy first param wins and get/post consistency

* Remove dead code

* Secure cookies based on protocol the client used

* Fix various issues with port_is_free

1. port_is_free answered the wrong question. It connect-probed ("is something answering?") rather than bind-probed ("can I bind?"). A port could report free and then kill startup at uvicorn's bind().
2. The bind-all remap crossed address families. :: was mapped to 127.0.0.1, probing IPv4 for an IPv6 bind — a regression against portend, which maps :: → ::1.
3. The call sites passed the wrong host. browserhost is a client-reachable address; the thing that has to be bindable is web_host.
4. Errors were swallowed. A bare except OSError hid gaierror, so an unresolvable host reported "free".
5. find_free_port had a port-0 trap. Under a bind-probe, currentport=0 always succeeds and returned 0 — the old failure sentinel. Now guarded, and None instead of 0.
6. Ports 80/443 were misdiagnosed. EACCES was folded into "occupied", producing ten futile probes and a panic claiming another program held the port. PermissionError now propagates to a dedicated panic explaining the actual remedies.
7. The tests were largely tautological. Three tests covering one branch, an IPv6 test with no IPv6 in it, a timeout test that never engaged the timeout, TOCTOU-prone fixed-range probes, no SO_REUSEADDR on the helper listener, and nothing asserting the property that matters — that "free" implies bindable.
8. A portability bug I introduced, then fixed. I'd baked Linux SO_REUSEADDR overlap semantics into four assertions; macOS differs. Now platform-aware, with the IPv6 regression re-covered by checking the socket family directly.

* Claim the bind address for uvicorn on startup, resolves "49" in err handling from cherrypy

* Rename function BaseRedirectResponse to base_redirect_response

* Restore error response on change web directory

* Add missing typings

* Fix return type of retry job for future types

* A better fix for xdist compatibility - test overwrote db_path

* Secure session cookies (rss flash)

* Inline or remove some functions

* Retry job futuretype behaviour

* Sneak a worksteal fix in

* Test and fix retry_job futuretype behaviour
2026-08-11 13:27:51 +01:00
mnightingale ec6ba3baae Speed up tests with pytest-xdist (#3546)
* Speed up tests with pytest-xdist

* Ignore request failures when the server disappeared

* Fix tests which modify module globals

* Fix lang change leaking

* Fix transaction compilation order breaking tests

* Always load a clean config and fix related tests

* No module globals

* Fix other test classes assigning module level by making calls

* Replace from sabnzbd.cfg import so monkeypatch works

* More global poisoning

* Fix dependant tests which worksteal breaks

* More global vars

* Drop loadscope until more failures are resolved

* Due to how pytest-xdist is implemented, the -s/--capture=no option does not work

* monkeypatch db_path

* Also patch startup_done

* loadscope
2026-08-10 17:44:18 +01:00
Safihre d6474cb695 Skip par2cmdline update for now due to custom macOS binaries 2026-08-10 14:11:30 +01:00
Safihre 4520de77ad Correct ssdp.py for black formatting 2026-08-10 12:44:20 +01:00
Safihre ada9d83593 Refine file extension utilities for efficiency and robustness
Adopt `frozenset` for extension collections to enable faster lookups and ensure immutability. Add explicit `utf-8` encoding to file reads to prevent locale-dependent issues when identifying text/NZB files. Optimize by caching results of `all_possible_extensions` to avoid redundant computations.
2026-08-10 12:36:16 +01:00
Safihre 58386e0b61 Refine local self-signed certificate generation
Adds `BasicConstraints` to the generated certificates for better compliance and explicitly marks them as non-CA. Changes the `SubjectAlternativeName` extension to non-critical, improving client compatibility. Improves robustness in SAN list generation and adds type hints for clarity.
2026-08-10 12:30:42 +01:00
SABnzbd Automation 4601f4b690 Update translatable texts
[skip ci]
2026-08-10 11:25:53 +00:00
coopa11yandcoopa11y fd191d1b79 Add screen reader names to Glitter and Config dialogs (#3545)
Co-authored-by: coopa11y <coopa11y@users.noreply.github.com>
2026-08-10 12:25:17 +01:00
Safihre 5014673f87 Improve SSDP XML generation and refine module implementation
Escapes dynamic data in the SSDP device description XML to prevent issues with special characters and improve robustness. Enhances code clarity and maintainability through explicit constructor parameters, type hints, and updated docstrings. Also adds debug logging for SSDP broadcast failures and renames the module-level singleton for better Python convention.
2026-08-10 12:24:30 +01:00
SABnzbd Automation 14ce150b31 Update translatable texts
[skip ci]
2026-08-10 10:50:18 +00:00
coopa11yandcoopa11y 8e39722359 Improve Glitter queue and history table accessibility (#3539)
Co-authored-by: coopa11y <coopa11y@users.noreply.github.com>
2026-08-10 12:49:33 +02:00
Safihre ba36e62695 Refactor Config object (#3552) 2026-08-10 09:24:53 +02:00
SABnzbd Automation 250a4dbf2a Update translatable texts
[skip ci]
2026-08-10 07:24:37 +00:00
Safihre 3867cefa0a Update version to 5.2.0-dev 2026-08-10 09:23:52 +02:00
Safihre d530764a6d Allow collaborators to trigger Claude review 2026-08-10 08:59:37 +02:00
renovate[bot] b5a7385d48 Update all dependencies (#3557)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-08-10 06:10:25 +00:00
Safihre 40860c68ca Correct Notifications page display 2026-08-08 21:34:48 +02:00
mnightingale 3213cd74cf Rebuild macOS par2 binary (#3554) 2026-08-08 11:52:17 +02:00
Safihre b8838c4728 Improvements to Notification options display 2026-08-07 11:03:39 +02:00
Safihre 047ed77dd4 Remove stray MULTIPAR_COMMAND 2026-08-07 10:11:51 +02:00
Safihre 78cb553b14 Fix config UI selection and update direct unpack log message
Correctly pre-select the 'all' option for `history_retention_option` in the config UI by using the correct variable.
2026-08-06 16:15:09 +02:00
jcfp c5ebea7bbd use XDG_CURRENT_DESKTOP as an alternative GUI indicator (#3551) 2026-08-06 11:42:51 +02:00
Safihre 7b02c74386 Add missing guard_restart to https_chain 2026-08-06 10:43:21 +02:00
SABnzbd Automation 0c3464e6a2 Update translatable texts
[skip ci]
2026-08-05 17:15:11 +00:00
Ben Hamilton (Ben Gertzfield) d889d4fd30 Don't log warning during clean shutdown (#3548) (#3549)
`SIGTERM` is an expected part of a clean shutdown.

Previously, the signal handler posted a log warning for any signal received.

This PR turns down the log level to info for `SIGTERM` (non-Windows only).

Tested:

% python3 -m venv venv
% source venv/bin/activate
% pip install -r dependencies.txt
% python3 -OO SABnzbd.py

In another terminal, ran `kill -TERM $PID`.

Confirmed in logs that log level was now set to `INFO`:

```
2026-08-05 08:37:10,296::INFO::[__init__:217] Signal 15 caught, saving and exiting...
```

Full logs:

https://gist.github.com/bhamiltoncx/2f8845b63c52e47fc4f00de263c21152
2026-08-05 19:12:55 +02:00
Safihre 70f89e98e8 Update text files for 5.1.0RC2 2026-08-03 15:54:12 +02:00
Safihre 2cb12a07c9 Update 'Mark as Completed' link to a button 2026-08-03 14:53:42 +02:00
mnightingale 3d7f5d4289 Relax disk space requirements when not unpacking and on the same device (#3535)
* Test existing behaviour

* Relax disk space requirements when not unpacking and the same device
2026-08-03 11:44:11 +02:00
Safihre 3abf4c7bdb Update sabctools to 9.6.3 2026-08-03 11:07:34 +02:00
mnightingale 947b2b815c Fix assembler ready bytes accounting (#3538) 2026-08-03 09:01:29 +02:00
thezoggy d167fb0fe7 fix active pagination for light theme (#3541) 2026-08-03 08:52:22 +02:00
renovate[bot] b7da5b6684 Update actions/setup-python action to v7 (#3543)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-08-03 08:42:06 +02:00
Andre Popovitch df9c57f539 Prevent shutdown deadlock when a second signal arrives during halt (#3537) 2026-08-03 08:40:32 +02:00
renovate[bot] d5a7f8631c Update dependency jaraco.functools to v4.6.0 (#3542)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-08-03 08:29:34 +02:00
mnightingale dcb618ce99 Improve wait for direct unpacker to finish (#3540) 2026-08-02 21:16:07 +02:00
mnightingale dc5620a52c cgroup-aware memory limits for the article cache (#3528)
* Apply memory limits from cgroups

* Apply cgroup limits to article cache

* Fix this bug

* _physical_memory return None when undetermined

* Mark platform on test group

* Remove MemoryInfo dataclass

* Cap memory with reservation

* Keep the slightly broken 32 vs 64 bit code

* 512 *MEBI because the numbers look nicer
2026-07-31 09:42:47 +03:00
mnightingale 40a3941b9e Fix os.umask race and unnecessary (#3530) 2026-07-30 11:24:06 +03:00
mnightingale eb9229d326 Prevent concurrent dirscanner scans (#3529) 2026-07-29 22:51:41 +03:00
coopa11yandcoopa11y 0411e8acc1 Label schedule time, enable, and delete controls for screen readers (#3525)
* Label schedule time, enable, and delete controls

* Use ARIA label for schedule enable control

---------

Co-authored-by: coopa11y <coopa11y@users.noreply.github.com>
2026-07-28 21:12:47 +03:00
mnightingale f405b8927a Reduce BPS allocations and fix scale (#3527)
* Replace per-second bps list copy

* Consistent x-axis scale after startup

* Advance time when padding
2026-07-28 19:50:14 +03:00
mnightingale 47a507f5a6 Fix flaky test_api_queue_sort test and to_unit at boundaries (#3522)
* Fix flaky sort test by comparing at the same resolution

* Fix consistency of values printed at reduced precision

* Replace the float-string roundtrip with rounding and adjust to next unit if required

* Comment styling
2026-07-28 17:59:47 +03:00
Amajiro bdca9715b2 Add Ayatana AppIndicator tray support on GNOME (#3508)
* Add Ayatana AppIndicator tray support on GNOME

* Only load tray icon fallback backends when needed
2026-07-28 17:44:00 +03:00
SABnzbd Automation 0954ac143d Update translatable texts
[skip ci]
2026-07-28 14:43:46 +00:00
coopa11yandcoopa11y 9122633907 Label SABnzbd login fields and help link for screen readers (#3524)
* Label SABnzbd login fields and help link

* Use ARIA labels for login fields

---------

Co-authored-by: coopa11y <coopa11y@users.noreply.github.com>
2026-07-28 17:42:48 +03:00
renovate[bot] 1b6aea3afb Update all dependencies (#3526)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-07-27 08:43:03 +03:00
coopa11yandcoopa11y af10190e0b Improve Glitter status table accessibility (#3523)
Co-authored-by: coopa11y <coopa11y@users.noreply.github.com>
2026-07-26 20:07:31 +03:00
SABnzbd Automation 1acfc5139c Update translatable texts
[skip ci]
2026-07-26 13:28:53 +00:00
Parsa YazdaniandQuixThe2nd 404060373b feat: add 'Sort by Remaining Size' queue sort option (#3512)
* feat: add 'Sort by Remaining Size' queue sort option

Add a new 'remaining_bytes' sort field that sorts the queue by absolute
remaining bytes (bytes - bytes_tried), complementing the existing
'remaining' sort which uses percentage downloaded and the 'size' sort
which uses total bytes.

Changes:
- nzbqueue.py: add remaining_bytes sort field, fix update_sort_order()
  to parse field+direction from config instead of hardcoding 'remaining'
- skintext.py: add Glitter-sortRemainingBytesAsc/Desc translation strings,
  update auto_sort explanation text
- Glitter UI: add dropdown entries and JS handlers for both directions
- Config: add auto_sort options for remaining_bytes asc/desc
- Tests: parametrize remaining_bytes asc/desc against sizeleft slot field

* style: wrap long string for Black formatting

* fix: limit remaining-size sorting to ascending

---------

Co-authored-by: QuixThe2nd <QuixThe2nd@users.noreply.github.com>
2026-07-26 16:28:08 +03:00
coopa11yandcoopa11y a84544bef8 Improve Glitter message accessibility (#3518)
Co-authored-by: coopa11y <coopa11y@users.noreply.github.com>
2026-07-25 12:50:52 +03:00
coopa11yandcoopa11y f38b602924 Improve interface options form accessibility (#3516)
Co-authored-by: coopa11y <coopa11y@users.noreply.github.com>
2026-07-25 11:50:58 +03:00
mnightingale db57321cb8 Fix postproc scripts on Windows (#3514)
* Verify external_processing failure on Windows

* Implement fix

* Clip complete_dir and newfiles in external_processing
2026-07-25 11:50:20 +03:00
mnightingale b825636d3f Fix crc32 may be being combined out-of-order (#3519) 2026-07-25 11:47:23 +03:00
SABnzbd Automation fa3f8735ea Update translatable texts
[skip ci]
2026-07-25 05:04:06 +00:00
mnightingale c359b21d89 Ruff styling for move None to the end of the type union (#3517) 2026-07-25 08:03:27 +03:00
coopa11yandcoopa11y 3ab1782dc2 Improve Add NZB form accessibility (#3511)
Co-authored-by: coopa11y <coopa11y@users.noreply.github.com>
2026-07-22 22:15:35 +03:00
Safihre 91c228353b Correct Signal Wakeup FD Registration for DirScanner
When `DirScanner` is initialized on the main thread, `asyncio.new_event_loop()` can register its self-pipe as the process-wide signal wakeup FD. As the `DirScanner`'s loop runs and is closed on a separate thread, this registration would become stale, potentially breaking signal delivery during application shutdown. Explicitly unregistering the FD prevents this issue.
2026-07-21 15:39:10 +02:00
coopa11yandcoopa11y 1336d66f78 Restore keyboard access to queue actions (#3503)
Co-authored-by: coopa11y <coopa11y@users.noreply.github.com>
2026-07-21 15:25:04 +02:00
coopa11yandcoopa11y 1f7bec651d Improve Glitter history row accessibility (#3504)
Co-authored-by: coopa11y <coopa11y@users.noreply.github.com>
2026-07-20 10:24:13 +02:00
SABnzbd Automation 8f02e59c31 Update translatable texts
[skip ci]
2026-07-20 08:24:01 +00:00
coopa11yandcoopa11y efb16cc69b Improve Glitter search and pagination accessibility (#3505)
* Improve Glitter search and pagination accessibility

* Fixed: Preserve pagination keyboard shortcuts

---------

Co-authored-by: coopa11y <coopa11y@users.noreply.github.com>
2026-07-20 10:23:24 +02:00
SABnzbd Automation f6020aa9f7 Update translatable texts
[skip ci]
2026-07-20 06:35:11 +00:00
renovate[bot] 2d2b88e771 Update all dependencies (#3509)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-07-20 08:34:32 +02:00
SABnzbd Automation 6731da2d68 Update translatable texts
[skip ci]
2026-07-17 10:23:59 +00:00
Safihre cf8ab5a8d5 Suggest NNTP pipelining increase for high-latency connections
During NNTP server tests, detect high latency and low pipelining settings.
Provide a helpful warning to users to consider increasing their 'Articles per request'
setting to improve download speed.

Closes #3284
2026-07-17 12:23:17 +02:00
Safihre 636aa91e21 Restore queue action buttons 2026-07-17 08:58:52 +02:00
Safihre a1c9a8aab1 Update text files for 5.1.0RC1 2026-07-16 21:50:07 +02:00
Safihre 73e7dd8084 Update sabctools to 9.6.2 2026-07-16 21:48:27 +02:00
SABnzbd Automation 6eb257388b Update translatable texts
[skip ci]
2026-07-16 19:25:19 +00:00
mnightingale ba2099d006 Add RSS Age rule (#3498)
* RSS age rule

* Support years (y) and months (mo) and do not approximate durations in seconds

* Better handling of entries with no age

* Formatting

* Allow >=, =>, <=, and =< aliases

* Implement feedback

* Fix existing issue with From SxxEyy in default row

* Test unitless ages
2026-07-16 21:24:41 +02:00
Safihre 8f044bb19e Remove Frugal Usenet IPv6 hostname mappings
@sanderjo they added IPv6 to their main host and the tests caught it :)
2026-07-16 21:20:30 +02:00
Safihre 889fbf688d Correct CSS for queue rows
After #3477
2026-07-16 21:17:53 +02:00
Safihre ab879cc7d0 Add workflow for manual Claude AI code reviews
Allows maintainers to trigger an AI code review on a pull request by commenting `@claude`.
2026-07-16 21:05:55 +02:00
SABnzbd Automation 5b7a89ffb8 Update translatable texts
[skip ci]
2026-07-16 05:52:39 +00:00
coopa11yandcoopa11y 1eed5c7866 Improve Glitter queue row accessibility (#3477)
* Improve Glitter queue row action labels

* Simplify queue row accessibility labels

---------

Co-authored-by: coopa11y <coopa11y@users.noreply.github.com>
2026-07-16 07:51:36 +02:00
Safihre 44777dc3cf Add Python 3.15-dev to integration testing matrix
Install libxml2 and libxslt for integration testing
2026-07-14 16:04:10 +02:00
mnightingale c91b53d290 Add SAB_FILES environment variable for postproc scripts (#3497)
* Add SAB_FILES environment variable for postproc scripts

* Test external_processing SAB_* environment variables
2026-07-14 14:34:33 +02:00
SABnzbd Automation ef2ee17c13 Update translatable texts
[skip ci]
2026-07-13 07:52:55 +00:00
mnightingale a2aec15a38 Remove unwanted extensions after unpack (#3493) 2026-07-13 09:52:20 +02:00
mnightingale 73af985e60 Optimise scanning for orphan jobs and retry all (#3492)
* Optimise scanning for orphan jobs and retry all

* Require start and limit for build_history
2026-07-13 09:26:17 +02:00
SABnzbd Automation 988955374c Update translatable texts
[skip ci]
2026-07-13 05:01:16 +00:00
renovate[bot] 28eaa6c20d Update all dependencies (#3495)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-07-13 07:00:26 +02:00
mnightingale 5c247e008c Fix test for when rarfile flags has_header_encryption after a failed password attempt (#3491) 2026-07-10 22:02:23 +02:00
Safihre bf02e9da44 Add defusedxml to release requirements 2026-07-10 15:46:07 +02:00
Safihre fb9dbcf824 Update text files for 5.1.0Beta3 2026-07-10 15:36:19 +02:00
Safihre 6a85b2daea Update sabctools dependency to 9.6.1 2026-07-10 15:30:04 +02:00
Safihre a2ac2b0435 Refactor RAR volume information extraction and deobfuscation
Modernizes `get_rar_extension` to leverage the `rarfile` library's internal header parsing, significantly improving the robustness and reliability of RAR volume number and original extension detection.

The `rar_renamer` function in post-processing is also refactored for improved clarity and maintainability, including the extraction of a dedicated helper function for renaming and various code simplifications.
2026-07-10 11:13:41 +02:00
mnightingale 8a92f4ac71 Verify RAR5 passwords when header encryption is not used (#3490)
* Verify RAR5 passwords when header encryption is not used

* Make flow a bit closer to rarfile

* Spelling

* Add note to trusting header verification

* Spelling

* Delegate to rarfile 4.3 where possible and cache rar*_s2k

* Explicit types instead of TypeVar

* Remove duplicate test and add a header encryption with correct password

* Verify after parse

* Rename method and explicit cache size

* Remove dead code

* Clearer verify after parse

* Performance early exit

* Clarify early exit

* Update comment, usually same password and salt

* Copy parse doc
2026-07-10 11:05:59 +02:00
Safihre 2556ef10c2 Refactor Windows registry interaction
Modernizes Windows Registry interaction by leveraging `with` statements for automatic
key closure, improving resource management and preventing potential leaks.

Simplifies registry value operations (get, set, delete) and consistently defines
full key paths in `reg_info`, reducing boilerplate. Adds type hints to enhance
code readability and maintainability.
2026-07-10 11:00:56 +02:00
Safihre 661b4ae535 Use native API for FAT filesystem detection on macOS
The previous method for detecting FAT filesystems on macOS relied on parsing `df` and `mount` command output, which is brittle and prone to issues with command output variations.

This change replaces the shell command parsing with a more robust and idiomatic approach using Foundation.framework's `NSURL` API. This directly queries the volume's localized format description, ensuring accurate identification of MS-DOS (FAT12/FAT16/FAT32) volumes while correctly distinguishing them from ExFAT.

Adds unit tests for the `isFAT` function to verify basic functionality and error handling.

Closes #3483
2026-07-10 10:28:08 +02:00
Safihre 0bcb28d576 Correctly fix dots-styling in pagination 2026-07-09 12:08:09 +02:00
Safihre 02ced74783 Validate SABnzbd web UI availability in snap build workflow
The previous smoke test only verified that the snap binary could be executed. This change enhances the CI workflow to confirm the SABnzbd web UI starts successfully and responds on its default port after snap installation, improving the robustness of the build pipeline.
2026-07-09 11:08:30 +02:00
renovate[bot] 8afb8a91e2 Update all dependencies (#3487)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-07-06 14:16:16 +02:00
Safihre dc421047d3 Render Config UI tooltips in body
Closes #3486
2026-07-04 20:32:28 +02:00
Safihre 7fd2848278 Skip DNS and public IP lookups without a selftest host
The `dnslookup` and `public_ip` functions rely on a configured `selftest_host`. This change prevents unnecessary network attempts and provides explicit debug logging when the host is not set.
Closes #3485
2026-07-04 20:27:44 +02:00
Safihre ef8df5b578 Consolidate Python versioning in build workflow
Moves the `PYTHON_VERSION` environment variable to a global scope in `build_release.yml`. This ensures consistent Python versions across build jobs (Windows and macOS) and centralizes it for easier future updates (e.g., via Renovate).
2026-07-03 14:09:41 +02:00
SABnzbd Automation 1b4525656f Update translatable texts
[skip ci]
2026-07-03 11:08:44 +00:00
coopa11yandcoopa11y fd6d8c71c7 Improve Config search accessibility (#3476)
Co-authored-by: coopa11y <coopa11y@users.noreply.github.com>
2026-07-03 13:07:20 +02:00
SABnzbd Automation 2e202eca23 Update translatable texts
[skip ci]
2026-07-03 07:23:36 +00:00
Safihre 6b76603c60 Add workflow for automated bundled tool updates (#3482)
* Add workflow for automated bundled tool updates

Introduces a GitHub Actions workflow to automatically check for new releases of unrar, 7-Zip, and par2cmdline-turbo. When new versions are found, it creates a pull request with the updated binaries for review.

* Use preinstalled 7Zip so we don't execute any downloaded executables

Leverage the preinstalled 7-Zip utility on GitHub Actions runners to
extract Windows UnRAR.exe and 7za.exe. This prevents executing internet executables with write-permission GH-token.

* Inline the unrar version detection

And rename to RAW_VERSION
2026-07-03 09:23:04 +02:00
Safihre e929a0c6b6 Use media duration for accurate sample detection (#3475)
* Use media duration for accurate sample detection

Enhances the `is_sample()` logic to reduce false positives for files
whose names contain "sample" or "proof" but are actually full-length
content.

Integrates the `hachoir` library to parse media files and extract their
duration. Files are only considered samples if their duration is less
than or equal to `SAMPLE_MAX_DURATION` (default 2 minutes).

If media duration cannot be determined (e.g., not a media file or
parsing fails), the detection falls back to the name-based approach.

* Refactor `is_sample` function to accept single path argument
2026-07-02 14:15:10 +02:00
jcfp ee8d9bdcf9 unrar 7.23 (#3478) 2026-07-01 21:50:38 +02:00
SABnzbd Automation d9cd7a72b1 Update translatable texts
[skip ci]
2026-06-30 18:16:24 +00:00
coopa11yandcoopa11y 6112ded96b Improve Glitter header menu labels (#3471)
* Improve Glitter header menu labels

* Improve Glitter header label localization

---------

Co-authored-by: coopa11y <coopa11y@users.noreply.github.com>
2026-06-30 20:15:40 +02:00
SABnzbd Automation dbe7cb336d Update translatable texts
[skip ci]
2026-06-29 10:18:38 +00:00
Safihre 376fc8a1bf Refactor Windows system tray icon implementation
Replaces the generic `SysTrayIconThread` base class with a dedicated `sabtraywin` module.
Added several optimizations and refactoring of the original sabtray code that was very ancient.
2026-06-29 12:18:04 +02:00
mnightingale 9e09817722 Abort postproc unrar on prompt (#3463)
* Abort postproc rar unpacker if prompted for volume or to retry

* Skip empty but preserve trailing whitespace when not whole lines

* Strings and fail_msg for write error / retry

* Not necessary not pausing for volumes and will output "Cannot find volume" and be handled

* dont sleep it waits anyway
2026-06-29 10:36:46 +02:00
renovate[bot] 6db37d9883 Update all dependencies (#3474)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-06-29 10:18:02 +02:00
Safihre 2a9e9954a8 Stop Renovate from updating cryptography
It is already pinned for macOS release and open-ended for all others (like the CI-tests).
2026-06-29 09:59:49 +02:00
Safihre e3e7fcf361 Prevent path traversal in orphaned job APIs
Adds security checks to `_api_delete_orphan` and `_api_add_orphan` endpoints
to ensure that operations are restricted strictly to the download directory.
This mitigates potential path traversal vulnerabilities, preventing
unauthorized file deletion or modification outside the intended scope.
Reported as GHSA-hxwh-mmrg-p8f5
2026-06-28 12:33:04 +02:00
thezoggy b057bc6afc pagination dot dark fix (#3472)
Fixes https://github.com/sabnzbd/sabnzbd/issues/3470
2026-06-27 23:16:52 +02:00
thezoggy 1b368a4278 26.02 - Some bugs and vulnerabilities were fixed. (#3469) 2026-06-26 18:37:46 +02:00
Safihre a55251f698 Adjust Windows artifact uploads for SignPath requirements
Unsigned Windows binaries and installers are now uploaded with `archive: true` for release tags. This creates a "zip-in-zip" layout that SignPath requires for consuming artifacts.
2026-06-26 14:46:54 +02:00
Safihre 196241d2cc Update text files for 5.1.0Beta2 2026-06-25 16:36:30 +02:00
Safihre 5b3cf86f68 Track files during cleanup to prevent removing unrelated files (#3462)
* Track files during cleanup to prevent removing unrelated files

* Remove redundant path normalization

The `os.path.abspath` function already includes path normalization,
rendering the explicit call to `os.path.normpath` redundant
when used in conjunction. Removing it simplifies the code
without changing its behavior.
2026-06-25 15:01:41 +02:00
mnightingale 6289b74470 Fix contiguous ready check article.on_disk or article.failed should skip (#3467) 2026-06-24 22:19:11 +02:00
mnightingale e9ff8a1595 Improve rarfile performance with RAR3 archives using header encryption and long passwords (#3466)
* Improve rarfile performance with RAR3 archives using header encryption and long passwords

* nzo password is optional anyway

* sabctools 9.5.0
2026-06-24 22:01:46 +02:00
SABnzbd Automation 972f4832c1 Update translatable texts
[skip ci]
2026-06-22 12:11:48 +00:00
Safihre 560e342460 Update Danish, Spanish, Hebrew, and Italian translations 2026-06-22 14:10:45 +02:00
SABnzbd Automation 7b90868007 Update translatable texts
[skip ci]
2026-06-22 08:26:21 +00:00
Safihre e21798b163 Conditionally push local translation edits to Transifex
The `translations.yml` workflow now fetches `fetch-depth: 2` and uses `git show`
to detect if the current commit includes local `.po` file changes. If local
translation edits are present, `tx push --translation` is used; otherwise,
only `--source` is pushed. This prevents accidental overwrites of Transifex
translations with outdated local files when no `.po` changes were intended.

This commit also includes updated translations in various `.po` files and adds
guidance for translators to `po_to_json.py` for consistency.
2026-06-22 10:24:51 +02:00
renovate[bot] 9b33f4ce85 Update all dependencies (#3464)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-06-22 09:08:33 +02:00
Safihre 7daa0159ec Add Renovate manager for Transifex CLI
Configures Renovate to automatically track and update the `transifex/cli`
version specified in the `translations.yml` GitHub Actions workflow.
2026-06-19 14:33:50 +02:00
Safihre f0d3e6b8b9 Restrict stale bot to 'Support' labeled issues
Once we confirm a request or bug, the Support label is removed
2026-06-19 14:06:44 +02:00
SABnzbd Automation 82aa38c087 Update translatable texts
[skip ci]
2026-06-19 07:10:52 +00:00
mnightingale 228e7e650e Implement article-level retry (#3435)
* Implement article-level retry

* Remove count because bit_count is 3.10

* Fix errors importing files without article db

* Fix bookkeeping of bytes downloaded and on_disk status for complete existing files

* Legacy queue tests

* Bitmap types

* Add docs to Bitmap

* Match renamed files

* Add repair_job tests

* Need to remember failed state

* Only finish_import when required and defer setting on_disk state

* KISS

Closes #2735
2026-06-19 09:10:19 +02:00
Safihre 89e8788b03 Improve installer compression with solid LZMA
Configure NSIS to use solid LZMA compression, which deduplicates identical files and shrinks the overall installer size.
2026-06-16 14:47:42 +02:00
Safihre 3e1812280e Set ujson specific version pin again 2026-06-16 12:05:40 +02:00
Safihre 5d3286654e Enforce explicit dependency pinning for improved build security
All direct and transitive dependencies are now explicitly listed in
`requirements.txt` files, and `pip install` commands consistently use
`--no-dependencies`.

This approach ensures that only the specified versions are installed,
enhancing supply chain security and build reproducibility by preventing
automatic dependency resolution. The `--upgrade` flag was removed from
these installations as it is no longer necessary with fully pinned
dependencies.
2026-06-16 11:54:11 +02:00
Safihre cdb87866ae Add PO-to-JSON script for AI-assisted translations
Since all the AI tools suck at dealing with the po files directly
2026-06-16 10:54:26 +02:00
Safihre 8919aa7ec0 Remove INSTALL.txt and update documentation references
The `INSTALL.txt` file has been removed, as all installation instructions are now exclusively maintained on the project wiki. This updates `README.md` and build artifacts to reflect this change.

Additionally, `COPYRIGHT.txt` has been updated to include a new active team member, streamline translation contributor credits, and provide a direct link to GNU licenses. A minor CSS adjustment for the mobile UI is also included.
2026-06-15 14:05:47 +02:00
Safihre 4d6df7dd22 Remove no-binary workaround for charset-normalizer
The explicit `--no-binary` flag for `charset-normalizer` is no longer required in the release build workflow, simplifying dependency installation.
2026-06-15 11:34:46 +02:00
Safihre 6f342dade5 Streamline release artifact handling and enforce Windows signing
This update improves the release workflow by:
*   Configuring GitHub Actions `upload-artifact` to transfer files directly without unnecessary zipping, simplifying artifact retrieval for subsequent jobs.
*   Integrating `pefile` to perform an Authenticode signature check on the Windows installer, ensuring all official releases are properly signed.
2026-06-15 10:26:24 +02:00
Safihre 957d0fcd01 Apprise now supports paho-mqtt 2.1.0 2026-06-15 08:54:00 +02:00
renovate[bot] 662dfb7f21 Update all dependencies (#3460)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-06-15 04:33:14 +00:00
Safihre 7c08959460 Tavern issue resolved so unpinning pytest 2026-06-14 21:37:22 +02:00
Safihre fee562d57f Increase default NNTP server connections to 16
Updates the default number of connections from 8 to 16 in the server configuration and wizard UI templates. This change aims to improve performance for users who do not explicitly configure this setting.

Additionally, ensures the API server test function defaults to at least 1 connection when none is specified.

Relates to #3284
2026-06-14 13:34:22 +02:00
Safihre 643e5182f7 Pin pytest<9.1 to work around tavern FormattedExcinfo import
pytest 9.1 removed FormattedExcinfo from _pytest._code.code, which tavern still imports unconditionally, breaking test collection on Python 3.11+.
Reported upstream: https://github.com/taverntesting/tavern/issues/1054
2026-06-14 13:11:42 +02:00
Safihre 129aae79a1 Pin cryptography version for macOS universal2 builds
Versions 49.0.0 and higher are arm64-only, which prevents universal2
wheel distribution. This ensures continued compatibility for macOS.
See https://github.com/pyca/cryptography/issues/13520
2026-06-14 12:01:25 +02:00
SABnzbd Automation ca93b63ca8 Update translatable texts
[skip ci]
2026-06-14 09:40:23 +00:00
Safihre 83ac44eabc Update text files for 5.1.0Beta1 2026-06-14 11:39:43 +02:00
SABnzbd Automation 3c922ee087 Update translatable texts
[skip ci]
2026-06-12 13:16:00 +00:00
mnightingaleandSafihre dcfe8b076f Support extracting tar files (#3456)
* Support extracting tar files

* Set fail_msg when Python is too old

* Update error to "Unpacking failed, TAR support requires Python 3.12 or later", set per set/file, and don't regard as a failure if unsupported

* Update sabnzbd/newsunpack.py

Co-authored-by: Safihre <safihre@sabnzbd.org>

* Use UNWANTED_FILE_PERMISSIONS (stat.S_ISUID and stat.S_ISGID) are already removed by tar_filter but this is cleaner

* TAR extraction test including non-executable check

* ruff

* Skip executable check on Windows

* Remove helloworld.sh

* get_unique_filename

* Test path traversal, owner/group and permissions

* Fix duplicate file issues with one_folder

* Rename filter

---------

Co-authored-by: Safihre <safihre@sabnzbd.org>
2026-06-12 15:15:23 +02:00
Safihre f1486e2d4b Add drop down menu options to the additional queue columns
Closes #2227

Beware that this causes extra CPU usage.
Claude has diagnosed:
What it almost certainly is: paint/raster cost of native form controls. Every refresh tick the progress bar width and text change, which invalidates a paint region spanning most of the row. Chrome re-rasterizes damaged tiles wholesale, and the selects sit in those tiles. A <select> — especially one with custom styling like ours, which knocks Chrome off the native-theme fast path — is much more expensive to rasterize than plain text, and there are 4 of them per row, re-painted every second while downloading. Rasterization runs on Chrome's compositor/raster threads, which is exactly the kind of cost that shows up in the browser Task Manager but is invisible to JS-side profiling (which is why my layout measurement showed no difference). Form controls also carry extra accessibility-tree bookkeeping on each DOM-adjacent change, which adds a little more.
2026-06-12 15:11:04 +02:00
Safihre aee1cd8e41 Improve NSIS installer and uninstaller robustness
Enhance installer and uninstaller logic to ensure more reliable operation:

- Properly stop, delete, and manage the Windows service during uninstall.
- Implement more robust process termination for SABnzbd.exe.
- Add checks to prevent accidental deletion of unrelated directories.
- Dynamically calculate and display the installed size in Add/Remove Programs.
- Correctly target user-specific settings during uninstallation.
2026-06-12 12:04:53 +02:00
Safihre 1ee9f56041 Correct pt_BR language code typo 2026-06-12 11:31:55 +02:00
Safihre 777db20158 Skip special Apple files during Moving stage
See https://forums.sabnzbd.org/viewtopic.php?t=27251
2026-06-11 13:52:59 +02:00
Safihre e969276828 Extend custom toggle switch styling to login and wizard 2026-06-11 11:40:11 +02:00
Safihre e3acbdc8c2 Use commit hash for static asset cache busting
Closes #3457
2026-06-11 11:10:43 +02:00
Safihre cc5c0037a5 Minor style fixes for front-end and variable names 2026-06-11 08:54:08 +02:00
mnightingale 45aafe0774 Also purge rss history for downloaded items (#3452) 2026-06-09 20:30:18 +02:00
Safihre afae687b6a Fix macOS app restart via LaunchServices
Previously, restarting the frozen macOS application directly launched the raw binary in Terminal.app. This change uses `open -n` and the application's bundle path to ensure the app relaunches correctly as a graphical application, providing a clean environment and proper menu-bar context. Fixes #3455.
2026-06-09 16:42:19 +02:00
Safihre 740eb442a8 Add toggle switches to Interface Settings modal 2026-06-08 23:02:45 +02:00
mnightingale a141dc942b Purge rss by seen_at (#3454) 2026-06-08 20:22:38 +02:00
Safihre 83d167415a Refine toggle styling implementation for Firefox compatibility
Replaces the `::after` pseudo-element with CSS `background-image` properties to draw the toggle's movable knob and its shadow.
2026-06-08 16:58:22 +02:00
Safihre 5c4d2f1d9f Improve on toggle redesign 2026-06-08 15:38:08 +02:00
Safihre 09c338dd5b Change checkboxes in Config to toggle 2026-06-08 14:44:31 +02:00
Safihre bff7d559f9 Refine button and navigation tab interactive states 2026-06-08 09:04:29 +02:00
SABnzbd Automation 51985d3bf6 Update translatable texts
[skip ci]
2026-06-08 04:48:38 +00:00
renovate[bot] 1f3cd7adbd Update all dependencies (#3453)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-06-08 06:48:04 +02:00
SABnzbd Automation 1f9a87e64f Update translatable texts
[skip ci]
2026-06-07 12:26:33 +00:00
Safihre 508ae12c8f Add warning for Windows connection limit
On Windows, `select()` has a hard limit of 512 connections. This change adds a check at startup to warn the user if their configured connections exceed this limit, preventing potential crashes.

Closes #3429
2026-06-07 14:25:49 +02:00
Safihre 92489bb624 Configure Renovate to track Python and sabctools versions
Adds custom managers to allow Renovate to detect and propose updates for the Python version used in the build workflow and the `SABCTOOLS_VERSION_REQUIRED` constant. Removes `sabctools` from the ignore list to enable these updates.
2026-06-07 13:03:03 +02:00
Safihre fa982d4f69 Update macOS build Python to 3.14.5 2026-06-07 12:42:59 +02:00
SABnzbd Automation 64f55c0834 Update translatable texts
[skip ci]
2026-06-07 06:43:44 +00:00
mnightingale babd8517e9 Database SQLite storage for RSS feeds (#3253)
* Store RSS in database

* Use context manager for rss repository in most instances

* Combine _evaluate_entry and _process_entry
2026-06-07 08:43:14 +02:00
Safihre 0950393b82 Correctly format tests based on new rules and remove star imports 2026-06-05 14:34:17 +02:00
Safihre 08bcddf8d2 Style login and wizard pages after recent changes 2026-06-05 14:32:04 +02:00
Safihre eae5f164e6 Correct rounding of the Warnings box 2026-06-05 10:39:07 +02:00
Safihre d5700bf8a3 Apply C4 linter rules for collection usage
Refactors collection initializations and comprehensions to use more concise and idiomatic Python syntax, such as set literals (`{...}`) instead of `set([...])`, empty dictionary literals (`{}`) instead of `dict()`, and `dict.fromkeys` for `str.maketrans`.
Also removes unnecessary list creation within `any()` calls.
2026-06-05 09:04:05 +02:00
Safihre d969d3df1a Skip File Rename When Paths Are Identical
Adds an explicit check to prevent unnecessary rename operations when the target path is the same as the original file's path. This provides clearer debug logging and avoids redundant filesystem calls.

Closes #3449, #3442
2026-06-04 13:59:40 +02:00
Safihre 5e3126cd14 Define NzoInfo with TypedDict
Explicitly defines the structure and types for the `nzo_info` dictionary.

Also introduces `BadArticleType` as a `Literal` type and cleans up
`__init__.py` by removing internal saver classes from the public API.
2026-06-04 12:48:05 +02:00
Safihre f3ef46e5c2 Add concurrency control to translations workflow
Cancel in-progress runs on the same branch to prevent redundant executions and conserve resources.
2026-06-04 11:08:31 +02:00
mnightingale ca5e02f2f1 Sanitise host_whitelist and login/request public IP addresses (#3437)
* Sanitise host_whitelist and login/request IP addresses

* Keep json keys

* Remove all IPv4 and IPv6 from logs

* Less crazy remote label replacements

* Remove unused import

* Sanitise public ipv4/ipv6 and test

* Add loopback/link-local test cases

* Switch is_local_addr to is_lan_addr because the former allows user configuration of trusted addresses

* Remove prefix group, ip regex should be good enough
2026-06-04 11:06:07 +02:00
renovate[bot] dc870495c5 Update all dependencies (#3448)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-06-04 11:05:39 +02:00
VisorCraft a6491750f7 Fix false "not writable" warning from non-atomic writability self-test (#3446)
directory_is_writable_with_file() removed its fixed-name temp file in a
non-atomic exists/remove/open/remove sequence wrapped in a blanket
"except Exception: return False". If the test file was removed between the
write and the final os.remove (e.g. by a concurrent writability check), the
FileNotFoundError was swallowed and reported as "is not writable at all.
This blocks downloads." -- a false negative on a fully writable folder.

Make the result depend only on creating and writing the file; treat cleanup
as best-effort. Genuine write failures still return False, and the unicode
and special-character capability checks are unchanged.

Adds regression tests for the cleanup race and for a genuine write failure.
2026-06-04 10:50:45 +02:00
mnightingale 6dacc71cb4 Add guards to blocking connections to prevent them processing the queue (#3444) 2026-06-04 10:48:48 +02:00
thezoggy 23c00b571c unrar 7.22 (#3447) 2026-06-03 10:02:02 +02:00
SABnzbd Automation cc61dc974b Update translatable texts
[skip ci]
2026-05-26 14:54:59 +00:00
mnightingale 7f421a3c0b Add logging of unhandled connection exceptions and wait_for result (#3440) 2026-05-26 16:54:19 +02:00
Safihre 2740ad20b6 Enable lazy evaluation of type hints in decorators
Addresses a runtime error encountered when using native union syntax (PEP 604) with `threading.Lock` and `RLock` in type hints. As factory functions, they cannot be directly used with `|` without `from __future__ import annotations`.
2026-05-26 16:54:05 +02:00
Safihre 57e371aba5 Pin tempora to 5.8.1
Tempora 5.9.0 introduced an incompatibility with jaraco.classes that caused issues. Downgrade to the previous stable version and prevent Renovate from automatically updating it again.
Closes #3441
2026-05-26 16:45:58 +02:00
Safihre c1096dc0ab Update type hints to use native union syntax and TypeAlias
Migrate to `A | B` syntax for unions (PEP 604) and introduce `typing.TypeAlias` for complex types, leveraging features available in Python 3.10+. This also updates `socket.timeout` to `TimeoutError` and adds `UP045` to Ruff ignores.
2026-05-26 16:44:49 +02:00
Safihre 08bdd12298 Enable slots for dataclasses
For improved performance. New in Python 3.10
2026-05-26 15:54:36 +02:00
Safihre 7b2e139e45 Update ruff target Python version to 3.10 2026-05-26 15:54:35 +02:00
renovate[bot] 1ce1440df8 Update all dependencies (#3436)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-05-26 15:53:21 +02:00
cardpuncher a7c897f2ad Add French & Turkish translations to appdata.xml (#3439)
Validation still passing:
$ appstreamcli validate --pedantic org.sabnzbd.sabnzbd.appdata.xml
✔ Validation was successful.
2026-05-26 14:38:21 +02:00
Safihre a344726c68 Drop support for Python 3.9
Luckily no Ubuntu LTS versions uses it!
https://documentation.ubuntu.com/ubuntu-for-developers/reference/availability/python/
2026-05-26 14:34:19 +02:00
Safihre b0c31c7e10 Pin tempora for Python 3.9 2026-05-26 09:50:46 +02:00
mnightingale 2c530fee47 Remove disableDelays because config is not defined (#3438) 2026-05-26 06:50:24 +02:00
Ryan HollisterandClaude Sonnet 4.6 25d20f0f08 Fix RuntimeError: dictionary changed size during iteration in stop_idle_jobs (#3432)
* Fix RuntimeError from dict mutation in stop_idle_jobs (#3431)

Collecting exhausted articles into a snapshot list while holding
nzf.lock, then calling register_article outside the iteration.
This prevents RuntimeError when register_article -> nzf.remove_article
pops from nzf.articles (a dict) while stop_idle_jobs is iterating it.

The original code in fd3ece31c used `nzf.articles[:]` which was safe
when articles was a list. When 44d94226e changed articles to a dict
the protective copy was dropped, leaving bare dict iteration that
mutates mid-loop.

The collect-then-act pattern matches nzf_remove_list in nzb/object.py
and the empty-nzo list already used in stop_idle_jobs itself. It also
correctly calls register_article outside nzf.lock, consistent with its
own "not locked for performance" contract.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Apply Black formatting to nzbqueue.py

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Soften comment per maintainer feedback

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 22:33:14 +02:00
SABnzbd Automation 3aaebed274 Update translatable texts
[skip ci]
2026-05-23 20:20:13 +00:00
Safihre f12725eea5 Adjust new rounded UI layout and spacing for consistency
Refines margins, paddings, and element positioning across various components to improve visual harmony and alignment with the modernized interface design.
2026-05-23 22:17:28 +02:00
Safihre dbf8ef15ef Refactor theming to use CSS variables
This modernizes the theming architecture by defining core colors as CSS variables,
allowing themes like 'Night' to override them concisely. This approach improves
maintainability and removes dynamic styling previously handled by JavaScript.
2026-05-23 22:05:46 +02:00
Safihre de5abc95f5 Further improvements to styling revamp 2026-05-23 22:05:45 +02:00
Safihre 04379e3faa Change main interface to more rounded version 2026-05-23 22:05:45 +02:00
mnightingale fec57377db Fix warnings when retrying jobs that have missing renamed files and fix the causes (#3428) 2026-05-23 18:59:44 +02:00
mnightingale 2f4750f77d Match previous RSS parsing behaviour (#3425)
* Match to previous RSS behaviour

* Cleanup logic due to normalisation

* Tests comparing 4.5.5 outcome

* Remove comment - fixed since 4.5.5

* Consistent types for prio, rule, season, and episode

* Rename matching_rule_index

* Fix type warnings
2026-05-22 22:34:30 +02:00
SABnzbd Automation 820442bdbd Update translatable texts
[skip ci]
2026-05-21 08:06:58 +00:00
Safihre e63648c9c4 Give the Config a little bit more modern feeling 2026-05-21 10:06:01 +02:00
Safihre a8751c5e77 Add extra logging when removing Movie category 2026-05-18 15:22:05 +02:00
renovate[bot] 196a92550c Update all dependencies (#3422)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-05-18 09:12:28 +02:00
SABnzbd Automation 526fcd3544 Update translatable texts
[skip ci]
2026-05-18 07:08:02 +00:00
Safihre f817e4f0fb Work around pyproject.toml handling in snap build
The Python plugin attempts to `pip install -U .` when `pyproject.toml`
is present. As SABnzbd is an application source tree and not a
pip-installable package, this temporarily hides `pyproject.toml`
during the build step to prevent an incorrect installation attempt.
2026-05-18 09:07:20 +02:00
mnightingale d7ca91e395 Try to clean the cache dir multiple times (#3419) 2026-05-16 22:43:07 +02:00
SABnzbd Automation e3c8d79dae Update translatable texts
[skip ci]
2026-05-16 14:16:43 +00:00
Safihre 2cb472f690 Allow setting options for URL-grabbing jobs
And correct mistake in priority-API field, which was 0 (int) instead of "Normal" fallback.
2026-05-16 16:15:43 +02:00
Safihre caee1d3bfe Front-end would show job with invalid date as URL-fetching
Closes #3418
2026-05-16 14:49:12 +02:00
Safihre 8069361faa Replace unreliable directory access check
The `os.access()` function can produce false negatives for directory writability, particularly in environments like NFS with UID mapping or root-squashing.

This change replaces the `os.access()` call with a direct I/O test (creating and deleting a temporary file) to ensure accurate and reliable verification of directory permissions.
2026-05-15 21:41:16 +02:00
Safihre 02c567fd8c Use reduced progress bar width in compact layout
Apply the table-header-progress-smaller class (25% width) to both queue and
history when compact layout is enabled, matching the behavior when extra columns
are shown. This provides more horizontal space for longer NZB titles.

Fixes #3414
2026-05-15 21:10:16 +02:00
Safihre 60f555117c Limit GitHub Actions workflow concurrency
Configures `concurrency` for the build, snap, and integration testing workflows. This prevents multiple concurrent runs of the same workflow on the same branch and automatically cancels older, in-progress runs when a new one is triggered.
2026-05-15 16:26:27 +02:00
Safihre f259e47110 URLGrabber would crash on new installs
Closes #3417
2026-05-15 14:45:01 +02:00
mnightingale 10ffcb2c82 Check SAB_CACHE_DIR exists first and allow to already exist (#3415) 2026-05-15 14:43:29 +02:00
mnightingale 1564ee3ccf Remove unreliable test (#3416) 2026-05-15 14:42:30 +02:00
SABnzbd Automation b6b992e521 Update translatable texts
[skip ci]
2026-05-15 07:39:06 +00:00
Safihre 290f85ca1c Extend Cleanup List with filename and path pattern matching (#3413)
* Extend Cleanup List with filename and path pattern matching

Previously limited to cleaning up files based only on their extensions, the 'Cleanup List' now supports more flexible matching. Users can specify:
- Exact filenames (e.g., `Thumbs.db`)
- Wildcard filename patterns (e.g., `*.tmp`, `cleanup.*`)
- Relative path patterns (e.g., `images/*`, `*/test.jpg`)

This enhancement provides greater control and flexibility for automated post-processing cleanup.
2026-05-15 09:38:22 +02:00
Safihre 80840ce743 Modernize Python type hints and enable Pyupgrade checks
Enable Pyupgrade (`UP`) checks in Ruff, which drives the modernization of type hints.
This includes replacing `typing.Deque` with `collections.deque` and `typing.Tuple` with built-in `tuple` where applicable, improving consistency and adhering to modern Python conventions.
2026-05-13 22:09:24 +02:00
Safihre 0434dca45f Enable additional Ruff checks and implement fixes 2026-05-13 21:50:32 +02:00
Safihre 81bc2a384f Add F401 unused imports check
This change enables `F401` (unused import) checks in Ruff and configures per-file ignores for intentional top-level imports. The newly enforced check led to the removal of several unused imports across the codebase.

Additionally, the module existence check for `certifi` was modernized to use `importlib.util.find_spec` for a more efficient and robust approach.
2026-05-13 20:37:55 +02:00
mnightingale 2184964d78 Reduce time to test (#3412)
* Remove UI delays

* Everything using set_config, set_platform, or pyfakefs

* tmp_path

* Scroll to top seems unnecessary but make it wait until it's done

* Remove sleep from test_rss_basic_flow

* Remove sleeps from test_daemonizing

* Define pytest markers

* Reduce sleep in test_queue_repair

* Remove sleeps from clean_cache_dir

* Suppress failures to connect during startup

* Reduce cache sleeps

* Reduce sleeps checking app started

* Reduce sleep removing cache dir
2026-05-13 18:49:41 +02:00
Safihre b942e06297 Introduce Ruff linter for improved code quality
Integrates the Ruff linter into the CI workflow alongside Black. This enhances code quality and consistency by catching common issues. Addresses an initial `F821` (undefined name) finding in `__init__.py` identified by Ruff, ensuring explicit module referencing.
2026-05-13 14:25:06 +02:00
SABnzbd Automation 5598db0d9e Update translatable texts
[skip ci]
2026-05-13 12:00:14 +00:00
Safihre f7ad95f298 Consolidate RSS feed processing and remove 'Apply filters' action
Performance of Apply Feed is good enough that we can run it on any filter change.
2026-05-13 13:59:17 +02:00
Safihre 61e30ab419 Generalize .gitignore rules for hidden directories
Replaces numerous specific ignore entries (e.g., `.idea`, `.vscode`, `.cache`, editor backups) with a blanket rule to ignore all hidden root-level directories (`.*/`). Necessary exceptions like `.github/` and `.tx/` are explicitly un-ignored. This approach simplifies maintenance and prevents accidental commits of new tool-generated files.
2026-05-13 13:41:56 +02:00
SABnzbd Automation 90798e5e4d Update translatable texts
[skip ci]
2026-05-11 13:57:52 +00:00
Safihre 8be8af018f Refactor RSS processing for clarity and option precedence
Refactor RSS feed processing logic to enhance clarity and ensure correct precedence for resolved options. Renames functions and variables for better understanding, and inlines feed configuration logic. Adds new tests to verify how category, post-processing, script, and priority settings are applied hierarchically. Also provides a default display for empty RSS log categories.
2026-05-11 15:45:24 +02:00
Safihre 7abb39d02d Move black config to pyproject
So later we can also add ruff
2026-05-10 12:00:01 +02:00
292 changed files with 23349 additions and 11567 deletions

No files matched your search

+25 -2
View File
@@ -17,13 +17,36 @@
"builder/release-requirements.txt"
]
},
"customManagers": [
{
"customType": "regex",
"fileMatch": ["^\\.github/workflows/build_release\\.yml$"],
"matchStrings": ["PYTHON_VERSION:\\s*\"(?<currentValue>.*?)\""],
"datasourceTemplate": "python-version",
"depNameTemplate": "python"
},
{
"customType": "regex",
"fileMatch": ["^sabnzbd/constants\\.py$"],
"matchStrings": ["SABCTOOLS_VERSION_REQUIRED\\s*=\\s*\"(?<currentValue>.*?)\""],
"datasourceTemplate": "pypi",
"depNameTemplate": "sabctools"
},
{
"customType": "regex",
"fileMatch": ["^\\.github/workflows/translations\\.yml$"],
"matchStrings": ["TX_VERSION=\"(?<currentValue>.*?)\""],
"datasourceTemplate": "github-releases",
"depNameTemplate": "transifex/cli"
}
],
"ignorePaths": [],
"ignoreDeps": [
"jaraco.text",
"jaraco.context",
"jaraco.collections",
"sabctools",
"paho-mqtt"
"tempora",
"cryptography"
],
"packageRules": [
{
+43 -29
View File
@@ -2,9 +2,16 @@ name: Build binaries and source distribution
on: [push, pull_request]
# Setting PYTHONNODEBUGRANGES reduces binary size
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
# Setting PYTHONNODEBUGRANGES reduces binary size
PYTHONNODEBUGRANGES: 1
# For macOS we download the official Python, because the GitHub one only support newer macOS 11+ versions
# We also use the specific pin for Windows release, so Renovate can update it all at once
PYTHON_VERSION: "3.14.7"
jobs:
build_windows:
@@ -22,11 +29,11 @@ jobs:
runs-on: ${{ matrix.runs-on }}
timeout-minutes: 15
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
- name: Set up Python
uses: actions/setup-python@v6
uses: actions/setup-python@v7
with:
python-version: "3.14"
python-version: ${{ env.PYTHON_VERSION }}
architecture: ${{ matrix.architecture }}
cache: pip
cache-dependency-path: "**/requirements.txt"
@@ -34,19 +41,21 @@ jobs:
# Without dependencies to make sure everything is covered in the requirements.txt
# Special settings:
# cryptography see https://github.com/pyca/cryptography/pull/14216
# charset-normalizer see https://github.com/jawah/charset_normalizer/issues/714
run: |
python --version
python -m pip install --upgrade pip wheel
pip install --upgrade -r requirements.txt --no-dependencies --only-binary=cryptography --no-binary charset-normalizer
pip install --upgrade -r builder/requirements.txt --no-dependencies
pip install -r requirements.txt --no-dependencies --only-binary=cryptography
pip install -r builder/requirements.txt --no-dependencies
- name: Build Windows standalone binary
id: windows_binary
run: python builder/package.py binary
- name: Upload Windows standalone binary (unsigned)
# When releasing, SignPath consumes this artifact directly and requires the
# zip-in-zip layout because they don't support the archive-flag
uses: actions/upload-artifact@v7
id: upload-unsigned-binary
with:
archive: ${{ contains(github.ref, 'refs/tags/') }}
path: "*-win*-bin.zip"
name: Windows standalone binary (${{ matrix.architecture }})
- name: Sign Windows standalone binary
@@ -62,11 +71,13 @@ jobs:
wait-for-completion: true
output-artifact-directory: "signed"
- name: Upload Windows standalone binary (signed)
# The release file is a .zip, which download-artifact would extract if
# uploaded raw, so keep it archived and let the release job unpack it
uses: actions/upload-artifact@v7
if: contains(github.ref, 'refs/tags/')
with:
name: Windows standalone binary (${{ matrix.architecture }}, signed)
path: "signed"
path: "signed/*-win*-bin.zip"
- name: Build Windows installer
if: matrix.architecture == 'x64'
run: python builder/package.py installer
@@ -75,6 +86,7 @@ jobs:
uses: actions/upload-artifact@v7
id: upload-unsigned-installer
with:
archive: ${{ contains(github.ref, 'refs/tags/') }}
path: "*-win-setup.exe"
name: Windows installer (${{ matrix.architecture }})
- name: Sign Windows installer
@@ -93,7 +105,7 @@ jobs:
if: matrix.architecture == 'x64' && contains(github.ref, 'refs/tags/')
uses: actions/upload-artifact@v7
with:
name: Windows installer (${{ matrix.architecture }}, signed)
archive: false
path: "signed/*-win-setup.exe"
build_macos:
@@ -101,26 +113,24 @@ jobs:
runs-on: macos-14
timeout-minutes: 15
env:
# We need the official Python, because the GA ones only support newer macOS versions
# The deployment target is picked up by the Python build tools automatically
# If updated, make sure to also set LSMinimumSystemVersion in SABnzbd.spec
PYTHON_VERSION: "3.14.4"
MACOSX_DEPLOYMENT_TARGET: "10.15"
# We need to force compile for universal2 support
CFLAGS: -arch x86_64 -arch arm64
ARCHFLAGS: -arch x86_64 -arch arm64
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
- name: Set up Python
# Only use this for the caching of pip packages!
uses: actions/setup-python@v6
uses: actions/setup-python@v7
with:
python-version: "3.14"
cache: pip
cache-dependency-path: "**/requirements.txt"
- name: Cache Python download
id: cache-python-download
uses: actions/cache@v5
uses: actions/cache@v6
with:
path: ~/python.pkg
key: cache-macOS-Python-${{ env.PYTHON_VERSION }}
@@ -134,8 +144,8 @@ jobs:
run: |
python3 --version
pip3 install --upgrade pip wheel
pip3 install --upgrade -r requirements.txt --no-binary cffi,CT3,PyYAML,charset_normalizer --no-dependencies
pip3 install --upgrade -r builder/requirements.txt --no-dependencies
pip3 install -r requirements.txt --no-binary cffi,CT3,PyYAML,httptools --no-dependencies
pip3 install -r builder/requirements.txt --no-dependencies
- name: Import macOS codesign certificates
# Taken from https://github.com/Apple-Actions/import-codesign-certs/pull/27 (comments)
env:
@@ -157,8 +167,8 @@ jobs:
- name: Upload source distribution
uses: actions/upload-artifact@v7
with:
archive: false
path: "*-src.tar.gz"
name: Source distribution
- name: Build macOS binary
env:
SIGNING_AUTH: ${{ secrets.SIGNING_AUTH }}
@@ -169,8 +179,8 @@ jobs:
- name: Upload macOS binary
uses: actions/upload-artifact@v7
with:
archive: false
path: "*-macos.dmg"
name: macOS binary
release:
name: Prepare Release
@@ -179,30 +189,34 @@ jobs:
if: contains(github.ref, 'refs/tags/')
environment: release
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
- name: Set up Python
uses: actions/setup-python@v6
uses: actions/setup-python@v7
with:
python-version: "3.14"
cache: pip
cache-dependency-path: "builder/release-requirements.txt"
- name: Download Source distribution artifact
- name: Download release artifacts
# The installer, source and macOS builds are uploaded with archive: false,
# so their artifact name is the release filename
uses: actions/download-artifact@v8
with:
name: Source distribution
- name: Download macOS artifact
pattern: "SABnzbd-*"
merge-multiple: true
- name: Download signed Windows binaries
# These stay archived (.zip release files), so download-artifact unpacks
# them back into the release .zip
uses: actions/download-artifact@v8
with:
name: macOS binary
- name: Download Windows artifacts
uses: actions/download-artifact@v8
with:
pattern: ${{ (contains(github.ref, 'refs/tags/')) && '*signed*' || '*Windows*' }}
pattern: "*signed*"
merge-multiple: true
- name: Prepare official release
env:
AUTOMATION_GITHUB_TOKEN: ${{ secrets.AUTOMATION_GITHUB_TOKEN }}
REDDIT_TOKEN: ${{ secrets.REDDIT_TOKEN }}
LINUXSERVER_WEBHOOK_TOKEN: ${{ secrets.LINUXSERVER_WEBHOOK_TOKEN }}
DISCORD_WEBHOOK_TESTING: ${{ secrets.DISCORD_WEBHOOK_TESTING }}
DISCORD_WEBHOOK_STABLE: ${{ secrets.DISCORD_WEBHOOK_STABLE }}
run: |
pip3 install -r builder/release-requirements.txt
pip3 install -r builder/release-requirements.txt --no-dependencies
python3 builder/release.py
+16 -4
View File
@@ -12,6 +12,10 @@ on:
- 'requirements.txt'
- '.github/workflows/build_snaps.yml'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: read
@@ -30,9 +34,9 @@ jobs:
linux_arch: arm64
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
- name: Cache par2cmdline-turbo tarball
uses: actions/cache@v5
uses: actions/cache@v6
id: cache-par2cmdline
# Clearing the cache in case of new version requires manual clearing in GitHub!
with:
@@ -50,8 +54,16 @@ jobs:
run: |
sudo snap install --dangerous *.snap
sudo snap connect sabnzbd:removable-media
# Basic smoke test - check that the binary exists and can show help
timeout 10s snap run sabnzbd --help || true
# Installing a daemon snap starts the service automatically.
if ! curl -fsSL -o /dev/null \
--retry 30 --retry-delay 2 --retry-connrefused --retry-all-errors \
http://127.0.0.1:8080/; then
echo "::error::SABnzbd web UI did not come up"
sudo snap logs sabnzbd -n 200 || true
systemctl status snap.sabnzbd.sabnzbd.service --no-pager || true
exit 1
fi
echo "SABnzbd web UI responded"
sudo snap remove sabnzbd
- name: Upload snap
uses: actions/upload-artifact@v7
+32
View File
@@ -0,0 +1,32 @@
name: "Claude Review (manual)"
on:
issue_comment:
types: [created]
permissions:
contents: read
pull-requests: write
issues: read
jobs:
review:
name: "Review PR on @claude mention"
# Only PR comments containing @claude, and only from maintainers
if: >
github.repository_owner == 'sabnzbd' &&
github.event.issue.pull_request &&
contains(github.event.comment.body, '@claude') &&
contains(fromJSON('["OWNER", "MEMBER", "COLLABORATOR"]'), github.event.comment.author_association)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
with:
# Full history so the review can diff the PR against its base branch
fetch-depth: 0
- uses: anthropics/claude-code-action@v1
with:
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
github_token: ${{ secrets.GITHUB_TOKEN }}
# Actual triggering is gated by the job-level `if` above; this disables
# the action's internal write-permission check, which contributors fail
allowed_non_write_users: "*"
+22 -13
View File
@@ -2,18 +2,22 @@ name: CI Tests
on: [push, pull_request]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: read
jobs:
black:
name: Black Code Formatter
black-and-ruff:
name: Black and Ruff checks
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- run: pip install black
# Tools folder excluded for now due to https://github.com/psf/black/issues/4963
- run: >
- uses: actions/checkout@v7
- run: pip install ruff black
- name: Black code formatter
run: >
black
SABnzbd.py
sabnzbd
@@ -21,9 +25,11 @@ jobs:
builder
builder/SABnzbd.spec
tests
--line-length=120
tools
--check
--diff
- name: Ruff checks
run: ruff check
test:
name: Test ${{ matrix.name }} - Python ${{ matrix.python-version }}
@@ -32,7 +38,8 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: [ "3.9", "3.10", "3.11", "3.12", "3.13", "3.14" ]
# TODO: Remove libxml2-dev libxslt-dev below once 3.15 is stable and lxml releases wheels!
python-version: [ "3.10", "3.11", "3.12", "3.13", "3.14", "3.15-dev" ]
name: ["Linux"]
os: [ubuntu-latest]
include:
@@ -44,21 +51,23 @@ jobs:
python-version: "3.14"
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v6
uses: actions/setup-python@v7
with:
python-version: ${{ matrix.python-version }}
cache: pip
cache-dependency-path: "**/requirements.txt"
- name: Install system dependencies
if: runner.os == 'Linux'
run: sudo apt-get install unrar 7zip par2
run: sudo apt-get install unrar 7zip par2 libxml2-dev libxslt-dev
- name: Install Python dependencies
run: |
python --version
python -m pip install --upgrade pip wheel
pip install --upgrade -r requirements.txt --no-dependencies
pip install -r requirements.txt --no-dependencies
pip install --upgrade -r tests/requirements.txt
- name: Install Playwright browser
run: python -m playwright install chromium --with-deps
- name: Test SABnzbd
run: pytest -s
run: pytest -n auto --dist loadscope
+2 -2
View File
@@ -14,7 +14,7 @@ jobs:
if: github.repository_owner == 'sabnzbd'
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v10
- uses: actions/stale@v11
with:
days-before-stale: 21
days-before-close: 7
@@ -23,7 +23,7 @@ jobs:
This issue has been automatically marked as stale because it has not had
recent activity. It will be closed if no further activity occurs. Thank you
for your contributions.
exempt-issue-labels: "Feature request, Work in progress, Bug"
only-issue-labels: "Support"
lock:
name: "Lock old issues"
+18 -4
View File
@@ -5,6 +5,10 @@ on:
branches:
- develop
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: write
@@ -12,10 +16,14 @@ jobs:
translations:
name: Update translatable texts
runs-on: ubuntu-latest
timeout-minutes: 10
env:
TX_TOKEN: ${{ secrets.TX_TOKEN }}
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
with:
# Need the parent commit too so we can diff HEAD and detect local translation edits
fetch-depth: 2
- name: Generate translatable texts
run: |
python3 tools/extract_pot.py
@@ -23,13 +31,19 @@ jobs:
if: env.TX_TOKEN
# We use fixed version and manual download (instead of their sh script) to prevent potential supply-chain attack.
# Changes are commited directly to the repo, so this is quite sensative and worth the extra hassle!
# Add --translation to the push command in order to update Transifex using local translation edits
# However, this prevents modifying existing translations in Transifex as they will be overwritten by the push!
# When the current commit contains local translation edits (changes to po/**/*.po), we add --translation so
# those edits are pushed to Transifex. Note: this overwrites the matching translations in Transifex with our
# local versions, so we only do it when local .po files actually changed.
run: |
TX_VERSION="v1.6.17"
curl -fsSL "https://github.com/transifex/cli/releases/download/${TX_VERSION}/tx-linux-amd64.tar.gz" -o tx-linux-amd64.tar.gz
tar -xzf tx-linux-amd64.tar.gz
./tx push --source
PUSH_ARGS="--source"
if git show --name-only --format= HEAD | grep -qE '^po/.+\.po$'; then
echo "Local translation edits detected; also pushing translations to Transifex."
PUSH_ARGS="--source --translation"
fi
./tx push ${PUSH_ARGS}
./tx pull --all --force
- name: Compile translations to validate them
run: |
+201
View File
@@ -0,0 +1,201 @@
name: Update bundled tools
on:
schedule:
# Nightly check for new releases of the bundled tools
- cron: "30 4 * * *"
workflow_dispatch:
permissions:
contents: write
pull-requests: write
jobs:
unrar:
name: Update unrar
if: github.repository == 'sabnzbd/sabnzbd'
runs-on: ubuntu-latest
env:
GH_TOKEN: ${{ secrets.AUTOMATION_GITHUB_TOKEN }}
BRANCH: automation/update-unrar
steps:
- uses: actions/checkout@v7
- name: Determine latest unrar version
id: version
run: |
RAW_VERSION=$(curl -fsSL --retry 3 https://www.rarlab.com/download.htm -o- | sed -n 's%.*/rarmacos-x64-\([0-9]\+\)\.tar\.gz.*%\1%p' | head -1)
if [ -z "$RAW_VERSION" ]; then
echo "Could not determine the latest unrar version from the rarlab download page"
exit 1
fi
# Version is encoded without separator, e.g. 723 = 7.23
VERSION="${RAW_VERSION%??}.${RAW_VERSION: -2}"
echo "Latest unrar version: $VERSION"
echo "raw_version=$RAW_VERSION" >> "$GITHUB_OUTPUT"
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
- name: Download and extract new binaries
env:
RAW_VERSION: ${{ steps.version.outputs.raw_version }}
run: |
mkdir -p "$RUNNER_TEMP/unrar"
cd "$RUNNER_TEMP/unrar"
for FILE in "rarmacos-x64-$RAW_VERSION.tar.gz" "rarmacos-arm-$RAW_VERSION.tar.gz" "winrar-x64-$RAW_VERSION.exe"; do
curl -fsSL --retry 3 -o "$FILE" "https://www.rarlab.com/rar/$FILE"
done
mkdir macos-x64 macos-arm64 windows
tar -xzf "rarmacos-x64-$RAW_VERSION.tar.gz" -C macos-x64
tar -xzf "rarmacos-arm-$RAW_VERSION.tar.gz" -C macos-arm64
# Unpack UnRAR.exe from the WinRAR installer, which is a self-extracting
# RAR archive that the preinstalled 7-Zip can read
7z x -y -owindows "winrar-x64-$RAW_VERSION.exe" UnRAR.exe > /dev/null
- name: Copy binaries into the repository
run: |
install -m 755 "$RUNNER_TEMP/unrar/macos-x64/rar/unrar" macos/unrar/unrar
install -m 755 "$RUNNER_TEMP/unrar/macos-arm64/rar/unrar" macos/unrar/arm64/unrar
install -m 755 "$RUNNER_TEMP/unrar/windows/UnRAR.exe" win/unrar/UnRAR.exe
- name: Create pull request
env:
VERSION: ${{ steps.version.outputs.version }}
run: |
if git diff --quiet; then
echo "Bundled unrar is already at version $VERSION"
exit 0
fi
git config user.name "SABnzbd Automation"
git config user.email "bugs@sabnzbd.org"
git add macos/unrar win/unrar
git commit -m "unrar $VERSION"
# Skip the push if an open PR already contains these exact binaries
OPEN_PRS=$(gh pr list --head "$BRANCH" --state open --json number --jq length)
if [ "$OPEN_PRS" != "0" ] && git fetch origin "$BRANCH" && git diff --quiet FETCH_HEAD HEAD -- macos/unrar win/unrar; then
echo "Existing pull request already updates unrar to $VERSION"
exit 0
fi
git push --force origin "HEAD:refs/heads/$BRANCH"
if [ "$OPEN_PRS" = "0" ]; then
gh pr create --head "$BRANCH" --title "unrar $VERSION" --body "Automated update of the bundled unrar binaries to version $VERSION.
[Changelog](https://www.rarlab.com/rarnew.htm)"
fi
sevenzip:
name: Update 7-Zip
if: github.repository == 'sabnzbd/sabnzbd'
runs-on: ubuntu-latest
env:
GH_TOKEN: ${{ secrets.AUTOMATION_GITHUB_TOKEN }}
BRANCH: automation/update-7zip
steps:
- uses: actions/checkout@v7
- name: Determine latest 7-Zip version
id: version
run: |
VERSION=$(gh api repos/ip7z/7zip/releases/latest --jq .tag_name)
# Assets are named without separator, e.g. 7z2602-extra.7z for 26.02
RAW_VERSION="${VERSION//./}"
echo "Latest 7-Zip version: $VERSION"
echo "raw_version=$RAW_VERSION" >> "$GITHUB_OUTPUT"
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
- name: Download and extract new binaries
env:
RAW_VERSION: ${{ steps.version.outputs.raw_version }}
VERSION: ${{ steps.version.outputs.version }}
run: |
mkdir -p "$RUNNER_TEMP/7zip"
cd "$RUNNER_TEMP/7zip"
gh release download "$VERSION" --repo ip7z/7zip \
--pattern "7z$RAW_VERSION-extra.7z" --pattern "7z$RAW_VERSION-mac.tar.xz"
mkdir macos windows
tar -xJf "7z$RAW_VERSION-mac.tar.xz" -C macos
# The preinstalled 7-Zip unpacks the extra package that contains
# the standalone Windows console version
7z x -y -owindows "7z$RAW_VERSION-extra.7z" 7za.exe > /dev/null
- name: Copy binaries into the repository
run: |
install -m 644 "$RUNNER_TEMP/7zip/windows/7za.exe" win/7zip/7za.exe
install -m 755 "$RUNNER_TEMP/7zip/macos/7zz" macos/7zip/7zz
- name: Create pull request
env:
VERSION: ${{ steps.version.outputs.version }}
run: |
if git diff --quiet; then
echo "Bundled 7-Zip is already at version $VERSION"
exit 0
fi
git config user.name "SABnzbd Automation"
git config user.email "bugs@sabnzbd.org"
git add macos/7zip win/7zip
git commit -m "7-Zip $VERSION"
# Skip the push if an open PR already contains these exact binaries
OPEN_PRS=$(gh pr list --head "$BRANCH" --state open --json number --jq length)
if [ "$OPEN_PRS" != "0" ] && git fetch origin "$BRANCH" && git diff --quiet FETCH_HEAD HEAD -- macos/7zip win/7zip; then
echo "Existing pull request already updates 7-Zip to $VERSION"
exit 0
fi
git push --force origin "HEAD:refs/heads/$BRANCH"
if [ "$OPEN_PRS" = "0" ]; then
gh pr create --head "$BRANCH" --title "7-Zip $VERSION" --body "Automated update of the bundled 7-Zip binaries to version $VERSION.
[Release notes](https://github.com/ip7z/7zip/releases/tag/$VERSION)"
fi
par2:
name: Update par2cmdline-turbo
if: github.repository == 'sabnzbd/sabnzbd'
runs-on: ubuntu-latest
env:
GH_TOKEN: ${{ secrets.AUTOMATION_GITHUB_TOKEN }}
BRANCH: automation/update-par2
steps:
- uses: actions/checkout@v7
- name: Determine latest par2cmdline-turbo version
id: version
run: |
TAG=$(gh api repos/animetosho/par2cmdline-turbo/releases/latest --jq .tag_name)
# Assets are named without the leading v, e.g. par2cmdline-turbo-1.4.0-win-x64.zip for tag v1.4.0
VERSION="${TAG#v}"
echo "Latest par2cmdline-turbo version: $VERSION"
echo "tag=$TAG" >> "$GITHUB_OUTPUT"
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
- name: Download and extract new binaries
env:
TAG: ${{ steps.version.outputs.tag }}
VERSION: ${{ steps.version.outputs.version }}
run: |
mkdir -p "$RUNNER_TEMP/par2"
cd "$RUNNER_TEMP/par2"
for ARCH in win-x64 win-arm64 macos-universal; do
gh release download "$TAG" --repo animetosho/par2cmdline-turbo \
--pattern "par2cmdline-turbo-$VERSION-$ARCH.zip"
unzip -q -d "$ARCH" "par2cmdline-turbo-$VERSION-$ARCH.zip"
done
- name: Copy binaries into the repository
run: |
install -m 644 "$RUNNER_TEMP/par2/win-x64/par2.exe" win/par2/par2.exe
install -m 644 "$RUNNER_TEMP/par2/win-arm64/par2.exe" win/par2/arm64/par2.exe
install -m 755 "$RUNNER_TEMP/par2/macos-universal/par2" macos/par2/par2
- name: Create pull request
env:
TAG: ${{ steps.version.outputs.tag }}
VERSION: ${{ steps.version.outputs.version }}
run: |
if git diff --quiet; then
echo "Bundled par2cmdline-turbo is already at version $VERSION"
exit 0
fi
git config user.name "SABnzbd Automation"
git config user.email "bugs@sabnzbd.org"
git add macos/par2 win/par2
git commit -m "par2cmdline-turbo $VERSION"
# Skip the push if an open PR already contains these exact binaries
OPEN_PRS=$(gh pr list --head "$BRANCH" --state open --json number --jq length)
if [ "$OPEN_PRS" != "0" ] && git fetch origin "$BRANCH" && git diff --quiet FETCH_HEAD HEAD -- macos/par2 win/par2; then
echo "Existing pull request already updates par2cmdline-turbo to $VERSION"
exit 0
fi
git push --force origin "HEAD:refs/heads/$BRANCH"
if [ "$OPEN_PRS" = "0" ]; then
gh pr create --head "$BRANCH" --title "par2cmdline-turbo $VERSION" --body "Automated update of the bundled par2cmdline-turbo binaries to version $VERSION.
[Release notes](https://github.com/animetosho/par2cmdline-turbo/releases/tag/$TAG)"
fi
+5 -13
View File
@@ -29,28 +29,20 @@ SABnzbd-*/
# WingIDE/PyCharm project files
*.wp[ru]
.idea
# VScode
.vscode/
# Testing folders
.cache
.xprocess
.pytest*
tests/cache
tests/cache_gw*
# General junk
*.keep
*.bak
*.log
# Some people use Emacs as an editor
\#*
.\#*
# AI tools
.claude/
# Ignore all hidden directories at repo root, except used ones
.*/
!.github/
!.tx/
# Apple
.DS_Store
+4 -17
View File
@@ -5,6 +5,7 @@ The SABnzbd-Team is:
Active team:
Safihre
mnightingale
sanderjo
jcfp
inpheaux
@@ -17,21 +18,8 @@ Sleeping members:
Honorary member (and original author):
Gregor Kaufmann
The main contributors and moderators of the translations:
Danish: Rene (nordjyden6), Scott
Dutch: ShyPike, Safihre
French: rAf, Fox Ace, Fred, Morback, Jih
German: Severin Heiniger, Tim Hartmann, DonPizza, Alex
Norwegian: Protx, mjelva, TomP, John
Romanian: nicusor
Serbian: Ozzii, Krišan Darko
Swedish: Malmis, Kim Joahnsson, Patrik-liind, Chris M
Spanish: Syquus, Adolfo Jayme
Portuguese (Brazil): lrrosa, diegosps
Russian: Pavel Maryanov
Polish: Tomasz 'Zen' Napierala
Chinese: XsLiDian
Finnish: Matti Ylönen
The contributors and moderators of the translations are credited in the
header of each translation file in the po/ directory.
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
@@ -44,7 +32,6 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
along with this program; if not, see <https://www.gnu.org/licenses/>.
See accompanying files GPL2.txt and GPL3.txt.
-110
View File
@@ -1,110 +0,0 @@
SABnzbd
-------------------------------------------------------------------------------
0) LICENSE
-------------------------------------------------------------------------------
(c) Copyright 2007-2026 by The SABnzbd-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
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
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 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 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 Windows binaries
-------------------------------------------------------------------------------
Unzip pre-built version to any folder of your liking.
Start the SABnzbd.exe program.
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 macOS binaries
-------------------------------------------------------------------------------
Download the DMG file, mount and drag the SABnzbd icon to Applications.
Just like you do with so many apps.
-------------------------------------------------------------------------------
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
Only Python 3.9 and above is supported.
On Linux systems you need to install:
par2 unrar python3-setuptools python3-pip
On non-X86 platforms, for which PyPI does not provide all pre-compiled packages,
you also need to install these development libraries (exact names might differ per platform):
libffi-dev libssl-dev
Unpack the ZIP-file containing the SABnzbd sources to any folder of your liking.
Inside the SABnzbd source directory, install all required Python packages by running:
python3 -m pip install -r requirements.txt
If you want non-English languages, you need to compile the translations.
Start this from a shell terminal (or command prompt):
python3 tools/make_mo.py
To start SABnzbd, run this from a shell terminal (or command prompt):
python3 -OO SABnzbd.py
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
-------------------------------------------------------------------------------
Your browser may start up with just an error page.
This means that SABnzbd cannot use the default port 8080 to run its web-server on.
Try to use another port, you'll need to use the a command window:
SABnzbd.exe -s localhost:7777
or
python3 SABnzbd.py -s localhost:7777
You may of course try other port numbers too.
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.
-------------------------------------------------------------------------------
6) MORE INFORMATION
-------------------------------------------------------------------------------
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.
-50
View File
@@ -1,50 +0,0 @@
*******************************************
*** Known issues ***
*******************************************
- To prevent unexpectedly large NZBs from eating your download quota you can set
the option 'size_limit' on the Config->Special page.
Any NZB larger than this size will be set to paused and get a low priority.
- 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.
- 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/3.4/special
- Some third-party utilities 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/3.4/special
- On macOS you may encounter downloaded files with foreign characters.
The par2 repair may fail when the files were created on a Windows system.
The problem is caused by the PAR2 utility and we cannot fix this now.
This does not apply to files inside RAR files.
- The "Watched Folder" sometimes fails to delete the NZB files it has
processed. This happens when other software still accesses these files.
Some third-party utilities supporting SABnzbd are known to do this.
We cannot solve this problem, because the Operating System (read Windows)
prevents the removal.
- When SABnzbd cannot send notification emails, check your virus scanner,
firewall or security suite. It may be blocking outgoing email.
- When you are using external drives or network shares on macOS or Linux
make sure that the drives are mounted.
The operating system will simply redirect your files to alternative locations.
You may have trouble finding the files when mounting the drive later.
On macOS, SABnzbd will not create new folders in /Volumes.
The result will be a failed job that can be retried once the volume has been mounted.
- If you use a mounted drive as "temporary download folder", it must be present when SABnzbd
starts up. If not, SABnzbd will use the default location.
You can make SABnzbd wait for a mount of the "temporary download folder" by setting
Config->Special->wait_for_dfolder to 1.
SABnzbd will appear to hang until the drive is mounted.
+3 -3
View File
@@ -16,15 +16,15 @@ If you want to know more you can head over to our website: https://sabnzbd.org.
SABnzbd has a few 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` (Python 3.9 and above, often called `python3`)
- Python modules listed in `requirements.txt`. Install with `python3 -m pip install -r requirements.txt -U`
- `python` (Python 3.10 and above, often called `python3`)
- Python modules listed in `requirements.txt`. Install with `python3 -m pip install -r requirements.txt`
- `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)
Optional:
- See `requirements.txt`
Your package manager should supply these. If not, we've got links in our [installation guide](https://github.com/sabnzbd/sabnzbd/blob/master/INSTALL.txt).
Your package manager should supply these. If not, we've got links in our [installation guide](https://sabnzbd.org/wiki/installation/install-off-modules).
## Running SABnzbd from source
+39 -54
View File
@@ -1,68 +1,53 @@
Release Notes - SABnzbd 5.0.0 Release Candidate 3
Release Notes - SABnzbd 5.1.0 Release Candidate 2
=========================================================
This is the third (and hopefully final) release candidate of version 5.0.
Please note the breaking changes described below!
This is the second release candidate of version 5.1.
## New features in 5.0.0
## New features in 5.1.0
* Added support for NNTP Pipelining which eliminates idle waiting between
requests, significantly improving speeds on high-latency connections.
Read more here: https://sabnzbd.org/wiki/advanced/nntp-pipelining
New servers will have `Articles per request` set to `2` by default.
* Implemented Direct Write to optimize assembly of downloaded files.
Read more here: https://sabnzbd.org/wiki/advanced/direct-write
* Post-processing scripts will always be executed, even for failed jobs.
Make sure to check the status of the job in your scripts!
* Removed Special setting `empty_postproc` as it is no longer needed.
* Complete redesign of article cache.
* Improved disk speed measurement in Status window.
* Reduce delays between jobs during post-processing.
* Improve handling of disks getting full.
* Diskspace check will now included Category-specific folders.
* If a download only has `.nzb` files inside, the new downloads
will include the name of the original download.
* No longer show tracebacks in the browser, only in the logs.
* Dropped support for Python 3.8.
* Windows: Added Windows ARM (portable) release.
* Windows and macOS: Added HTML version of the Release Notes.
* Windows and macOS: Update Python to 3.14.4, Unrar to 7.20,
par2cmdline-turbo to 1.4 and 7zip to 26.00.
* Refreshed the interface.
* When Retrying a job, only the actually missing articles are tried again.
This only works for jobs downloaded in 5.1.0 (or newer).
* RSS items are now stored in the database instead of on disk.
* New RSS `Age` rule to filter jobs based on their age.
* Downloaded RSS items are cleared from the RSS database after 3 days.
* Added support for filename and path pattern matching to the `Cleanup List`.
* `Unwanted extensions` will also be removed after unpacking.
* Allow job setting changes directly from `Extra queue columns`.
* Added support for unpacking `.tar` files during post-processing.
* Improved anonymization of the logs when using `Show Logging`.
* Added `SAB_FILES` environment variable to Post-processing scripts,
listing all files that resulted from the job.
* New Servers will default to 16 connections instead of 8.
* Use media duration as part of `Ignore Samples` detection.
* Added option to (auto) sort the queue by Remaining Percentage.
* Improved support for screen readers.
* Parsing of header-encrypted RARs will use significantly fewer resources.
* Add Ayatana AppIndicator tray support on GNOME.
* Removed redundant `INSTALL.txt` file.
* Dropped support for Python 3.9.
* Windows and macOS: Updated Python to 3.14.6, Unrar to 7.23 and 7zip to 26.02.
## Bug fixes in 5.0.0
* `Check before download` could get stuck or fail to reject.
* Several stability changes for the core download process.
* If only par2 files were missing, jobs could get incorrectly aborted.
* No error was shown in case NZB upload failed.
* Correct mobile layout if `Full Width` is enabled.
* Handle passwords that exceed Unrar's password limit.
* Aborted Direct Unpack could result in no files being unpacked.
* Encrypted RAR detection could sometimes fail.
* Unwanted Extension check was unreliable on passworded downloads.
* No longer exclusively lock all directory operations.
* Improved handling of non-NFC unicode filenames.
* Sorting of files inside jobs was inconsistent.
* Linux: No longer break test-data in release package.
* Windows: Tray icon disappears after Explorer restart.
* macOS: Slow to start on some network setups.
## Changes to default settings for new installations in 5.0.0
* Default `Minimum Free Space for Temporary Download Folder` set to `500M`.
* Enabled `verify_xff_header` setting by default.
## Bug fixes in 5.1.0
* Prevent incorrect warnings about non-writeable directories.
* Files unrelated to job could get removed by the `Cleanup List` logic.
* Prevent path traversal in orphaned job APIs.
* `Disk Full` errors from unrar were not handled gracefully during unpacking.
* Memory leak could occur during article decoding.
* In containers (like Docker), the Article Cache could exceed the memory limit.
* Diskspace checks would be too strict when unpacking to different disk.
* Prevent deadlock when a second signal arrives during shutdown procedure.
* Verification using SFV-checks could fail, even though files were correct.
* Windows: Uninstall would not remove Settings or Windows Service.
* Windows: Warn for OS-limit if more than 1024 connections are configured.
* macOS: AppleDouble files could result in failures in `Moving` stage.
* macOS: Restarting the application resulted in Terminal window.
## Upgrade notices
* You can directly upgrade from version 3.0.0 and newer.
* Upgrading from older versions will require performing a `Queue repair`.
* Downgrading from version 4.2.0 or newer to 3.7.2 or older will require
performing a `Queue repair` due to changes in the internal data format.
## Known problems and solutions
* Read `ISSUES.txt` or https://sabnzbd.org/wiki/introduction/known-issues
## Code Signing Policy
+151 -237
View File
@@ -19,8 +19,8 @@ import sys
# Trick to show a better message on older Python
# releases that don't support walrus operator
if Python_39_is_required_to_run_SABnzbd := sys.hexversion < 0x03090000:
print("Sorry, requires Python 3.9 or above")
if Python_39_is_required_to_run_SABnzbd := sys.hexversion < 0x030A00F0:
print("Sorry, requires Python 3.10 or above")
print("You can read more at: https://sabnzbd.org/wiki/installation/install-off-modules")
sys.exit(1)
@@ -35,11 +35,11 @@ import socket
import subprocess
import multiprocessing
import ssl
import shlex
import time
import re
import gc
import threading
import http.cookies
from typing import Any
try:
@@ -47,9 +47,7 @@ try:
import Cheetah
import feedparser
import configobj
import cherrypy
import cheroot.errors
import portend
import uvicorn
import cryptography
import charset_normalizer
import guessit
@@ -68,6 +66,7 @@ import sabnzbd.interface
from sabnzbd.constants import (
DEF_NETWORKING_TIMEOUT,
DEF_LOG_ERRFILE,
DEF_LOG_ACCESSFILE,
DEF_MAIN_TMPL,
DEF_STD_WEB_DIR,
DEF_WORKDIR,
@@ -80,13 +79,17 @@ from sabnzbd.constants import (
RSS_FILE_NAME,
DEF_LOG_FILE,
DEF_STD_CONFIG,
DEF_LOG_CHERRY,
CONFIG_BACKUP_HTTPS,
)
import sabnzbd.newsunpack
from sabnzbd.misc import (
exit_sab,
split_host,
port_is_free,
find_free_port,
bind_web_socket,
HostNotAvailableError,
xff_trusted_networks,
create_https_certificates,
ip_extract,
set_serv_parms,
@@ -98,7 +101,7 @@ from sabnzbd.misc import (
set_https_verification,
)
from sabnzbd.filesystem import get_ext, real_path, long_path, globber_full, remove_file
from sabnzbd.panic import panic_tmpl, panic_port, panic_host, panic, launch_a_browser
from sabnzbd.panic import panic_tmpl, panic_port, panic_port_permission, panic_host, panic, launch_a_browser
import sabnzbd.config as config
import sabnzbd.cfg
import sabnzbd.notifier as notifier
@@ -115,7 +118,7 @@ try:
from win32com.shell import shell, shellcon
from sabnzbd.utils.apireg import get_connection_info, set_connection_info
import sabnzbd.sabtray
import sabnzbd.sabtraywin
win32api.SetConsoleCtrlHandler(sabnzbd.sig_handler, True)
except ImportError:
@@ -194,7 +197,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>" % sabnzbd.MY_NAME)
print()
print("Options marked [*] are stored in the config file")
print()
@@ -204,7 +207,7 @@ def print_help():
print(" -t --templates <templ> Template directory [*]")
print()
print(" -l --logging <-1..2> Set logging level (-1=off, 0=least,2= most) [*]")
print(" -w --weblogging Enable cherrypy access logging")
print(" -w --weblogging Enable uvicorn access logging")
print()
print(" -b --browser <0..1> Auto browser launch (0= off, 1= on) [*]")
if sabnzbd.WINDOWS:
@@ -236,7 +239,7 @@ def print_help():
def print_version():
print(("""
print("""
%s-%s
(C) Copyright 2007-2026 by The SABnzbd-Team (sabnzbd.org)
@@ -245,7 +248,7 @@ This is free software, and you are welcome to redistribute it
under certain conditions. It is licensed under the
GNU GENERAL PUBLIC LICENSE Version 2 or (at your option) any later version.
""" % (sabnzbd.MY_NAME, sabnzbd.__version__)))
""" % (sabnzbd.MY_NAME, sabnzbd.__version__))
def daemonize():
@@ -290,18 +293,41 @@ def daemonize():
os.dup2(f.fileno(), sys.stderr.fileno())
def abort_and_show_error(browserhost, web_port, err=""):
"""Abort program because of CherryPy troubles"""
def abort_and_show_error(browserhost, web_port, err="", panic_func=panic_port):
"""Abort program because the web-interface could not be started"""
logging.error(T("Failed to start web-interface") + " : " + str(err))
if not sabnzbd.DAEMON:
if "49" in err:
panic_host(browserhost, web_port)
else:
panic_port(browserhost, web_port)
panic_func(browserhost, web_port)
sabnzbd.halt()
exit_sab(2)
def abort_for_unusable_address(browserhost, port, err):
"""Abort with the panic that matches why the address could not be used"""
if isinstance(err, PermissionError):
abort_and_show_error(browserhost, port, err, panic_func=panic_port_permission)
else:
abort_and_show_error(browserhost, port, err, panic_func=panic_host)
def port_is_free_or_abort(host, port, browserhost):
"""port_is_free(), but abort when the address cannot be used at all. Saying so
up front beats a search that can only fail, followed by a message claiming
some other program holds the port."""
try:
return port_is_free(host, port)
except (PermissionError, HostNotAvailableError) as err:
abort_for_unusable_address(browserhost, port, err)
def find_free_port_or_abort(host, currentport, browserhost):
"""find_free_port(), but abort when the address cannot be used at all"""
try:
return find_free_port(host, currentport)
except (PermissionError, HostNotAvailableError) as err:
abort_for_unusable_address(browserhost, currentport, err)
def identify_web_template(key, defweb, wdir):
"""Determine a correct web template set, return full template path"""
if wdir is None:
@@ -651,19 +677,6 @@ def get_webhost(web_host, web_port, https_port):
return web_host, web_port, browserhost, https_port
def attach_server(host, port, cert=None, key=None, chain=None):
"""Define and attach server, optionally HTTPS"""
if sabnzbd.cfg.ipv6_hosting() or "::1" not in host:
http_server = cherrypy._cpserver.Server()
http_server.bind_addr = (host, port)
if cert and key:
http_server.ssl_module = "builtin"
http_server.ssl_certificate = cert
http_server.ssl_private_key = key
http_server.ssl_certificate_chain = chain
http_server.subscribe()
def is_sabnzbd_running(url):
"""Return True when there's already a SABnzbd instance running."""
try:
@@ -677,19 +690,6 @@ def is_sabnzbd_running(url):
return False
def find_free_port(host, currentport):
"""Return a free port, 0 when nothing is free"""
n = 0
while n < 10 and currentport <= 49151:
try:
portend.free(host, currentport, timeout=0.025)
return currentport
except Exception:
currentport += 5
n += 1
return 0
def check_for_sabnzbd(url, upload_nzbs, allow_browser=True):
"""Check for a running instance of sabnzbd on this port
allow_browser==True|None will launch the browser, False will not.
@@ -836,7 +836,7 @@ def main():
web_host = None
web_port = None
https_port = None
cherrypylogging = None
weblogging = None
clean_up = False
logging_level = None
console_logging = False
@@ -882,7 +882,7 @@ def main():
elif opt in ("-c", "--clean"):
clean_up = True
elif opt in ("-w", "--weblogging"):
cherrypylogging = True
weblogging = True
elif opt in ("-l", "--logging"):
try:
logging_level = int(arg)
@@ -1001,78 +1001,54 @@ def main():
# When this is a daemon, just check and bail out if port in use
if sabnzbd.DAEMON:
if enable_https and https_port:
try:
portend.free(web_host, https_port, timeout=0.05)
except IOError:
if not port_is_free_or_abort(web_host, https_port, browserhost):
abort_and_show_error(browserhost, web_port)
except Exception:
abort_and_show_error(browserhost, web_port, "49")
try:
portend.free(web_host, web_port, timeout=0.05)
except IOError:
if not port_is_free_or_abort(web_host, web_port, browserhost):
abort_and_show_error(browserhost, web_port)
except Exception:
abort_and_show_error(browserhost, web_port, "49")
# Windows instance is reachable through registry
url = None
if sabnzbd.WINDOWS and not new_instance:
url = get_connection_info()
url = get_connection_info(inifile)
if url and check_for_sabnzbd(url, upload_nzbs, autobrowser):
exit_sab(0)
# SSL
if enable_https:
port = https_port or web_port
try:
portend.free(browserhost, port, timeout=0.05)
except IOError as error:
if str(error) == "Port not bound.":
pass
else:
if not url:
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():
abort_and_show_error(browserhost, web_port)
# 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
web_port = newport
sabnzbd.cfg.web_port.set(newport)
except Exception:
# Something else wrong, probably badly specified host
abort_and_show_error(browserhost, web_port, "49")
if not port_is_free_or_abort(web_host, port, browserhost):
if not url:
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():
abort_and_show_error(browserhost, web_port)
# Find free port to bind
newport = find_free_port_or_abort(web_host, port, browserhost)
if newport:
# Save the new port
if https_port:
https_port = newport
sabnzbd.cfg.https_port.set(newport)
else:
# In case HTTPS == HTTP port
web_port = newport
sabnzbd.cfg.web_port.set(newport)
# NonSSL check if there's no HTTPS or we only use 1 port
if not (enable_https and not https_port):
try:
portend.free(browserhost, web_port, timeout=0.05)
except IOError as error:
if str(error) == "Port not bound.":
pass
else:
if not url:
url = "http://%s:%s%s/api?" % (browserhost, web_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():
abort_and_show_error(browserhost, web_port)
# Find free port to bind
port = find_free_port(browserhost, web_port)
if port > 0:
sabnzbd.cfg.web_port.set(port)
web_port = port
except Exception:
# Something else wrong, probably badly specified host
abort_and_show_error(browserhost, web_port, "49")
if not port_is_free_or_abort(web_host, web_port, browserhost):
if not url:
url = "http://%s:%s%s/api?" % (browserhost, web_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():
abort_and_show_error(browserhost, web_port)
# Find free port to bind
port = find_free_port_or_abort(web_host, web_port, browserhost)
if port:
sabnzbd.cfg.web_port.set(port)
web_port = port
# We found a port, now we never check again
sabnzbd.cfg.fixed_ports.set(True)
@@ -1110,6 +1086,9 @@ def main():
rollover_log = logging.handlers.RotatingFileHandler(
sabnzbd.LOGFILE, "a+", sabnzbd.cfg.log_size(), sabnzbd.cfg.log_backups()
)
# Set the level here as well as on the logger, so records that were
# created at a higher level and lowered afterwards are also dropped
rollover_log.setLevel(LOGLEVELS[logging_level + 1])
rollover_log.setFormatter(logging.Formatter(logformat))
logger.addHandler(rollover_log)
@@ -1170,8 +1149,8 @@ def main():
# Handle the several tray icons
if sabnzbd.cfg.tray_icon() and not sabnzbd.DAEMON and not sabnzbd.WIN_SERVICE:
if sabnzbd.WINDOWS:
sabnzbd.WINTRAY = sabnzbd.sabtray.SABTrayThread()
elif sabnzbd.LINUX_POWER and os.environ.get("DISPLAY"):
sabnzbd.WINTRAY = sabnzbd.sabtraywin.SABTrayThread()
elif sabnzbd.LINUX_POWER and (os.environ.get("DISPLAY") or os.environ.get("XDG_CURRENT_DESKTOP")):
try:
import gi
@@ -1218,41 +1197,14 @@ def main():
# Starting of the webserver
# Determine if this system has multiple definitions for 'localhost'
hosts = all_localhosts()
multilocal = len(hosts) > 1 and web_host in ("localhost", "0.0.0.0")
# For 0.0.0.0 CherryPy will always pick IPv4, so make sure the secondary localhost is IPv6
if multilocal and web_host == "0.0.0.0" and hosts[1] == "127.0.0.1":
hosts[1] = "::1"
# The Windows binary requires numeric localhost as primary address
if web_host == "localhost":
web_host = hosts[0]
if enable_https:
if https_port:
# Extra HTTP port for primary localhost
attach_server(web_host, web_port)
if multilocal:
# Extra HTTP port for secondary localhost
attach_server(hosts[1], web_port)
# Extra HTTPS port for secondary localhost
attach_server(hosts[1], https_port, https_cert, https_key, https_chain)
web_port = https_port
elif multilocal:
# Extra HTTPS port for secondary localhost
attach_server(hosts[1], web_port, https_cert, https_key, https_chain)
cherrypy.config.update(
{
"server.ssl_module": "builtin",
"server.ssl_certificate": https_cert,
"server.ssl_private_key": https_key,
"server.ssl_certificate_chain": https_chain,
}
)
elif multilocal:
# Extra HTTP port for secondary localhost
attach_server(hosts[1], web_port)
if enable_https and https_port:
# Separate HTTPS port: switch the main server to the HTTPS port
web_port = https_port
if no_login:
sabnzbd.cfg.username.set("")
@@ -1262,108 +1214,55 @@ def main():
if inet_exposure:
sabnzbd.cfg.inet_exposure.set(inet_exposure)
mime_gzip = (
"text/*",
"application/javascript",
"application/x-javascript",
"application/json",
"application/xml",
"application/vnd.ms-fontobject",
"application/font*",
"image/svg+xml",
)
cherrypy.config.update(
{
"server.environment": "production",
"server.socket_host": web_host,
"server.socket_port": web_port,
"server.shutdown_timeout": 0,
"engine.autoreload.on": False,
"tools.encode.on": True,
"tools.gzip.on": True,
"tools.gzip.mime_types": mime_gzip,
"error_page.401": sabnzbd.panic.error_page_401,
"error_page.404": sabnzbd.panic.error_page_404,
}
)
# Monkey-patch key validation to prevent cherrypy from stumbling over invalid cookies
http.cookies._is_legal_key = lambda _: True
# Catch shutdown errors that can break cherrypy/cheroot
# See https://github.com/cherrypy/cheroot/issues/710
try:
cheroot.errors.acceptable_sock_shutdown_exceptions += (OSError,)
except AttributeError:
pass
# Do we want CherryPy Logging? Cannot be done via the config
cherrypy.log.screen = False
cherrypy.log.access_log.propagate = False
if cherrypylogging:
sabnzbd.WEBLOGFILE = os.path.join(logdir, DEF_LOG_CHERRY)
cherrypy.log.access_file = str(sabnzbd.WEBLOGFILE)
# 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.auth_basic.on": False,
"tools.response_headers.on": True,
"tools.response_headers.headers": [("Access-Control-Allow-Origin", "*")],
},
"/static": static,
"/wizard/static": wizard_static,
"/favicon.ico": {
"tools.staticfile.on": True,
"tools.staticfile.filename": os.path.join(sabnzbd.WEB_DIR_CONFIG, "staticcfg", "ico", "favicon.ico"),
},
"/staticcfg": staticcfg,
}
# Make available from both URLs
main_page = sabnzbd.interface.MainPage()
cherrypy.Application.relative_urls = "server"
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)
logging.info("Starting web-interface on %s:%s", web_host, web_port)
sabnzbd.cfg.log_level.callback(guard_loglevel)
try:
cherrypy.engine.start()
except Exception:
# Since the webserver is started by cherrypy in a separate thread, we can't really catch any
# start-up errors. This try/except only catches very few errors, the rest is only shown in the console.
logging.error(T("Failed to start web-interface: "), exc_info=True)
abort_and_show_error(browserhost, web_port)
# Create a record of the active cert/key/chain files, for use with config.create_config_backup()
if enable_https:
for setting in CONFIG_BACKUP_HTTPS.values():
if full_path := getattr(sabnzbd.cfg, setting).get_path():
sabnzbd.CONFIG_BACKUP_HTTPS_OK.append(full_path)
# Do we want web-server access logging? Cannot be done via the config
if weblogging:
sabnzbd.WEBLOGFILE = os.path.join(logdir, DEF_LOG_ACCESSFILE)
# Catch logging using SABnzbd handlers
uvicorn_logging_config = sabnzbd.interface.uvicorn_logging_config(sabnzbd.WEBLOGFILE)
# Claim the port here rather than letting uvicorn do it, because this is the
# first point where the host and port are final. Everything before this was
# only picking a port, and anything could still have taken it since.
try:
web_socket = bind_web_socket(web_host, web_port)
except (PermissionError, HostNotAvailableError) as err:
abort_for_unusable_address(browserhost, web_port, err)
except OSError as err:
logging.error(T("Failed to start web-interface: "), exc_info=True)
abort_and_show_error(browserhost, web_port, err)
server_config = uvicorn.Config(
sabnzbd.interface.create_app(),
host=web_host,
port=web_port,
log_config=uvicorn_logging_config,
ssl_keyfile=https_key if enable_https else None,
ssl_certfile=https_cert if enable_https else None,
ssl_ca_certs=https_chain if enable_https else None,
proxy_headers=bool(sabnzbd.cfg.verify_xff_header()),
forwarded_allow_ips=xff_trusted_networks(),
)
sabnzbd.WEB_SERVER = sabnzbd.interface.ThreadedServer(config=server_config, sockets=[web_socket])
try:
sabnzbd.WEB_SERVER.run_in_thread()
except Exception:
# The webserver runs in a separate thread; if it fails to start (bad cert,
# unusable TLS material) surface the error and abort instead of hanging.
# Details are also shown on the console.
logging.error(T("Failed to start web-interface: "), exc_info=True)
abort_and_show_error(browserhost, web_port)
# Set URL for browser
if enable_https:
sabnzbd.BROWSER_URL = "https://%s:%s%s" % (browserhost, web_port, sabnzbd.cfg.url_base())
@@ -1372,7 +1271,7 @@ def main():
if sabnzbd.WINDOWS:
# Write URL for uploads and version check directly to registry
set_connection_info(f"{sabnzbd.BROWSER_URL}/api?apikey={sabnzbd.cfg.api_key()}")
set_connection_info(f"{sabnzbd.BROWSER_URL}/api?apikey={sabnzbd.cfg.api_key()}", inifile)
if pid_path or pid_file:
sabnzbd.pid_file(pid_path, pid_file, web_port)
@@ -1420,6 +1319,8 @@ def main():
logger.setLevel(level)
if console_logging:
console.setLevel(level)
if not no_file_log:
rollover_log.setLevel(level)
# 300 sec polling tasks
if not timer % 100:
@@ -1463,11 +1364,16 @@ def main():
# Binaries require special restart
if hasattr(sys, "frozen"):
if sabnzbd.MACOS:
# On macOS restart of app instead of embedded python
my_args = " ".join(sys.argv[1:])
cmd = 'kill -9 %s && open "%s" --args %s' % (os.getpid(), sys.executable, my_args)
logging.info("Launching: %s", cmd)
os.system(cmd)
# Relaunch the .app via LaunchServices (clean env, proper menu-bar
# context). Opening sys.executable directly makes macOS launch the
# raw binary in Terminal.app — issue #3455.
from Foundation import NSBundle
subprocess.Popen(
'open -n "%s" --args %s'
% (NSBundle.mainBundle().bundlePath(), " ".join(shlex.quote(arg) for arg in sys.argv[1:])),
shell=True,
)
elif sabnzbd.WIN_SERVICE:
# Use external service handler to do the restart
# Wait 5 seconds to clean up
@@ -1476,13 +1382,21 @@ def main():
# Just a simple restart of the exe
os.execv(sys.executable, ['"%s"' % arg for arg in sys.argv])
else:
# CherryPy has special logic to include interpreter options such as "-OO"
cherrypy.engine._do_execv()
# Re-exec with interpreter flags (replaces cherrypy.engine._do_execv)
args = [sys.executable]
if sys.flags.optimize == 1:
args.append("-O")
elif sys.flags.optimize >= 2:
args.append("-OO")
args.extend(sys.argv)
os.execv(sys.executable, args)
# Send our final goodbyes!
notifier.send_notification("SABnzbd", T("SABnzbd shutdown finished"), "startup")
logging.info("Leaving SABnzbd")
sabnzbd.pid_file()
if sabnzbd.WEB_SERVER:
sabnzbd.WEB_SERVER.stop()
try:
sys.stderr.flush()
@@ -1593,7 +1507,7 @@ def handle_windows_service():
# In this case check for required parameters
path = get_f_option(sab_opts)
if not path:
print(("The -f <path> parameter is required.\n" "Use: -f <path> %s" % service))
print("The -f <path> parameter is required.\n" "Use: -f <path> %s" % service)
return True
# First run the service installed, because this will
+2 -2
View File
@@ -7,13 +7,13 @@ from PyInstaller.building.build_main import Analysis
from PyInstaller.building.osx import BUNDLE
from PyInstaller.utils.hooks import collect_data_files, collect_submodules
from builder.constants import EXTRA_FILES, EXTRA_FOLDERS, RELEASE_VERSION, RELEASE_VERSION_BASE, RELEASE_VERSION_TUPLE
from builder.common import EXTRA_FILES, EXTRA_FOLDERS, RELEASE_VERSION, RELEASE_VERSION_BASE, RELEASE_VERSION_TUPLE
# Add extra files in the PyInstaller-spec
extra_pyinstaller_files = []
# Add hidden imports
extra_hiddenimports = ["Cheetah.DummyTransaction", "cheroot.ssl.builtin", "certifi"]
extra_hiddenimports = ["Cheetah.DummyTransaction", "certifi"]
extra_hiddenimports.extend(collect_submodules("apprise"))
extra_hiddenimports.extend(collect_submodules("babelfish.converters"))
extra_hiddenimports.extend(collect_submodules("guessit.data"))
+15 -5
View File
@@ -32,14 +32,14 @@ with open(VERSION_FILE) as version_file:
exec(version_file.read())
RELEASE_VERSION = __version__
# Pre-releases are longer than 6 characters (e.g. 3.1.0Beta1 vs 3.1.0, but also 3.0.11)
PRERELEASE = len(RELEASE_VERSION) > 5
# Parse the version info for Windows file properties information
version_regexed = re.search(r"(\d+)\.(\d+)\.(\d+)([a-zA-Z]*)(\d*)", RELEASE_VERSION)
RELEASE_VERSION_TUPLE = (int(version_regexed.group(1)), int(version_regexed.group(2)), int(version_regexed.group(3)), 0)
RELEASE_VERSION_BASE = f"{RELEASE_VERSION_TUPLE[0]}.{RELEASE_VERSION_TUPLE[1]}.{RELEASE_VERSION_TUPLE[2]}"
# Pre-releases carry an alpha suffix (e.g. 5.1.0Beta1); stable releases don't (e.g. 5.1.0 or 5.1.10)
PRERELEASE = bool(version_regexed.group(4))
# Define release name
RELEASE_NAME = "SABnzbd-%s" % RELEASE_VERSION
RELEASE_TITLE = "SABnzbd %s" % RELEASE_VERSION
@@ -59,12 +59,10 @@ if platform.machine() == "ARM64":
EXTRA_FILES = [
RELEASE_README,
"README.html",
"INSTALL.txt",
"LICENSE.txt",
"GPL2.txt",
"GPL3.txt",
"COPYRIGHT.txt",
"ISSUES.txt",
]
EXTRA_FOLDERS = [
"scripts/",
@@ -76,3 +74,15 @@ EXTRA_FOLDERS = [
"interfaces/Config/",
"icons/",
]
def pe_has_authenticode_signature(pe_file: str) -> bool:
"""Check if a PE file (.exe) has an embedded Authenticode signature by
inspecting the certificate table in the optional header data directory."""
# Imported here as tools/extract_pot.py execs this file without
# the build dependencies to get the RELEASE_VERSION_BASE
import pefile
pe = pefile.PE(pe_file, fast_load=True)
security = pe.OPTIONAL_HEADER.DATA_DIRECTORY[pefile.DIRECTORY_ENTRY["IMAGE_DIRECTORY_ENTRY_SECURITY"]]
return security.VirtualAddress != 0 and security.Size != 0
+13 -7
View File
@@ -16,6 +16,7 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
import glob
import importlib.util
import re
import sys
import os
@@ -31,7 +32,7 @@ import markdown
import packaging.version
import xml.etree.ElementTree as ET
from constants import (
from common import (
RELEASE_VERSION,
RELEASE_VERSION_TUPLE,
VERSION_FILE,
@@ -47,6 +48,7 @@ from constants import (
EXTRA_FOLDERS,
APPDATA_FILE,
RELEASE_VERSION_BASE,
pe_has_authenticode_signature,
)
@@ -88,7 +90,7 @@ def run_external_command(command: list[str], print_output: bool = True, **kwargs
def run_git_command(parms):
"""Run git command, raise error if it failed"""
return run_external_command(["git"] + parms)
return run_external_command(["git", *parms])
def patch_version_file(release_name):
@@ -193,7 +195,7 @@ def test_macos_min_version(binary_path: str):
print(lines)
raise RuntimeError(f"Could not determine minimum macOS version for {binary_path}")
else:
print(f"Skipping macOS version check, MACOSX_DEPLOYMENT_TARGET not set")
print("Skipping macOS version check, MACOSX_DEPLOYMENT_TARGET not set")
def test_sab_binary(binary_path: str):
@@ -244,7 +246,7 @@ def test_sab_binary(binary_path: str):
# Parse API-key so we can do a graceful shutdown
sab_config = configobj.ConfigObj(os.path.join(config_dir, "sabnzbd.ini"))
urllib.request.urlopen(base_url + "shutdown/?apikey=" + sab_config["misc"]["api_key"], timeout=10)
urllib.request.urlopen(base_url + "api?mode=shutdown&apikey=" + sab_config["misc"]["api_key"], timeout=10)
sabnzbd_process.wait()
# Print logs for verification
@@ -268,9 +270,7 @@ if __name__ == "__main__":
raise FileNotFoundError("Run from the main SABnzbd source folder: python builder/package.py")
# Check if we have the needed certificates
try:
import certifi
except ImportError:
if importlib.util.find_spec("certifi") is None:
raise FileNotFoundError("Need certifi module")
# Check if we have correct appdata file
@@ -337,6 +337,12 @@ if __name__ == "__main__":
# Make sure it exists
if not os.path.exists("dist/SABnzbd/SABnzbd.exe"):
raise FileNotFoundError("SABnzbd executable not found, signed zip extraction failed")
# Make sure the executables are actually signed
for exe_to_check in ("dist/SABnzbd/SABnzbd.exe", "dist/SABnzbd/SABnzbd-console.exe"):
if not pe_has_authenticode_signature(exe_to_check):
raise RuntimeError("%s is not signed!" % exe_to_check)
print("%s has an Authenticode signature" % exe_to_check)
elif RELEASE_THIS:
raise FileNotFoundError("Signed SABnzbd executable not found, required for release!")
else:
+27 -2
View File
@@ -1,2 +1,27 @@
PyGithub==2.9.1
praw==7.8.1
# All sub-dependencies are listed below and installed with --no-dependencies for security
PyGithub==2.10.0
praw==8.0.3
pefile==2024.8.26
# PyGithub dependencies
pynacl==1.6.2
requests==2.34.2
pyjwt==2.13.0
typing-extensions==4.16.0
urllib3==2.7.0
# praw dependencies
prawcore==4.0.0
defusedxml==0.7.1
update-checker==1.0.0
websocket-client==1.9.0
# requests dependencies
charset_normalizer==3.5.1
idna==3.19
certifi==2026.7.22
# cryptography (via pyjwt[crypto]) dependencies
cryptography==49.0.0
cffi==2.1.1
pycparser==3.0
+78 -55
View File
@@ -15,15 +15,15 @@
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
import hashlib
import json
import os
import re
import github
import praw
import requests
from constants import (
from common import (
RELEASE_VERSION,
PRERELEASE,
RELEASE_SRC,
@@ -34,6 +34,7 @@ from constants import (
RELEASE_README,
RELEASE_THIS,
RELEASE_TITLE,
pe_has_authenticode_signature,
)
# Verify we have all assets
@@ -47,18 +48,14 @@ files_to_check = (
)
for file_to_check in files_to_check:
if not os.path.exists(file_to_check):
raise RuntimeError("Not all release files are present!")
raise RuntimeError("Release file %s is missing!" % file_to_check)
print("All release files are present")
# Calculate hashes for Synology release
with open(RELEASE_SRC, "rb") as inp_file:
source_data = inp_file.read()
print("---- Synology spksrc digest hashes ---- ")
print(RELEASE_SRC, "SHA1", hashlib.sha1(source_data).hexdigest())
print(RELEASE_SRC, "SHA256", hashlib.sha256(source_data).hexdigest())
print(RELEASE_SRC, "MD5", hashlib.md5(source_data).hexdigest())
print("----")
# When releasing, the Windows installer must be signed
if RELEASE_THIS:
if not pe_has_authenticode_signature(RELEASE_WIN_INSTALLER):
raise RuntimeError("%s is not signed!" % RELEASE_WIN_INSTALLER)
print("%s has an Authenticode signature" % RELEASE_WIN_INSTALLER)
# Check if tagged as release and check for token
gh_token = os.environ.get("AUTOMATION_GITHUB_TOKEN", "")
@@ -66,18 +63,15 @@ if RELEASE_THIS and gh_token:
gh_obj = github.Github(auth=github.Auth.Token(gh_token))
gh_repo = gh_obj.get_repo("sabnzbd/sabnzbd")
# Read the release notes
# Read the release notes (reused for the Reddit post below)
with open(RELEASE_README, "r") as readme_file:
readme_data = readme_file.read()
# We have to manually check if we already created this release
for release in gh_repo.get_releases():
if release.tag_name == RELEASE_VERSION:
gh_release = release
print("Found existing release %s" % gh_release.name)
break
else:
# Did not find it, so create the release, use the GitHub tag we got as input
# Find the existing release for this tag, or create a fresh draft
try:
gh_release = gh_repo.get_release(RELEASE_VERSION)
print("Found existing release %s" % gh_release.name)
except github.UnknownObjectException:
print("Creating GitHub release SABnzbd %s" % RELEASE_VERSION)
gh_release = gh_repo.create_git_release(
tag=RELEASE_VERSION,
@@ -87,38 +81,26 @@ if RELEASE_THIS and gh_token:
prerelease=PRERELEASE,
)
# Fetch existing assets, as overwriting is not allowed by GitHub
gh_assets = gh_release.get_assets()
# Overwriting an asset isn't allowed by GitHub, so delete any that already exist
existing_assets = {asset.name: asset for asset in gh_release.get_assets()}
for file_to_upload in files_to_check:
if file_to_upload in existing_assets:
print("Removing existing asset %s" % file_to_upload)
existing_assets[file_to_upload].delete_asset()
print("Uploading %s to release %s" % (file_to_upload, gh_release.name))
gh_release.upload_asset(file_to_upload)
# Upload the assets
for file_to_check in files_to_check:
if os.path.exists(file_to_check):
# Check if this file was previously uploaded
if gh_assets.totalCount:
for gh_asset in gh_assets:
if gh_asset.name == file_to_check:
print("Removing existing asset %s " % gh_asset.name)
gh_asset.delete_asset()
# Upload the new one
print("Uploading %s to release %s" % (file_to_check, gh_release.name))
gh_release.upload_asset(file_to_check)
# Check if we now have all files
gh_new_assets = gh_release.get_assets()
if gh_new_assets.totalCount:
all_assets = [gh_asset.name for gh_asset in gh_new_assets]
# Check if we have all files, using set-comparison
if set(files_to_check) == set(all_assets):
print("All assets present, releasing %s" % RELEASE_VERSION)
# Publish release
gh_release.update_release(
tag_name=RELEASE_VERSION,
name=RELEASE_TITLE,
message=readme_data,
draft=False,
prerelease=PRERELEASE,
)
# Publish the release once all assets are attached
uploaded_assets = {asset.name for asset in gh_release.get_assets()}
if set(files_to_check).issubset(uploaded_assets):
print("All assets present, releasing %s" % RELEASE_VERSION)
gh_release.update_release(
tag_name=RELEASE_VERSION,
name=RELEASE_TITLE,
message=readme_data,
draft=False,
prerelease=PRERELEASE,
)
# Update the website
gh_repo_web = gh_obj.get_repo("sabnzbd/sabnzbd.github.io")
@@ -205,6 +187,20 @@ if RELEASE_THIS and gh_token:
print("Merging pull request in sabnzbd/sabnzbd.github.io for the update")
update_pr.merge(merge_method="squash")
# Trigger the Docker image build at linuxserver.io
# Branch "develop" builds the pre-releases, "master" the stable releases
if linuxserver_token := os.environ.get("LINUXSERVER_WEBHOOK_TOKEN", ""):
linuxserver_branch = "develop" if PRERELEASE else "master"
print("Triggering linuxserver.io Docker build for branch %s" % linuxserver_branch)
requests.post(
"https://ci.linuxserver.io/generic-webhook-trigger/invoke?sabnzbd",
headers={"Authorization": "Bearer %s" % linuxserver_token},
json={"branch": linuxserver_branch},
timeout=30,
).raise_for_status()
else:
print("Missing LINUXSERVER_WEBHOOK_TOKEN")
# Only with GitHub success we proceed to Reddit
if reddit_token := os.environ.get("REDDIT_TOKEN", ""):
# Token format (without whitespace):
@@ -221,9 +217,8 @@ if RELEASE_THIS and gh_token:
subreddit_sabnzbd = reddit.subreddit("sabnzbd")
subreddit_usenet = reddit.subreddit("usenet")
# Read the release notes
with open(RELEASE_README, "r") as readme_file:
readme_lines = readme_file.readlines()
# Reuse the release notes read earlier, split into lines
readme_lines = readme_data.splitlines(keepends=True)
# Put the download link after the title
readme_lines[2] = "## https://sabnzbd.org/downloads\n\n"
@@ -253,6 +248,34 @@ if RELEASE_THIS and gh_token:
else:
print("Missing REDDIT_TOKEN")
# Push release notes to Discord via an incoming webhook
# Separate channels for testing (pre-release) and stable, both are required
discord_webhook_testing = os.environ.get("DISCORD_WEBHOOK_TESTING", "")
discord_webhook_stable = os.environ.get("DISCORD_WEBHOOK_STABLE", "")
if discord_webhook_testing and discord_webhook_stable:
discord_webhook = discord_webhook_testing if PRERELEASE else discord_webhook_stable
print("Posting release notes to Discord")
# Reuse the release notes read earlier: line 0 is the title header
discord_lines = readme_data.splitlines(keepends=True)
discord_title = discord_lines[0].lstrip("# ").strip()
discord_body = "".join(discord_lines[2:])[:4096]
requests.post(
discord_webhook,
json={
"embeds": [
{
"title": discord_title,
"url": gh_release.html_url,
"description": discord_body,
"color": 0xFFA500 if PRERELEASE else 0x00A550,
}
]
},
timeout=30,
).raise_for_status()
else:
print("Missing DISCORD_WEBHOOK_TESTING and/or DISCORD_WEBHOOK_STABLE")
else:
print("To push release to GitHub, first tag the commit.")
print("Or missing the AUTOMATION_GITHUB_TOKEN, cannot push to GitHub without it.")
+13 -10
View File
@@ -1,20 +1,23 @@
# Basic build requirements
# Note that not all sub-dependencies are listed, but only ones we know could cause trouble
pyinstaller==6.20.0
packaging==26.2
pyinstaller-hooks-contrib==2026.4
# All sub-dependencies are listed below and installed with --no-dependencies for security
pyinstaller==6.22.2
packaging==26.3
pyinstaller-hooks-contrib==2026.6
altgraph==0.17.5
wrapt==2.1.2
setuptools==82.0.1
markdown==3.10.2
wrapt==2.3.0
setuptools==84.0.0
markdown==3.10.3
pefile==2024.8.26
# For the Windows build
pefile==2024.8.26; sys_platform == 'win32'
pywin32-ctypes==0.2.3; sys_platform == 'win32'
# For the macOS build
dmgbuild==1.6.7; sys_platform == 'darwin'
mac-alias==2.2.3; sys_platform == 'darwin'
macholib==1.16.4; sys_platform == 'darwin'
ds-store==1.3.2; sys_platform == 'darwin'
ds-store==1.3.3; sys_platform == 'darwin'
PyNaCl==1.6.2; sys_platform == 'darwin'
# 48.0.0 is the last cryptography version shipping a macOS universal2 wheel (49+ is arm64-only)
# See https://github.com/pyca/cryptography/issues/13520
cryptography<49.0.0; sys_platform == 'darwin'
+59 -14
View File
@@ -18,6 +18,10 @@
Unicode true
; Must precede any data/header output. Solid LZMA dedups identical files (e.g. the
; two SABnzbd*.pkg archives) and shrinks the whole installer.
SetCompressor /SOLID lzma
!addplugindir builder\win\nsis\Plugins
!addincludedir builder\win\nsis\Include
@@ -86,7 +90,6 @@ Unicode true
;------------------------------------------------------------------
; Some default compiler settings (uncomment and change at will):
SetCompress auto ; (can be off or force)
SetDatablockOptimize on ; (can be off)
CRCCheck on ; (can be off)
AutoCloseWindow false ; (can be true for the window go away automatically at end)
@@ -151,7 +154,6 @@ Unicode true
!insertmacro MUI_PAGE_FINISH
!insertmacro MUI_UNPAGE_CONFIRM
!define MUI_UNPAGE_COMPONENTSPAGE_NODESC
!insertmacro MUI_UNPAGE_COMPONENTS
!insertmacro MUI_UNPAGE_INSTFILES
@@ -165,6 +167,7 @@ Function PageFinishRun
${If} $0 == true
; Service is installed, start the service
!insertmacro SERVICE "start" "SABnzbd" ""
Pop $0
${Else}
; Service not installed, run executable as user
${StdUtils.ExecShellAsUser} $0 "$INSTDIR\SABnzbd.exe" "" ""
@@ -221,21 +224,32 @@ Section "SABnzbd" SecDummy
; Shutdown any running service
!insertmacro SERVICE "stop" "SABnzbd" ""
Pop $0
;------------------------------------------------------------------
; Terminate SABnzbd.exe
; Ask nicely first, but kill forcefully if it does not exit in time
StrCpy $R1 0
loop:
${nsProcess::FindProcess} "SABnzbd.exe" $R0
StrCmp $R0 0 0 endcheck
IntCmp $R1 20 forcekill 0 forcekill
${nsProcess::CloseProcess} "SABnzbd.exe" $R0
IntOp $R1 $R1 + 1
Sleep 500
Goto loop
forcekill:
${nsProcess::KillProcess} "SABnzbd.exe" $R0
Sleep 500
endcheck:
${nsProcess::Unload}
;------------------------------------------------------------------
; Make sure old versions are gone (reg-key already read in onInt)
StrCmp $PREV_INST_DIR "" noPrevInstallRemove
; Only remove the directory if it actually contains a SABnzbd installation,
; the registry value could point anywhere
IfFileExists "$PREV_INST_DIR\SABnzbd.exe" 0 noPrevInstallRemove
${RemovePrev} "$PREV_INST_DIR"
Goto continueSetupAfterRemove
@@ -264,7 +278,9 @@ Section "SABnzbd" SecDummy
WriteRegStr HKEY_LOCAL_MACHINE "Software\Microsoft\Windows\CurrentVersion\Uninstall\SABnzbd" "Comments" 'The automated Usenet download tool'
WriteRegStr HKEY_LOCAL_MACHINE "Software\Microsoft\Windows\CurrentVersion\Uninstall\SABnzbd" "DisplayIcon" '$INSTDIR\icons\sabnzbd.ico'
WriteRegDWORD HKEY_LOCAL_MACHINE "Software\Microsoft\Windows\CurrentVersion\Uninstall\SABnzbd" "EstimatedSize" 40674
; Calculate the actual installed size for Add/Remove Programs
${GetSize} "$INSTDIR" "/S=0K" $0 $1 $2
WriteRegDWORD HKEY_LOCAL_MACHINE "Software\Microsoft\Windows\CurrentVersion\Uninstall\SABnzbd" "EstimatedSize" "$0"
WriteRegDWORD HKEY_LOCAL_MACHINE "Software\Microsoft\Windows\CurrentVersion\Uninstall\SABnzbd" "NoRepair" -1
WriteRegDWORD HKEY_LOCAL_MACHINE "Software\Microsoft\Windows\CurrentVersion\Uninstall\SABnzbd" "NoModify" -1
@@ -351,7 +367,6 @@ Function .onInit
endCheckDesktop:
SetShellVarContext all
Push $1
ReadRegStr $1 HKCR ".nzb" "" ; read current file association
StrCmp "$1" "NZB File" noPrevInstall 0
SectionSetFlags ${assoc} 0 ; Uncheck it when it wasn't checked before
@@ -367,9 +382,34 @@ FunctionEnd
; begin uninstall settings/section
UninstallText $(MsgUninstall)
; Uninstaller variants of the servicelib functions, needed by the SERVICE macro.
; APITAG is undefined again at the end of servicelib.nsh, so redefine it here.
!define APITAG "W"
Function un.Service
!insertmacro FUNC_SERVICE "un."
FunctionEnd
Function un.GetParam
!insertmacro FUNC_GETPARAM
FunctionEnd
!undef APITAG
Section "un.$(MsgDelProgram)" Uninstall
;make sure sabnzbd.exe isn't running..if so shut it down
DetailPrint $(MsgShutting)
; Stop and remove the SABnzbd service, if it is installed
!insertmacro SERVICE "installed" "SABnzbd" ""
Pop $0
${If} $0 == true
!insertmacro SERVICE "stop" "SABnzbd" ""
Pop $0
!insertmacro SERVICE "delete" "SABnzbd" ""
Pop $0
${EndIf}
${nsProcess::KillProcess} "SABnzbd.exe" $R0
${nsProcess::Unload}
@@ -380,7 +420,10 @@ Section "un.$(MsgDelProgram)" Uninstall
DeleteRegKey HKEY_CURRENT_USER "Software\Classes\AppUserModelId\SABnzbd"
DeleteRegKey HKEY_CURRENT_USER "Software\SABnzbd"
${RemovePrev} "$INSTDIR"
; Only remove the directory if it actually contains a SABnzbd installation
IfFileExists "$INSTDIR\SABnzbd.exe" 0 skipRemoveDir
${RemovePrev} "$INSTDIR"
skipRemoveDir:
${RemovePrevShortcuts}
; Remove firewall entries
@@ -393,8 +436,10 @@ Section "un.$(MsgDelProgram)" Uninstall
SectionEnd ; end of uninstall section
Section /o "un.$(MsgDelSettings)" DelSettings
DetailPrint "Uninstall settings $LOCALAPPDATA"
Delete "$LOCALAPPDATA\sabnzbd\sabnzbd.ini"
; The main uninstall section leaves the context on "all", which would make
; $LOCALAPPDATA point to C:\ProgramData instead of the user's profile
SetShellVarContext current
DetailPrint "Uninstall settings $LOCALAPPDATA\sabnzbd"
RMDir /r "$LOCALAPPDATA\sabnzbd"
SectionEnd
@@ -503,14 +548,14 @@ SectionEnd
LangString MsgNoWin7 ${LANG_SIMPCHINESE} "SABnzbd 仅支持 Windows 8.1 及更高版本。"
LangString MsgARM64Notice ${LANG_ENGLISH} "An ARM version of SABnzbd is available on our Downloads page. This installer only contains the regular version.$\nPress OK to continue or Cancel to exit."
LangString MsgARM64Notice ${LANG_CZECH} "An ARM version of SABnzbd is available on our Downloads page. This installer only contains the regular version.$\nPress OK to continue or Cancel to exit."
LangString MsgARM64Notice ${LANG_DANISH} "An ARM version of SABnzbd is available on our Downloads page. This installer only contains the regular version.$\nPress OK to continue or Cancel to exit."
LangString MsgARM64Notice ${LANG_GERMAN} "An ARM version of SABnzbd is available on our Downloads page. This installer only contains the regular version.$\nPress OK to continue or Cancel to exit."
LangString MsgARM64Notice ${LANG_SPANISH} "An ARM version of SABnzbd is available on our Downloads page. This installer only contains the regular version.$\nPress OK to continue or Cancel to exit."
LangString MsgARM64Notice ${LANG_CZECH} "Na naší stránce „Ke stažení“ je k dispozici verze SABnzbd pro architekturu ARM. Tento instalační program obsahuje pouze standardní verzi.$\nStiskněte tlačítko OK pro pokračování nebo Zrušit pro ukončení."
LangString MsgARM64Notice ${LANG_DANISH} "En ARM-version af SABnzbd er tilgængelig på vores downloadside. Dette installationsprogram indeholder kun den almindelige version.$\nTryk på OK for at fortsætte eller Annuller for at afslutte."
LangString MsgARM64Notice ${LANG_GERMAN} "Eine ARM-Version von SABnzbd ist auf unserer Downloads-Seite verfügbar. Dieses Installationsprogramm enthält nur die reguläre Version.$\nKlicken Sie auf OK, um fortzufahren, oder auf Abbrechen, um zu beenden."
LangString MsgARM64Notice ${LANG_SPANISH} "Hay una versión ARM de SABnzbd disponible en nuestra página de descargas. Este instalador solo contiene la versión normal.$\nPulsa Aceptar para continuar o Cancelar para salir."
LangString MsgARM64Notice ${LANG_FINNISH} "An ARM version of SABnzbd is available on our Downloads page. This installer only contains the regular version.$\nPress OK to continue or Cancel to exit."
LangString MsgARM64Notice ${LANG_FRENCH} "Une version ARM de SABnzbd est disponible sur notre page de téléchargement. Ce programme d'installation ne contient que la version standard.$\nCliquez sur OK pour continuer ou sur Annuler pour quitter."
LangString MsgARM64Notice ${LANG_HEBREW} "An ARM version of SABnzbd is available on our Downloads page. This installer only contains the regular version.$\nPress OK to continue or Cancel to exit."
LangString MsgARM64Notice ${LANG_ITALIAN} "An ARM version of SABnzbd is available on our Downloads page. This installer only contains the regular version.$\nPress OK to continue or Cancel to exit."
LangString MsgARM64Notice ${LANG_HEBREW} "גרסת ARM של SABnzbd זמינה בעמוד ההורדות שלנו. תוכנת התקנה זו מכילה רק את הגרסה הרגילה.$\nלחץ על אישור כדי להמשיך או על ביטול כדי לצאת."
LangString MsgARM64Notice ${LANG_ITALIAN} "Una versione ARM di SABnzbd è disponibile nella nostra pagina dei download. Questo programma di installazione contiene solo la versione normale.$\nPremi OK per continuare o Annulla per uscire."
LangString MsgARM64Notice ${LANG_NORWEGIAN} "An ARM version of SABnzbd is available on our Downloads page. This installer only contains the regular version.$\nPress OK to continue or Cancel to exit."
LangString MsgARM64Notice ${LANG_DUTCH} "Er is een ARM versie van SABnzbd beschikbaar op onze Downloads pagina. Deze installatie bevat alleen de normale (niet-ARM) versie.$\nKlik OK om door te gaan of Annuleren om af te breken."
LangString MsgARM64Notice ${LANG_POLISH} "An ARM version of SABnzbd is available on our Downloads page. This installer only contains the regular version.$\nPress OK to continue or Cancel to exit."
@@ -520,7 +565,7 @@ SectionEnd
LangString MsgARM64Notice ${LANG_SERBIAN} "An ARM version of SABnzbd is available on our Downloads page. This installer only contains the regular version.$\nPress OK to continue or Cancel to exit."
LangString MsgARM64Notice ${LANG_SWEDISH} "An ARM version of SABnzbd is available on our Downloads page. This installer only contains the regular version.$\nPress OK to continue or Cancel to exit."
LangString MsgARM64Notice ${LANG_TURKISH} "SABnzbd'nin ARM sürümü İndirmeler sayfamızda mevcuttur. Bu kurulum programı sadece normal sürümü içermektedir.$\nDevam etmek için Tamam'a veya çıkmak için İptal'e tıklayın."
LangString MsgARM64Notice ${LANG_SIMPCHINESE} "An ARM version of SABnzbd is available on our Downloads page. This installer only contains the regular version.$\nPress OK to continue or Cancel to exit."
LangString MsgARM64Notice ${LANG_SIMPCHINESE} "我们的下载页面提供 SABnzbd 的 ARM 版本。此安装程序仅包含常规版本。$\n按“确定”继续,或按“取消”退出。"
LangString MsgShutting ${LANG_ENGLISH} "Shutting down SABnzbd"
LangString MsgShutting ${LANG_CZECH} "Vypínání SABnzbd"
+1 -1
View File
@@ -24,7 +24,7 @@
- Packaging/build
- `builder/`: platform build scripts (DMG/EXE specs, `package.py`, `release.py`).
- Platform folders `win/`, `macos/`, `linux/`, `snap/`: installer or platform-specific assets.
- `admin/`, `builder/constants.py`, `licenses/`: release and licensing support files.
- `admin/`, `builder/common.py`, `licenses/`: release and licensing support files.
- Documentation
- Documentation website source is stored in the `sabnzbd.github.io` repo.
@@ -2,12 +2,12 @@
<div class="clearfix"></div>
<!-- Filebrowser modal -->
<div class="modal fade" id="filebrowser_modal">
<div class="modal fade" id="filebrowser_modal" tabindex="-1" role="dialog" aria-modal="true" aria-labelledby="filebrowser-modal-title">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">&times;</button>
<h4 class="modal-title"></h4>
<button type="button" class="close" data-dismiss="modal" aria-label="$T('close')"><span aria-hidden="true">&times;</span></button>
<h4 class="modal-title" id="filebrowser-modal-title"></h4>
</div>
<div class="modal-body">
</div>
@@ -33,4 +33,4 @@
</div>
</div>
</body>
</html>
</html>
+30 -38
View File
@@ -1,10 +1,5 @@
#if $pane == "Config"#
#set global $root = '../'#
#else#
#set global $root = '../../'#
#end if#
<!DOCTYPE HTML>
<html lang="$active_lang" #if $rtl#dir="rtl"#end if#>
<html lang="$active_lang" #if $rtl#dir="rtl"#end if# data-color-scheme="$color_scheme">
<head>
<title>
SABnzbd $T('menu-config')
@@ -14,7 +9,7 @@
#if $pane == "Switches" then $T('cmenu-switches') else ""#
#if $pane == "Servers" then $T('cmenu-servers') else ""#
#if $pane == "Scheduling" then $T('cmenu-scheduling') else ""#
#if $pane == "Email" then $T('cmenu-notif') else ""#
#if $pane == "Notify" then $T('cmenu-notif') else ""#
#if $pane == "Categories" then $T('cmenu-cat') else ""#
#if $pane == "Sorting" then $T('cmenu-sorting') else ""#
#if $pane == "Special" then $T('cmenu-special') else ""#
@@ -25,20 +20,17 @@
<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" />
<link rel="apple-touch-icon" sizes="120x120" href="${root}staticcfg/ico/apple-touch-icon-120x120-precomposed.png" />
<link rel="apple-touch-icon" sizes="152x152" href="${root}staticcfg/ico/apple-touch-icon-152x152-precomposed.png" />
<link rel="apple-touch-icon" sizes="180x180" href="${root}staticcfg/ico/apple-touch-icon-180x180-precomposed.png" />
<link rel="apple-touch-icon" sizes="192x192" href="${root}staticcfg/ico/android-192x192.png" />
<link rel="apple-touch-icon" sizes="76x76" href="$url('staticcfg/ico/apple-touch-icon-76x76-precomposed.png', v=$cache_buster)" />
<link rel="apple-touch-icon" sizes="120x120" href="$url('staticcfg/ico/apple-touch-icon-120x120-precomposed.png', v=$cache_buster)" />
<link rel="apple-touch-icon" sizes="152x152" href="$url('staticcfg/ico/apple-touch-icon-152x152-precomposed.png', v=$cache_buster)" />
<link rel="apple-touch-icon" sizes="180x180" href="$url('staticcfg/ico/apple-touch-icon-180x180-precomposed.png', v=$cache_buster)" />
<link rel="apple-touch-icon" sizes="192x192" href="$url('staticcfg/ico/android-192x192.png', v=$cache_buster)" />
<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/chartist.min.css" />
<link rel="stylesheet" type="text/css" href="${root}staticcfg/css/style.css?v=$version" />
<!--#if $color_scheme not in ('Light', '') #-->
<link rel="stylesheet" type="text/css" href="${root}staticcfg/css/${color_scheme}.css?v=$version"/>
<!--#end if#-->
<link rel="stylesheet" type="text/css" href="$url('staticcfg/bootstrap/css/bootstrap.min.css', v=$cache_buster)" />
<link rel="stylesheet" type="text/css" href="$url('staticcfg/css/chartist.min.css', v=$cache_buster)" />
<link rel="stylesheet" type="text/css" href="$url('staticcfg/css/style.css', v=$cache_buster)" />
<link rel="shortcut icon" href="${root}staticcfg/ico/favicon.ico?v=$version" />
<link rel="shortcut icon" href="$url('staticcfg/ico/favicon.ico', v=$cache_buster)" />
<script type="text/javascript">
// Keeping track of the form state
@@ -46,10 +38,10 @@
var formWasSubmitted = false;
// Information we need
var sabSession = '$apikey';
var rootURL = '${root}'
var csrfToken = '$csrf_token';
var rootURL = '$url()'
var urlBase = '${url_base}'
var folderBrowseUrl = '${root}api?mode=browse&output=json&apikey=$apikey';
var folderBrowseUrl = "$url('api', mode='browse', output='json')";
var folderSeperator = '#if $os.sep == '\\' then '\\\\' else '/'#'
// Translations
@@ -65,9 +57,9 @@
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.5.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" src="$url('staticcfg/js/jquery-3.7.1.min.js', v=$cache_buster)"></script>
<script type="text/javascript" src="$url('staticcfg/bootstrap/js/bootstrap.min.js', v=$cache_buster)"></script>
<script type="text/javascript" src="$url('staticcfg/js/script.js', v=$cache_buster)"></script>
<script type="text/javascript">
// Set default functions for the autocomplete everywhere
@@ -92,62 +84,62 @@
<span class="icon-bar"></span>
</button>
<a class="navbar-logo navbar-logo-small" href="${root}" title="$T('Home')" data-placement="bottom">
<a class="navbar-logo navbar-logo-small" href="$url()" title="$T('Home')" data-placement="bottom">
#include $webdir + "/staticcfg/images/logo-small.svg"#
</a>
</div>
<div id="navbar" class="navbar-collapse collapse">
<ul class="nav navbar-nav">
<li>
<a href="${root}config/general/" #if $pane == "General" then 'class="active"' else ""#>
<a href="$url('config/general')" #if $pane == "General" then 'class="active"' else ""#>
<span class="glyphicon glyphicon-cog"></span>
<strong>$T('cmenu-general')</strong>
</a>
</li>
<li>
<a href="${root}config/folders/" #if $pane == "Folders" then 'class="active"' else ""#>
<a href="$url('config/folders')" #if $pane == "Folders" then 'class="active"' else ""#>
<span class="glyphicon glyphicon-folder-open"></span>
<strong>$T('cmenu-folders')</strong>
</a>
</li>
<li>
<a href="${root}config/server/" #if $pane == "Servers" then 'class="active"' else ""#>
<a href="$url('config/server')" #if $pane == "Servers" then 'class="active"' else ""#>
<span class="glyphicon glyphicon-sort"></span>
<strong>$T('cmenu-servers')</strong>
</a>
</li>
<li>
<a href="${root}config/categories/" #if $pane == "Categories" then 'class="active"' else ""#>
<a href="$url('config/categories')" #if $pane == "Categories" then 'class="active"' else ""#>
<span class="glyphicon glyphicon-tag"></span>
<strong>$T('cmenu-cat')</strong>
</a>
</li>
<li>
<a href="${root}config/switches/" #if $pane == "Switches" then 'class="active"' else ""#>
<a href="$url('config/switches')" #if $pane == "Switches" then 'class="active"' else ""#>
<span class="glyphicon glyphicon-check"></span>
<strong>$T('cmenu-switches')</strong>
</a>
</li>
<li>
<a href="${root}config/sorting/" #if $pane == "Sorting" then 'class="active"' else ""#>
<a href="$url('config/sorting')" #if $pane == "Sorting" then 'class="active"' else ""#>
<span class="glyphicon glyphicon-sort-by-alphabet"></span>
<strong>$T('cmenu-sorting')</strong>
</a>
</li>
<li>
<a href="${root}config/notify/" #if $pane == "Email" then 'class="active"' else ""#>
<a href="$url('config/notify')" #if $pane == "Notify" then 'class="active"' else ""#>
<span class="glyphicon glyphicon-comment"></span>
<strong>$T('cmenu-notif')</strong>
</a>
</li>
<li>
<a href="${root}config/scheduling/" #if $pane == "Scheduling" then 'class="active"' else ""#>
<a href="$url('config/scheduling')" #if $pane == "Scheduling" then 'class="active"' else ""#>
<span class="glyphicon glyphicon-time"></span>
<strong>$T('cmenu-scheduling')</strong>
</a>
</li>
<li>
<a href="${root}config/rss/" #if $pane == "RSS" then 'class="active"' else ""#>
<a href="$url('config/rss')" #if $pane == "RSS" then 'class="active"' else ""#>
<svg class="rss-icon-svg" viewBox="0 0 8 8">
<rect class="rss-button" width="8" height="8" />
<circle class="rss-symbol" cx="2" cy="6" r="1" />
@@ -158,7 +150,7 @@
</a>
</li>
<li>
<a href="${root}config/special/" #if $pane == "Special" then 'class="active"' else ""#>
<a href="$url('config/special')" #if $pane == "Special" then 'class="active"' else ""#>
<span class="glyphicon glyphicon-education"></span>
<strong>$T('cmenu-special')</strong>
</a>
@@ -170,14 +162,14 @@
</a>
</li>
<li class="dropdown" id="search-menu">
<a data-toggle="dropdown" href="#">
<a data-toggle="dropdown" href="#" aria-label="$T('cmenu-search')" aria-controls="search-dropdown" aria-haspopup="true" aria-expanded="false">
<span class="glyphicon glyphicon-search"></span>
<strong><span class="glyphicon glyphicon-search"></span></strong>
</a>
<ul class="dropdown-menu" id="search-dropdown">
<li>
<form onsubmit="return false">
<input type="text" name="config-search" id="search-box" placeholder="$T('cmenu-search')" onkeyup="doConfigSearch(this)">
<input type="text" name="config-search" id="search-box" placeholder="$T('cmenu-search')" aria-label="$T('cmenu-search')" aria-controls="search-dropdown" onkeyup="doConfigSearch(this)">
</form>
</li>
</ul>
+1 -1
View File
@@ -95,7 +95,7 @@
</div>
<div class="colmask">
<div class="padding">
<div class="section padding">
<h5 class="copyright">Copyright &copy; 2007-2026 by The SABnzbd-Team (<a href="https://sabnzbd.org/" target="_blank">sabnzbd.org</a>)</h5>
<p class="copyright"><small>$T('yourRights')</small></p>
</div>
+5 -6
View File
@@ -10,7 +10,7 @@
<h5 class="darkred"><strong>$T('explain-relFolder'):</strong> <span class="path">$defdir</span></h5>
<!--#for $cur, $slot in enumerate($slotinfo)#-->
<!--#set $cansort = $slot.name != '*' and $slot.name != ''#-->
<form action="save" method="post" <!--#if $cansort#-->class="sorting-row"<!--#end if#-->>
<form action="$url('config/categories/save')" method="post" class="fullform <!--#if $cansort then 'sorting-row' else ''#-->">
<table class="catTable">
<!--#if $cur == 0#-->
<tr>
@@ -28,7 +28,6 @@
<span class="glyphicon glyphicon-option-vertical"></span>
</td>
<td>
<input type="hidden" name="apikey" value="$apikey" />
<input type="hidden" name="order" value="$slot.order" />
<input type="hidden" value="$slot.name" name="name" />
<!--#if $slot.name != '*'#-->
@@ -103,12 +102,12 @@
</div>
</div>
</div>
<script type="text/javascript" src="${root}staticcfg/js/jquery-ui.min.js"></script>
<script type="text/javascript" src="$url('staticcfg/js/jquery-ui.min.js', v=$cache_buster)"></script>
<script type="text/javascript">
jQuery(document).ready(function() {
jQuery('.delCat').click(function() {
var theForm = jQuery(this).closest("form");
theForm.attr("action", "delete").submit();
theForm.attr("action", "$url('config/categories/delete')").submit();
});
// Add autocomplete and file-browser
@@ -128,8 +127,8 @@
var data = {}
jQuery(elm).extractFormDataTo(data);
jQuery.ajax({
type: "GET",
url: window.location.pathname + 'save',
type: "POST",
url: "$url('config/categories/save')",
data: data,
async: false // To prevent race-conditions when updating categories
})
@@ -8,10 +8,8 @@
<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="apikey" name="apikey" value="$apikey" />
<form action="$url('config/folders/save')" method="post" class="fullform" autocomplete="off">
<input type="hidden" name="output" value="json" />
<input type="hidden" id="ajax" name="ajax" value="1" />
<div class="section">
<div class="col2">
<h3>$T('userFolders') <a href="$help_uri" target="_blank"><span class="glyphicon glyphicon-question-sign"></span></a></h3>
@@ -35,7 +33,7 @@
<div class="field-pair">
<label class="config" for="complete_dir">$T('opt-complete_dir')</label>
<input type="text" name="complete_dir" id="complete_dir" value="$complete_dir" data-initialdir="$my_home" />
<a class="btn btn-default" href="${root}config/sorting/"><span class="glyphicon glyphicon-sort-by-alphabet"></span> $T('cmenu-sorting')</a>
<a class="btn btn-default" href="$url('config/sorting')"><span class="glyphicon glyphicon-sort-by-alphabet"></span> $T('cmenu-sorting')</a>
<span class="desc">$T('explain-complete_dir') <br/> $T('explain-complete_dir-sorting')</span>
</div>
<div class="field-pair advanced-settings">
@@ -113,7 +111,7 @@
<div class="field-pair">
<label class="config" for="log_dir">$T('opt-log_dir')</label>
<input type="text" name="log_dir" id="log_dir" value="$log_dir" data-initialdir="$my_lcldata" />
<a class="btn btn-default" id="purge_log_files" href="${root}"><span class="glyphicon glyphicon-trash"></span> $T('purge_log_files')</a>
<a class="btn btn-default" id="purge_log_files" href="$url()"><span class="glyphicon glyphicon-trash"></span> $T('purge_log_files')</a>
<span class="desc">$T('explain-log_dir')</span>
</div>
<div class="field-pair">
@@ -139,8 +137,8 @@
if ( confirm("$T('confirm')") ) {
$.ajax({
type: "POST",
url: "../../api",
data: {mode: 'config', name: 'purge_log_files', output: 'json', apikey: jQuery('#apikey').val()}
url: "$url('api')",
data: {mode: 'config', name: 'purge_log_files', output: 'json'}
})
} else {
return false;
+15 -24
View File
@@ -8,9 +8,7 @@
<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="apikey" name="apikey" value="$apikey" />
<input type="hidden" id="ajax" name="ajax" value="1" />
<form action="$url('config/general/save')" method="post" class="fullform" autocomplete="off">
<input type="hidden" name="output" value="json" />
<div class="section">
<div class="col2">
@@ -46,7 +44,6 @@
<!--#end if#-->
<!--#end for#-->
</select>
<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>
@@ -223,9 +220,7 @@
</div><!-- /section -->
</form>
<form action="uploadConfig" method="post" name="fullform" class="fullform" autocomplete="off" enctype="multipart/form-data">
<input type="hidden" name="apikey" value="$apikey" />
<input type="hidden" name="ajax" value="1" />
<form action="$url('config/general/upload_config')" method="post" class="fullform" autocomplete="off" enctype="multipart/form-data">
<input type="hidden" name="output" value="json" />
<div class="section">
<div class="col2">
@@ -252,7 +247,7 @@
</form>
</div><!-- /colmask -->
<div class="modal fade" id="modal_qr">
<div class="modal fade" id="modal_qr" tabindex="-1" role="dialog" aria-modal="true" aria-label="$T('explain-qr-code')">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-body">
@@ -280,8 +275,6 @@ jQuery(document).ready(function(){
// Skip the fancy stuff, just submit
this.submit()
})
// No JSON response
jQuery('#ajax').val('')
})
hideOrShowTranslate()
@@ -312,17 +305,16 @@ jQuery(document).ready(function(){
safeCheck.on('change', checkSafety)
// Click functions
jQuery('#apikey, #nzbkey').click(function () { jQuery(this).select() });
jQuery('#apikey_display, #nzbkey').click(function () { jQuery(this).select() });
jQuery('#generate_new_apikey').click(function () {
if (confirm("$T('confirm')")) {
$.ajax({
type: "POST",
url: "../../api",
data: {mode:'config', name:'set_apikey', apikey: jQuery('#apikey').val()},
url: "$url('api')",
data: { mode: 'config', name: 'set_apikey' },
success: function(msg){
jQuery('#apikey').val(msg);
document.location = document.location;
jQuery('#apikey_display').val(msg.apikey);
}
});
}
@@ -331,11 +323,10 @@ jQuery(document).ready(function(){
if (confirm("$T('confirm')")) {
$.ajax({
type: "POST",
url: "../../api",
data: { mode:'config', name:'set_nzbkey', apikey: jQuery('#apikey').val() },
success: function(msg){
jQuery('#nzbkey').val(msg);
document.location = document.location;
url: "$url('api')",
data: { mode: 'config', name: 'set_nzbkey' },
success: function(msg) {
jQuery('#nzbkey').val(msg.nzbkey);
}
});
}
@@ -366,8 +357,8 @@ jQuery(document).ready(function(){
// Submit request and then restart
$.ajax({
type: "POST",
url: "../../api",
data: { mode: 'config', name: 'regenerate_certs', apikey: jQuery('#apikey').val() },
url: "$url('api')",
data: { mode: 'config', name: 'regenerate_certs' },
success: function(msg) {
do_restart()
}
@@ -404,8 +395,8 @@ jQuery(document).ready(function(){
jQuery('#create_backup').click(function () {
$.ajax({
type: "POST",
url: "../../api",
data: {mode:'config', name:'create_backup', output:'json', apikey: jQuery('#apikey').val()},
url: "$url('api')",
data: { mode: 'config', name: 'create_backup', output: 'json' },
success: function(data) {
if(data.value.result) {
alert("$T('backup'):\n" + data.value.message)
+51 -51
View File
@@ -1,4 +1,4 @@
<!--#set global $pane="Email"#-->
<!--#set global $pane="Notify"#-->
<!--#set global $help_uri = $confighelpuri + "notifications"#-->
<!--#include $webdir + "/_inc_header_uc.tmpl"#-->
@@ -7,10 +7,11 @@
<!--#def show_notify_checkboxes($section_label)#-->
<!--#for $type in $notify_types#-->
<div class="field-pair">
<label class="config wide" for="${section_label}_prio_$type">
<input type="checkbox" class="notify-checkboxes" name="${section_label}_prio_$type" id="${section_label}_prio_$type" value="1" <!--#if $getVar($section_label + '_prio_' + $type) then 'checked="checked"' else ""#--> />
<label class="config" for="${section_label}_prio_$type">
$T($notify_types[$type]).replace('/', ' / ')
</label>
<input type="checkbox" name="${section_label}_prio_$type" id="${section_label}_prio_$type" value="1" <!--#if $getVar($section_label + '_prio_' + $type) then 'checked="checked"' else ""#--> />
</div>
<!--#end for#-->
<!--#end def#-->
@@ -29,10 +30,8 @@
<!--#end def#-->
<div class="colmask">
<form action="saveNotify" method="post" name="fullform" class="fullform" autocomplete="off">
<input type="hidden" id="apikey" name="apikey" value="$apikey" />
<form action="$url('config/notify/save')" method="post" class="fullform" autocomplete="off">
<input type="hidden" name="output" value="json" />
<input type="hidden" id="ajax" name="ajax" value="1" />
<div class="section" id="email">
<div class="col2">
<h3>$T('cmenu-email') <a href="$help_uri#toc0" target="_blank"><span class="glyphicon glyphicon-question-sign"></span></a></h3>
@@ -93,10 +92,10 @@
</div>
<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>
<div class="field-pair result-box">
<div class="alert"></div>
<button class="btn btn-default" type="button" id="test_email" rel="$T('askTestEmail')" aria-busy="false"><span class="glyphicon glyphicon-envelope" aria-hidden="true"></span> $T('link-testEmail')</button>
<div class="result-box" role="status" aria-live="polite" aria-atomic="true">
<div class="alert"></div>
</div>
</div>
</fieldset>
</div>
@@ -117,10 +116,10 @@
$show_notify_checkboxes('ncenter')
<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>
<div class="field-pair result-box">
<div class="alert"></div>
<button class="btn btn-default" type="button" id="test_notif" aria-busy="false"><span class="glyphicon glyphicon-comment" aria-hidden="true"></span> $T('testNotify')</button>
<div class="result-box" role="status" aria-live="polite" aria-atomic="true">
<div class="alert"></div>
</div>
</div>
</fieldset>
</div>
@@ -143,10 +142,10 @@
$show_notify_checkboxes('acenter')
<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>
<div class="field-pair result-box">
<div class="alert"></div>
<button class="btn btn-default" type="button" id="test_windows" aria-busy="false"><span class="glyphicon glyphicon-comment" aria-hidden="true"></span> $T('testNotify')</button>
<div class="result-box" role="status" aria-live="polite" aria-atomic="true">
<div class="alert"></div>
</div>
</div>
</fieldset>
</div>
@@ -169,10 +168,10 @@
$show_notify_checkboxes('ntfosd')
<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>
<div class="field-pair result-box">
<div class="alert"></div>
<button class="btn btn-default" type="button" id="test_osd" aria-busy="false"><span class="glyphicon glyphicon-comment" aria-hidden="true"></span> $T('testNotify')</button>
<div class="result-box" role="status" aria-live="polite" aria-atomic="true">
<div class="alert"></div>
</div>
</div>
</fieldset>
</div>
@@ -209,17 +208,17 @@
<label class="config" for="${section_label}_target_${type}">
$T($notify_types[$type]).replace('/', ' / ')
</label>
<input type="checkbox" name="${section_label}_target_${type}_enable" id="${section_label}_target_${type}_enable" value="1" <!--#if $getVar($section_label + '_target_' + $type + '_enable') then 'checked="checked"' else ""#--> />
<input type="checkbox" class="notify-checkboxes" name="${section_label}_target_${type}_enable" id="${section_label}_target_${type}_enable" value="1" <!--#if $getVar($section_label + '_target_' + $type + '_enable') then 'checked="checked"' else ""#--> />
<input type="text" name="${section_label}_target_${type}" id="${section_label}_target_${type}" value="$getVar($section_label + '_target_' + $type)" placeholder="$T('opt-apprise_urls')" />
</div>
<!--#end for#-->
<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_apprise"><span class="glyphicon glyphicon-comment"></span> $T('testNotify')</button>
</div>
<div class="field-pair result-box">
<div class="alert"></div>
<button class="btn btn-default saveButton"><span class="glyphicon glyphicon-ok"></span> $T('button-saveChanges')</button>
<button class="btn btn-default" type="button" id="test_apprise" aria-busy="false"><span class="glyphicon glyphicon-comment" aria-hidden="true"></span> $T('testNotify')</button>
<div class="result-box" role="status" aria-live="polite" aria-atomic="true">
<div class="alert"></div>
</div>
</div>
</fieldset>
</div>
@@ -255,10 +254,10 @@
$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>
<button class="btn btn-default" type="button" id="test_nscript" aria-busy="false"><span class="glyphicon glyphicon-comment" aria-hidden="true"></span> $T('testNotify')</button>
<div class="result-box" role="status" aria-live="polite" aria-atomic="true">
<div class="alert"></div>
</div>
</div>
</fieldset>
</div>
@@ -300,10 +299,10 @@
<!--#end for#-->
<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>
<div class="field-pair result-box">
<div class="alert"></div>
<button class="btn btn-default" type="button" id="test_prowl" aria-busy="false"><span class="glyphicon glyphicon-comment" aria-hidden="true"></span> $T('testNotify')</button>
<div class="result-box" role="status" aria-live="polite" aria-atomic="true">
<div class="alert"></div>
</div>
</div>
</fieldset>
</div>
@@ -366,10 +365,10 @@
<!--#end for#-->
<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>
<div class="field-pair result-box">
<div class="alert"></div>
<button class="btn btn-default" type="button" id="test_pushover" aria-busy="false"><span class="glyphicon glyphicon-comment" aria-hidden="true"></span> $T('testNotify')</button>
<div class="result-box" role="status" aria-live="polite" aria-atomic="true">
<div class="alert"></div>
</div>
</div>
</fieldset>
</div>
@@ -402,11 +401,11 @@
<!--#end if#-->
$show_notify_checkboxes('pushbullet')
<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>
<div class="field-pair result-box">
<div class="alert"></div>
<button class="btn btn-default saveButton"><span class="glyphicon glyphicon-ok"></span> $T('button-saveChanges')</button>
<button class="btn btn-default" type="button" id="test_pushbullet" aria-busy="false"><span class="glyphicon glyphicon-comment" aria-hidden="true"></span> $T('testNotify')</button>
<div class="result-box" role="status" aria-live="polite" aria-atomic="true">
<div class="alert"></div>
</div>
</div>
</fieldset>
</div>
@@ -426,7 +425,6 @@ jQuery(document).ready(function(){
jQuery(this).parents('.col2').find('.col2-cats').hide()
}
jQuery('form').submit()
addRowColor()
})
jQuery('#email_endjob').change(function() {
if(jQuery(this).val() > 0) {
@@ -446,8 +444,9 @@ jQuery(document).ready(function(){
}
// Disable button and get the data
jQuery(buttonObj).attr("disabled", "disabled")
jQuery(buttonObj).attr("aria-busy", "true")
jQuery(buttonObj).find('span').toggleClass('glyphicon-comment glyphicon-refresh spin-glyphicon')
var data = { mode: buttonObj.id, apikey: '$apikey', output: 'json' };
var data = { mode: buttonObj.id, output: 'json' };
jQuery(buttonObj).parents('.section').extractFormDataTo(data);
// Clear up the box
@@ -455,22 +454,23 @@ jQuery(document).ready(function(){
// Get the request
jQuery.ajax({
type: "GET",
url: "../../api",
type: "POST",
url: "$url('api')",
data: data
}).then(function(data) {
// Remove disabled and make the box
jQuery(buttonObj).removeAttr("disabled")
jQuery(buttonObj).attr("aria-busy", "false")
jQuery(buttonObj).find('span').toggleClass('glyphicon-comment glyphicon-refresh spin-glyphicon')
resultBox.removeClass('alert-success alert-danger').show()
if(data.status) {
resultBox.addClass('alert-success')
resultBox.text('$T('notifications-notesent')')
resultBox.prepend('<span class="glyphicon glyphicon-ok-sign"></span> ')
resultBox.prepend('<span class="glyphicon glyphicon-ok-sign" aria-hidden="true"></span> ')
} else {
resultBox.addClass('alert-danger')
resultBox.text(data.error)
resultBox.prepend('<span class="glyphicon glyphicon-exclamation-sign"></span> ')
resultBox.prepend('<span class="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></span> ')
}
})
}
+135 -147
View File
@@ -8,8 +8,7 @@
<div class="padTable">
<a class="main-helplink" href="$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">
<input type="hidden" name="apikey" value="$apikey" />
<form data-form="add-rss-feed" action="$url('config/rss/add_rss_feed')" method="post" autocomplete="off">
<table class="catTable addRssTable">
<tr>
<th>&nbsp;</th>
@@ -38,8 +37,7 @@
<!--#if $rss#-->
<div class="section">
<div class="padTable">
<form action="save_rss_feed" method="post" autocomplete="off">
<input type="hidden" name="apikey" value="$apikey" />
<form action="$url('config/rss/save_rss_feed')" method="post" autocomplete="off">
<table id="subscriptions">
<tbody>
<!--#set $feeds = sorted($rss.keys(), key=lambda x: x.lower())#-->
@@ -75,8 +73,7 @@
</form>
<!--#if $feeds#-->
<br/>
<form action="rss_now" method="post" autocomplete="off">
<input type="hidden" name="apikey" value="$apikey" />
<form action="$url('config/rss/rss_now')" method="post" autocomplete="off">
<button type="submit" class="btn btn-default readAll"><span class="glyphicon glyphicon-sort"></span> $T('button-rssNow')</button>
</form>
<!--#end if#-->
@@ -84,8 +81,7 @@
</div>
<!--#end if#-->
<div class="section">
<form action="save_rss_rate" method="post" autocomplete="off">
<input type="hidden" name="apikey" value="$apikey" />
<form action="$url('config/rss/save_rss_rate')" method="post" autocomplete="off">
<div class="col1">
<fieldset>
<div class="field-pair">
@@ -107,7 +103,7 @@
<div class="padTable">
<a class="main-helplink" href="$help_uri" target="_blank"><span class="glyphicon glyphicon-question-sign"></span></a>
<h2 class="nomargin activeRSS">
<a href="${root}config/rss/">$T('cmenu-rss')</a> &raquo;
<a href="$url('config/rss')">$T('cmenu-rss')</a> &raquo;
$active_feed
</h2>
<!--#if $error#-->
@@ -116,8 +112,7 @@
$error
</div>
<!--#end if#-->
<form action="upd_rss_feed" method="post">
<input type="hidden" name="apikey" value="$apikey" />
<form action="$url('config/rss/upd_rss_feed')" method="post">
<input type="hidden" name="feed" value="$active_feed" />
<input type="hidden" name="uri" value="$rss[$feed]['uris']" />
<table class="catTable">
@@ -156,7 +151,8 @@
<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') ($T('rss-accept'))</option>
<option value="S"> $T('rss-from-show') ($T('rss-accept'))</option>
<option value="G"> $T('rss-age')</option>
</select>
</td>
<td>
@@ -207,8 +203,7 @@
</table>
</form>
<!-- add new filter -->
<form action="upd_rss_filter" method="post">
<input type="hidden" name="apikey" value="$apikey" />
<form data-form="update-rss-filter" action="$url('config/rss/upd_rss_filter')" method="post">
<input type="hidden" name="index" value="$rss[$feed]['filtercount']" />
<input type="hidden" name="feed" value="$active_feed" />
<table class="catTable">
@@ -230,6 +225,7 @@
<option value="<"> $T('rss-atmost')</option>
<option value="F"> $T('rss-from')</option>
<option value="S"> $T('rss-from-show') ($T('rss-accept'))</option>
<option value="G"> $T('rss-age')</option>
</select>
</td>
<td>
@@ -283,8 +279,7 @@
<!--#set $fnum = 0#-->
<!--#for $filter in $rss[$feed].filters#-->
<!--#set $odd = not $odd#-->
<form action="upd_rss_filter" method="post" autocomplete="off">
<input type="hidden" name="apikey" value="$apikey" />
<form data-form="update-rss-filter" action="$url('config/rss/upd_rss_filter')" method="post" autocomplete="off">
<input type="hidden" name="index" value="$fnum" />
<input type="hidden" name="feed" value="$active_feed" />
<table class="catTable">
@@ -306,6 +301,7 @@
<option value="<" <!--#if $filter[3]=="&lt;" then 'selected="selected"' else ""#-->> $T('rss-atmost')</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>
<option value="G" <!--#if $filter[3]=="G" then 'selected="selected"' else ""#-->> $T('rss-age')</option>
</select>
</td>
<td>
@@ -361,137 +357,48 @@
</form>
<!--#set $fnum = $fnum+1#-->
<!--#end for#-->
<form action="download_rss_feed" method="post">
<input type="hidden" name="apikey" value="$apikey" />
<form action="$url('config/rss/download_rss_feed')" method="post">
<input type="hidden" name="feed" value="$active_feed" />
<div class="padding">
<button type="button" class="btn btn-default testFeed" rel="$active_feed"><span class="glyphicon glyphicon-sort"></span> $T('button-preFeed')</button>
<button type="submit" class="btn btn-default Save"><span class="glyphicon glyphicon-forward"></span> $T('button-forceFeed')</button>
<button type="button" class="btn btn-default cleanFeed"><span class="glyphicon glyphicon-trash"></span> $T('button-clear') $T('rss-done')</button>
<!--#if $evalButton#-->
<button type="button" class="btn btn-default evalFeed" rel="$active_feed"><span class="glyphicon glyphicon-ok-circle"></span> $T('button-evalFeed')</button>
<!--#end if#-->
</div>
</form>
</div>
</div>
<br/>
<div style="padding-left: 10px; border-bottom: 1px solid #DFDEDE;">
<ul class="tabs">
<li class="active"><a href="#rss-tab-matched" data-toggle="tab">$T('rss-matched') <span class="count"><!--#echo len($matched)#--></span></a></li>
<li><a href="#rss-tab-not-matched">$T('rss-notMatched') <span class="count"><!--#echo len($unmatched)#--></span></a></li>
<li><a href="#rss-tab-done">$T('rss-done') <span class="count"><!--#echo len($downloaded)#--></span></a></li>
</ul>
</div>
<div class="tab-content">
<div class="tab-pane fade in active padTable" id="rss-tab-matched">
<!--#if $matched#-->
<table class="catTable">
<thead>
<tr>
<th class="no-sort">$T('link-download')</th>
<th>$T('rss-filter')</th>
<th>$T('size')</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#-->
<tr class="infoTableSeperator">
<td>
<form action="download" method="get">
<input type="hidden" value="$active_feed" name="feed" />
<input type="hidden" name="apikey" value="$apikey" />
<input type="hidden" name="url" value="$job['url']" />
<input type="hidden" name="nzbname" value="$job['nzbname']" />
<button type="submit" class="btn btn-default"><span class="glyphicon glyphicon-plus-sign"></span> $T('link-download')</button>
</form>
</td>
<td>$job['rule'] $job['skip']</td>
<td data-sort-value="$job['size']">$job['size_units']</td>
<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>
<!--#else#-->
$T('none')
<!--#end if#-->
</div>
<div class="tab-pane padTable" id="rss-tab-not-matched">
<!--#if $unmatched#-->
<table class="catTable">
<thead>
<tr>
<th class="no-sort">$T('link-download')</th>
<th>$T('rss-filter')</th>
<th>$T('size')</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#-->
<tr class="infoTableSeperator">
<td>
<form action="download" method="get">
<input type="hidden" value="$active_feed" name="feed" />
<input type="hidden" name="apikey" value="$apikey" />
<input type="hidden" name="url" value="$job['url']" />
<input type="hidden" name="nzbname" value="$job['nzbname']" />
<button type="submit" class="btn btn-default"><span class="glyphicon glyphicon-plus-sign"></span> $T('link-download')</button>
</form>
</td>
<td>$job['rule'] $job['skip']</td>
<td data-sort-value="$job['size']">$job['size_units']</td>
<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>
<!--#else#-->
$T('none')
<!--#end if#-->
</div>
<div class="tab-pane padTable" id="rss-tab-done">
<!--#if $downloaded#-->
<form action="clean_rss_jobs" method="post">
<input type="hidden" value="$active_feed" name="feed" />
<input type="hidden" name="apikey" value="$apikey" />
<ul class="nav nav-tabs">
<li class="active"><a href="#rss-tab-matched" data-toggle="tab">$T('rss-matched') <span class="count"><!--#echo len($matched)#--></span></a></li>
<li><a href="#rss-tab-not-matched">$T('rss-notMatched') <span class="count"><!--#echo len($unmatched)#--></span></a></li>
<li><a href="#rss-tab-done">$T('rss-done') <span class="count"><!--#echo len($downloaded)#--></span></a></li>
</ul>
<div class="section">
<div class="tab-content">
<div class="tab-pane fade in active padTable" id="rss-tab-matched">
<!--#if $matched#-->
<table class="catTable">
<thead>
<tr>
<th class="default-sort">$T('rss-added')</th>
<th class="no-sort">$T('link-download')</th>
<th>$T('rss-filter')</th>
<th>$T('size')</th>
<th width="60%">$T('sort-title')</th>
<th>$T('category')</th>
<th>$T('nzo-age')</th>
<th class="default-sort">$T('nzo-age')</th>
<th>$T('source')</th>
</tr>
</thead>
<!--#for $job in $downloaded#-->
<!--#for $job in $matched#-->
<tr class="infoTableSeperator">
<td data-sort-value="$job['time_downloaded_ms']">$job['time_downloaded']</td>
<td>
<form data-form="download-rss-job" action="$url('config/rss/download')" method="post">
<input type="hidden" value="$active_feed" name="feed" />
<input type="hidden" name="url" value="$job['url']" />
<input type="hidden" name="nzbname" value="$job['nzbname']" />
<button type="submit" class="btn btn-default"><span class="glyphicon glyphicon-plus-sign"></span> $T('link-download')</button>
</form>
</td>
<td>$job['rule'] $job['skip']</td>
<td data-sort-value="$job['size']">$job['size_units']</td>
<td>$job['title']</td>
@@ -507,22 +414,104 @@
</tr>
<!--#end for#-->
</table>
</form>
<!--#else#-->
<!--#else#-->
$T('none')
<!--#end if#-->
</div>
<div class="tab-pane padTable" id="rss-tab-not-matched">
<!--#if $unmatched#-->
<table class="catTable">
<thead>
<tr>
<th class="no-sort">$T('link-download')</th>
<th>$T('rss-filter')</th>
<th>$T('size')</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#-->
<tr class="infoTableSeperator">
<td>
<form data-form="download-rss-job" action="$url('config/rss/download')" method="post">
<input type="hidden" value="$active_feed" name="feed" />
<input type="hidden" name="url" value="$job['url']" />
<input type="hidden" name="nzbname" value="$job['nzbname']" />
<button type="submit" class="btn btn-default"><span class="glyphicon glyphicon-plus-sign"></span> $T('link-download')</button>
</form>
</td>
<td>$job['rule'] $job['skip']</td>
<td data-sort-value="$job['size']">$job['size_units']</td>
<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>
<!--#else#-->
$T('none')
<!--#end if#-->
</div>
<div class="tab-pane padTable" id="rss-tab-done">
<!--#if $downloaded#-->
<form action="$url('config/rss/clean_rss_jobs')" method="post">
<input type="hidden" value="$active_feed" name="feed" />
<table class="catTable">
<thead>
<tr>
<th class="default-sort">$T('rss-added')</th>
<th>$T('rss-filter')</th>
<th>$T('size')</th>
<th width="60%">$T('sort-title')</th>
<th>$T('category')</th>
<th>$T('nzo-age')</th>
<th>$T('source')</th>
</tr>
</thead>
<!--#for $job in $downloaded#-->
<tr class="infoTableSeperator">
<td data-sort-value="$job['time_downloaded_ms']">$job['time_downloaded']</td>
<td>$job['rule'] $job['skip']</td>
<td data-sort-value="$job['size']">$job['size_units']</td>
<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>
</form>
<!--#else#-->
$T('none')
<!--#end if#-->
</div>
<!--#end if#-->
</div>
<!--#end if#-->
</div>
</div>
<!-- /colmask -->
<form method="post" action="save_rss_feed" class="modal fade" id="rss_edit_modal">
<form method="post" action="$url('config/rss/save_rss_feed')" class="modal fade" id="rss_edit_modal" tabindex="-1" role="dialog" aria-modal="true" aria-labelledby="rss-edit-modal-title">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">&times;</button>
<h4 class="modal-title">$T('Edit') <span id="feed_edit_name_label"></span></h4>
<button type="button" class="close" data-dismiss="modal" aria-label="$T('close')"><span aria-hidden="true">&times;</span></button>
<h4 class="modal-title" id="rss-edit-modal-title">$T('Edit') <span id="feed_edit_name_label"></span></h4>
</div>
<div class="modal-body">
<div class="form-group">
@@ -535,7 +524,6 @@
<span class="help-block">$T('addMultipleFeeds')</span>
</div>
<input type="hidden" name="feed" id="feed_edit_old_name" />
<input type="hidden" name="apikey" value="$apikey" />
</div>
<div class="modal-footer">
<button type="button" class="btn btn-danger" data-dismiss="modal"><span class="glyphicon glyphicon-remove"></span> $T('cancel')</button>
@@ -545,7 +533,7 @@
</div><!-- /.modal-dialog -->
</form><!-- /.modal -->
<script type="text/javascript" src="${root}staticcfg/js/jquery.tablesort.min.js"></script>
<script type="text/javascript" src="$url('staticcfg/js/jquery.tablesort.min.js', v=$cache_buster)"></script>
<script type="text/javascript">
function urlencode(str) {
return encodeURIComponent(str).replace(/!/g, '%21').replace(/'/g, '%27').replace(/\(/g, '%28').replace(/\)/g, '%29').replace(/\*/g, '%2A').replace(/%20/g, '+');
@@ -564,7 +552,7 @@ jQuery(document).ready(function(){
}
})
jQuery('.tabs a').click(function (e) {
jQuery('.nav-tabs a').click(function (e) {
e.preventDefault()
jQuery(this).tab('show')
})
@@ -589,8 +577,8 @@ jQuery(document).ready(function(){
var whichFeed = jQuery(this).attr("rel");
jQuery.ajax({
type: "POST",
url: "del_rss_feed",
data: {feed: whichFeed, apikey: "$apikey" }
url: "$url('config/rss/del_rss_feed')",
data: {feed: whichFeed }
}).done(function( msg ) {
// Let us leave!
formWasSubmitted = true;
@@ -604,8 +592,8 @@ jQuery(document).ready(function(){
var whichFeed = jQuery(this).attr("rel");
jQuery.ajax({
type: "POST",
url: "toggle_rss_feed",
data: {feed: whichFeed, apikey: "$apikey" }
url: "$url('config/rss/toggle_rss_feed')",
data: {feed: whichFeed }
}).done(function() {
// Let us leave!
formWasSubmitted = true;
@@ -615,7 +603,7 @@ jQuery(document).ready(function(){
});
// Only the Accept filter needs all the options
jQuery('form[action="upd_rss_filter"]').find('select[name="filter_type"]').change(function() {
jQuery('form[data-form="update-rss-filter"]').find('select[name="filter_type"]').change(function() {
jQuery(this).parent().parent().find('select:not([name="filter_type"])').attr('disabled', jQuery(this).val() !== "A" && jQuery(this).val() !== "S")
})
// Trigger on-load for all
@@ -644,8 +632,8 @@ jQuery(document).ready(function(){
var whichFeed = jQuery(this).attr("rel");
jQuery.ajax({
type: "POST",
url: "test_rss_feed",
data: {feed: whichFeed, apikey: "$apikey" }
url: "$url('config/rss/test_rss_feed')",
data: {feed: whichFeed }
}).done(function( msg ) {
// Let us leave!
formWasSubmitted = true;
@@ -657,21 +645,21 @@ jQuery(document).ready(function(){
jQuery('.cleanFeed').click(function(){
setActiveIcon(this)
var theForm = jQuery(this).closest("form");
theForm.attr("action", "clean_rss_jobs").submit();
theForm.attr("action", "$url('config/rss/clean_rss_jobs')").submit();
});
jQuery('.evalFeed').click(function(){
setActiveIcon(this)
var theForm = jQuery(this).closest("form");
theForm.attr("action", "eval_rss_feed").submit();
theForm.attr("action", "$url('config/rss/eval_rss_feed')").submit();
});
jQuery('.delFilter').click(function(){
var theForm = jQuery(this).closest("form");
theForm.attr("action", "del_rss_filter").submit();
theForm.attr("action", "$url('config/rss/del_rss_filter')").submit();
});
jQuery('form[action="download"]').ajaxForm({
jQuery('form[data-form="download-rss-job"]').ajaxForm({
datatype: 'json',
beforeSubmit: function (_, form) {
jQuery(form).find('button').attr("disabled", "disabled")
@@ -17,17 +17,16 @@ else:
<div class="col2">
<h3>$T('addSchedule') <a href="$help_uri" target="_blank"><span class="glyphicon glyphicon-question-sign"></span></a></h3>
</div><!-- /col2 -->
<form action="addSchedule" method="post" autocomplete="off">
<input type="hidden" id="apikey" name="apikey" value="$apikey" />
<form data-form="add-schedule" action="$url('config/scheduling/add_schedule')" method="post" autocomplete="off">
<div class="col1">
<fieldset>
<div class="field-pair">
<label class="config" for="hour">$T('hour').capitalize() : $T('minute').capitalize()</label>
<select name="hour" id="hour">
<select name="hour" id="hour" aria-label="$T('hour').capitalize()">
<!--#for $i in range(24)#-->
<option value="$i" <!--#if hour == i then 'selected="selected"' else ""#--> > $str($i).zfill(2) </option>
<!--#end for#-->
</select>&nbsp;<b>:</b>&nbsp;<select name="minute" id="minute">
</select>&nbsp;<b>:</b>&nbsp;<select name="minute" id="minute" aria-label="$T('minute').capitalize()">
<!--#for $i in range(60)#-->
<option value="$i"> $str($i).zfill(2) </option>
<!--#end for#-->
@@ -90,13 +89,12 @@ else:
<!--#set $odd = True#-->
<!--#for $schednum, $line in enumerate($schedlines)#-->
<!--#set $odd = not $odd#-->
<form action="delSchedule" method="post">
<input type="hidden" name="apikey" value="$apikey"/>
<input type="hidden" name="line" id="line" value="$line"/>
<form data-form="del-schedule" action="$url('config/scheduling/del_schedule')" method="post">
<input type="hidden" name="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">
<input type="checkbox" name="schedenabled" value="$line" aria-label="$T('enabled')" aria-describedby="schedule-description-$schednum" <!--#if int($taskinfo[$schednum][5]) > 0 then 'checked="checked"' else ""#-->>
<button class="btn btn-default float-left" type="submit" aria-label="$T('nzo-delete')" aria-describedby="schedule-description-$schednum"><span class="glyphicon glyphicon-trash" aria-hidden="true"></span></button>
<div class="scheduleEntry" id="schedule-description-$schednum">
<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>
@@ -136,8 +134,8 @@ else:
jQuery('[name="schedenabled"]').click(function() {
jQuery.ajax({
type: "POST",
url: "toggleSchedule",
data: {line: jQuery(this).val(), apikey: "$apikey" }
url: "$url('config/scheduling/toggle_schedule')",
data: {line: jQuery(this).val() }
}).done(function() {
// Let us leave!
formWasSubmitted = true;
+56 -36
View File
@@ -40,14 +40,12 @@
<h3>$T('addServer') <a href="$help_uri" target="_blank"><span class="glyphicon glyphicon-question-sign"></span></a></h3>
</div>
<div class="col1">
<form action="addServer" method="post" autocomplete="off" onsubmit="removeObfuscation();">
<input type="hidden" name="apikey" value="$apikey" />
<form data-form="add-server" action="$url('config/server/add_server')" method="post" autocomplete="off">
<input type="hidden" name="output" value="json" />
<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 advanced-settings">
<label class="config" for="displayname">$T('srv-displayname')</label>
@@ -77,7 +75,7 @@
</div>
<div class="field-pair">
<label class="config" for="connections">$T('srv-connections')</label>
<input type="number" name="connections" id="connections" min="1" max="500" value="8" required />
<input type="number" name="connections" id="connections" min="1" max="500" value="16" required />
</div>
<div class="field-pair">
<label class="config" for="priority">$T('srv-priority')</label>
@@ -139,11 +137,12 @@
</div>
<div class="field-pair no-field-pair-bg">
<button class="btn btn-default addNewServer" disabled data-toggle="tooltip" data-placement="top" title="$T('wizard-test-server-required')"><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>
<button class="btn btn-default testServer" type="button" aria-busy="false"><span class="glyphicon glyphicon-sort" aria-hidden="true"></span> $T('button-testServer')</button>
<div class="result-box" role="status" aria-live="polite" aria-atomic="true">
<div class="alert"></div>
</div>
</div>
</fieldset>
</form>
</div>
@@ -153,11 +152,9 @@
<!--#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="apikey" value="$apikey" />
<form action="$url('config/server/save_server')" method="post" class="fullform" autocomplete="off">
<input type="hidden" name="output" value="json" />
<input type="hidden" name="server" value="$server['name']" />
<input type="hidden" 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 ""#-->">
@@ -276,15 +273,16 @@
</div>
<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 testServer" type="button"><span class="glyphicon glyphicon-sort"></span> $T('button-testServer')</button>
<button class="btn btn-default testServer" type="button" aria-busy="false"><span class="glyphicon glyphicon-sort" aria-hidden="true"></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 class="result-box" role="status" aria-live="polite" aria-atomic="true">
<div class="alert"></div>
</div>
</div>
</fieldset>
</div>
<div class="col1" style="display:block;">
<div class="col1 padding" style="display:block;">
<!--#if 'amounts' in $server#-->
<div class="server-amounts-text">
<p>
@@ -327,8 +325,8 @@
<!--#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" src="$url('staticcfg/js/chartist.min.js', v=$cache_buster)"></script>
<script type="text/javascript" src="$url('staticcfg/js/filesize.min.js', v=$cache_buster)"></script>
<script type="text/javascript">
// Standardize chart options
var chartOptions = {
@@ -479,11 +477,9 @@
// Initialize tooltips
jQuery('[data-toggle="tooltip"]').tooltip()
// Reload form in case we change items that make the servers appear different
// Reload the page after saving in case we change items that make the servers appear different
jQuery('input[name="priority"], input[name="displayname"], textarea[name="notes"]').on('change', function(event) {
var parentForm = jQuery(event.target).parents("form")
parentForm.unbind("submit")
parentForm.find('input[name="ajax"]').val('')
jQuery(event.target).parents("form").attr("data-reload-on-save", "")
})
/**
@@ -517,14 +513,10 @@
// Recalculate the charts if changed while details were open
showCharts()
}
// Add coloring
addRowColor()
});
jQuery('#addServerButton').click(function(){
jQuery('#addServerContent').show();
// Add coloring
addRowColor()
});
jQuery('[name="ssl"]').click(function() {
@@ -552,28 +544,31 @@
var theButton = jQuery(this)
var resultBox = theButton.parents('.col1').find('.result-box .alert');
theButton.attr("disabled", "disabled")
theButton.attr("aria-busy", "true")
theButton.find('span').toggleClass('glyphicon-sort glyphicon-refresh spin-glyphicon')
resultBox.removeClass('alert-success alert-danger').addClass('alert-info').html("$T('srv-testing')").show()
jQuery.ajax({
type: "POST",
url: "../../api",
url: "$url('api')",
data: "mode=config&name=test_server&" + jQuery(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.removeClass('alert-info alert-success alert-danger').show()
resultBox.html(msg)
theButton.removeAttr("disabled")
theButton.attr("aria-busy", "false")
theButton.find('span').toggleClass('glyphicon-sort glyphicon-refresh spin-glyphicon')
// Success or not?
if(data.value.result) {
resultBox.addClass('alert-success')
resultBox.prepend('<span class="glyphicon glyphicon-ok-sign"></span> ')
resultBox.prepend('<span class="glyphicon glyphicon-ok-sign" aria-hidden="true"></span> ')
// Allow adding the new server if we are in the new-server section
if(theButton.parents("form[action='addServer']").length) {
if(theButton.parents("form[data-form='add-server']").length) {
jQuery(".addNewServer").removeAttr("disabled")
jQuery(".addNewServer").removeAttr("data-toggle")
jQuery(".addNewServer").removeAttr("title")
@@ -581,10 +576,10 @@
}
} else {
resultBox.addClass('alert-danger')
resultBox.prepend('<span class="glyphicon glyphicon-exclamation-sign"></span> ')
resultBox.prepend('<span class="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></span> ')
// Disable the adding of new server, just to be sure
if(theButton.parents("form[action='addServer']").length) {
if(theButton.parents("form[data-form='add-server']").length) {
jQuery(".addNewServer").attr("disabled", "disabled")
jQuery(".addNewServer").attr("data-toggle", "tooltip")
jQuery(".addNewServer").attr("data-placement", "top")
@@ -595,9 +590,34 @@
});
});
jQuery('.addNewServer').click(function(event){
event.preventDefault()
removeObfuscation()
var theButton = jQuery(this)
var resultBox = theButton.parents('.col1').find('.result-box .alert')
theButton.attr("disabled", "disabled")
jQuery.ajax({
type: "POST",
url: "$url('config/server/add_server')",
data: theButton.parents('form:first').serialize()
}).then(function(data) {
if(data.status) {
// Success — reload to show the new server
formWasSubmitted = true
formHasChanged = false
location.reload()
} else {
// Show error in the result box
resultBox.removeClass('alert-success').addClass('alert-danger').show()
resultBox.html('<span class="glyphicon glyphicon-exclamation-sign"></span> ' + data.error)
theButton.removeAttr("disabled")
}
})
})
jQuery('.delServer').click(function(){
if( confirm("$T('confirm')") ) {
jQuery(this).parents('form:first').attr('action','delServer').submit();
jQuery(this).parents('form:first').attr('action',"$url('config/server/delete_server')").submit();
// Let us leave!
formWasSubmitted = true;
formHasChanged = false;
@@ -608,7 +628,7 @@
jQuery('.clrServer').click(function(){
if( confirm("$T('confirm')") ) {
jQuery(this).parents('form:first').attr('action','clrServer').submit();
jQuery(this).parents('form:first').attr('action',"$url('config/server/clear_server')").submit();
// Let us leave!
formWasSubmitted = true;
formHasChanged = false;
@@ -621,8 +641,8 @@
var whichServer = jQuery(this).attr("name");
jQuery.ajax({
type: "POST",
url: "toggleServer",
data: {server: whichServer, apikey: "$apikey" }
url: "$url('config/server/toggle_server')",
data: {server: whichServer }
}).done(function() {
// Let us leave!
formWasSubmitted = true;
+10 -18
View File
@@ -20,8 +20,7 @@
<!--#set $selected_types = ["0"] if not len($selected_types) else $selected_types #-->
<!-- SORTER $cur -->
<div class="section <!--#if $cur != 0#-->sorter<!--#end if#-->" id="sorter_$cur" <!--#if $cur == 0#-->style="display: none;"<!--#end if#-->>
<form action="save_sorter" method="post" autocomplete="off" <!--#if $cur != 0#-->class="sorting-row"<!--#end if#-->>
<input type="hidden" name="apikey" value="$apikey" />
<form action="$url('config/sorting/save_sorter')" method="post" autocomplete="off" <!--#if $cur != 0#-->class="sorting-row"<!--#end if#-->>
<input type="hidden" name="order" value="$slot.order" />
<input type="hidden" value="$slot.name" name="name" />
<!--#if $cur == 0#-->
@@ -347,8 +346,7 @@
<div class="section align-center sorting-quick-setup">
<h3>$T('sort-quick-add'):</h3>
<!--#if "tv" in $categories#-->
<form action="save_sorter" method="post" autocomplete="off">
<input type="hidden" name="apikey" value="$apikey" />
<form action="$url('config/sorting/save_sorter')" method="post" autocomplete="off">
<input type="hidden" name="order" value="0" />
<input type="hidden" name="is_active" value="1" />
<input type="hidden" name="name" value="">
@@ -366,8 +364,7 @@
</form>
<!--#end if#-->
<!--#if "movies" in $categories#-->
<form action="save_sorter" method="post" autocomplete="off">
<input type="hidden" name="apikey" value="$apikey" />
<form action="$url('config/sorting/save_sorter')" method="post" autocomplete="off">
<input type="hidden" name="order" value="0" />
<input type="hidden" name="is_active" value="1" />
<input type="hidden" name="name" value="">
@@ -388,7 +385,7 @@
</div>
<!--#end if#-->
</div>
<script type="text/javascript" src="${root}staticcfg/js/jquery-ui.min.js"></script>
<script type="text/javascript" src="$url('staticcfg/js/jquery-ui.min.js', v=$cache_buster)"></script>
<script type="text/javascript">
// http://stackoverflow.com/questions/2219924/
var typewatch = (function(){
@@ -414,13 +411,12 @@
typewatch(function () {
jQuery.ajax({
type: "GET",
url: "../../api",
url: "$url('api')",
data: {
mode: 'eval_sort',
job_name: jQuery('#preview_name_' + sort_nr).val(),
sort_string: preview_sort_string,
multipart_label: jQuery('#multipart_label_' + sort_nr).val(),
apikey: '$apikey',
output: 'json'
},
success: function(data) {
@@ -437,13 +433,11 @@
jQuery(document).ready(function() {
jQuery('.delSorter').click(function() {
var theForm = jQuery(this).closest("form");
theForm.attr("action", "delete").submit();
theForm.attr("action", "$url('config/sorting/delete')").submit();
});
jQuery('.addSorter').click(function(){
jQuery('#sorter_0').toggle();
// Add coloring
addRowColor()
});
jQuery('.showSorter').click(function () {
@@ -455,8 +449,6 @@
} else {
theSwitch.html(theSwitch.html().replace("$T('hideDetails')", "$T('showDetails')"));
}
// Add coloring
addRowColor()
});
// Make the configured sorters sortable
@@ -498,8 +490,8 @@
var data = {}
jQuery(elm).extractFormDataTo(data);
jQuery.ajax({
type: "GET",
url: window.location.pathname + 'save_sorter',
type: "POST",
url: "$url('config/sorting/save_sorter')",
data: data,
async: false // To prevent race-conditions when updating sorters
})
@@ -513,8 +505,8 @@
var whichSorter = jQuery(this).attr("rel");
jQuery.ajax({
type: "POST",
url: "toggle_sorter",
data: {sorter: whichSorter, apikey: "$apikey" }
url: "$url('config/sorting/toggle_sorter')",
data: {sorter: whichSorter }
}).done(function() {
formWasSubmitted = true;
formHasChanged = false;
@@ -3,8 +3,7 @@
<!--#include $webdir + "/_inc_header_uc.tmpl"#-->
<div class="colmask">
<form action="saveSpecial" method="post" autocomplete="off">
<input type="hidden" id="apikey" name="apikey" value="$apikey" />
<form action="$url('config/special/save')" method="post" class="fullform" autocomplete="off">
<div class="padTable">
<h4 class="darkred nomargin">$T('explain-special')</h4>
</div>
@@ -8,9 +8,7 @@
<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="apikey" name="apikey" value="$apikey" />
<input type="hidden" id="ajax" name="ajax" value="1" />
<form action="$url('config/switches/save')" method="post" class="fullform" autocomplete="off">
<input type="hidden" name="output" value="json" />
<div class="section advanced-settings">
<div class="col2">
@@ -152,6 +150,7 @@
<select name="auto_sort" id="auto_sort">
<option value="">$T('default')</option>
<option value="remaining asc" <!--#if $auto_sort == "remaining asc" then 'selected="selected"' else ""#--> >$T('Glitter-sortRemaining')</option>
<option value="remaining_bytes asc" <!--#if $auto_sort == "remaining_bytes asc" then 'selected="selected"' else ""#--> >$T('Glitter-sortRemainingBytesAsc')</option>
<option value="avg_age desc" <!--#if $auto_sort == "avg_age desc" then 'selected="selected"' else ""#--> >$T('Glitter-sortAgeAsc')</option>
<option value="avg_age asc" <!--#if $auto_sort == "avg_age asc" then 'selected="selected"' else ""#--> >$T('Glitter-sortAgeDesc')</option>
<option value="name asc" <!--#if $auto_sort == "name asc" then 'selected="selected"' else ""#--> >$T('Glitter-sortNameAsc')</option>
@@ -261,7 +260,7 @@
<div class="field-pair">
<label class="config" for="history_retention_option">$T('opt-history_retention')</label>
<select name="history_retention_option" id="history_retention_option">
<option value="all" <!--#if $auto_sort == "all" then 'selected="selected"' else ""#-->>$T('history_retention-all')</option>
<option value="all" <!--#if $history_retention_option == "all" then 'selected="selected"' else ""#-->>$T('history_retention-all')</option>
<option value="number-archive" <!--#if $history_retention_option == "number-archive" then 'selected="selected"' else ""#-->>$T('history_retention-number-archive')</option>
<option value="number-delete" <!--#if $history_retention_option == "number-delete" then 'selected="selected"' else ""#-->>$T('history_retention-number-delete')</option>
<option value="days-archive" <!--#if $history_retention_option == "days-archive" then 'selected="selected"' else ""#-->>$T('history_retention-days-archive')</option>
@@ -394,9 +393,9 @@ jQuery(document).ready(function() {
// Send request
jQuery.ajax({
type: "GET",
url: "../../api",
data: "mode=set_config_default&apikey=${apikey}&output=json&keyword=" + key_container.join('&keyword=')
type: "POST",
url: "$url('api')",
data: "mode=set_config_default&output=json&keyword=" + key_container.join('&keyword=')
}).then(function(data) {
// Reload page
document.location = document.location
+19 -23
View File
@@ -1,5 +1,5 @@
<!DOCTYPE HTML>
<html lang="$active_lang">
<html lang="$active_lang" data-color-scheme="$color_scheme">
<head>
<title>SABnzbd - $T('login')</title>
@@ -7,44 +7,40 @@
<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" />
<link rel="apple-touch-icon" sizes="120x120" href="../staticcfg/ico/apple-touch-icon-120x120-precomposed.png" />
<link rel="apple-touch-icon" sizes="152x152" href="../staticcfg/ico/apple-touch-icon-152x152-precomposed.png" />
<link rel="apple-touch-icon" sizes="180x180" href="../staticcfg/ico/apple-touch-icon-180x180-precomposed.png" />
<link rel="apple-touch-icon" sizes="192x192" href="../staticcfg/ico/android-192x192.png" />
<link rel="shortcut icon" href="../staticcfg/ico/favicon.ico?v=$version" />
<link rel="apple-touch-icon" sizes="76x76" href="$url('staticcfg/ico/apple-touch-icon-76x76-precomposed.png', v=$cache_buster)" />
<link rel="apple-touch-icon" sizes="120x120" href="$url('staticcfg/ico/apple-touch-icon-120x120-precomposed.png', v=$cache_buster)" />
<link rel="apple-touch-icon" sizes="152x152" href="$url('staticcfg/ico/apple-touch-icon-152x152-precomposed.png', v=$cache_buster)" />
<link rel="apple-touch-icon" sizes="180x180" href="$url('staticcfg/ico/apple-touch-icon-180x180-precomposed.png', v=$cache_buster)" />
<link rel="apple-touch-icon" sizes="192x192" href="$url('staticcfg/ico/android-192x192.png', v=$cache_buster)" />
<link rel="shortcut icon" href="$url('staticcfg/ico/favicon.ico', v=$cache_buster)" />
<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" />
<!--#if $color_scheme not in ('Light', '') #-->
<link rel="stylesheet" type="text/css" href="../staticcfg/css/${color_scheme}.css?v=$version"/>
<!--#end if#-->
<link rel="stylesheet" type="text/css" href="$url('staticcfg/bootstrap/css/bootstrap.min.css', v=$cache_buster)" />
<link rel="stylesheet" type="text/css" href="$url('staticcfg/css/login.css', v=$cache_buster)" />
<script type="text/javascript" src="../staticcfg/js/jquery-3.5.1.min.js?v=$version"></script>
<script type="text/javascript" src="../staticcfg/bootstrap/js/bootstrap.min.js?v=$version"></script>
<script type="text/javascript" src="$url('staticcfg/js/jquery-3.7.1.min.js', v=$cache_buster)"></script>
<script type="text/javascript" src="$url('staticcfg/bootstrap/js/bootstrap.min.js', v=$cache_buster)"></script>
</head>
<html>
<body>
<div class="account-wall">
<div class="text-center logo-header">
<!--#include $webdir + "/staticcfg/images/logo-full.svg"#-->
<a href="https://sabnzbd.org/wiki/faq#why-login" target="_blank">
<span class="glyphicon glyphicon-question-sign"></span>
<a href="https://sabnzbd.org/wiki/faq#why-login" target="_blank" aria-label="$T('menu-help')">
<span class="glyphicon glyphicon-question-sign" aria-hidden="true"></span>
</a>
</div>
<form class="form-signin" action="./" method="post">
<form class="form-signin" action="./login" method="post">
<!--#if $error#-->
<div class="alert alert-danger" role="alert">$error</div>
<!--#end if#-->
<input type="text" class="form-control" name="username" placeholder="$T('srv-username')" autocomplete="username" required autofocus>
<input type="password" class="form-control" name="password" placeholder="$T('srv-password')" autocomplete="current-password" required>
<input type="text" class="form-control" name="username" placeholder="$T('srv-username')" aria-label="$T('srv-username')" autocomplete="username" required autofocus>
<input type="password" class="form-control" name="password" placeholder="$T('srv-password')" aria-label="$T('srv-password')" autocomplete="current-password" required>
<button class="btn btn-default"><span class="glyphicon glyphicon-circle-arrow-right"></span> $T('login') </button>
<div class="checkbox text-center" data-toggle="tooltip" data-placement="bottom" title="$T('explain-sessionExpire')">
<div class="text-center" data-toggle="tooltip" data-placement="bottom" title="$T('explain-sessionExpire')">
<label>
<input type="checkbox" name="remember_me" value="1"> $T('rememberme')
$T('rememberme') <input type="checkbox" name="remember_me" value="1">
</label>
</div>
</form>
@@ -63,4 +59,4 @@
} catch(err) { }
</script>
</body>
</html>
</html>
@@ -1 +0,0 @@
@import url('Night.css') screen and (prefers-color-scheme: dark);
@@ -1,298 +0,0 @@
body {
background-color: black;
color: #EBEBEB !important;
}
a:not(.btn) {
color: #63a7e1;
}
.btn {
box-shadow: 1px 1px 1px rgba(255, 255, 255, .1) !important;
}
.btn:not(.btn-danger),
.btn-default,
input,
select,
textarea,
.advanced-button,
.list-group-item {
border-color: #252525 !important;
}
#addFeed,
#addFeedContent,
.section {
border-bottom: 1px solid #555555;
}
.col2 p,
.col2-cats {
color: #AAA;
}
.col2 h3 {
background: none repeat scroll 0 0 #555555;
}
.catTable,
.dropdown-menu,
.dropdown-menu .divider,
.even,
.Key tr:nth-child(odd),
.language:hover,
.navbar-default .navbar-nav>.open>a,
.navbar-default .navbar-nav>.open>a:focus,
.navbar-default .navbar-nav>.open>a:hover,
.navbar-default .navbar-nav>li>a.active,
.navbar-default .navbar-nav>li>a:hover,
.navbar-default .navbar-nav>li>a:focus,
.navbar-logo:hover,
.quoteBlock,
.server-disabled,
#serverResponse,
.table>tbody>tr:nth-child(odd),
.table-striped>tbody>tr:nth-child(odd),
ul.tabs li.active a,
select[disabled],
select:hover {
background-color: #444444 !important;
color: #EBEBEB !important;
}
.failed {
color: #ff3333 !important;
}
#rightGreyText,
small {
color: #c7c7c7 !important;
}
.Categories form.sorting-row:nth-child(2n-1) tr,
.advanced-button,
.advanced-buttonSeperator,
.alt,
.infoTableSeperator.alt,
.btn:not(.btn-danger),
.btn-default.disabled:active,
.btn-default.disabled:focus,
.btn-default.disabled:hover,
a.btn.btn-default,
select.form-control,
.form-control[disabled],
.input-group-addon,
#inner,
.navbar-default,
.search-box input,
.select,
.table-striped>tbody>tr:nth-child(even),
.table>tbody>tr:nth-child(even),
.tab-pane tr:nth-child(odd),
textarea,
ul.tabs a.active,
a.list-group-item,
.dropdown-menu>li>a,
input[type="text"],
input[type="email"],
input[type="url"],
input[type="date"],
input[type="number"],
input[type="password"],
input[disabled],
textarea,
select {
background-color: #555555;
color: #EBEBEB;
}
.btn:hover:not(.btn-danger),
.btn-default:hover,
.tab-content .catTable tr:hover td,
input:hover,
textarea:hover,
a.list-group-item:hover,
select:hover,
textarea:hover,
input[type="date"]:hover,
input[type="datetime"]:hover,
input[type="datetime-local"]:hover,
input[type="email"]:hover,
input[type="month"]:hover,
input[type="number"]:hover,
input[type="password"]:hover,
input[type="search"]:hover,
input[type="tel"]:hover,
input[type="text"]:hover,
input[type="time"]:hover,
input[type="url"]:hover,
input[type="week"]:hover,
textarea:focus,
select:focus,
input[type="date"]:focus,
input[type="datetime"]:focus,
input[type="datetime-local"]:focus,
input[type="email"]:focus,
input[type="month"]:focus,
input[type="number"]:focus,
input[type="password"]:focus,
input[type="search"]:focus,
input[type="tel"]:focus,
input[type="text"]:focus,
input[type="time"]:focus,
input[type="url"]:focus,
input[type="week"]:focus {
background-color: #666;
color: #EBEBEB;
}
.btn-default:focus,
.form-control:focus,
input:focus,
textarea:focus,
select:focus {
border-color: #707070 !important;
outline: initial !important;
box-shadow: 0 0 0 0.25rem rgba(255, 255, 255, 0.3) !important;
}
.modal-backdrop {
background-color: #262626 !important;
}
.Key tr {
border: none;
}
.table>tbody>tr>td,
.table>tbody>tr>th,
.infoTableSeperator,
.modal-footer,
.data-row {
border-top: 1px solid #555555;
}
hr {
border-top: 1px solid #555555;
}
.btn-danger {
border-color: #7b2b28;
}
.tab-content .catTable tbody,
ul.tabs a,
.colmask,
#subscriptions,
.RSS form[action="add_rss_feed"] tr:nth-child(even),
.Config .table {
border: 1px solid #555555;
}
.Categories form:first-of-type tr:last-of-type,
.default,
.dropdown-menu>li>a:focus,
.dropdown-menu>li>a:hover {
background-color: #696969;
}
.activeRSS,
.activeRSS a,
.activeRSS a:visited,
.btn-default,
.checkbox label,
.feed-row td,
.help-block,
#content,
.navbar-default .navbar-nav>li>a,
.navbar-default .navbar-nav>li>a>.glyphicon,
.path,
.Servers .ct-label,
.time,
.main-restarting.in,
#search-dropdown .dropdown-header,
ul.tabs a,
a.wizard-advanced-settings,
.quoteBlock a,
a.main-helplink,
col2 h3 a,
.text-center a,
.text-center a:hover {
color: #EBEBEB;
}
.container,
#content {
background-color: unset !important;
}
#content>div.colmask>div:nth-child(3) {
border-bottom: 1px solid #555555 !important;
}
.Servers .ct-series-a .ct-line,
.Servers .ct-series-a .ct-point {
stroke: #EBEBEB;
}
#inner,
.colmask {
background-color: #303030;
}
.modal-header {
background-color: #3C3C3C;
}
.modal-content,
.modal-body,
.modal-footer {
background-color: #727272;
}
#modal_qr .modal-body {
background-color: #EBEBEB;
}
.form-signin .btn.btn-default {
color: black;
}
.rss-icon-svg {
fill: white;
}
.rss-symbol {
fill: #555555;
}
::placeholder {
color: #EBEBEB !important;
opacity: 0.5;
}
.tooltip-inner {
background-color: #E4E4E4 !important;
color: #000 !important;
}
/* for login */
.tooltip.bottom .tooltip-arrow {
border-bottom-color: #E4E4E4 !important;
}
/* config>general - host-warning */
.tooltip.top .tooltip-arrow {
border-top-color: #E4E4E4 !important;
}
.tooltip.left .tooltip-arrow {
border-left-color: #E4E4E4 !important;
}
.tooltip.right .tooltip-arrow {
border-right-color: #E4E4E4 !important;
}
.Special .glyphicon-asterisk {
color: #E4E4E4 !important;
}
@@ -1,18 +1,32 @@
:root {
/* Follow the OS by default (Auto); forced by data-color-scheme below */
color-scheme: light dark;
/* Color palette — light-dark(light value, dark value) */
--clr-page-bg: light-dark(#E4E4E4, black);
--clr-surface: light-dark(#FFFFFF, #303030);
--clr-input-bg: light-dark(#FFFFFF, #555555);
--clr-border: light-dark(rgba(0, 0, 0, 0.15), #555555);
--clr-text: light-dark(#000000, #EBEBEB);
--clr-text-muted: light-dark(#636363, #c7c7c7);
--radius-lg: 8px;
--clr-toggle-active: #f9ca33;
--clr-toggle-inactive: light-dark(#aaaaaa, #666666);
}
/* The server renders the configured scheme into this attribute; empty/unknown = Auto */
html[data-color-scheme="light" i] {
color-scheme: light;
}
html[data-color-scheme="night" i] {
color-scheme: dark;
}
body {
overflow-y: scroll;
background-color: #E4E4E4;
}
* {
border-radius: 0 !important;
}
.btn,
.btn:hover,
.btn:active,
.btn:focus {
box-shadow: 1px 1px 1px rgba(0,0,0,.1) !important;
background-color: white !important;
background-color: var(--clr-page-bg);
color: var(--clr-text);
}
.logo-header {
@@ -25,38 +39,55 @@ body {
}
.account-wall {
width: 300px;
width: 400px;
position: absolute;
top: 30%;
left: 50%;
transform: translate(-50%, -30%);
transform: translate(-50%, -30%);
background-color: var(--clr-surface);
border: 1px solid var(--clr-border);
border-radius: var(--radius-lg);
padding: 25px 40px 15px;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.3);
}
.text-center a {
position: absolute;
right: 13px;
top: 12px;
color: #636363;
color: var(--clr-text-muted);
font-size: 1.2em
}
.text-center a:hover {
color: black;
color: var(--clr-text);
}
.form-signin .alert {
margin: 5px 0px;
}
.form-signin input {
margin-top: 5px
}
.form-signin input,
.form-signin .btn {
background-color: var(--clr-input-bg);
color: var(--clr-text);
border-color: light-dark(#ccc, #252525);
}
.form-signin .btn {
width: 100%;
margin-top: 10px;
font-weight: bold;
box-shadow: 1px 1px 1px light-dark(transparent, rgba(255, 255, 255, .1));
}
::placeholder {
color: var(--clr-text) !important;
opacity: 0.5;
}
.btn .glyphicon {
@@ -66,12 +97,109 @@ body {
font-size: 1.2em;
}
input[type="checkbox"] {
width: 16px;
height: 16px;
margin-top: 2px;
/* Toggle switch — text on the left, switch on the right */
label {
margin-top: 10px;
color: var(--clr-text-muted);
}
label {
color: #636363;
}
input[type="checkbox"] {
appearance: none;
-webkit-appearance: none;
position: relative;
top: 5px;
left: 5px;
width: 36px;
height: 20px;
background-color: var(--clr-toggle-inactive);
background-image:
radial-gradient(circle at 10px 10px, #ffffff 6.5px, rgba(255,255,255,0) 7px),
radial-gradient(circle at 11px 11px, rgba(0,0,0,0.30) 6.5px, transparent 9.5px);
background-size: 20px 20px;
background-repeat: no-repeat;
background-position: 0px center;
border-radius: 10px;
border: none;
cursor: pointer;
transition: background-color 0.2s, background-position 0.15s;
}
input[type="checkbox"]:checked {
background-color: var(--clr-toggle-active);
background-position: 16px center;
background-image:
radial-gradient(circle at 10px 10px, #ffffff 6.5px, rgba(255,255,255,0) 7px),
radial-gradient(circle at 10.5px 10.5px, rgba(0,0,0,0.30) 6.5px, transparent 9.5px);
}
input[type="checkbox"]:focus-visible {
outline: 2px solid var(--clr-toggle-active);
outline-offset: 2px;
border-radius: 10px;
}
/***
Dark-mode structural rules
Colors live in the light-dark() tokens above; these are the rules that cannot
be expressed as a color value. The two blocks below MUST stay identical:
the first applies when Night is forced, the second when the OS is dark and Light is not forced.
***/
html[data-color-scheme="night" i] {
/* Focus / active border */
.form-signin input:focus,
.form-signin .btn:focus {
border-color: #707070 !important;
outline: initial !important;
box-shadow: 0 0 0 0.25rem rgba(255, 255, 255, 0.3) !important;
}
.text-center a,
.text-center a:hover {
color: var(--clr-text);
}
/* Tooltip — inverted (light tooltip on dark page) */
.tooltip-inner {
background-color: #E4E4E4 !important;
color: #000 !important;
}
.tooltip.bottom .tooltip-arrow {
border-bottom-color: #E4E4E4 !important;
}
.tooltip.top .tooltip-arrow {
border-top-color: #E4E4E4 !important;
}
}
@media (prefers-color-scheme: dark) {
html:not([data-color-scheme="light" i]) {
/* Focus / active border */
.form-signin input:focus,
.form-signin .btn:focus {
border-color: #707070 !important;
outline: initial !important;
box-shadow: 0 0 0 0.25rem rgba(255, 255, 255, 0.3) !important;
}
.text-center a,
.text-center a:hover {
color: var(--clr-text);
}
/* Tooltip — inverted (light tooltip on dark page) */
.tooltip-inner {
background-color: #E4E4E4 !important;
color: #000 !important;
}
.tooltip.bottom .tooltip-arrow {
border-bottom-color: #E4E4E4 !important;
}
.tooltip.top .tooltip-arrow {
border-top-color: #E4E4E4 !important;
}
}
}
File diff suppressed because it is too large. Load diff
File diff suppressed because one or more lines are too long.
File diff suppressed because one or more lines are too long.
File diff suppressed because one or more lines are too long.
@@ -243,6 +243,11 @@ $.fn.extractFormDataTo = function(target) {
*
* (c) 2015 SABnzbd Team, Inc. All rights reserved.
*/
$.ajaxSetup({
headers: { "X-SABnzbd-CSRF": csrfToken }
});
function config_success() {
$('.saveButton[disabled=disabled]').each(function () {
$(this).removeAttr("disabled").html('<span class="glyphicon glyphicon-ok"></span> '+configTranslate.saveChanges);
@@ -291,7 +296,7 @@ function do_restart() {
$('.main-restarting .restarting-url').text(urlTotal)
// Initiate restart
$.ajax({ url: '../../api?mode=restart&apikey=' + sabSession,
$.ajax({ url: rootURL + 'api?mode=restart', type: 'POST',
complete: function() {
// Keep counter of failures
var loopCounter = 0;
@@ -340,14 +345,6 @@ function removeObfuscation() {
return true
}
// Add coloring to rows (shorthand function)
function addRowColor() {
// Have to do it seperate for each section
$('.section').each(function(i, elmn) {
$(elmn).find('.field-pair:visible').removeClass('even').filter(':even').addClass('even')
})
}
// Set default functions for the autocomplete everywhere
jQuery.extend(jQuery.fn.typeahead.defaults, {
source: function (query, process) {
@@ -379,6 +376,8 @@ jQuery.extend(jQuery.fn.typeahead.defaults, {
})
$(document).ready(function () {
$('form').append('<input type="hidden" name="csrf_token" value="' + csrfToken + '">');
/**
Restart function
**/
@@ -411,7 +410,7 @@ $(document).ready(function () {
$(this).attr("disabled", "disabled").removeClass('btn-danger').html('<span class="glyphicon glyphicon-transfer"></span> ' + configTranslate.saving);
});
},
success: function (json) {
success: function (json, statusText, xhr, form) {
if (json.error) {
$('#config_err_msg').text(json.error);
alert(json.error)
@@ -425,6 +424,12 @@ $(document).ready(function () {
$('#config_err_msg').text(" ");
setTimeout(config_success, 1000);
}
} else if(form && form.is('[data-reload-on-save]')) {
// Values changed that are rendered by the server, so reload the page
$('#config_err_msg').text(" ");
formWasSubmitted = true;
formHasChanged = false;
location.reload();
} else {
$('#config_err_msg').text(" ");
setTimeout(config_success, 1000);
@@ -482,16 +487,15 @@ $(document).ready(function () {
$('.advanced-button').on('change', function(event){
localStorage.setItem('advanced-settings', !$('.advanced-settings').is(':visible'))
$('.advanced-settings').toggle()
addRowColor()
})
if(localStorage.getItem('advanced-settings') === 'true') {
$('.advanced-settings').show()
$('#advanced-settings-button').prop('checked', true)
addRowColor()
}
addRowColor()
// Add tooltips
// Render tooltips in the body, otherwise they get clipped by sections with overflow: hidden
jQuery.fn.tooltip.Constructor.DEFAULTS.container = 'body'
jQuery('[title]').tooltip()
});
@@ -512,16 +516,56 @@ var pagesContainer = new Array(arrPages.length);
// Add trigger to focus on the field and load the results
$(document).ready(function() {
$('#search-menu').on('shown.bs.dropdown', function(event) {
setConfigSearchExpanded(true)
// Focus so easy typing
$('#search-box').focus()
// Load things to search if we haven't yet
if(!pagesContainer[0]) {
$.each(arrPages, function(index, page) {
$.get(rootURL + 'config/' + page + '/', function(data) {
$.get(rootURL + 'config/' + page, function(data) {
pagesContainer[index] = $(data).find('label')
});
});
}
}).on('hidden.bs.dropdown', function(event) {
setConfigSearchExpanded(false)
});
$('#search-box').on('keydown', function(event) {
var key = event.key || event.which;
var isArrowDown = key === 'ArrowDown' || key === 40;
var isArrowUp = key === 'ArrowUp' || key === 38;
var isEscape = key === 'Escape' || key === 'Esc' || key === 27;
if(isArrowDown || isArrowUp) {
event.preventDefault();
event.stopPropagation();
focusConfigSearchResult(isArrowDown ? 1 : -1)
} else if(isEscape) {
event.preventDefault();
event.stopPropagation();
closeConfigSearch()
}
});
$('#search-dropdown').on('keydown', 'a.config-search-result', function(event) {
var key = event.key || event.which;
var isArrowDown = key === 'ArrowDown' || key === 40;
var isArrowUp = key === 'ArrowUp' || key === 38;
var isEscape = key === 'Escape' || key === 'Esc' || key === 27;
if(isArrowDown || isArrowUp) {
event.preventDefault();
event.stopPropagation();
focusConfigSearchResult(isArrowDown ? 1 : -1)
} else if(isEscape) {
event.preventDefault();
event.stopPropagation();
closeConfigSearch()
}
}).on('mouseenter focus', 'a.config-search-result', function() {
getConfigSearchResults().parent().removeClass('active')
$(this).parent().addClass('active')
});
// For the scrolling
@@ -547,11 +591,46 @@ $(document).ready(function() {
// Don't go too fast
var searchTimeout
function getConfigSearchResults() {
return $('#search-dropdown').find('a.config-search-result')
}
function setConfigSearchExpanded(expanded) {
$('#search-menu > a[data-toggle="dropdown"]').attr('aria-expanded', expanded ? 'true' : 'false')
}
function closeConfigSearch() {
$('#search-menu').removeClass('open')
setConfigSearchExpanded(false)
$('#search-box').focus()
}
function focusConfigSearchResult(offset) {
var $results = getConfigSearchResults()
var activeIndex = $results.index(document.activeElement)
var nextIndex
if(!$results.length) return
if(activeIndex < 0) {
nextIndex = offset > 0 ? 0 : $results.length - 1
} else {
nextIndex = activeIndex + offset
if(nextIndex >= $results.length) nextIndex = 0
if(nextIndex < 0) nextIndex = $results.length - 1
}
getConfigSearchResults().parent().removeClass('active')
$results.eq(nextIndex).parent().addClass('active')
$results.eq(nextIndex).focus()
}
function doConfigSearch(value) {
// Cancel previous
clearTimeout(searchTimeout)
// Build new search
var searchTerm = $(value).val().replace('"', '\"')
var searchTerm = $.trim($(value).val())
var searchOutput = $('#search-dropdown')
// Build new search
@@ -562,14 +641,24 @@ function doConfigSearch(value) {
if (searchTerm.length < 2) return
// Find some results!
$.each(pagesContainer, function(page_index, results) {
if(!results) return
// Get the matches
var subResults = results.filter(':contains("'+searchTerm+'")')
var subResults = results.filter(function() {
return $(this).text().toUpperCase().indexOf(searchTerm.toUpperCase()) >= 0
})
if(subResults.length > 0) {
// Add the section
searchOutput.append('<li class="divider"></li>')
searchOutput.append('<li class="dropdown-header">'+configTranslate.searchPages[page_index]+'</li>')
searchOutput.append($('<li class="divider"></li>'))
searchOutput.append($('<li class="dropdown-header"></li>').text(configTranslate.searchPages[page_index]))
$.each(subResults, function(index, result) {
searchOutput.append('<li><a href="'+rootURL + 'config/' + arrPages[page_index] + '/#' + $(result).attr('for') +'">'+$(result).text()+'</a></li>')
var resultUrl = rootURL + 'config/' + arrPages[page_index] + '#' + $(result).attr('for')
searchOutput.append(
$('<li></li>').append(
$('<a class="config-search-result"></a>')
.attr('href', resultUrl)
.text($(result).text())
)
)
})
}
})
@@ -1,21 +1,21 @@
<div class="history" id="history-tab">
<div class="history-header">
<h2>$T('menu-history') <small data-bind="visible: history.showArchive()">($T('archive'))</small></h2>
<a href="#" data-bind="click: history.showMultiEdit, visible: hasHistory()">
<a href="#" aria-label="$T('Glitter-multiOperations')" data-bind="click: history.showMultiEdit, visible: hasHistory()">
<span class="glyphicon glyphicon-tasks" data-tooltip="true" data-placement="left" title="$T('Glitter-multiOperations')"></span>
</a>
</div>
<table class="table table-hover history-table paginated">
<table class="table table-hover history-table paginated" aria-label="$T('menu-history')">
<thead>
<tr>
<th style="width: 25px;"></th>
<th></th>
<th class="table-status-header" data-bind="css: { 'table-header-status-smaller' : extraHistoryColumns().length }"></th>
<th scope="col" style="width: 25px;"><span class="sr-only">$T('Glitter-result')</span></th>
<th scope="col"><span class="sr-only">$T('name')</span></th>
<th class="table-status-header" scope="col" data-bind="css: { 'table-header-status-smaller' : extraHistoryColumns().length || displayCompact() }"><span class="sr-only">$T('status')</span></th>
<!-- ko foreach: extraHistoryColumns -->
<th class="table-header-extra"></th>
<th class="table-header-extra" scope="col"><span class="sr-only" data-bind="text: glitterTranslate.column[\$data]"></span></th>
<!-- /ko -->
<th style="width: 130px;"></th>
<th style="width: 60px;"></th>
<th scope="col" style="width: 130px;"><span class="sr-only">$T('post-Completed')</span></th>
<th scope="col" style="width: 60px;"><span class="sr-only">$T('Glitter-actions')</span></th>
</tr>
</thead>
<!-- ko if: !hasHistory() -->
@@ -30,10 +30,10 @@
<tbody data-bind="foreach: history.historyItems, visible: hasHistory()" style="display: none;">
<tr class="history-item" data-bind="css: {'history-failed-download':failed()}">
<td>
<div data-bind="visible: processingWaiting()">
<span class="glyphicon glyphicon-time"></span>
<div role="img" aria-label="$T('post-Queued')" data-bind="visible: processingWaiting()">
<span class="glyphicon glyphicon-time" aria-hidden="true"></span>
</div>
<div data-bind="visible: processingDownload()">
<div role="img" aria-label="$T('stage-download')" data-bind="visible: processingDownload()">
<div class="processing-download">
<div class="loader-bar-one"></div>
<div class="loader-bar-two"></div>
@@ -41,17 +41,17 @@
<div class="loader-bar-four"></div>
</div>
</div>
<div data-bind="visible: (!failed() && !processingDownload() && !processingWaiting())">
<span class="glyphicon glyphicon-ok"></span>
<div role="img" aria-label="$T('post-Completed')" data-bind="visible: (!failed() && !processingDownload() && !processingWaiting())">
<span class="glyphicon glyphicon-ok" aria-hidden="true"></span>
</div>
<div data-bind="visible: failed() && canRetry()">
<a class="retry-button" href="#" data-bind="click: retry">
<span class="glyphicon glyphicon-exclamation-sign"></span>
<a class="retry-button" href="#" aria-label="$T('button-retry')" data-bind="click: retry">
<span class="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></span>
</a>
</div>
<div data-bind="visible: failed() && !canRetry()">
<span class="retry-button-inactive">
<span class="glyphicon glyphicon-exclamation-sign"></span>
<span class="retry-button-inactive" role="img" aria-label="$T('post-Failed')">
<span class="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></span>
</span>
</div>
</td>
@@ -70,10 +70,10 @@
<td class="history-completedon row-wrap-text" data-bind="text: completedOn(), attr: { 'data-timestamp': completed }" onclick="showDetails(this)"></td>
<td class="delete">
<label data-bind="visible: parent.isMultiEditing()">
<input type="checkbox" name="multiedit" title="$T('Glitter-multiSelect')" data-bind="click: parent.addMultiEdit, attr: { 'id': 'multiedit_' + id } " />
<input type="checkbox" name="multiedit" title="$T('Glitter-multiSelect')" aria-label="$T('Glitter-selectJob')" data-bind="click: parent.addMultiEdit, attr: { 'id': 'multiedit_' + id } " />
</label>
<div class="dropdown" data-bind="visible: !parent.isMultiEditing()">
<a href="#" data-toggle="dropdown" data-bind="click: updateAllHistoryInfo">
<a href="#" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" aria-label="$T('nzoDetails')" data-bind="click: updateAllHistoryInfo">
<span class="caret"></span>
</a>
<!-- ko if: hasDropdown() -->
@@ -95,7 +95,6 @@
<div class="col-sm-2">$T('status')</div>
<div class="col-sm-10">
<span data-bind="text: glitterTranslate.status[historyStatus.status()] ? glitterTranslate.status[historyStatus.status()] : statusText()"></span>
<a href="#" class="mark-completed-link" data-bind="visible: failed(), click: markAsCompleted" title="$T('button-mark-completed')"><span class="glyphicon glyphicon-ok"></span> $T('post-Completed')</a>
</div>
</div>
<div class="row">
@@ -120,11 +119,14 @@
<div class="col-sm-10 history-status-modallink" data-bind="longText: actions, longTextType: name()"></div>
</div>
<!-- /ko -->
<div class="col-sm-12 text-right" data-bind="visible: failed()">
<a href="#" class="btn btn-default mark-completed-btn" data-bind="click: markAsCompleted"><span class="glyphicon glyphicon-ok"></span> $T('button-mark-completed')</a>
</div>
</div>
</div>
<!-- /ko -->
</div>
<a href="#" data-bind="click: parent.triggerRemoveDownload">
<a href="#" data-bind="click: parent.triggerRemoveDownload, attr: { 'aria-label': processingDownload() == 2 ? '$T('abort')' : '$T('nzo-delete')' }">
<span class="hover-button glyphicon glyphicon-trash" data-bind="css: { 'glyphicon-stop' : processingDownload() == 2, disabled : processingDownload() == 1 }, attr: { title: processingDownload() == 2 ? '$T('abort')' : '$T('nzo-delete')' }"></span>
</a>
</td>
@@ -132,16 +134,23 @@
</tbody>
</table>
<div class="history-footer">
<div class="input-group search-box">
<input type="text" class="form-control" placeholder="$T('Glitter-search')" required data-bind="textInput: history.searchTerm, event: { keydown: history.clearSearchTerm }" />
<a data-bind="event: { mousedown: history.clearSearchTerm }">
<input type="text" class="form-control" placeholder="$T('Glitter-search')" aria-label="$T('Glitter-search')" required data-bind="textInput: history.searchTerm, event: { keydown: history.clearSearchTerm }" />
<a href="#" data-tooltip="true" data-html="true" data-placement="right" title="$T('Glitter-searchExplain')" data-bind="click: history.clearSearchTerm, attr: { 'aria-label': history.searchTerm() ? '$T('Glitter-clearSearch')' : '$T('Glitter-search')' }">
<span class="glyphicon" data-bind="css: { 'glyphicon-search' : !history.searchTerm(), 'glyphicon-remove' : history.searchTerm() }"></span>
</a>
</div>
<ul class="pagination" data-bind="foreach: history.pagination.allpages(), visible: history.pagination.hasPagination" style="display: none;">
<li data-bind="css: { active: isCurrent, disabled: isDots }, click: onclick">
<span data-bind="text: page"></span>
<li data-bind="css: { active: isCurrent, disabled: isDots }">
<!-- ko ifnot: isDots -->
<a href="#" data-bind="text: page, click: onclick, attr: { 'aria-current': isCurrent ? 'page' : null }"></a>
<!-- /ko -->
<!-- ko if: isDots -->
<span aria-hidden="true" data-bind="text: page"></span>
<!-- /ko -->
</li>
</ul>
@@ -172,4 +181,6 @@
<span data-bind="text: history.downloadedMonth"></span>B $T('Glitter-thisMonth')
<span data-bind="text: history.downloadedTotal"></span>B $T('Glitter-total')
</div>
</div><!-- /.history-footer -->
</div>
+33 -19
View File
@@ -1,19 +1,19 @@
<nav class="navbar navbar-inverse main-navbar">
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar-collapse">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar-collapse" aria-label="$T('Glitter-toggleNavigation')" aria-controls="navbar-collapse" aria-expanded="false">
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-logo" href="./">
<a class="navbar-logo" href="$url()" aria-label="SABnzbd $T('menu-queue')">
#include $webdir + "/../../Config/templates/staticcfg/images/logo-small.svg"#
</a>
<div class="btn-group">
<div class="btn-group">
<button type="button" class="btn btn-default navbar-btn" data-bind="click: pauseToggle, attr: { 'title': downloadsPaused() ? '$T('link-resume')' : '$T('link-pause')' }">
<button type="button" class="btn btn-default navbar-btn" data-bind="click: pauseToggle, attr: { 'title': downloadsPaused() ? '$T('link-resume')' : '$T('link-pause')', 'aria-label': downloadsPaused() ? '$T('link-resume')' : '$T('link-pause')' }">
<span class="glyphicon" data-bind="css: { 'glyphicon-play' : downloadsPaused(), 'glyphicon-pause' : !downloadsPaused() }"></span>
</button>
<button type="button" class="btn btn-default navbar-btn dropdown-toggle" data-toggle="dropdown">
<button type="button" class="btn btn-default navbar-btn dropdown-toggle" data-toggle="dropdown" aria-label="$T('pauseFor')" aria-haspopup="true" aria-expanded="false">
<span class="caret"></span>
</button>
<ul class="dropdown-menu">
@@ -26,7 +26,7 @@
<li class="divider"></li>
<li><a href="#" data-bind="click: openCustomPauseTime">$T('Glitter-custom')</a></li>
</ul>
<span class="btn btn-default navbar-btn navbar-timeleft" data-bind="text: timeLeft" data-toggle="dropdown">0:00</span>
<span class="btn btn-default navbar-btn navbar-timeleft" data-bind="text: timeLeft" data-toggle="dropdown">0:00</span>
</div>
</div>
</div>
@@ -37,18 +37,18 @@
<li class="speedlimit-dropdown dropdown">
<div class="btn-group">
<div class="btn-group">
<button type="button" class="btn btn-default navbar-btn navbar-speed" data-toggle="dropdown">
<button type="button" class="btn btn-default navbar-btn navbar-speed" data-toggle="dropdown" aria-haspopup="true">
<span data-bind="text: speedText">0 KB/s</span>
</button>
<button type="button" class="btn btn-default navbar-btn dropdown-toggle" data-toggle="dropdown" onclick="keepOpen(this)">
<button type="button" class="btn btn-default navbar-btn dropdown-toggle" data-toggle="dropdown" onclick="keepOpen(this)" aria-hidden="true" tabindex="-1">
<span class="caret"></span>
</button>
<a href="#" class="max-speed-input-clear hover-button" data-bind="click: clearSpeedLimit, visible:(speedLimit() < 100 && speedLimit() > 0)" style="display: none;">
<a href="#" class="max-speed-input-clear hover-button" aria-label="$T('button-clear')" data-bind="click: clearSpeedLimit, visible:(speedLimit() < 100 && speedLimit() > 0)" style="display: none;">
<span class="glyphicon glyphicon-link"></span>
</a>
<div class="dropdown-menu max-speed-input">
<div data-bind="visible: !bandwithLimit()">
<a href="./config/general/#bandwidth_max_value">
<a href="$url('config/general#bandwidth_max_value')">
<span class="glyphicon glyphicon-cog"></span> $T('Glitter-setMaxLinespeed')
</a>
</div>
@@ -61,14 +61,14 @@
</div>
</li>
<li data-tooltip="true" data-placement="bottom" title="$T('Glitter-addNZB')">
<a href="#modal-add-nzb" data-toggle="modal"><span class="glyphicon glyphicon-plus-sign"></span></a>
<a href="#modal-add-nzb" data-toggle="modal" aria-label="$T('Glitter-addNZB')"><span class="glyphicon glyphicon-plus-sign"></span></a>
</li>
<li data-tooltip="true" data-placement="bottom" title="$T('Glitter-statusInterfaceOptions')" data-bind="click: loadStatusInfo">
<a href="#modal-options" data-toggle="modal"><span class="glyphicon glyphicon-wrench"></span></a>
<a href="#modal-options" data-toggle="modal" aria-label="$T('Glitter-statusInterfaceOptions')"><span class="glyphicon glyphicon-wrench"></span></a>
</li>
<!--#if $have_rss_defined#-->
<li data-tooltip="true" data-placement="bottom" title="$T('cmenu-rss')">
<a href="./config/rss/">
<a href="$url('config/rss')" aria-label="$T('cmenu-rss')">
<svg class="rss-icon-svg" viewBox="0 0 8 8">
<rect class="rss-button" width="8" height="8" />
<circle class="rss-symbol" cx="2" cy="6" r="1" />
@@ -79,10 +79,10 @@
</li>
<!--#end if#-->
<li data-tooltip="true" data-placement="bottom" title="SABnzbd $T('menu-config')">
<a href="./config/"><span class="glyphicon glyphicon-cog"></span></a>
<a href="$url('config')" aria-label="SABnzbd $T('menu-config')"><span class="glyphicon glyphicon-cog"></span></a>
</li>
<li class="dropdown main-menu-link" data-bind="css: { 'active-on-queue-finish-menu': finishaction()}">
<a href="#" data-toggle="dropdown" onclick="keepOpen(this)">
<a href="#" data-toggle="dropdown" onclick="keepOpen(this)" aria-label="$T('Glitter-mainMenu')" aria-haspopup="true" aria-expanded="false">
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
@@ -91,18 +91,32 @@
<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_logout#--><li>
<form method="post" action="$url('logout')">
<input type="hidden" name="csrf_token" value="$csrf_token" />
<button type="submit"><span class="glyphicon glyphicon-log-out"></span> $T('logout')</button>
</form>
</li><!--#end if#-->
<!--#if $have_quota#--><li><a href="#" data-bind="click: doQueueAction" data-mode="reset_quota">$T('link-resetQuota')</a></li><!--#end if#-->
<!--#if $have_rss_defined#--><li><a href="#" data-bind="click: doQueueAction" data-mode="rss_now">$T('button-rssNow')</a></li><!--#end if#-->
<!--#if $have_watched_dir#--><li><a href="#" data-bind="click: doQueueAction" data-mode="watched_now">$T('sch-scan_folder')</a></li><!--#end if#-->
<!--#if $pp_pause_event#--><li><a href="#" data-bind="click: doQueueAction" data-mode="resume_pp">$T('sch-resume_post')</a></li><!--#end if#-->
<li class="divider"></li>
<li><a href="./shutdown/?apikey=$apikey&amp;pid=$pid" data-bind="click: shutdownSAB">$T('shutdownSab')</a></li>
<li><a href="#" data-bind="click: restartSAB">$T('Glitter-restartSab')</a></li>
<li>
<form method="post" action="$url('shutdown')" data-bind="submit: shutdownSAB">
<input type="hidden" name="csrf_token" value="$csrf_token" />
<button type="submit">$T('shutdownSab')</button>
</form>
</li>
<li>
<form data-bind="submit: restartSAB">
<button type="submit">$T('Glitter-restartSab')</button>
</form>
</li>
<li class="divider"></li>
<li class="dropdown-header"><span class="glyphicon glyphicon-off"></span> $T('Glitter-onFinish'):</li>
<li>
<select data-bind="value: finishaction, event: { change: setOnQueueFinish }" class="form-control">
<select data-bind="value: finishaction, event: { change: setOnQueueFinish }" class="form-control" aria-label="$T('Glitter-onFinish')">
<option value=""></option>
<option value="shutdown_program">$T('shutdownSab')</option>
<!--#if $power_options#-->
@@ -116,4 +130,4 @@
</li>
</ul>
</div>
</nav>
</nav>
@@ -1,8 +1,8 @@
<div id="queue-messages" class="queue-messages" data-bind="visible: hasMessages() || displayTabbed()" style="display: none;">
<table class="table table-hover table-messages">
<table class="table table-hover table-messages" aria-label="$T('warnings')">
<thead>
<tr>
<th class="table-messages-header-label"></th>
<th class="table-messages-header-label"><span class="sr-only">$T('warnings')</span></th>
<th style="width: 30px;"></th>
</tr>
</thead>
@@ -11,7 +11,7 @@
<tr>
<td class="table-messages-hide"></td>
<td data-bind="attr: { 'rowspan': parseInt(nrWarnings())+1 }, click: clearWarnings" class="table-messages-remove">
<a href="#" class="hover-button"><span class="glyphicon glyphicon-remove"></span></a>
<a href="#" class="hover-button" aria-label="$T('button-clear')"><span class="glyphicon glyphicon-remove" aria-hidden="true"></span></a>
</td>
</tr>
<!-- ko foreach: allWarnings -->
@@ -32,7 +32,7 @@
</td>
<!-- ko if: \$data.hasOwnProperty("clear") -->
<td class="table-messages-remove">
<a href="#" data-bind="click: clear" class="hover-button"><span class="glyphicon glyphicon-remove"></span></a>
<a href="#" data-bind="click: clear" class="hover-button" aria-label="$T('button-clear')"><span class="glyphicon glyphicon-remove" aria-hidden="true"></span></a>
</td>
<!-- /ko -->
</tr>
@@ -45,4 +45,4 @@
<!-- /ko -->
</tbody>
</table>
</div>
</div>
@@ -72,13 +72,13 @@
</div>
<!-- Modals -->
<div id="modal-options" class="modal fade" tabindex="-1">
<div id="modal-options" class="modal fade" tabindex="-1" role="dialog" aria-modal="true" aria-labelledby="modal-options-title">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">&times;</button>
<button type="button" class="close" data-dismiss="modal" aria-label="$T('close')"><span aria-hidden="true">&times;</span></button>
<a href="#" data-bind="click: loadStatusInfo, css: { 'rotate-refresh': !hasStatusInfo() }" title="$T('Glitter-interfaceRefresh')"><span class="glyphicon glyphicon-repeat"></span></a>
<h4 class="modal-title">$T('Glitter-statusInterfaceOptions')</h4>
<h4 class="modal-title" id="modal-options-title">$T('Glitter-statusInterfaceOptions')</h4>
</div>
<div class="modal-body">
<ul class="nav nav-tabs">
@@ -197,9 +197,12 @@
</div>
<div class="row options-function-box">
<div class="col-sm-6">
<a href="./api?mode=showlog&apikey=$apikey" 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>
<form method="post" action="$url('log')" target="_blank">
<input type="hidden" name="csrf_token" value="$csrf_token" />
<button type="submit" class="btn btn-default" data-tooltip="true" data-placement="top" title="$T('Glitter-logText')">
<span class="glyphicon glyphicon-file"></span> $T('link-showLog')
</button>
</form>
</div>
<div class="col-sm-6">
<div class="input-group" data-tooltip="true" data-placement="top" title="$T('logging')">
@@ -274,7 +277,7 @@
</div>
<!-- ko if: serverconnections().length > 0 -->
<table class="table table-hover table-server-connections" data-bind="visible: \$root.showActiveConnections()">
<table class="table table-hover table-server-connections" aria-label="$T('connections')" data-bind="visible: \$root.showActiveConnections()">
<thead>
<tr>
<th><strong>$T('article-id')</strong></th>
@@ -304,7 +307,7 @@
<a href="#" class="hover-button process-all-orphaned" data-bind="click: removeAllOrphaned">$T('Glitter-purgeOrphaned') <span class="glyphicon glyphicon-trash"></span></a>
<a href="#" class="hover-button process-all-orphaned" data-bind="click: addAllOrphaned">$T('Glitter-retryAllOrphaned') <span class="glyphicon glyphicon-plus-sign"></span></a>
<div class="clearfix"></div>
<table class="table table-hover table-striped">
<table class="table table-hover table-striped" aria-label="$T('Glitter-orphanedJobs')">
<thead>
<tr>
<th style="width: 30px;"></th>
@@ -333,7 +336,7 @@
</label>
</div>
<div class="form-group">
<label class="col-sm-6 control-label">$T('Glitter-interfaceRefresh')</label>
<label class="col-sm-6 control-label" for="refreshRate-option">$T('Glitter-interfaceRefresh')</label>
<div class="col-sm-4">
<select id="refreshRate-option" class="form-control" data-bind="value: refreshRate">
<option value="1">1 $T('second')</option>
@@ -351,9 +354,9 @@
</div>
</div>
<div class="form-group">
<label class="col-sm-6 control-label">$T('Glitter-queueItemLimit')</label>
<label class="col-sm-6 control-label" for="queue-pagination-limit">$T('Glitter-queueItemLimit')</label>
<div class="col-sm-4">
<select name="queue-pagination-limit" class="form-control" data-bind="value: queue.paginationLimit">
<select name="queue-pagination-limit" id="queue-pagination-limit" class="form-control" data-bind="value: queue.paginationLimit">
<option value="5">5 / $T('Glitter-page')</option>
<option value="10">10 / $T('Glitter-page')</option>
<option value="20">20 / $T('Glitter-page')</option>
@@ -366,9 +369,9 @@
</div>
</div>
<div class="form-group">
<label class="col-sm-6 control-label">$T('Glitter-historyItemLimit')</label>
<label class="col-sm-6 control-label" for="history-pagination-limit">$T('Glitter-historyItemLimit')</label>
<div class="col-sm-4">
<select name="history-pagination-limit" class="form-control" data-bind="value: history.paginationLimit">
<select name="history-pagination-limit" id="history-pagination-limit" class="form-control" data-bind="value: history.paginationLimit">
<option value="5">5 / $T('Glitter-page')</option>
<option value="10">10 / $T('Glitter-page')</option>
<option value="20">20 / $T('Glitter-page')</option>
@@ -381,9 +384,9 @@
</div>
</div>
<div class="form-group">
<label class="col-sm-6 control-label">$T('Glitter-dateFormat')</label>
<label class="col-sm-6 control-label" for="general-date-format">$T('Glitter-dateFormat')</label>
<div class="col-sm-4">
<select name="general-date-format" class="form-control" data-bind="value: dateFormat">
<select name="general-date-format" id="general-date-format" class="form-control" data-bind="value: dateFormat">
<option value="DD/MM/YYYY HH:mm"></option>
<option value="DD/MM/YYYY h:mma"></option>
<option value="MM/DD/YYYY HH:mm"></option>
@@ -399,12 +402,12 @@
</div>
</div>
<div class="form-group">
<label class="col-sm-6 control-label">
<label class="col-sm-6 control-label" for="general-extra-queue-columns">
$T('Glitter-showExtraQueueColumn')<br />
<span class="label label-warning">&gt; 1200 pixels</span>
</label>
<div class="col-sm-4">
<select name="general-extra-column" class="form-control-multiselect form-control" data-bind="selectedOptions: extraQueueColumns" multiple="true">
<select name="general-extra-column" id="general-extra-queue-columns" class="form-control-multiselect form-control" data-bind="selectedOptions: extraQueueColumns" multiple="true">
<option value="category">$T('category')</option>
<option value="priority">$T('priority')</option>
<option value="processing">$T('swtag-pp')</option>
@@ -414,12 +417,12 @@
</div>
</div>
<div class="form-group">
<label class="col-sm-6 control-label">
<label class="col-sm-6 control-label" for="general-extra-history-columns">
$T('Glitter-showExtraHistoryColumn')<br />
<span class="label label-warning">&gt; 1200 pixels</span>
</label>
<div class="col-sm-4">
<select name="general-extra-column" class="form-control-multiselect form-control" data-bind="selectedOptions: extraHistoryColumns" multiple="true">
<select name="general-extra-column" id="general-extra-history-columns" class="form-control-multiselect form-control" data-bind="selectedOptions: extraHistoryColumns" multiple="true">
<option value="category">$T('category')</option>
<option value="size">$T('size')</option>
<option value="speed">$T('Glitter-speed')</option>
@@ -427,53 +430,53 @@
</div>
</div>
<div class="form-group form-checkbox">
<label class="col-sm-6 control-label">
<label class="col-sm-6 control-label" for="displayCompact">
$T("Glitter-displayCompact")
</label>
<div class="col-sm-4">
<input type="checkbox" name="displayCompact" value="true" data-bind="checked: displayCompact" />
<input type="checkbox" name="displayCompact" id="displayCompact" value="true" data-bind="checked: displayCompact" />
</div>
</div>
<div class="form-group form-checkbox">
<label class="col-sm-6 control-label">
<label class="col-sm-6 control-label" for="displayFullWidth">
$T("Glitter-displayFullWidth")
</label>
<div class="col-sm-4">
<input type="checkbox" name="displayFullWidth" value="true" data-bind="checked: displayFullWidth" />
<input type="checkbox" name="displayFullWidth" id="displayFullWidth" value="true" data-bind="checked: displayFullWidth" />
</div>
</div>
<div class="form-group form-checkbox">
<label class="col-sm-6 control-label">
<label class="col-sm-6 control-label" for="displayTabbed">
$T("Glitter-displayTabbed")
</label>
<div class="col-sm-4">
<input type="checkbox" name="displayTabbed" value="true" data-bind="checked: displayTabbed" />
<input type="checkbox" name="displayTabbed" id="displayTabbed" value="true" data-bind="checked: displayTabbed" />
</div>
</div>
<div class="form-group form-checkbox">
<label class="col-sm-6 control-label">
<label class="col-sm-6 control-label" for="confirmDeleteQueue">
$T("Glitter-confirmDeleteQueue")
</label>
<div class="col-sm-4">
<input type="checkbox" name="confirmDeleteQueue" value="true" data-bind="checked: confirmDeleteQueue" />
<input type="checkbox" name="confirmDeleteQueue" id="confirmDeleteQueue" value="true" data-bind="checked: confirmDeleteQueue" />
</div>
</div>
<div class="form-group form-checkbox">
<label class="col-sm-6 control-label">
<label class="col-sm-6 control-label" for="confirmDeleteHistory">
$T("Glitter-confirmDeleteHistory")
</label>
<div class="col-sm-4">
<input type="checkbox" name="confirmDeleteHistory" value="true" data-bind="checked: confirmDeleteHistory" />
<input type="checkbox" name="confirmDeleteHistory" id="confirmDeleteHistory" value="true" data-bind="checked: confirmDeleteHistory" />
</div>
</div>
<div class="form-group form-checkbox">
<label class="col-sm-6 control-label">
<label class="col-sm-6 control-label" for="keyboardShortcuts">
$T("Glitter-keyboardShortcuts")
<span class="glyphicon glyphicon-question-sign" data-tooltip="true" data-placement="top" data-html="true"
data-original-title="P: $T('link-pause')<br>A: $T('Glitter-addNZB')<br>S: $T('Glitter-statusInterfaceOptions')<br>C: $T('menu-config')<br>$T('Glitter-keyboardShortcuts-arrows')"></span>
</label>
<div class="col-sm-4">
<input type="checkbox" name="keyboardShortcuts" value="true" data-bind="checked: keyboardShortcuts" />
<input type="checkbox" name="keyboardShortcuts" id="keyboardShortcuts" value="true" data-bind="checked: keyboardShortcuts" />
</div>
</div>
</form>
@@ -485,12 +488,12 @@
</div>
</div>
<div id="modal-add-nzb" class="modal fade" tabindex="-1">
<div id="modal-add-nzb" class="modal fade" tabindex="-1" role="dialog" aria-modal="true" aria-labelledby="modal-add-nzb-title">
<div class="modal-dialog">
<form class="modal-content" data-bind="submit: addNZB">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">&times;</button>
<h4 class="modal-title">$T('Glitter-addNZB')</h4>
<button type="button" class="close" data-dismiss="modal" aria-label="$T('close')"><span aria-hidden="true">&times;</span></button>
<h4 class="modal-title" id="modal-add-nzb-title">$T('Glitter-addNZB')</h4>
</div>
<div class="modal-body form-horizontal">
<div class="row">
@@ -524,29 +527,29 @@
<div class="clearfix"></div>
<hr />
<div class="form-group">
<label class="col-sm-4 control-label">$T('name')</label>
<label class="col-sm-4 control-label" for="nzbname">$T('name')</label>
<div class="col-sm-6">
<input type="text" name="nzbname" id="nzbname" placeholder="$T('Glitter-addnzbFilename')" class="form-control" />
</div>
</div>
<div class="form-group">
<label class="col-sm-4 control-label">$T('srv-password')</label>
<label class="col-sm-4 control-label" for="password">$T('srv-password')</label>
<div class="col-sm-6">
<input type="text" name="password" id="password" placeholder="$T('srv-optional')" class="form-control" />
</div>
</div>
<div class="form-group">
<label class="col-sm-4 control-label">$T('category')</label>
<label class="col-sm-4 control-label" for="add-nzb-category">$T('category')</label>
<div class="col-sm-6">
<select name="Category" class="form-control" data-bind="options: queue.categoriesList, optionsValue: 'catValue', optionsText: 'catText', optionsCaption: ''"></select>
<select name="Category" id="add-nzb-category" class="form-control" data-bind="options: queue.categoriesList, optionsValue: 'catValue', optionsText: 'catText', optionsCaption: ''"></select>
<span class="glyphicon glyphicon-tag"></span>
</div>
</div>
<div class="form-group">
<label class="col-sm-4 control-label">$T('priority')</label>
<label class="col-sm-4 control-label" for="add-nzb-priority">$T('priority')</label>
<div class="col-sm-6">
<!-- This list is different from the one during download! -->
<select name="Priority" class="form-control">
<select name="Priority" id="add-nzb-priority" class="form-control">
<option value=""></option>
<option value="2">$T('pr-force')</option>
<option value="1">$T('pr-high')</option>
@@ -558,16 +561,16 @@
</div>
</div>
<div class="form-group">
<label class="col-sm-4 control-label">$T('swtag-pp')</label>
<label class="col-sm-4 control-label" for="add-nzb-processing">$T('swtag-pp')</label>
<div class="col-sm-6">
<select name="Processing" class="form-control" data-bind="options: queue.processingOptions, optionsValue: 'value', optionsText: 'name', optionsCaption: ''"></select>
<select name="Processing" id="add-nzb-processing" class="form-control" data-bind="options: queue.processingOptions, optionsValue: 'value', optionsText: 'name', optionsCaption: ''"></select>
<span class="glyphicon glyphicon-check"></span>
</div>
</div>
<div class="form-group">
<label class="col-sm-4 control-label">$T('eoq-scripts')</label>
<label class="col-sm-4 control-label" for="add-nzb-script">$T('eoq-scripts')</label>
<div class="col-sm-6">
<select name="Post-processing" class="form-control" data-bind="options: queue.scriptsList, optionsCaption: '', optionsValue: 'scriptValue', optionsText: 'scriptText', enable: (queue.scriptsList().length > 1)"></select>
<select name="Post-processing" id="add-nzb-script" class="form-control" data-bind="options: queue.scriptsList, optionsCaption: '', optionsValue: 'scriptValue', optionsText: 'scriptText', enable: (queue.scriptsList().length > 1)"></select>
<span class="glyphicon glyphicon-flash"></span>
</div>
</div>
@@ -577,12 +580,12 @@
</div>
</div>
<div id="modal-item-files" class="modal fade" tabindex="-1">
<div id="modal-item-files" class="modal fade" tabindex="-1" role="dialog" aria-modal="true" aria-labelledby="modal-item-files-title">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">&times;</button>
<h4 class="modal-title row-wrap-text" data-bind="text: filelist.filelist_name">$T('Glitter-loading')</h4>
<button type="button" class="close" data-dismiss="modal" aria-label="$T('close')"><span aria-hidden="true">&times;</span></button>
<h4 class="modal-title row-wrap-text" id="modal-item-files-title" data-bind="text: filelist.filelist_name">$T('Glitter-loading')</h4>
</div>
<div class="modal-body">
<div id="modal-item-filelist">
@@ -645,12 +648,12 @@
</div>
</div>
<div id="modal-delete-queue-job" class="modal modal-delete-job fade" tabindex="-1">
<div id="modal-delete-queue-job" class="modal modal-delete-job fade" tabindex="-1" role="dialog" aria-modal="true" aria-labelledby="modal-delete-queue-job-title">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">&times;</button>
<h4 class="modal-title row-wrap-text">$T('removeNZB-Files')</h4>
<button type="button" class="close" data-dismiss="modal" aria-label="$T('close')"><span aria-hidden="true">&times;</span></button>
<h4 class="modal-title row-wrap-text" id="modal-delete-queue-job-title">$T('removeNZB-Files')</h4>
</div>
<form data-bind="submit: queue.removeDownloads">
<div class="modal-body">
@@ -669,12 +672,12 @@
</div>
</div>
<div id="modal-delete-history-job" class="modal modal-delete-job fade" tabindex="-1">
<div id="modal-delete-history-job" class="modal modal-delete-job fade" tabindex="-1" role="dialog" aria-modal="true" aria-labelledby="modal-delete-history-job-title">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">&times;</button>
<h4 class="modal-title row-wrap-text">$T('nzo-delete')</h4>
<button type="button" class="close" data-dismiss="modal" aria-label="$T('close')"><span aria-hidden="true">&times;</span></button>
<h4 class="modal-title row-wrap-text" id="modal-delete-history-job-title">$T('nzo-delete')</h4>
</div>
<form data-bind="submit: history.removeDownloads">
<div class="modal-body">
@@ -698,12 +701,12 @@
</div>
</div>
<div id="modal-retry-job" class="modal modal-small fade" tabindex="-1">
<div id="modal-retry-job" class="modal modal-small fade" tabindex="-1" role="dialog" aria-modal="true" aria-labelledby="modal-retry-job-title">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">&times;</button>
<h4 class="modal-title">$T('Glitter-retryJob')</h4>
<button type="button" class="close" data-dismiss="modal" aria-label="$T('close')"><span aria-hidden="true">&times;</span></button>
<h4 class="modal-title" id="modal-retry-job-title">$T('Glitter-retryJob')</h4>
</div>
<form id="retry-job-form" data-bind="submit: history.retryJob">
<div class="modal-body">
@@ -740,24 +743,24 @@
</div>
</div>
<div id="history-script-log" class="modal fade" tabindex="-1">
<div id="history-script-log" class="modal fade" tabindex="-1" role="dialog" aria-modal="true" aria-labelledby="history-script-log-title">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">&times;</button>
<h4 class="modal-title">$T('Glitter-scriptLog')</h4>
<button type="button" class="close" data-dismiss="modal" aria-label="$T('close')"><span aria-hidden="true">&times;</span></button>
<h4 class="modal-title" id="history-script-log-title">$T('Glitter-scriptLog')</h4>
</div>
<div class="modal-body"></div>
<div class="modal-body"><pre></pre></div>
</div>
</div>
</div>
<div id="modal-help" class="modal fade">
<div id="modal-help" class="modal fade" tabindex="-1" role="dialog" aria-modal="true" aria-labelledby="modal-help-title">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">&times;</button>
<h4 class="modal-title">$T('menu-help')</h4>
<button type="button" class="close" data-dismiss="modal" aria-label="$T('close')"><span aria-hidden="true">&times;</span></button>
<h4 class="modal-title" id="modal-help-title">$T('menu-help')</h4>
</div>
<div class="modal-body">
#include $webdir + "/../../Config/templates/staticcfg/images/logo-full.svg"#
@@ -790,12 +793,12 @@
</div>
</div>
<div id="modal-purge-history" class="modal modal-small fade" tabindex="-1">
<div id="modal-purge-history" class="modal modal-small fade" tabindex="-1" role="dialog" aria-modal="true" aria-labelledby="modal-purge-history-title">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">&times;</button>
<h4 class="modal-title">$T('Glitter-clearHistory')</h4>
<button type="button" class="close" data-dismiss="modal" aria-label="$T('close')"><span aria-hidden="true">&times;</span></button>
<h4 class="modal-title" id="modal-purge-history-title">$T('Glitter-clearHistory')</h4>
</div>
<div class="modal-body">
<button type="button" class="btn btn-danger" data-bind="click: history.emptyHistory" data-action="history-purge-failed"><span class="glyphicon glyphicon-floppy-remove"></span> $T('purgeFailed')</button>
@@ -814,12 +817,12 @@
</div>
</div>
<div id="modal-custom-pause" class="modal modal-small fade" tabindex="-1">
<div id="modal-custom-pause" class="modal modal-small fade" tabindex="-1" role="dialog" aria-modal="true" aria-labelledby="modal-custom-pause-title">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">&times;</button>
<h4 class="modal-title">$T('Glitter-pauseFor')</h4>
<button type="button" class="close" data-dismiss="modal" aria-label="$T('close')"><span aria-hidden="true">&times;</span></button>
<h4 class="modal-title" id="modal-custom-pause-title">$T('Glitter-pauseFor')</h4>
</div>
<div class="modal-body">
<form data-bind="submit: saveCustomPause">
+59 -37
View File
@@ -47,6 +47,7 @@
</a>
<ul class="dropdown-menu">
<li><a href="#" data-action="sortRemainingAsc" data-bind="click: queue.queueSorting">$T('Glitter-sortRemaining')</a></li>
<li><a href="#" data-action="sortRemainingBytesAsc" data-bind="click: queue.queueSorting">$T('Glitter-sortRemainingBytesAsc')</a></li>
<li><a href="#" data-action="sortAgeAsc" data-bind="click: queue.queueSorting">$T('Glitter-sortAgeAsc')</a></li>
<li><a href="#" data-action="sortAgeDesc" data-bind="click: queue.queueSorting">$T('Glitter-sortAgeDesc')</a></li>
<li><a href="#" data-action="sortNameAsc" data-bind="click: queue.queueSorting">$T('Glitter-sortNameAsc')</a></li>
@@ -57,17 +58,17 @@
</div>
</div>
<div class="clearfix"></div>
<table class="table table-striped table-hover queue-table">
<table class="table table-striped table-hover queue-table" aria-label="$T('menu-queue')">
<thead>
<tr>
<th style="width: 25px;"></th>
<th></th>
<th scope="col" style="width: 25px;"><span class="sr-only">$T('status')</span></th>
<th scope="col"><span class="sr-only">$T('name')</span></th>
<!-- ko foreach: extraQueueColumns -->
<th class="table-header-extra"></th>
<th class="table-header-extra" scope="col"><span class="sr-only" data-bind="text: glitterTranslate.column[\$data]"></span></th>
<!-- /ko -->
<th class="table-header-progress" data-bind="css: { 'table-header-progress-smaller' : extraQueueColumns().length }"></th>
<th style="width: 85px;"></th>
<th style="width: 60px;"></th>
<th class="table-header-progress" scope="col" data-bind="css: { 'table-header-progress-smaller' : extraQueueColumns().length || displayCompact() }"><span class="sr-only">$T('Glitter-progress')</span></th>
<th scope="col" style="width: 85px;"><span class="sr-only">$T('status')</span></th>
<th scope="col" style="width: 60px;"><span class="sr-only">$T('Glitter-actions')</span></th>
</tr>
</thead>
<!-- ko if: !hasQueue() -->
@@ -84,7 +85,7 @@
<tbody data-bind="visible: hasQueue(), sortable: { data: queue.queueItems, afterMove: queue.move, options: { start: queue.dragStart, stop: queue.dragStop, axis: 'y', containment: '.queue', distance: 10 } }" style="display: none;">
<tr class="queue-item">
<td>
<a href="#" data-bind="click: pauseToggle, attr: { 'title': pausedStatus() ? '$T('link-resume')' : '$T('link-pause')' }">
<a href="#" data-bind="click: pauseToggle, attr: { 'title': pausedStatus() ? '$T('link-resume')' : '$T('link-pause')', 'aria-label': pausedStatus() ? '$T('link-resume')' : '$T('link-pause')' }">
<span class="hover-button glyphicon" data-bind="css: queueIcon"></span>
</a>
</td>
@@ -102,27 +103,41 @@
<!-- /ko -->
</div>
<form data-bind="submit: editingNameSubmit">
<input type="text" data-bind="value: nameForEdit, visible: editingName(), hasfocus: editingName" />
<input type="text" aria-label="$T('Glitter-rename')" data-bind="value: nameForEdit, visible: editingName(), hasfocus: editingName" />
</form>
<div class="name-icons direct-unpack hover-button" data-bind="visible: direct_unpack() && !editingName()">
<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>
<a href="#" data-bind="click: \$parent.queue.moveButton" class="hover-button buttonMoveToTop" title="$T('Glitter-top')" aria-label="$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')" aria-label="$T('Glitter-bottom')"><span class="glyphicon glyphicon-chevron-down"></span></a>
<a href="#" data-bind="click: editName" class="hover-button" title="$T('Glitter-rename')" aria-label="$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')" aria-label="$T('nzoDetails')"><span class="glyphicon glyphicon-folder-open"></span></a>
<small data-bind="text: avg_age"></small>
</div>
</td>
<!-- ko foreach: parent.parent.extraQueueColumns -->
<td class="row-extra-text">
<!-- ko if: \$data == 'category' -->
<select class="form-control row-extra-select" data-bind="options: \$parent.parent.categoriesList, optionsValue: 'catValue', optionsText: 'catText', value: \$parent.category, valueAllowUnset: true, event: { change: \$parent.changeCat }"></select>
<!-- /ko -->
<!-- ko if: \$data == 'priority' && !\$parent.isFetchingBlocks -->
<select class="form-control row-extra-select" data-bind="options: \$parent.parent.priorityOptions, optionsValue: 'value', optionsText: 'name', value: \$parent.priority, valueAllowUnset: true, event: { change: \$parent.changePriority }"></select>
<!-- /ko -->
<!-- ko if: \$data == 'processing' -->
<select class="form-control row-extra-select" data-bind="options: \$parent.parent.processingOptions, optionsValue: 'value', optionsText: 'name', value: \$parent.unpackopts, valueAllowUnset: true, event: { change: \$parent.changeProcessing }"></select>
<!-- /ko -->
<!-- ko if: \$data == 'scripts' -->
<select class="form-control row-extra-select" data-bind="options: \$parent.parent.scriptsList, optionsValue: 'scriptValue', optionsText: 'scriptText', value: \$parent.script, valueAllowUnset: true, event: { change: \$parent.changeScript }, enable: (\$parent.parent.scriptsList().length > 1)"></select>
<!-- /ko -->
<!-- ko if: \$data == 'age' -->
<div class="row-wrap-text" data-bind="text: \$parent.showColumn(\$data)"></div>
<!-- /ko -->
</td>
<!-- /ko -->
<td class="progress-indicator">
<div class="progress">
<div class="progress-bar progress-bar-info" data-bind="attr: { 'style': 'width: ' + percentage() + '%; background-color: ' + progressColor() + ';' }">
<div class="progress-bar progress-bar-info" role="progressbar" aria-label="$T('Glitter-progress')" aria-valuemin="0" aria-valuemax="100" data-bind="attr: { 'style': 'width: ' + percentage() + '%; background-color: ' + progressColor() + ';', 'aria-valuenow': percentage() }">
<strong data-bind="text: percentage() + '%'"></strong>
<i data-bind="text: missingText"></i>
</div>
@@ -132,65 +147,65 @@
<td class="timeleft row-wrap-text" data-bind="text: statusText"></td>
<td class="delete">
<label data-bind="visible: parent.isMultiEditing()">
<input type="checkbox" name="multiedit" title="$T('Glitter-multiSelect')" data-bind="click: parent.addMultiEdit, attr: { 'id': 'multiedit_' + id } " />
<input type="checkbox" name="multiedit" title="$T('Glitter-multiSelect')" aria-label="$T('Glitter-selectJob')" data-bind="click: parent.addMultiEdit, attr: { 'id': 'multiedit_' + id } " />
</label>
<!-- ko if: !isGrabbing() -->
<div class="dropdown" data-bind="visible: !parent.isMultiEditing()">
<a href="#" data-toggle="dropdown" data-bind="click: toggleDropdown">
<a href="#" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" aria-label="$T('nzoDetails')" data-bind="click: toggleDropdown">
<span class="caret"></span>
</a>
<!-- ko if: hasDropdown() -->
<ul class="dropdown-menu queue-item-settings">
<li title="$T('category')" data-tooltip="true" data-placement="left">
<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>
<select name="Category" class="form-control" aria-label="$T('category')" data-bind="options: parent.categoriesList, optionsValue: 'catValue', optionsText: 'catText', value: category, valueAllowUnset: true, event: { change: changeCat }"></select>
</li>
<!-- 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>
<select name="Priority" class="form-control" aria-label="$T('priority')" data-bind="options: parent.priorityOptions, optionsValue: 'value', optionsText: 'name', value: priority, valueAllowUnset: true, event: { change: changePriority }"></select>
</li>
<!-- /ko -->
<li title="$T('swtag-pp')" data-tooltip="true" data-placement="left">
<span class="glyphicon glyphicon-check"></span>
<select name="Processing" class="form-control" data-bind="options: parent.processingOptions, optionsValue: 'value', optionsText: 'name', value: unpackopts, event: { change: changeProcessing }"></select>
<select name="Processing" class="form-control" aria-label="$T('swtag-pp')" data-bind="options: parent.processingOptions, optionsValue: 'value', optionsText: 'name', value: unpackopts, valueAllowUnset: true, event: { change: changeProcessing }"></select>
</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, optionsValue: 'scriptValue', optionsText: 'scriptText', event: { change: changeScript }, enable: (parent.scriptsList().length > 1)"></select>
<select name="Post-processing" class="form-control" aria-label="$T('eoq-scripts')" data-bind="options: parent.scriptsList, value: script, valueAllowUnset: true, optionsValue: 'scriptValue', optionsText: 'scriptText', event: { change: changeScript }, enable: (parent.scriptsList().length > 1)"></select>
</li>
</ul>
<!-- /ko -->
</div>
<!-- /ko -->
<a href="#" class="hover-button" title="$T('removeNZB-Files')" data-bind="click: parent.triggerRemoveDownload"><span class="glyphicon glyphicon-trash"></span></a>
<a href="#" class="hover-button" title="$T('removeNZB-Files')" aria-label="$T('removeNZB-Files')" data-bind="click: parent.triggerRemoveDownload"><span class="glyphicon glyphicon-trash"></span></a>
</td>
</tr>
</tbody>
</table>
<div class="queue-footer">
<form class="multioperations-selector" data-bind="visible: (hasQueue() && queue.isMultiEditing())" style="display: none;">
<div class="add-nzb-inputbox add-nzb-inputbox-small add-nzb-inputbox-options">
<label for="multiedit-checkall-queue">
<input type="checkbox" name="multieditCheckAll" id="multiedit-checkall-queue" title="$T('Glitter-checkAll')" data-bind="click: queue.checkAllJobs" data-tooltip="true" data-placement="top" />
<input type="checkbox" name="multieditCheckAll" id="multiedit-checkall-queue" title="$T('Glitter-checkAll')" aria-label="$T('Glitter-checkAll')" data-bind="click: queue.checkAllJobs" data-tooltip="true" data-placement="top" />
</label>
<a href="#" class="hover-button" title="$T('removeNZB-Files')" data-bind="click: queue.doMultiDelete" data-tooltip="true" data-placement="top">
<a href="#" class="hover-button" title="$T('removeNZB-Files')" aria-label="$T('Glitter-removeSelected')" data-bind="click: queue.doMultiDelete" data-tooltip="true" data-placement="top">
<span class="glyphicon glyphicon-trash"></span>
</a>
</div>
<div class="add-nzb-inputbox add-nzb-inputbox-small">
<label for="multiedit-play" data-bind="event: { mousedown: queue.handleMultiEditStatusMouseDown }">
<input type="radio" name="multiedit-status" value="resume" id="multiedit-play" data-bind="event: { change: queue.doMultiEditUpdate }" />
<input type="radio" name="multiedit-status" value="resume" id="multiedit-play" aria-label="$T('link-resume')" data-bind="event: { change: queue.doMultiEditUpdate }" />
<span class="glyphicon glyphicon-play" title="$T('link-resume')" data-tooltip="true" data-placement="top"></span>
</label>
<label for="multiedit-pause" data-bind="event: { mousedown: queue.handleMultiEditStatusMouseDown }">
<input type="radio" name="multiedit-status" value="pause" id="multiedit-pause" data-bind="event: { change: queue.doMultiEditUpdate }" />
<input type="radio" name="multiedit-status" value="pause" id="multiedit-pause" aria-label="$T('link-pause')" data-bind="event: { change: queue.doMultiEditUpdate }" />
<span class="glyphicon glyphicon-pause" title="$T('link-pause')" data-tooltip="true" data-placement="top"></span>
</label>
<a href="#" class="hover-button" title="$T('Glitter-top')" data-bind="click: queue.doMultiMoveToTop" data-tooltip="true" data-placement="top">
<a href="#" class="hover-button" title="$T('Glitter-top')" aria-label="$T('Glitter-top')" data-bind="click: queue.doMultiMoveToTop" data-tooltip="true" data-placement="top">
<span class="glyphicon glyphicon-chevron-up"></span>
</a>
<a href="#" class="hover-button" title="$T('Glitter-bottom')" data-bind="click: queue.doMultiMoveToBottom" data-tooltip="true" data-placement="top">
<a href="#" class="hover-button" title="$T('Glitter-bottom')" aria-label="$T('Glitter-bottom')" data-bind="click: queue.doMultiMoveToBottom" data-tooltip="true" data-placement="top">
<span class="glyphicon glyphicon-chevron-down"></span>
</a>
<span class="label label-default" data-bind="text: queue.multiEditItems().length">0</span>
@@ -198,33 +213,40 @@
<div class="add-nzb-inputbox-clear"></div>
<div class="add-nzb-inputbox" data-tooltip="true" data-placement="top" title="$T('category')">
<span class="glyphicon glyphicon-tag"></span>
<select name="Category" class="form-control" data-bind="options: queue.categoriesList, optionsValue: 'catValue', optionsText: 'catText', optionsCaption: '', event: { change: queue.doMultiEditUpdate }"></select>
<select name="Category" class="form-control" aria-label="$T('category')" data-bind="options: queue.categoriesList, optionsValue: 'catValue', optionsText: 'catText', optionsCaption: '', event: { change: queue.doMultiEditUpdate }"></select>
</div>
<div class="add-nzb-inputbox" data-tooltip="true" data-placement="top" title="$T('priority')">
<span class="glyphicon glyphicon-sort-by-attributes-alt"></span>
<select name="Priority" class="form-control" data-bind="options: queue.priorityOptions, optionsValue: 'value', optionsText: 'name', optionsCaption: '', event: { change: queue.doMultiEditUpdate }"></select>
<select name="Priority" class="form-control" aria-label="$T('priority')" data-bind="options: queue.priorityOptions, optionsValue: 'value', optionsText: 'name', optionsCaption: '', event: { change: queue.doMultiEditUpdate }"></select>
</div>
<div class="add-nzb-inputbox" data-tooltip="true" data-placement="top" title="$T('swtag-pp')">
<span class="glyphicon glyphicon-check"></span>
<select name="Processing" class="form-control" data-bind="options: queue.processingOptions, optionsValue: 'value', optionsText: 'name', optionsCaption: '', event: { change: queue.doMultiEditUpdate }"></select>
<select name="Processing" class="form-control" aria-label="$T('swtag-pp')" data-bind="options: queue.processingOptions, optionsValue: 'value', optionsText: 'name', optionsCaption: '', event: { change: queue.doMultiEditUpdate }"></select>
</div>
<div class="add-nzb-inputbox" data-tooltip="true" data-placement="top" title="$T('eoq-scripts')">
<span class="glyphicon glyphicon-flash"></span>
<select name="Post-processing" class="form-control" data-bind="options: queue.scriptsList, optionsValue: 'scriptValue', optionsText: 'scriptText', optionsCaption: '', event: { change: queue.doMultiEditUpdate }"></select>
<select name="Post-processing" class="form-control" aria-label="$T('eoq-scripts')" data-bind="options: queue.scriptsList, optionsValue: 'scriptValue', optionsText: 'scriptText', optionsCaption: '', event: { change: queue.doMultiEditUpdate }"></select>
</div>
<div class="clearfix"></div>
</form>
<div class="input-group search-box" data-bind="visible: queue.hasQueueSearch" style="display: none;">
<input type="text" class="form-control" placeholder="$T('Glitter-search')" required data-bind="textInput: queue.searchTerm, event: { keydown: queue.clearSearchTerm }" />
<a data-bind="event: { mousedown: queue.clearSearchTerm }">
<input type="text" class="form-control" placeholder="$T('Glitter-search')" aria-label="$T('Glitter-search')" required data-bind="textInput: queue.searchTerm, event: { keydown: queue.clearSearchTerm }" />
<a href="#" data-tooltip="true" data-html="true" data-placement="right" title="$T('Glitter-searchExplain')" data-bind="click: queue.clearSearchTerm, attr: { 'aria-label': queue.searchTerm() ? '$T('Glitter-clearSearch')' : '$T('Glitter-search')' }">
<span class="glyphicon" data-bind="css: { 'glyphicon-search' : !queue.searchTerm(), 'glyphicon-remove' : queue.searchTerm() }"></span>
</a>
</div>
<ul class="pagination" data-bind="foreach: queue.pagination.allpages, visible: queue.pagination.hasPagination" style="display: none;">
<li data-bind="css: { active: isCurrent, disabled: isDots }, click: onclick">
<span data-bind="text: page"></span>
<li data-bind="css: { active: isCurrent, disabled: isDots }">
<!-- ko ifnot: isDots -->
<a href="#" data-bind="text: page, click: onclick, attr: { 'aria-current': isCurrent ? 'page' : null }"></a>
<!-- /ko -->
<!-- ko if: isDots -->
<span aria-hidden="true" data-bind="text: page"></span>
<!-- /ko -->
</li>
</ul>
</div><!-- /.queue-footer -->
</div>
+53 -42
View File
@@ -1,6 +1,6 @@
<!DOCTYPE html>
<!--#set $active_lang=$active_lang.replace('_', '-').lower()#-->
<html lang="$active_lang" <!--#if $rtl#-->dir="rtl"<!--#end if#--> id="sabnzbd" data-bind="filedrop: { overlaySelector: '.main-filedrop', onFileDrop: addNZBFromFile }">
<html lang="$active_lang" <!--#if $rtl#-->dir="rtl"<!--#end if#--> id="sabnzbd" data-color-scheme="$color_scheme" data-bind="filedrop: { overlaySelector: '.main-filedrop', onFileDrop: addNZBFromFile }">
<head>
<!--
Glitter V2
@@ -25,25 +25,23 @@
<meta name="msapplication-navbutton-color" content="#000000" />
<meta name="theme-color" content="#000000" />
<link rel="apple-touch-icon" sizes="76x76" href="./staticcfg/ico/apple-touch-icon-76x76-precomposed.png" />
<link rel="apple-touch-icon" sizes="120x120" href="./staticcfg/ico/apple-touch-icon-120x120-precomposed.png" />
<link rel="apple-touch-icon" sizes="152x152" href="./staticcfg/ico/apple-touch-icon-152x152-precomposed.png" />
<link rel="apple-touch-icon" sizes="180x180" href="./staticcfg/ico/apple-touch-icon-180x180-precomposed.png" />
<link rel="apple-touch-icon" sizes="192x192" href="./staticcfg/ico/android-192x192.png" />
<link rel="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="apple-touch-icon" sizes="76x76" href="$url('staticcfg/ico/apple-touch-icon-76x76-precomposed.png', v=$cache_buster)" />
<link rel="apple-touch-icon" sizes="120x120" href="$url('staticcfg/ico/apple-touch-icon-120x120-precomposed.png', v=$cache_buster)" />
<link rel="apple-touch-icon" sizes="152x152" href="$url('staticcfg/ico/apple-touch-icon-152x152-precomposed.png', v=$cache_buster)" />
<link rel="apple-touch-icon" sizes="180x180" href="$url('staticcfg/ico/apple-touch-icon-180x180-precomposed.png', v=$cache_buster)" />
<link rel="apple-touch-icon" sizes="192x192" href="$url('staticcfg/ico/android-192x192.png', v=$cache_buster)" />
<link rel="mask-icon" href="$url('staticcfg/ico/safari-pinned-tab.svg', v=$cache_buster)" color="#383F45">
<link rel="shortcut icon" type="image/ico" href="$url('staticcfg/ico/favicon.ico', v=$cache_buster)" data-bind="attr: { 'href': SABIcon }" />
<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 ('Light', '') #-->
<link rel="stylesheet" type="text/css" href="./static/stylesheets/colorschemes/${color_scheme}.css?v=$version"/>
<!--#end if#-->
<link rel="stylesheet" type="text/css" href="$url('static/bootstrap/css/bootstrap.min.css', v=$cache_buster)" />
<link rel="stylesheet" type="text/css" href="$url('static/stylesheets/glitter.css', v=$cache_buster)" />
<link rel="stylesheet" type="text/css" href="$url('static/stylesheets/glitter.mobile.css', v=$cache_buster)" media="all and (max-width: 768px)" />
<!-- Make translations available in scripts -->
<script type="text/javascript">
var apiKey = "$apikey";
var csrfToken = "$csrf_token";
var displayLang = "$active_lang";
var dateScriptUrl = "$url('static/javascripts/date.min.js', v=$cache_buster)";
var sabSpeedHistory = [$bytespersec_list];
var newRelease = "$new_release";
var newReleaseUrl = "$new_rel_url";
@@ -76,7 +74,14 @@
glitterTranslate.defaultText = "$T('default')";
glitterTranslate.noneText = "$T('None')";
glitterTranslate.moreText = "$T('Glitter-more')";
glitterTranslate.column = [];
glitterTranslate.column['category'] = "$T('category')";
glitterTranslate.column['priority'] = "$T('priority')";
glitterTranslate.column['processing'] = "$T('swtag-pp')";
glitterTranslate.column['scripts'] = "$T('eoq-scripts')";
glitterTranslate.column['age'] = "$T('nzo-age')";
glitterTranslate.column['size'] = "$T('size')";
glitterTranslate.column['speed'] = "$T('Glitter-speed')";
glitterTranslate.status = [];
glitterTranslate.status['DirectUnpack'] = "$T('opt-direct_unpack')";
glitterTranslate.status['Completed'] = "$T('post-Completed')";
@@ -117,21 +122,27 @@
glitterTranslate.priority['Stop'] = "$T('pr-stop')";
</script>
<!-- Inclusion is faster than external scripts. We load momentJS locale separately so failure won't break anything -->
<script type="text/javascript" src="$url('staticcfg/js/jquery-3.7.1.min.js', v=$cache_buster)"></script>
<script type="text/javascript" src="$url('staticcfg/js/jquery-ui.min.js', v=$cache_buster)"></script>
<script type="text/javascript" src="$url('static/javascripts/jquery.peity.min.js', v=$cache_buster)"></script>
<script type="text/javascript" src="$url('static/javascripts/jquery.hotkeys.min.js', v=$cache_buster)"></script>
<script type="text/javascript" src="$url('static/javascripts/moment-2.26.0.min.js', v=$cache_buster)"></script>
<script type="text/javascript" src="$url('static/javascripts/knockout-3.5.1.min.js', v=$cache_buster)"></script>
<script type="text/javascript" src="$url('static/javascripts/knockout-extensions.js', v=$cache_buster)"></script>
<script type="text/javascript" src="$url('static/javascripts/search-query-parser.js', v=$cache_buster)"></script>
<script type="text/javascript" src="$url('staticcfg/bootstrap/js/bootstrap.min.js', v=$cache_buster)"></script>
<script type="text/javascript" src="$url('static/javascripts/glitter.basic.js', v=$cache_buster)"></script>
<script type="text/javascript" src="$url('static/javascripts/glitter.main.js', v=$cache_buster)"></script>
<script type="text/javascript" src="$url('static/javascripts/glitter.queue.js', v=$cache_buster)"></script>
<script type="text/javascript" src="$url('static/javascripts/glitter.history.js', v=$cache_buster)"></script>
<script type="text/javascript" src="$url('static/javascripts/glitter.filelist.pagination.js', v=$cache_buster)"></script>
<script type="text/javascript">
<!--#include raw $webdir + "/static/javascripts/jquery-3.5.1.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/jquery.hotkeys.min.js"#-->
<!--#include raw $webdir + "/static/javascripts/moment-2.26.0.min.js"#-->
<!--#include raw $webdir + "/static/javascripts/knockout-3.5.1.min.js"#-->
<!--#include raw $webdir + "/static/javascripts/knockout-extensions.js"#-->
<!--#include raw $webdir + "/static/javascripts/search-query-parser.js"#-->
<!--#include raw $webdir + "/static/bootstrap/js/bootstrap.min.js"#-->
<!--#include $webdir + "/static/javascripts/glitter.js"#-->
jQuery(function () {
ko.applyBindings(new ViewModel(), document.getElementById("sabnzbd"));
});
</script>
<!--#if active_lang != 'en'#-->
<script type="text/javascript" src="./static/javascripts/momentjs_locale/${active_lang}.js"></script>
<script type="text/javascript" src="$url('static/javascripts/momentjs_locale/' + $active_lang + '.js', v=$cache_buster)"></script>
<!--#end if#-->
</head>
<body>
@@ -139,21 +150,21 @@
<!--#include $webdir + "/include_menu.tmpl"#-->
</div>
<div class="container main-content">
<div class="history-queue-swicher">
<ul class="nav nav-tabs">
<li class="active">
<a href="#queue-tab" data-toggle="tab">$T('menu-queue') <span class="badge" data-bind="text: queue.totalItems"></span></a>
</li>
<li>
<a href="#history-tab" data-toggle="tab">$T('menu-history') <span class="badge badge-info" data-bind="text: history.ppItems, visible: history.ppItems"></span><span class="badge" data-bind="text: history.totalItems"></span></a>
</li>
<li>
<a href="#queue-messages" data-toggle="tab">$T('warnings') <span class="badge" data-bind="text: hasMessages, css: { 'badge-warning': hasMessages() }"></span></a>
</li>
</ul>
</div>
<div class="container history-queue-swicher">
<ul class="nav nav-tabs">
<li class="active">
<a href="#queue-tab" data-toggle="tab">$T('menu-queue') <span class="badge" data-bind="text: queue.totalItems"></span></a>
</li>
<li>
<a href="#history-tab" data-toggle="tab">$T('menu-history') <span class="badge badge-info" data-bind="text: history.ppItems, visible: history.ppItems"></span><span class="badge" data-bind="text: history.totalItems"></span></a>
</li>
<li>
<a href="#queue-messages" data-toggle="tab">$T('warnings') <span class="badge" data-bind="text: hasMessages, css: { 'badge-warning': hasMessages() }"></span></a>
</li>
</ul>
</div>
<div class="container main-content" data-bind="css: { 'main-content-loaded': isLoaded }">
<!--#include $webdir + "/include_queue.tmpl"#-->
<!--#include $webdir + "/include_messages.tmpl"#-->
<!--#include $webdir + "/include_history.tmpl"#-->
File diff suppressed because one or more lines are too long.
@@ -23,15 +23,17 @@ if(isMobile) {
});
}
$.ajaxSetup({
headers: { "X-SABnzbd-CSRF": csrfToken }
});
// Basic API-call
function callAPI(data, timeout = 10000) {
// Fill basis var's
data.output = "json";
data.apikey = apiKey;
var ajaxQuery = $.ajax({
url: "./api",
type: "GET",
cache: false,
type: "POST",
data: data,
timeout: timeout
});
@@ -150,7 +150,6 @@ function HistoryListModel(parent) {
data.append("nzbfile", $(form.nzbFile)[0].files[0]);
data.append("value", $('#modal-retry-job input[name="retry_job_id"]').val());
data.append("password", $('#retry_job_password').val());
data.append("apikey", apiKey);
// Add
$.ajax({
@@ -184,15 +183,15 @@ function HistoryListModel(parent) {
// Clear searchterm
self.clearSearchTerm = function(data, event) {
// Was it escape key or click?
if(event.type === 'mousedown' || (event.keyCode && event.keyCode === 27)) {
if(event.type === 'click' || (event.keyCode && event.keyCode === 27)) {
// Set the loader so it doesn't flicker and then switch
self.isLoading(true)
self.searchTerm('');
}
// Was it click and the field is empty? Then we focus on the field
if(event.type === 'mousedown' && self.searchTerm() === '') {
if(event.type === 'click' && self.searchTerm() === '') {
$(event.target).parents('.search-box').find('input[type="text"]').focus()
return;
return false;
}
// Need to return true to allow typing
return true;
@@ -638,14 +637,13 @@ function HistoryModel(parent, data) {
$(this).hide()
} else {
// Info in modal
$('#history-script-log .modal-body').load($(this).attr('href'), function(result) {
// Set title and then remove it
$('#history-script-log .modal-title').text($(this).find("h3").text())
$(this).find("h3, title").remove()
$('#history-script-log').modal('show');
$('#history-script-log .modal-body pre').load($(this).attr('href'), function(result) {
var modal = $('#history-script-log');
modal.find('.modal-title').text(self.historyStatus.name());
modal.modal('show');
});
}
return false;
})
}
}
}
@@ -1,56 +0,0 @@
/******
Glitter V1
By Safihre (2015) - safihre@sabnzbd.org
Code extended from Shiny-template
Code examples used from Knockstrap-template
The setup is hierarchical, 1 main ViewModel that contains:
- ViewModel
- QueueListModel
- paginationModel
- QueueModel (item 1)
- QueueModel (item 2)
- ...
- QueueModel (item n+1)
- HistoryListModel
- paginationModel
- HistoryModel (item 1)
- HistoryModel (item 2)
- ...
- HistoryModel (item n+1)
- Fileslisting
- FileslistingModel (file 1)
- FileslistingModel (file 2)
- ...
- FileslistingModel (file n+1)
ViewModel also contains all the code executed on document ready and
functions responsible for the status information, adding NZB, etc.
The QueueModel/HistoryModel's get added to the list-models when
jobs are added or on switching of pages (using paginationModel).
Once added only the properties that changed during a refresh
get updated. In the history all the detailed information is only
updated when created and when the user clicks on a detail.
The Fileslisting is only populated and updated when it is opened
for one of the QueueModel's.
******/
#include raw $webdir + "/static/javascripts/glitter.basic.js"#
/**
GLITTER CODE
**/
\$(function() {
'use strict';
#include raw $webdir + "/static/javascripts/glitter.main.js"#
#include raw $webdir + "/static/javascripts/glitter.queue.js"#
#include raw $webdir + "/static/javascripts/glitter.history.js"#
#include raw $webdir + "/static/javascripts/glitter.filelist.pagination.js"#
// GO!!!
ko.applyBindings(new ViewModel(), document.getElementById("sabnzbd"));
});
@@ -10,6 +10,7 @@ function ViewModel() {
// Set status varibales
self.isRestarting = ko.observable(false);
self.isLoaded = ko.observable(false); // Becomes true after the first queue response
self.useGlobalOptions = ko.observable(true).extend({ persist: 'useGlobalOptions' });
self.refreshRate = ko.observable(1).extend({ persist: 'pageRefreshRate' });
self.dateFormat = ko.observable('fromNow').extend({ persist: 'pageDateFormat' });
@@ -424,7 +425,7 @@ function ViewModel() {
self.openCustomPauseTime = function() {
// Was it loaded already?
if (!Date.i18n) {
jQuery.getScript('./static/javascripts/date.min.js').then(function() {
jQuery.getScript(dateScriptUrl).then(function() {
// After loading we start again
self.openCustomPauseTime()
})
@@ -701,7 +702,6 @@ function ViewModel() {
data.append("priority", $('#modal-add-nzb select[name="Priority"]').val())
data.append("pp", $('#modal-add-nzb select[name="Processing"]').val())
data.append("script", $('#modal-add-nzb select[name="Post-processing"]').val())
data.append("apikey", apiKey);
// Add this one
$.ajax({
@@ -1108,7 +1108,6 @@ function ViewModel() {
})
}
// Already set if we are using a proxy
if (response.config.misc.socks5_proxy_url) self.statusInfo.active_socks5_proxy(true)
@@ -1165,6 +1164,9 @@ function ViewModel() {
}
});
}
// First refresh has completed (success or failure): fade in the UI
self.isLoaded(true);
})
// Orphaned folder check - Not for 5 days if user ignored it
@@ -1221,7 +1223,7 @@ function ViewModel() {
}
});
$(document).bind('keydown', 'c', function(e) {
window.location.href = './config/';
window.location.href = './config';
});
$(document).bind('keydown', 's', function(e) {
// Update the data
@@ -1233,41 +1235,41 @@ function ViewModel() {
});
$(document).bind('keydown', 'shift+left', function(e) {
if($("body").hasClass("container-tabbed")) {
$('#history-tab.active > ul.pagination li.active').prev().click();
$('#queue-tab.active > ul.pagination li.active').prev().click();
$('#history-tab.active > ul.pagination li.active').prev().children('a').click();
$('#queue-tab.active > ul.pagination li.active').prev().children('a').click();
} else {
$('#history-tab > ul.pagination li.active').prev().click();
$('#queue-tab > ul.pagination li.active').prev().click();
$('#history-tab > ul.pagination li.active').prev().children('a').click();
$('#queue-tab > ul.pagination li.active').prev().children('a').click();
}
e.preventDefault();
});
$(document).bind('keydown', 'shift+right', function(e) {
if($("body").hasClass("container-tabbed")) {
$('#history-tab.active > ul.pagination li.active').next().click();
$('#queue-tab.active > ul.pagination li.active').next().click();
$('#history-tab.active > ul.pagination li.active').next().children('a').click();
$('#queue-tab.active > ul.pagination li.active').next().children('a').click();
} else {
$('#history-tab > ul.pagination li.active').next().click();
$('#queue-tab > ul.pagination li.active').next().click();
$('#history-tab > ul.pagination li.active').next().children('a').click();
$('#queue-tab > ul.pagination li.active').next().children('a').click();
}
e.preventDefault();
});
$(document).bind('keydown', 'shift+up', function(e) {
if($("body").hasClass("container-tabbed")) {
$('#history-tab.active > ul.pagination li').first().click();
$('#queue-tab.active > ul.pagination li').first().click();
$('#history-tab.active > ul.pagination li').first().children('a').click();
$('#queue-tab.active > ul.pagination li').first().children('a').click();
} else {
$('#history-tab > ul.pagination li').first().click();
$('#queue-tab > ul.pagination li').first().click();
$('#history-tab > ul.pagination li').first().children('a').click();
$('#queue-tab > ul.pagination li').first().children('a').click();
}
e.preventDefault();
});
$(document).bind('keydown', 'shift+down', function(e) {
if($("body").hasClass("container-tabbed")) {
$('#history-tab.active > ul.pagination li').last().click();
$('#queue-tab.active > ul.pagination li').last().click();
$('#history-tab.active > ul.pagination li').last().children('a').click();
$('#queue-tab.active > ul.pagination li').last().children('a').click();
} else {
$('#history-tab > ul.pagination li').last().click();
$('#queue-tab > ul.pagination li').last().click();
$('#history-tab > ul.pagination li').last().children('a').click();
$('#queue-tab > ul.pagination li').last().children('a').click();
}
e.preventDefault();
});
@@ -204,14 +204,14 @@ function QueueListModel(parent) {
// Clear searchterm
self.clearSearchTerm = function(data, event) {
// Was it escape key or click?
if(event.type === 'mousedown' || (event.keyCode && event.keyCode === 27)) {
if(event.type === 'click' || (event.keyCode && event.keyCode === 27)) {
self.isLoading(true)
self.searchTerm('');
}
// Was it click and the field is empty? Then we focus on the field
if(event.type === 'mousedown' && self.searchTerm() === '') {
if(event.type === 'click' && self.searchTerm() === '') {
$(event.target).parents('.search-box').find('input[type="text"]').focus()
return;
return false;
}
// Need to return true to allow typing
return true;
@@ -228,6 +228,10 @@ function QueueListModel(parent) {
sort = 'remaining';
dir = 'asc';
break;
case 'sortRemainingBytesAsc':
sort = 'remaining_bytes';
dir = 'asc';
break;
case 'sortAgeAsc':
sort = 'avg_age';
dir = 'desc';
@@ -549,7 +553,7 @@ function QueueModel(parent, data) {
self.index = ko.observable(data.index);
self.status = ko.observable(data.status);
self.labels = ko.observableArray(data.labels);
self.isGrabbing = ko.observable(data.status === 'Grabbing' || data.avg_age === '-')
self.isGrabbing = ko.observable(data.status === 'Grabbing' || !parseFloat(data.mb)) // mb===0 catches paused URL-grab jobs (status 'Paused'), needed to block show-files
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))
@@ -669,7 +673,7 @@ function QueueModel(parent, data) {
self.password(data.password);
self.index(data.index);
self.status(data.status)
self.isGrabbing(data.status === 'Grabbing' || data.avg_age === '-')
self.isGrabbing(data.status === 'Grabbing' || !parseFloat(data.mb))
self.totalMB(parseFloat(data.mb));
self.remainingMB(parseFloat(data.mbleft));
self.missingMB(parseFloat(data.mbmissing))
@@ -782,39 +786,39 @@ function QueueModel(parent, data) {
}
// Change of settings
self.changeCat = function(item, event) {
self.changeCat = function() {
callAPI({
mode: 'change_cat',
value: item.id,
value2: item.category()
value: self.id,
value2: self.category()
}).then(function() {
// Hide all tooltips before we refresh
$('.queue-item-settings li').filter('[data-tooltip="true"]').tooltip('hide')
self.parent.parent.refresh()
})
}
self.changeScript = function(item) {
self.changeScript = function() {
callAPI({
mode: 'change_script',
value: item.id,
value2: item.script()
value: self.id,
value2: self.script()
})
}
self.changeProcessing = function(item) {
self.changeProcessing = function() {
callAPI({
mode: 'change_opts',
value: item.id,
value2: item.unpackopts()
value: self.id,
value2: self.unpackopts()
})
}
self.changePriority = function(item, event) {
self.changePriority = function() {
// Not if we are fetching extra blocks for repair!
if(item.isFetchingBlocks) return
if(self.isFetchingBlocks) return
callAPI({
mode: 'queue',
name: 'priority',
value: item.id,
value2: item.priority()
value: self.id,
value2: self.priority()
}).then(function() {
// Hide all tooltips before we refresh
$('.queue-item-settings li').filter('[data-tooltip="true"]').tooltip('hide')
File diff suppressed because one or more lines are too long.
File diff suppressed because one or more lines are too long.
@@ -1 +1 @@
@import url('Night.css') screen and (prefers-color-scheme: dark);
/* Placeholder — themes live in glitter.css; this file only populates the scheme dropdown */
@@ -1,287 +1 @@
body,
.modal-backdrop {
background-color: black;
color: #EBEBEB
}
nav {
background-color: #444444 !important;
border: 0 !important;
border-bottom: 1px solid black !important;
}
nav .btn {
background-color: #E4E4E4;
border-color: #5D5D5D !important;
}
@media (max-width: 767px) {
nav {
background-color: black !important;
}
}
a, a:active, a:hover, a:focus,
.hover-button.glyphicon-play,
.hover-button.glyphicon-stop,
.dropdown-menu>li>a,
legend,
#modal-options legend label,
#modal-options legend a,
#modal-item-files .item-files-table .progress small,
#modal-options .options-switch label,
#modal-options #options-orphans .process-all-orphaned,
.text-muted {
color: #EBEBEB;
}
.dropdown-menu>li>a:hover,
.dropdown-menu>li>a:focus,
.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:hover,
.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:focus,
#feedback-slider {
background-color: #555555;
color: white !important;
}
.main-restarting,
.main-filedrop,
.nav-tabs>li>a {
color: white !important;
}
.hover-button,
.fileControls a:hover {
opacity: 0.7;
}
.form-control[disabled] {
opacity: 0.65;
}
.progress {
background-color: #DADADA;
}
.main-notification-box {
background-color: #727272;
border-color: #252525 !important;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
}
.main-notification-box-uploading-failed {
color: #F95151;
}
.container,
.modal-body,
.modal-footer {
background-color: #727272;
}
.dropdown-menu,
.navbar-collapse.in .dropdown-menu {
background-color: #666;
}
.navbar-collapse.in .dropdown-menu {
border: none;
}
.max-speed-input-clear,
.max-speed-input-clear:hover,
.nav-tabs>li>a:hover,
.fileControls a {
color: black;
}
.nav-tabs>li.active>a,
.nav-tabs>li.active>a:focus,
.nav-tabs>li.active>a:hover,
.nav-tabs>li>a:hover,
.nav-tabs>li>a:focus {
background-color: #ddd;
color: #555 !important;
box-shadow: 0px -1px 1px 1px rgba(0,0,0,0.15);
}
.processing-download > div {
background-color: #EBEBEB;
}
.table-striped>tbody>tr:nth-child(odd),
.table>tbody>tr:nth-child(odd),
.multioperations-selector,
.pagination li.active span,
.pagination li span:hover,
.dropdown-menu .divider,
.history-queue-swicher .badge {
background-color: #444444;
color: #EBEBEB;
}
table,
.table-striped>tbody>tr:nth-child(even),
.table>tbody>tr:nth-child(even),
.table th,
.pagination li span,
.pagination>.disabled>span,
.pagination>.disabled>span:hover,
.search-box input,
input,
input.form-control,
.input-group-addon,
select.form-control,
.main-content .btn-default,
.modal-body .btn-default,
.modal-footer .btn-default,
.btn-default.disabled:hover,
.btn-default.disabled:active,
.btn-default.disabled:focus,
.form-control[disabled],
#modal-options .options-function-box .input-group-addon {
background-color: #555555;
color: #EBEBEB;
}
.btn-default:not(.navbar-btn):hover,
select:hover,
input:hover {
background-color: #666666;
}
/* Needed to force the text-color */
.table-striped>tbody>tr:nth-child(odd)>td,
.table>tbody>tr:nth-child(odd)>td,
.table-striped>tbody>tr:nth-child(even)>td,
.table>tbody>tr:nth-child(even)>td {
color: #EBEBEB;
}
/* Hovering the queue and the history */
.table>tbody>tr:hover {
background-color: #666;
}
.table,
tr td,
tbody>tr>td:last-child,
tbody>tr>td:first-child,
tbody>tr:last-child td,
.table-messages .table-messages-remove,
.multioperations-selector,
.pagination li span,
.pagination li a,
.search-box input,
input,
input.form-control,
.input-group-addon,
.search-box input:focus,
.search-box input:valid,
select.form-control,
#modal-options .table-server-connections th,
.main-content .btn-default,
.modal-body .btn-default,
.modal-footer .btn-default,
.queue .table-active-sorting {
border-color: #252525 !important;
}
.history-failed-download td.status,
.retry-buttontext,
.retry-button, .retry-button-inactive,
.history-options-show-failed,
.queue-error-info,
.options-bad-status,
.history-failed-download:hover .retry-button .glyphicon:before,
.retry-button:hover .glyphicon:before {
color: #F95151 !important;
}
.ui-sortable-helper td {
background-color: #555555 !important;
}
tbody .caret {
border-top-color: #EBEBEB !important;
}
.info-container,
#modal-options .options-status-box small,
#modal-options #options-status small,
#modal-options .tab-content h4,
h2 small {
color: #D6D6D6;
}
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;
}
legend,
hr {
border-color: #C6C6C6 !important;
}
.ui-slider .ui-slider-handle {
background-color: #969696;
border-color: #969696
}
#modal-help table td,
#modal-help table {
background-color: transparent;
}
#modal-item-files .item-files-table td {
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;
margin-bottom: 1px;
}
.queue-item .dropdown-menu:after,
.history-item .dropdown-menu:after,
.info-container-box-sorting .dropdown-menu:after {
border-bottom-color: #666;
}
.container-compact .progress,
.container-compact .queue .progress-bar {
box-shadow: inset 1px 0px 1px rgba(0, 0, 0, 1);
}
.modal-content {
border-color: #727272;
border-top: none;
}
.form-control:focus,
input[type="submit"]:focus,
button:focus {
box-shadow: 0 0 0 0.25rem rgba(255, 255, 255, 0.3) !important;
outline: initial;
}
.archive-icon {
fill: #EBEBEB;
}
::placeholder {
color: #EBEBEB !important;
opacity: 0.5 !important;
}
/* Placeholder — themes live in glitter.css; this file only populates the scheme dropdown */
File diff suppressed because it is too large. Load diff
@@ -22,7 +22,8 @@
.main-content {
border: 0;
margin-bottom: 0 !important;
margin: 0;
padding: 0;
}
.main-menu-link .icon-bar:first-child {
@@ -74,6 +75,10 @@ h2 {
QUEUE & HISTORY
**/
.history-queue-swicher {
margin-bottom: 0;
}
.info-container {
margin-top: 0px;
margin-bottom: 10px;
@@ -95,13 +100,6 @@ h2 {
white-space: pre;
}
.info-container .info-container-box-sorting {
position: absolute;
right: 10px;
top: 0px;
font-size: 1em;
}
.queue-table .table-header-progress {
width: 0%;
}
@@ -128,7 +126,7 @@ h2 {
}
.queue-table .progress {
margin: 0px 8px;
margin: 0px 15px;
}
.queue-table td.name .row-wrap-text,
@@ -151,6 +149,9 @@ tr.queue-item>td:first-child>a {
.queue-table td.name .name-options {
display: inline-block;
opacity: 1;
pointer-events: auto;
position: static;
}
.queue-table td.name .name-options small {
@@ -274,4 +275,4 @@ tr.queue-item>td:first-child>a {
#modal-item-files .form-group {
clear: left;
margin-bottom: 5px;
}
}
+12 -9
View File
@@ -1,16 +1,19 @@
<!DOCTYPE html>
<html lang="$active_lang">
<html lang="$active_lang" data-color-scheme="$color_scheme">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>$T('wizard-quickstart')</title>
<link rel="stylesheet" type="text/css" href="../staticcfg/bootstrap/css/bootstrap.min.css?v=$version"/>
<link rel="stylesheet" type="text/css" href="static/style.css?v=$version"/>
<!--#if $color_scheme not in ('Light', '') #-->
<link rel="stylesheet" type="text/css" href="../staticcfg/css/${color_scheme}.css?v=$version"/>
<!--#end if#-->
<link rel="shortcut icon" href="../staticcfg/ico/favicon.ico?v=$version" />
<script type="text/javascript" src="../staticcfg/js/jquery-3.5.1.min.js?v=$version"></script>
<script type="text/javascript" src="../staticcfg/bootstrap/js/bootstrap.min.js?v=$version"></script>
<link rel="stylesheet" type="text/css" href="$url('staticcfg/bootstrap/css/bootstrap.min.css', v=$cache_buster)"/>
<link rel="stylesheet" type="text/css" href="$url('wizard/static/style.css', v=$cache_buster)"/>
<link rel="shortcut icon" href="$url('staticcfg/ico/favicon.ico', v=$cache_buster)" />
<script type="text/javascript" src="$url('staticcfg/js/jquery-3.7.1.min.js', v=$cache_buster)"></script>
<script type="text/javascript" src="$url('staticcfg/bootstrap/js/bootstrap.min.js', v=$cache_buster)"></script>
<script type="text/javascript">
var csrfToken = "$csrf_token";
$.ajaxSetup({
headers: { "X-SABnzbd-CSRF": csrfToken }
});
</script>
</head>
<body>
<div id="logo">
+5 -3
View File
@@ -1,5 +1,6 @@
<!--#include $webdir + "/inc_top.tmpl"#-->
<form action="./one" method="post">
<form action="$url('wizard/one')" method="post">
<input type="hidden" name="csrf_token" value="$csrf_token" />
<div class="container">
<div id="inner">
<div id="content" class="bigger">
@@ -27,10 +28,10 @@
<hr />
<div class="row">
<div class="col-md-4 text-center">
<a class="btn btn-danger" href="../shutdown/?apikey=$apikey&amp;pid=$pid"><span class="glyphicon glyphicon-remove"></span> $T('wizard-exit')</a>
<button class="btn btn-danger" type="submit" form="shutdown-form"><span class="glyphicon glyphicon-remove"></span> $T('wizard-exit')</button>
</div>
<div class="col-md-4 text-center">
<a class="btn btn-default" href="../config/general/#config_backup_file"><span class="glyphicon glyphicon-open"></span> $T('restore-backup')</a>
<a class="btn btn-default" href="$url('config/general#config_backup_file')"><span class="glyphicon glyphicon-open"></span> $T('restore-backup')</a>
</div>
<div class="col-md-4 text-center">
<button class="btn btn-default">$T('wizard-start') <span class="glyphicon glyphicon-chevron-right"></span></button>
@@ -39,4 +40,5 @@
</div>
</div>
</form>
<form id="shutdown-form" method="post" action="$url('shutdown')"><input type="hidden" name="csrf_token" value="$csrf_token" /></form>
<!--#include $webdir + "/inc_bottom.tmpl"#-->
+7 -6
View File
@@ -1,5 +1,6 @@
<!--#include $webdir + "/inc_top.tmpl"#-->
<form action="./two" method="post" autocomplete="off">
<form action="$url('wizard/two')" method="post" autocomplete="off">
<input type="hidden" name="csrf_token" value="$csrf_token" />
<div class="container">
<div id="inner">
<div id="content" class="bigger">
@@ -65,7 +66,7 @@
<div class="form-group">
<label for="connections" class="col-sm-4 control-label">$T('srv-connections')</label>
<div class="col-sm-8">
<input type="number" class="form-control" name="connections" id="connections" value="<!--#if $connections then $connections else '8'#-->" min="1" max="500" data-toggle="tooltip" data-placement="right" title="$T('wizard-server-con-explain') $T('wizard-server-con-eg')" required />
<input type="number" class="form-control" name="connections" id="connections" value="<!--#if $connections then $connections else '16'#-->" min="1" max="500" data-toggle="tooltip" data-placement="right" title="$T('wizard-server-con-explain') $T('wizard-server-con-eg')" required />
</div>
</div>
<div class="form-group">
@@ -89,10 +90,10 @@
</div>
<div class="row">
<div class="col-sm-4">
<button id="serverTest" class="btn btn-default" data-toggle="tooltip" data-placement="left"><span class="glyphicon glyphicon-sort"></span> $T('wizard-button-testServer')</button>
<button id="serverTest" class="btn btn-default" data-toggle="tooltip" data-placement="left" aria-busy="false"><span class="glyphicon glyphicon-sort" aria-hidden="true"></span> $T('wizard-button-testServer')</button>
</div>
<div class="col-sm-8">
<div id="serverResponse" class="well well-sm">$T('wizard-server-text')</div>
<div id="serverResponse" class="well well-sm" role="status" aria-live="polite" aria-atomic="true">$T('wizard-server-text')</div>
</div>
</div>
@@ -102,12 +103,12 @@
<iframe style="float: right; width: 325px; height: 325px;" frameborder="0" src="https://sabnzbd.org/wizard#$active_lang"></iframe>
</div>
</div>
<input type="hidden" name="apikey" value="$apikey" />
<input type="hidden" name="enable" value="1">
</div>
<hr />
<div class="row">
<div class="col-xs-4 text-center">
<a class="btn btn-default" href="./index"><span class="glyphicon glyphicon-chevron-left"></span> $T('wizard-previous')</a>
<a class="btn btn-default" href="$url('wizard')"><span class="glyphicon glyphicon-chevron-left"></span> $T('wizard-previous')</a>
</div>
<div class="col-xs-4 text-center">
</div>
@@ -41,22 +41,25 @@ $(document).ready(function() {
return false;
}
$('#serverTest').attr('aria-busy', 'true');
$('#serverResponse').html(txtChecking);
$.getJSON(
"../api?mode=config&name=test_server&output=json",
$("form").serialize(),
function(result) {
$.ajax({
type: 'POST',
url: "../api?mode=config&name=test_server&output=json",
data: $("form").serialize(),
success: function(result) {
if (result.value.result) {
r = '<span class="success"><span class="glyphicon glyphicon-ok"></span> ' + result.value.message + '</span>';
r = '<span class="success"><span class="glyphicon glyphicon-ok" aria-hidden="true"></span> ' + result.value.message + '</span>';
setTestResult(true);
} else {
r = '<span class="failed"><span class="glyphicon glyphicon-minus-sign"></span> ' + result.value.message + '</span>';
r = '<span class="failed"><span class="glyphicon glyphicon-minus-sign" aria-hidden="true"></span> ' + result.value.message + '</span>';
setTestResult(false);
}
r = r.replace('https://sabnzbd.org/certificate-errors', '<a href="https://sabnzbd.org/certificate-errors" class="failed" target="_blank">https://sabnzbd.org/certificate-errors</a>')
$('#serverResponse').html(r);
$('#serverTest').attr('aria-busy', 'false');
}
);
});
return false;
});
@@ -88,4 +91,4 @@ $(document).ready(function() {
event.preventDefault();
}
})
});
});
+167 -14
View File
@@ -1,16 +1,50 @@
:root {
/* Border radius scale */
--radius-sm: 4px; /* small elements: progress bars */
--radius-md: 6px; /* search bar, multi-edit toolbar, file controls */
--radius-lg: 8px; /* cards, modals, notification box */
/* Follow the OS by default (Auto); forced by data-color-scheme below */
color-scheme: light dark;
/* Color palette — light-dark(light value, dark value) */
--clr-page-bg: light-dark(#E4E4E4, black);
--clr-surface: light-dark(#FFFFFF, #303030);
--clr-surface-alt: light-dark(#F5F5F5, #444444);
--clr-surface-hover: light-dark(#E4E4E4, #666666);
--clr-border: light-dark(rgba(0, 0, 0, 0.2), #555555);
--clr-text: light-dark(#000000, #EBEBEB);
--clr-text-muted: light-dark(#777777, #c7c7c7);
--clr-link: light-dark(#555555, #63a7e1);
--clr-input-bg: light-dark(#FFFFFF, #555555);
--clr-input-border: light-dark(#cccccc, #252525);
--clr-toggle-active: #f9ca33;
--clr-toggle-inactive: light-dark(#aaaaaa, #666666);
}
/* The server renders the configured scheme into this attribute; empty/unknown = Auto */
html[data-color-scheme="light" i] {
color-scheme: light;
}
html[data-color-scheme="night" i] {
color-scheme: dark;
}
body {
background-color: #E4E4E4;
background-color: var(--clr-page-bg);
color: var(--clr-text);
text-align: center;
}
.container {
max-width: 1000px;
border: 1px solid rgba(0, 0, 0, 0.2);
background-color: #fff;
text-align: left;
}
#inner {
background-color: #fff;
padding: 4px 32px 20px 32px;
border: 1px solid var(--clr-border);
border-radius: var(--radius-lg);
background-color: var(--clr-surface);
}
#content {
min-height: 550px;
@@ -33,7 +67,7 @@ body {
float: left;
}
.language:hover {
background-color: #E4E4E4;
background-color: var(--clr-surface-hover);
}
.float {
float: left;
@@ -51,7 +85,7 @@ body {
padding: 12px;
margin-top: 1px;
margin-bottom: 15px;
background-color: #f5f5f5;
background-color: var(--clr-surface-alt);
}
.quoteBlock a,
a[target="_blank"] {
@@ -62,10 +96,10 @@ a[target="_blank"] {
color: #00cc22;
}
.failed {
color: red !important;
color: light-dark(red, #ff3333) !important;
}
#rightGreyText {
color: #ccc;
color: light-dark(#cccccc, #c7c7c7);
width: 100%;
text-align: right;
padding-top: 3px;
@@ -80,6 +114,39 @@ label {
.input-checkbox {
padding-top: 7px !important;
}
/* Toggle switch */
input[type="checkbox"] {
appearance: none;
-webkit-appearance: none;
display: inline-block;
vertical-align: middle;
margin: 0;
width: 36px;
height: 20px;
background-color: var(--clr-toggle-inactive);
background-image:
radial-gradient(circle at 10px 10px, #ffffff 6.5px, rgba(255,255,255,0) 7px),
radial-gradient(circle at 11px 11px, rgba(0,0,0,0.30) 6.5px, transparent 9.5px);
background-size: 20px 20px;
background-repeat: no-repeat;
background-position: 0px center;
border-radius: 10px;
border: none;
cursor: pointer;
transition: background-color 0.2s, background-position 0.15s;
}
input[type="checkbox"]:checked {
background-color: var(--clr-toggle-active);
background-position: 16px center;
background-image:
radial-gradient(circle at 10px 10px, #ffffff 6.5px, rgba(255,255,255,0) 7px),
radial-gradient(circle at 10.5px 10.5px, rgba(0,0,0,0.30) 6.5px, transparent 9.5px);
}
input[type="checkbox"]:focus-visible {
outline: 2px solid var(--clr-toggle-active);
outline-offset: 2px;
border-radius: 10px;
}
.input-checkbox input+.help-block {
display: none;
}
@@ -136,24 +203,110 @@ label {
Bootstrap overwrites
***/
* {
border-radius: 0 !important;
}
a,
a:hover,
a:active,
a:visited,
#serverResponse {
color: #555;
color: var(--clr-link);
}
.btn {
box-shadow: 1px 1px 1px rgba(0, 0, 0, .1) !important;
min-width: 150px;
box-shadow: 1px 0px 1px light-dark(rgba(0,0,0,.1), rgba(255,255,255,.1));
}
.btn-default,
input[type="text"],
input[type="number"],
input[type="email"],
input[type="password"],
input[type="url"],
select,
textarea {
background-color: var(--clr-input-bg);
color: var(--clr-text);
border-color: var(--clr-input-border);
}
::placeholder {
color: var(--clr-text) !important;
opacity: 0.5;
}
.btn .glyphicon,
a .glyphicon {
top: 2px;
}
small {
color: #777;
color: var(--clr-text-muted);
}
/***
Dark-mode structural rules
Colors live in the light-dark() tokens above; these are the rules that cannot
be expressed as a color value. The two blocks below MUST stay identical:
the first applies when Night is forced, the second when the OS is dark and Light is not forced.
***/
html[data-color-scheme="night" i] {
.language:hover,
.quoteBlock,
.server-disabled,
#serverResponse {
background-color: var(--clr-surface-alt) !important;
color: var(--clr-text) !important;
}
.quoteBlock a,
.help-block,
a.wizard-advanced-settings {
color: var(--clr-text);
}
#inner {
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
}
/* Focus / active border */
.btn-default:focus,
.form-control:focus,
input:focus,
textarea:focus,
select:focus {
border-color: #707070 !important;
outline: initial !important;
box-shadow: 0 0 0 0.25rem rgba(255, 255, 255, 0.3) !important;
}
}
@media (prefers-color-scheme: dark) {
html:not([data-color-scheme="light" i]) {
.language:hover,
.quoteBlock,
.server-disabled,
#serverResponse {
background-color: var(--clr-surface-alt) !important;
color: var(--clr-text) !important;
}
.quoteBlock a,
.help-block,
a.wizard-advanced-settings {
color: var(--clr-text);
}
#inner {
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
}
/* Focus / active border */
.btn-default:focus,
.form-control:focus,
input:focus,
textarea:focus,
select:focus {
border-color: #707070 !important;
outline: initial !important;
box-shadow: 0 0 0 0.25rem rgba(255, 255, 255, 0.3) !important;
}
}
}
+5 -6
View File
@@ -12,8 +12,8 @@
<p>$T('wizard-tip4')</p>
<div class="quoteBlock">
<!--#for $url in $urls#-->
<a href="$url">$url</a><br />
<!--#for $access_url in $urls#-->
<a href="$access_url">$access_url</a><br />
<!--#end for#-->
</div>
@@ -22,13 +22,13 @@
<p><strong>$T('opt-complete_dir')</strong></p>
<div class="quoteBlock">
$complete_dir
<a href="${access_url}/config/folders#complete_dir" class="indented"><span class="glyphicon glyphicon-cog"></span></a>
<a href="$url('config/folders#complete_dir')" class="indented"><span class="glyphicon glyphicon-cog"></span></a>
</div>
<p><strong>$T('opt-download_dir')</strong></p>
<div class="quoteBlock">
$download_dir
<a href="${access_url}/config/folders#complete_dir" class="indented"><span class="glyphicon glyphicon-cog"></span></a>
<a href="$url('config/folders#complete_dir')" class="indented"><span class="glyphicon glyphicon-cog"></span></a>
</div>
<hr/>
@@ -38,8 +38,7 @@
<hr />
<div class="row">
<div class="col-xs-12 text-center">
<input type="hidden" name="apikey" id="apikey" value="$apikey"/>
<a class="btn btn-success" href="$access_url">$T('wizard-goto') <span class="glyphicon glyphicon-chevron-right"></span></a>
<a class="btn btn-success" href="$url()">$T('wizard-goto') <span class="glyphicon glyphicon-chevron-right"></span></a>
</div>
</div>
</div>
+74
View File
@@ -5,6 +5,8 @@
<metadata_license>MIT</metadata_license>
<name>SABnzbd</name>
<summary>Free and easy binary newsreader</summary>
<summary xml:lang="fr">Lecteur de news binaire libre et facile à utiliser</summary>
<summary xml:lang="tr">Özgür ve kullanımı kolay Usenet ikili okuyucu</summary>
<branding>
<color type="primary" scheme_preference="light">#e7e7e7</color>
<color type="primary" scheme_preference="dark">#444444</color>
@@ -20,6 +22,26 @@
over a dozen languages, and integrates with a host of tools, apps
and services that help automate the download process.
</p>
<p xml:lang="fr">
SABnzbd est un lecteur binaire de newsgroups libre et gratuit prenant
en charge le format nzb très répandu. Il simplifie grandement le processus
de téléchargement depuis Usenet grâce à une interface web conviviale
et à des options avancées de post-traitement intégrées qui comprennent
la capacité à vérifier, réparer, extraire et nettoyer automatiquement
les messages téléchargés. Il fonctionne partout, est fourni avec une
douzaine de traductions et s'intègre avec de nombreux outils, applications
et services qui aident à automatiser le processus de téléchargement.
</p>
<p xml:lang="tr">
SABnzbd, popüler nzb biçimini destekleyen özgür ve ücretsiz bir ikili
haber grupları okuyucusudur. Kullanıcı dostu Web tabanlı arayüzü ve
indirilen mesajları otomatik olarak doğrulama, tamir etme, çıkarma ve
temizleme kabiliyetine sahip gelişmiş yerleşik son işlem seçenekleri
sayesinde Usenet ağından indirmeyi büyük ölçüde kolaylaştırır. Her yerde
çalışabilir, bir düzineden fazla dil desteği sunar ve indirme sürecini
otomatik hâle getirmeye yardımcı olan birçok araç, uygulama ve hizmet ile
birlikte çalışabilir.
</p>
</description>
<keywords>
<keyword>usenet</keyword>
@@ -27,6 +49,13 @@
<keyword>download</keyword>
<keyword>newsreader</keyword>
<keyword>binary</keyword>
<keyword xml:lang="fr">usenet</keyword>
<keyword xml:lang="fr">nzb</keyword>
<keyword xml:lang="fr">téléchargement</keyword>
<keyword xml:lang="fr">binaires</keyword>
<keyword xml:lang="tr">usenet</keyword>
<keyword xml:lang="tr">nzb</keyword>
<keyword xml:lang="tr">indirme</keyword>
</keywords>
<categories>
<category>Network</category>
@@ -42,6 +71,21 @@
<url type="faq">https://sabnzbd.org/wiki/faq</url>
<url type="contact">https://sabnzbd.org/live-chat.html</url>
<releases>
<release version="5.1.0" date="2026-08-01" type="stable">
<url type="details">https://github.com/sabnzbd/sabnzbd/releases/tag/5.1.0</url>
</release>
<release version="5.0.4" date="2026-06-11" type="stable">
<url type="details">https://github.com/sabnzbd/sabnzbd/releases/tag/5.0.4</url>
</release>
<release version="5.0.3" date="2026-05-15" type="stable">
<url type="details">https://github.com/sabnzbd/sabnzbd/releases/tag/5.0.3</url>
</release>
<release version="5.0.2" date="2026-05-14" type="stable">
<url type="details">https://github.com/sabnzbd/sabnzbd/releases/tag/5.0.2</url>
</release>
<release version="5.0.1" date="2026-05-01" type="stable">
<url type="details">https://github.com/sabnzbd/sabnzbd/releases/tag/5.0.1</url>
</release>
<release version="5.0.0" date="2026-05-01" type="stable">
<url type="details">https://github.com/sabnzbd/sabnzbd/releases/tag/5.0.0</url>
</release>
@@ -101,62 +145,92 @@
<screenshot type="default">
<image>https://sabnzbd.org/images/landing/screenshots/interface.png</image>
<caption>Intuitive interface</caption>
<caption xml:lang="fr">Interface intuitive</caption>
<caption xml:lang="tr">Sezgisel arayüz</caption>
</screenshot>
<screenshot>
<image>https://sabnzbd.org/images/landing/screenshots/night-mode.png</image>
<caption>Also comes in Night-mode</caption>
<caption xml:lang="fr">Fourni également avec le mode nuit</caption>
<caption xml:lang="tr">Gece kipi ile de sunulur</caption>
</screenshot>
<screenshot>
<image>https://sabnzbd.org/images/landing/screenshots/add-nzb.png</image>
<caption>Add NZB's or use drag-and-drop!</caption>
<caption xml:lang="fr">Ajoutez des fichiers NZB ou utilisez le glisser - déposer !</caption>
<caption xml:lang="tr">NZB dosyaları ekleyin veya kaydır - bırak kullanın!</caption>
</screenshot>
<screenshot>
<image>https://sabnzbd.org/images/landing/screenshots/phone-interface.png</image>
<caption>Scales to any screen size</caption>
<caption xml:lang="fr">S'adapte à toutes les tailles d'écran</caption>
<caption xml:lang="tr">Her ekran boyutuna uyum sağlar</caption>
</screenshot>
<screenshot>
<image>https://sabnzbd.org/images/landing/screenshots/history-details.png</image>
<caption>Easy overview of all history details</caption>
<caption xml:lang="fr">Vue d'ensemble aisée de tous les détails de l'historique</caption>
<caption xml:lang="tr">Tarihçenin tüm ayrıntıları için kolay genel bakış</caption>
</screenshot>
<screenshot>
<image>https://sabnzbd.org/images/landing/screenshots/phone-extra.png</image>
<caption>Every option, on every screen size</caption>
<caption xml:lang="fr">Toutes les options, quelle que soit la taille d'écran</caption>
<caption xml:lang="tr">Ekran boyutu ne olursa olsun, tüm seçenekler elinizin altında</caption>
</screenshot>
<screenshot>
<image>https://sabnzbd.org/images/landing/screenshots/file-lists.png</image>
<caption>Manage a job's individual files</caption>
<caption xml:lang="fr">Gérez les fichiers individuels d'une tâche</caption>
<caption xml:lang="tr">Herhangi bir görevin dosyalarını tek tek yönetin</caption>
</screenshot>
<screenshot>
<image>https://sabnzbd.org/images/landing/screenshots/set-speedlimit.png</image>
<caption>Easy speed limiting</caption>
<caption xml:lang="fr">Limitation facile du débit</caption>
<caption xml:lang="tr">Kolay hız sınırlaması</caption>
</screenshot>
<screenshot>
<image>https://sabnzbd.org/images/landing/screenshots/set-options.png</image>
<caption>Quickly change settings</caption>
<caption xml:lang="fr">Changement rapide des réglages</caption>
<caption xml:lang="tr">Ayarları hızlıca değiştirin</caption>
</screenshot>
<screenshot>
<image>https://sabnzbd.org/images/landing/screenshots/dashboard.png</image>
<caption>Easy system check</caption>
<caption xml:lang="fr">Vérification facile du système</caption>
<caption xml:lang="tr">Sistemi kolayca kontrol edin</caption>
</screenshot>
<screenshot>
<image>https://sabnzbd.org/images/landing/screenshots/connections-overview.png</image>
<caption>See active connections</caption>
<caption xml:lang="fr">Voyez les connexions actives</caption>
<caption xml:lang="tr">Etkin bağlantıları görüntüleyin</caption>
</screenshot>
<screenshot>
<image>https://sabnzbd.org/images/landing/screenshots/skin-settings.png</image>
<caption>Customize the interface</caption>
<caption xml:lang="fr">Personnalisez l'interface</caption>
<caption xml:lang="tr">Arayüzü kişiselleştirin</caption>
</screenshot>
<screenshot>
<image>https://sabnzbd.org/images/landing/screenshots/tabbed.png</image>
<caption>Tabbed-mode</caption>
<caption xml:lang="fr">Mode avec onglets</caption>
<caption xml:lang="tr">Sekmeli kip</caption>
</screenshot>
<screenshot>
<image>https://sabnzbd.org/images/landing/screenshots/set-custom-pause.png</image>
<caption>Specify any pause duration</caption>
<caption xml:lang="fr">Spécifiez une durée de pause quelconque</caption>
<caption xml:lang="tr">Herhangi bir duraklama süresi belirtin</caption>
</screenshot>
<screenshot>
<image>https://sabnzbd.org/images/landing/screenshots/config.png</image>
<caption>Easy configuration</caption>
<caption xml:lang="fr">Configuration facile</caption>
<caption xml:lang="tr">Kolay yapılandırma</caption>
</screenshot>
</screenshots>
<content_rating type="oars-1.1"/>
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.
+1 -1
View File
@@ -4,7 +4,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-5.1.0\n"
"Project-Id-Version: SABnzbd-5.2.0\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: team@sabnzbd.org\n"
"Language-Team: SABnzbd <team@sabnzbd.org>\n"
+93 -1
View File
@@ -1,10 +1,14 @@
# SABnzbd Translation Template file EMAIL
# Copyright 2007-2026 by The SABnzbd-Team (sabnzbd.org)
#
# Translators:
# Iamanro, 2026
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-5.0.0\n"
"Project-Id-Version: SABnzbd-5.2.0\n"
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
"Last-Translator: Iamanro, 2026\n"
"Language-Team: Czech (https://app.transifex.com/sabnzbd/teams/111101/cs/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -56,6 +60,47 @@ msgid ""
"Sorry!\n"
"<!--#end if#-->\n"
msgstr ""
"##\n"
"## Výchozí e-mailová šablona pro SABnzbd\n"
"## Toto je šablona Cheetah\n"
"## Dokumentace: https://sabnzbd.org/wiki/extra/email-templates\n"
"##\n"
"## Nové řádky a mezery jsou významné!\n"
"##\n"
"## Toto jsou hlavičky e-mailu\n"
"To: $to\n"
"From: $from\n"
"Date: $date\n"
"Subject: SABnzbd <!--#if $status then \"dokončil\" else \"nedokončil\" #--> úlohu $name\n"
"X-priority: 5\n"
"X-MS-priority: 5\n"
"## Poté následuje tělo zprávy, prázdný řádek je povinný!\n"
"\n"
"Dobrý den,\n"
"<!--#if $status #-->\n"
"SABnzbd stáhl \"$name\" <!--#if $msgid==\"\" then \"\" else \"(newzbin #\" + $msgid + \")\"#-->\n"
"<!--#else#-->\n"
"SABnzbd nedokázal stáhnout \"$name\" <!--#if $msgid==\"\" then \"\" else \"(newzbin #\" + $msgid + \")\"#-->\n"
"<!--#end if#-->\n"
"Dokončeno v $end_time\n"
"Staženo $size\n"
"\n"
"Výsledky úlohy:\n"
"<!--#for $stage in $stages #-->\n"
"Fáze $stage <!--#slurp#-->\n"
"<!--#for $result in $stages[$stage]#-->\n"
"$result <!--#slurp#-->\n"
"<!--#end for#-->\n"
"<!--#end for#-->\n"
"<!--#if $script!=\"\" #-->\n"
"Výstup z uživatelského skriptu \"$script\" (Návratový kód = $script_ret):\n"
"$script_output\n"
"<!--#end if#-->\n"
"<!--#if $status #-->\n"
"Užijte si to!\n"
"<!--#else#-->\n"
"Je nám líto!\n"
"<!--#end if#-->\n"
#: email/rss.tmpl:1
msgid ""
@@ -85,6 +130,31 @@ msgid ""
"\n"
"Bye\n"
msgstr ""
"##\n"
"## E-mailová šablona RSS pro SABnzbd\n"
"## Toto je šablona Cheetah\n"
"## Dokumentace: https://sabnzbd.org/wiki/extra/email-templates\n"
"##\n"
"## Nové řádky a mezery jsou významné!\n"
"##\n"
"## Toto jsou hlavičky e-mailu\n"
"To: $to\n"
"From: $from\n"
"Date: $date\n"
"Subject: SABnzbd přidal $amount úloh do fronty\n"
"X-priority: 5\n"
"X-MS-priority: 5\n"
"## Poté následuje tělo zprávy, prázdný řádek je povinný!\n"
"\n"
"Dobrý den,\n"
"\n"
"SABnzbd přidal $amount úloh do fronty.\n"
"Pocházejí z RSS kanálu \"$feed\".\n"
"<!--#for $job in $jobs#-->\n"
"$job <!--#slurp#-->\n"
"<!--#end for#-->\n"
"\n"
"Na shledanou\n"
#: email/badfetch.tmpl:1
msgid ""
@@ -111,3 +181,25 @@ msgid ""
"\n"
"Bye\n"
msgstr ""
"##\n"
"## E-mailová šablona SABnzbd pro neúspěšné načtení URL\n"
"## Toto je šablona Cheetah\n"
"## Dokumentace: https://sabnzbd.org/wiki/extra/email-templates\n"
"##\n"
"## Nové řádky a mezery jsou významné!\n"
"##\n"
"## Toto jsou hlavičky e-mailu\n"
"To: $to\n"
"From: $from\n"
"Date: $date\n"
"Subject: SABnzbd nedokázal získat NZB\n"
"X-priority: 5\n"
"X-MS-priority: 5\n"
"## Poté následuje tělo zprávy, prázdný řádek je povinný!\n"
"\n"
"Dobrý den,\n"
"\n"
"SABnzbd nedokázal získat NZB z $url.\n"
"Chybová zpráva byla: $msg\n"
"\n"
"Na shledanou\n"
+1 -1
View File
@@ -3,7 +3,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-5.0.0\n"
"Project-Id-Version: SABnzbd-5.2.0\n"
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
"Language-Team: Danish (https://app.transifex.com/sabnzbd/teams/111101/da/)\n"
"MIME-Version: 1.0\n"
+1 -1
View File
@@ -6,7 +6,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-5.0.0\n"
"Project-Id-Version: SABnzbd-5.2.0\n"
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2025\n"
"Language-Team: German (https://app.transifex.com/sabnzbd/teams/111101/de/)\n"
+1 -1
View File
@@ -3,7 +3,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-5.0.0\n"
"Project-Id-Version: SABnzbd-5.2.0\n"
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
"Language-Team: Spanish (https://app.transifex.com/sabnzbd/teams/111101/es/)\n"
"MIME-Version: 1.0\n"
+1 -1
View File
@@ -3,7 +3,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-5.0.0\n"
"Project-Id-Version: SABnzbd-5.2.0\n"
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
"Language-Team: Finnish (https://app.transifex.com/sabnzbd/teams/111101/fi/)\n"
"MIME-Version: 1.0\n"
+1 -1
View File
@@ -6,7 +6,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-5.0.0\n"
"Project-Id-Version: SABnzbd-5.2.0\n"
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
"Last-Translator: Fred L <88com88@gmail.com>, 2025\n"
"Language-Team: French (https://app.transifex.com/sabnzbd/teams/111101/fr/)\n"
+1 -1
View File
@@ -6,7 +6,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-5.0.0\n"
"Project-Id-Version: SABnzbd-5.2.0\n"
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2025\n"
"Language-Team: Hebrew (https://app.transifex.com/sabnzbd/teams/111101/he/)\n"
+1 -1
View File
@@ -3,7 +3,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-5.0.0\n"
"Project-Id-Version: SABnzbd-5.2.0\n"
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
"Language-Team: Italian (https://app.transifex.com/sabnzbd/teams/111101/it/)\n"
"MIME-Version: 1.0\n"
+1 -1
View File
@@ -3,7 +3,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-5.0.0\n"
"Project-Id-Version: SABnzbd-5.2.0\n"
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
"Language-Team: Norwegian Bokmål (https://app.transifex.com/sabnzbd/teams/111101/nb/)\n"
"MIME-Version: 1.0\n"
+1 -1
View File
@@ -6,7 +6,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-5.0.0\n"
"Project-Id-Version: SABnzbd-5.2.0\n"
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2025\n"
"Language-Team: Dutch (https://app.transifex.com/sabnzbd/teams/111101/nl/)\n"
+1 -1
View File
@@ -3,7 +3,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-5.0.0\n"
"Project-Id-Version: SABnzbd-5.2.0\n"
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
"Language-Team: Polish (https://app.transifex.com/sabnzbd/teams/111101/pl/)\n"
"MIME-Version: 1.0\n"
+1 -1
View File
@@ -3,7 +3,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-5.0.0\n"
"Project-Id-Version: SABnzbd-5.2.0\n"
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
"Language-Team: Portuguese (Brazil) (https://app.transifex.com/sabnzbd/teams/111101/pt_BR/)\n"
"MIME-Version: 1.0\n"
+1 -1
View File
@@ -3,7 +3,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-5.0.0\n"
"Project-Id-Version: SABnzbd-5.2.0\n"
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
"Language-Team: Romanian (https://app.transifex.com/sabnzbd/teams/111101/ro/)\n"
"MIME-Version: 1.0\n"
+1 -1
View File
@@ -3,7 +3,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-5.0.0\n"
"Project-Id-Version: SABnzbd-5.2.0\n"
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
"Language-Team: Russian (https://app.transifex.com/sabnzbd/teams/111101/ru/)\n"
"MIME-Version: 1.0\n"
+1 -1
View File
@@ -3,7 +3,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-5.0.0\n"
"Project-Id-Version: SABnzbd-5.2.0\n"
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
"Language-Team: Serbian (https://app.transifex.com/sabnzbd/teams/111101/sr/)\n"
"MIME-Version: 1.0\n"
+1 -1
View File
@@ -3,7 +3,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-5.0.0\n"
"Project-Id-Version: SABnzbd-5.2.0\n"
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
"Language-Team: Swedish (https://app.transifex.com/sabnzbd/teams/111101/sv/)\n"
"MIME-Version: 1.0\n"
+1 -1
View File
@@ -6,7 +6,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-5.0.0\n"
"Project-Id-Version: SABnzbd-5.2.0\n"
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
"Last-Translator: mauron, 2025\n"
"Language-Team: Turkish (https://app.transifex.com/sabnzbd/teams/111101/tr/)\n"
+1 -1
View File
@@ -3,7 +3,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-5.0.0\n"
"Project-Id-Version: SABnzbd-5.2.0\n"
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
"Language-Team: Chinese (China) (https://app.transifex.com/sabnzbd/teams/111101/zh_CN/)\n"
"MIME-Version: 1.0\n"
+155 -110
View File
@@ -4,7 +4,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-5.1.0\n"
"Project-Id-Version: SABnzbd-5.2.0\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: team@sabnzbd.org\n"
"Language-Team: SABnzbd <team@sabnzbd.org>\n"
@@ -105,7 +105,6 @@ msgstr ""
msgid "SABnzbd shutdown finished"
msgstr ""
#. Warning message
#: sabnzbd/__init__.py
msgid "Signal %s caught, saving and exiting..."
msgstr ""
@@ -207,6 +206,10 @@ msgstr ""
msgid "Unknown SSL protocol: Try disabling SSL or connecting on a different port."
msgstr ""
#: sabnzbd/api.py
msgid "Could not determine connection result (%s)"
msgstr ""
#: sabnzbd/api.py
msgid "Server requires username and password."
msgstr ""
@@ -215,16 +218,9 @@ msgstr ""
msgid "Connection Successful!"
msgstr ""
#: sabnzbd/api.py, sabnzbd/interface.py
msgid "Authentication failed, check username/password."
msgstr ""
#. Warning message
#: sabnzbd/api.py
msgid "Too many connections, please pause downloading or try again later"
msgstr ""
#: sabnzbd/api.py
msgid "Could not determine connection result (%s)"
msgid "Server %s took %d ms to respond. On high-latency connections, increasing 'Articles per request' to a value between 5 and 10 can improve download speed. See: https://sabnzbd.org/wiki/advanced/nntp-pipelining"
msgstr ""
#: sabnzbd/api.py
@@ -242,12 +238,12 @@ msgid "Default"
msgstr ""
#. Error message
#: sabnzbd/assembler.py
#: sabnzbd/assembler.py, sabnzbd/decoder.py
msgid "Disk full! Forcing Pause"
msgstr ""
#. Error message
#: sabnzbd/assembler.py
#: sabnzbd/assembler.py, sabnzbd/decoder.py
msgid "Disk error on creating file %s"
msgstr ""
@@ -476,7 +472,7 @@ msgid "Resuming"
msgstr ""
#. PP status - Priority pick list
#: sabnzbd/downloader.py, sabnzbd/macosmenu.py, sabnzbd/sabtray.py, sabnzbd/sabtraylinux.py, sabnzbd/skintext.py
#: sabnzbd/downloader.py, sabnzbd/macosmenu.py, sabnzbd/sabtraylinux.py, sabnzbd/sabtraywin.py, sabnzbd/skintext.py
msgid "Paused"
msgstr ""
@@ -514,15 +510,7 @@ msgid "Fatal error in Downloader"
msgstr ""
#: sabnzbd/downloader.py
msgid "Too many connections to server %s [%s]"
msgstr ""
#: sabnzbd/downloader.py
msgid "Login from too many different IP addresses to server %s [%s] - https://sabnzbd.org/multiple-adresses"
msgstr ""
#: sabnzbd/downloader.py
msgid "Failed login for server %s [%s]"
msgid "Server %s reported: %s"
msgstr ""
#. Error message
@@ -530,6 +518,11 @@ msgstr ""
msgid "Connecting %s@%s failed, message=%s"
msgstr ""
#. Warning message
#: sabnzbd/downloader.py
msgid "Total number of configured connections (%d) is greater than the operating system limit (512), downloading will crash if all connections become active"
msgstr ""
#: sabnzbd/downloader.py, sabnzbd/skintext.py
msgid "Shutting down"
msgstr ""
@@ -681,11 +674,16 @@ msgid "Direct Write mode has been disabled because %s does not support sparse fi
msgstr ""
#: sabnzbd/interface.py
msgid "Refused connection from:"
msgid "Refused connection with hostname \"%s\" from:"
msgstr ""
#: sabnzbd/interface.py
msgid "Refused connection with hostname \"%s\" from:"
msgid "API Key incorrect, Use the api key from Config->General in your 3rd party program:"
msgstr ""
#. Warning message
#: sabnzbd/interface.py, sabnzbd/security.py
msgid "Refused connection from:"
msgstr ""
#: sabnzbd/interface.py
@@ -693,16 +691,16 @@ msgid "API Key missing, please enter the api key from Config->General into your
msgstr ""
#: sabnzbd/interface.py
msgid "API Key incorrect, Use the api key from Config->General in your 3rd party program:"
msgid "Too many failed login attempts, try again later."
msgstr ""
#. Warning message
#: sabnzbd/interface.py
msgid "Login attempt refused, too many failures from %s"
msgstr ""
#: sabnzbd/interface.py
msgid "User logged in to the web interface"
msgstr ""
#. Notification
#: sabnzbd/interface.py, sabnzbd/notifier.py
msgid "User logged in"
msgid "Authentication failed, check username/password."
msgstr ""
#. Warning message
@@ -763,14 +761,6 @@ msgstr ""
msgid "Category folder cannot be a subfolder of the Temporary Download Folder."
msgstr ""
#: sabnzbd/interface.py, sabnzbd/skintext.py
msgid "ERROR:"
msgstr ""
#: sabnzbd/interface.py
msgid "Back"
msgstr ""
#. Warning message
#: sabnzbd/internetspeed.py
msgid "Internet Bandwidth of IPv4 significantly different from IPv6: %f MB/s versus %f MB/s"
@@ -781,7 +771,7 @@ msgstr ""
msgid "Warnings"
msgstr ""
#: sabnzbd/macosmenu.py, sabnzbd/sabtray.py, sabnzbd/sabtraylinux.py, sabnzbd/skintext.py
#: sabnzbd/macosmenu.py, sabnzbd/sabtraylinux.py, sabnzbd/sabtraywin.py, sabnzbd/skintext.py
msgid "Idle"
msgstr ""
@@ -810,7 +800,7 @@ msgid "Limit Speed"
msgstr ""
#. Notification - Pause downloading - Four way switch for duplicates - Config->Scheduling
#: sabnzbd/macosmenu.py, sabnzbd/notifier.py, sabnzbd/sabtray.py, sabnzbd/sabtraylinux.py, sabnzbd/skintext.py
#: sabnzbd/macosmenu.py, sabnzbd/notifier.py, sabnzbd/sabtraylinux.py, sabnzbd/sabtraywin.py, sabnzbd/skintext.py
msgid "Pause"
msgstr ""
@@ -820,7 +810,7 @@ msgid "min"
msgstr ""
#. Notification - Resume downloading - Config->Scheduling
#: sabnzbd/macosmenu.py, sabnzbd/notifier.py, sabnzbd/sabtray.py, sabnzbd/sabtraylinux.py, sabnzbd/skintext.py
#: sabnzbd/macosmenu.py, sabnzbd/notifier.py, sabnzbd/sabtraylinux.py, sabnzbd/sabtraywin.py, sabnzbd/skintext.py
msgid "Resume"
msgstr ""
@@ -829,7 +819,7 @@ msgstr ""
msgid "Scan watched folder"
msgstr ""
#: sabnzbd/macosmenu.py, sabnzbd/sabtray.py, sabnzbd/sabtraylinux.py
#: sabnzbd/macosmenu.py, sabnzbd/sabtraylinux.py, sabnzbd/sabtraywin.py
msgid "Read all RSS feeds"
msgstr ""
@@ -841,16 +831,16 @@ msgstr ""
msgid "Incomplete Folder"
msgstr ""
#: sabnzbd/macosmenu.py, sabnzbd/sabtray.py
#: sabnzbd/macosmenu.py, sabnzbd/sabtraywin.py
msgid "Troubleshoot"
msgstr ""
#. Config->Scheduling
#: sabnzbd/macosmenu.py, sabnzbd/sabtray.py, sabnzbd/sabtraylinux.py, sabnzbd/skintext.py
#: sabnzbd/macosmenu.py, sabnzbd/sabtraylinux.py, sabnzbd/sabtraywin.py, sabnzbd/skintext.py
msgid "Restart"
msgstr ""
#: sabnzbd/macosmenu.py, sabnzbd/sabtray.py
#: sabnzbd/macosmenu.py, sabnzbd/sabtraywin.py
msgid "Restart without login"
msgstr ""
@@ -1040,6 +1030,10 @@ msgstr ""
msgid "see logfile"
msgstr ""
#: sabnzbd/newsunpack.py
msgid "Unpacking skipped, TAR support requires Python 3.12 or later"
msgstr ""
#: sabnzbd/newsunpack.py
msgid "Quick Checking"
msgstr ""
@@ -1196,6 +1190,11 @@ msgstr ""
msgid "Queue finished"
msgstr ""
#. Notification
#: sabnzbd/notifier.py
msgid "User logged in"
msgstr ""
#. Notification
#: sabnzbd/notifier.py
msgid "Other Messages"
@@ -1207,7 +1206,7 @@ msgid "Open folder"
msgstr ""
#. Notification action
#: sabnzbd/notifier.py, sabnzbd/sabtray.py, sabnzbd/sabtraylinux.py
#: sabnzbd/notifier.py, sabnzbd/sabtraylinux.py, sabnzbd/sabtraywin.py
msgid "Open complete folder"
msgstr ""
@@ -1345,7 +1344,7 @@ msgstr ""
msgid "Downloaded in %s at an average of %sB/s"
msgstr ""
#. Job details page, file age column header
#. Config->RSS filter-type selection menu (e.g. ">3d", "<12h") - Job details page, file age column header
#: sabnzbd/nzb/object.py, sabnzbd/skintext.py
msgid "Age"
msgstr ""
@@ -1430,6 +1429,21 @@ msgstr ""
msgid "If you get this error message again, please try a different number.<br>"
msgstr ""
#: sabnzbd/panic.py
msgid ""
"\n"
" SABnzbd is not allowed to use port %s on %s.<br>\n"
" Low port numbers are reserved for privileged processes on most systems,\n"
" so the port itself is available but cannot be claimed.<br>\n"
" <br>\n"
" Either run SABnzbd with the privileges needed for that port, or restart it\n"
" with a port number of 1024 or higher."
msgstr ""
#: sabnzbd/panic.py
msgid "To keep using the low port, put a reverse proxy in front of SABnzbd instead.<br>"
msgstr ""
#: sabnzbd/panic.py
msgid ""
"\n"
@@ -1492,19 +1506,15 @@ msgstr ""
msgid "Unable to bind to port %s on %s. Some other software uses the port or SABnzbd is already running."
msgstr ""
#: sabnzbd/panic.py
msgid "Not allowed to bind to port %s on %s. Low ports need elevated privileges."
msgstr ""
#. Warning message
#: sabnzbd/panic.py
msgid "Cannot launch the browser, probably not found"
msgstr ""
#: sabnzbd/panic.py
msgid "Access denied"
msgstr ""
#: sabnzbd/panic.py
msgid "Error %s: You need to provide a valid username and password."
msgstr ""
#. Warning message
#: sabnzbd/postproc.py
msgid "Old queue detected, use Status->Repair to convert the queue"
@@ -1633,6 +1643,10 @@ msgstr ""
msgid "Removing %s failed"
msgstr ""
#: sabnzbd/postproc.py
msgid "Removed %s files with unwanted extensions"
msgstr ""
#. Error message
#: sabnzbd/powersup.py
msgid "Failed to hibernate system"
@@ -1657,10 +1671,6 @@ msgstr ""
msgid "Empty RSS entry found (%s)"
msgstr ""
#: sabnzbd/rss.py
msgid "Incompatible feed"
msgstr ""
#. Error message
#: sabnzbd/rss.py
msgid "Incorrect RSS feed description \"%s\""
@@ -1682,50 +1692,15 @@ msgstr ""
msgid "Server %s uses an untrusted HTTPS certificate"
msgstr ""
#. Warning message
#: sabnzbd/rss.py
msgid "RSS Feed %s was empty"
msgstr ""
#: sabnzbd/sabtray.py, sabnzbd/sabtraylinux.py
msgid "Show interface"
#: sabnzbd/rss.py
msgid "Incompatible feed"
msgstr ""
#. Queue page button or entry box
#: sabnzbd/sabtray.py, sabnzbd/skintext.py
msgid "Pause for"
msgstr ""
#: sabnzbd/sabtray.py, sabnzbd/skintext.py
msgid "Pause for 5 minutes"
msgstr ""
#: sabnzbd/sabtray.py, sabnzbd/skintext.py
msgid "Pause for 15 minutes"
msgstr ""
#: sabnzbd/sabtray.py, sabnzbd/skintext.py
msgid "Pause for 30 minutes"
msgstr ""
#: sabnzbd/sabtray.py, sabnzbd/skintext.py
msgid "Pause for 1 hour"
msgstr ""
#: sabnzbd/sabtray.py, sabnzbd/skintext.py
msgid "Pause for 3 hours"
msgstr ""
#: sabnzbd/sabtray.py, sabnzbd/skintext.py
msgid "Pause for 6 hours"
msgstr ""
#. Config->Scheduling
#: sabnzbd/sabtray.py, sabnzbd/sabtraylinux.py, sabnzbd/skintext.py
msgid "Shutdown"
msgstr ""
#: sabnzbd/sabtray.py, sabnzbd/sabtraylinux.py
#: sabnzbd/sabtraylinux.py, sabnzbd/sabtraywin.py
msgid "Remaining"
msgstr ""
@@ -1733,6 +1708,44 @@ msgstr ""
msgid "Add NZB"
msgstr ""
#: sabnzbd/sabtraylinux.py, sabnzbd/sabtraywin.py
msgid "Show interface"
msgstr ""
#. Config->Scheduling
#: sabnzbd/sabtraylinux.py, sabnzbd/sabtraywin.py, sabnzbd/skintext.py
msgid "Shutdown"
msgstr ""
#. Queue page button or entry box
#: sabnzbd/sabtraywin.py, sabnzbd/skintext.py
msgid "Pause for"
msgstr ""
#: sabnzbd/sabtraywin.py, sabnzbd/skintext.py
msgid "Pause for 5 minutes"
msgstr ""
#: sabnzbd/sabtraywin.py, sabnzbd/skintext.py
msgid "Pause for 15 minutes"
msgstr ""
#: sabnzbd/sabtraywin.py, sabnzbd/skintext.py
msgid "Pause for 30 minutes"
msgstr ""
#: sabnzbd/sabtraywin.py, sabnzbd/skintext.py
msgid "Pause for 1 hour"
msgstr ""
#: sabnzbd/sabtraywin.py, sabnzbd/skintext.py
msgid "Pause for 3 hours"
msgstr ""
#: sabnzbd/sabtraywin.py, sabnzbd/skintext.py
msgid "Pause for 6 hours"
msgstr ""
#. Warning message
#: sabnzbd/scheduler.py
msgid "Bad schedule %s at %s:%s"
@@ -2068,6 +2081,10 @@ msgstr ""
msgid "Cancel"
msgstr ""
#: sabnzbd/skintext.py
msgid "Close"
msgstr ""
#: sabnzbd/skintext.py
msgid "Log in"
msgstr ""
@@ -2623,10 +2640,6 @@ msgstr ""
msgid "Web Interface Theme"
msgstr ""
#: sabnzbd/skintext.py
msgid "Choose a theme."
msgstr ""
#: sabnzbd/skintext.py
msgid "SABnzbd Username"
msgstr ""
@@ -2745,7 +2758,7 @@ msgid "Cleanup List"
msgstr ""
#: sabnzbd/skintext.py
msgid "List of file extensions that should be deleted after download.<br />For example: <b>nfo</b> or <b>nfo, sfv</b>"
msgid "List of file extensions, filenames or patterns that should be deleted after download.<br />Examples: <b>nfo, sfv</b> (extensions) or <b>thumbs.db</b> (filenames) or <b>*.tmp, images/*, */test.jpg</b> (patterns)."
msgstr ""
#: sabnzbd/skintext.py
@@ -3210,7 +3223,7 @@ msgid "Automatically sort jobs in the queue when a new job is added."
msgstr ""
#: sabnzbd/skintext.py
msgid "The queue will resort every 30 seconds if % downloaded is selected."
msgid "The queue will resort every 30 seconds if % downloaded or Remaining Size is selected."
msgstr ""
#: sabnzbd/skintext.py
@@ -3331,7 +3344,7 @@ msgid "Naming"
msgstr ""
#: sabnzbd/skintext.py
msgid "How much can be downloaded this month (K/M/G)"
msgid "How much can be downloaded this Quota period (K/M/G)"
msgstr ""
#. Reset day of the download quota
@@ -3584,10 +3597,6 @@ msgstr ""
msgid "Force Download"
msgstr ""
#: sabnzbd/skintext.py
msgid "Apply filters"
msgstr ""
#. Config->RSS edit button
#: sabnzbd/skintext.py
msgid "Edit"
@@ -4212,6 +4221,14 @@ msgstr ""
msgid "Filename"
msgstr ""
#: sabnzbd/skintext.py
msgid "Toggle SABnzbd navigation menu"
msgstr ""
#: sabnzbd/skintext.py
msgid "Open SABnzbd main menu"
msgstr ""
#: sabnzbd/skintext.py
msgid "Free Space"
msgstr ""
@@ -4220,6 +4237,14 @@ msgstr ""
msgid "Temp Folder"
msgstr ""
#: sabnzbd/skintext.py
msgid "Clear search"
msgstr ""
#: sabnzbd/skintext.py
msgid "Search by name, or refine with filters <code>cat:</code>, <code>status:</code> and <code>priority:</code> (Queue). For example: <code>my show cat:tv</code>"
msgstr ""
#: sabnzbd/skintext.py
msgid "Multi-Operations"
msgstr ""
@@ -4228,6 +4253,10 @@ msgstr ""
msgid "Hold shift key to select a range"
msgstr ""
#: sabnzbd/skintext.py
msgid "Select job"
msgstr ""
#: sabnzbd/skintext.py
msgid "Check all"
msgstr ""
@@ -4260,6 +4289,10 @@ msgstr ""
msgid "WARNING:"
msgstr ""
#: sabnzbd/skintext.py
msgid "ERROR:"
msgstr ""
#: sabnzbd/skintext.py
msgid "Fetch"
msgstr ""
@@ -4296,6 +4329,14 @@ msgstr ""
msgid "Extra history columns"
msgstr ""
#: sabnzbd/skintext.py
msgid "Progress"
msgstr ""
#: sabnzbd/skintext.py
msgid "Actions"
msgstr ""
#: sabnzbd/skintext.py
msgid "page"
msgstr ""
@@ -4456,6 +4497,10 @@ msgstr ""
msgid "Sort by % downloaded <small>Most&rarr;Least</small>"
msgstr ""
#: sabnzbd/skintext.py
msgid "Sort by Remaining Size <small>Smallest&rarr;Largest</small>"
msgstr ""
#: sabnzbd/skintext.py
msgid "Sort by Age <small>Oldest&rarr;Newest</small>"
msgstr ""
+1011 -711
View File
File diff suppressed because it is too large. Load diff
+205 -132
View File
@@ -6,7 +6,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-5.0.0\n"
"Project-Id-Version: SABnzbd-5.2.0\n"
"PO-Revision-Date: 2020-06-27 15:49+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2026\n"
"Language-Team: Danish (https://app.transifex.com/sabnzbd/teams/111101/da/)\n"
@@ -120,7 +120,6 @@ msgstr "SABnzbd %s startet"
msgid "SABnzbd shutdown finished"
msgstr "SABnzbd lukning udført"
#. Warning message
#: sabnzbd/__init__.py
msgid "Signal %s caught, saving and exiting..."
msgstr "Signal %s modtaget, gemmer og lukker..."
@@ -151,6 +150,7 @@ msgstr ""
#: sabnzbd/__init__.py
msgid "Windows ARM version of SABnzbd is available from our Downloads page!"
msgstr ""
"Windows ARM-versionen af SABnzbd er tilgængelig på vores downloadside!"
#. Warning message
#: sabnzbd/__init__.py
@@ -250,6 +250,10 @@ msgstr ""
"Ukendt SSL protokol: Prøv at deaktivere SSL eller forbinder på en anden "
"port."
#: sabnzbd/api.py
msgid "Could not determine connection result (%s)"
msgstr "Det lykkedes ikke at tilslutte (%s)"
#: sabnzbd/api.py
msgid "Server requires username and password."
msgstr "Serveren kræver brugernavn og adgangskode."
@@ -258,18 +262,13 @@ msgstr "Serveren kræver brugernavn og adgangskode."
msgid "Connection Successful!"
msgstr "Tilslutning lykkedes!"
#: sabnzbd/api.py, sabnzbd/interface.py
msgid "Authentication failed, check username/password."
msgstr "Godkendelse mislykkedes, kontrollere brugernavn/adgangskode."
#. Warning message
#: sabnzbd/api.py
msgid "Too many connections, please pause downloading or try again later"
msgid ""
"Server %s took %d ms to respond. On high-latency connections, increasing "
"'Articles per request' to a value between 5 and 10 can improve download "
"speed. See: https://sabnzbd.org/wiki/advanced/nntp-pipelining"
msgstr ""
"Alt for mange forbindelser, pause en download eller forsøg igen senere"
#: sabnzbd/api.py
msgid "Could not determine connection result (%s)"
msgstr "Det lykkedes ikke at tilslutte (%s)"
#: sabnzbd/api.py
msgid "Resolving address"
@@ -286,12 +285,12 @@ msgid "Default"
msgstr "Standard"
#. Error message
#: sabnzbd/assembler.py
#: sabnzbd/assembler.py, sabnzbd/decoder.py
msgid "Disk full! Forcing Pause"
msgstr "Disken er fuld! Pauser"
#. Error message
#: sabnzbd/assembler.py
#: sabnzbd/assembler.py, sabnzbd/decoder.py
msgid "Disk error on creating file %s"
msgstr "Diskfejl ved oprettelse af fil %s"
@@ -550,8 +549,8 @@ msgid "Resuming"
msgstr "Genoptager"
#. PP status - Priority pick list
#: sabnzbd/downloader.py, sabnzbd/macosmenu.py, sabnzbd/sabtray.py,
#: sabnzbd/sabtraylinux.py, sabnzbd/skintext.py
#: sabnzbd/downloader.py, sabnzbd/macosmenu.py, sabnzbd/sabtraylinux.py,
#: sabnzbd/sabtraywin.py, sabnzbd/skintext.py
msgid "Paused"
msgstr "Sat på pause"
@@ -591,26 +590,24 @@ msgid "Fatal error in Downloader"
msgstr "Alvorlig fejl i Downloader"
#: sabnzbd/downloader.py
msgid "Too many connections to server %s [%s]"
msgstr "Alt for mange forbindelser til serveren %s [%s]"
#: sabnzbd/downloader.py
msgid ""
"Login from too many different IP addresses to server %s [%s] - "
"https://sabnzbd.org/multiple-adresses"
msgid "Server %s reported: %s"
msgstr ""
"Login fra for mange forskellige IP-adresser til server %s [%s] - "
"https://sabnzbd.org/multiple-adresses"
#: sabnzbd/downloader.py
msgid "Failed login for server %s [%s]"
msgstr "Det lykkedes ikke at logge på serveren %s [%s]"
#. Error message
#: sabnzbd/downloader.py
msgid "Connecting %s@%s failed, message=%s"
msgstr "Forbindelse %s@%s mislykkedes, besked %s"
#. Warning message
#: sabnzbd/downloader.py
msgid ""
"Total number of configured connections (%d) is greater than the operating "
"system limit (512), downloading will crash if all connections become active"
msgstr ""
"Det samlede antal konfigurerede forbindelser (%d) er større end "
"operativsystemets grænse (512); downloadingen vil crashe, hvis alle "
"forbindelser bliver aktive"
#: sabnzbd/downloader.py, sabnzbd/skintext.py
msgid "Shutting down"
msgstr "Påbegynder lukning af SABnzbd"
@@ -776,15 +773,27 @@ msgid ""
"files. For details and possible solutions, see: "
"https://sabnzbd.org/wiki/advanced/direct-write"
msgstr ""
#: sabnzbd/interface.py
msgid "Refused connection from:"
msgstr "Afviste forbindelse fra:"
"Direct Write-tilstand er blevet deaktiveret, fordi %s ikke understøtter "
"sparse-filer. For detaljer og mulige løsninger, se: "
"https://sabnzbd.org/wiki/advanced/direct-write"
#: sabnzbd/interface.py
msgid "Refused connection with hostname \"%s\" from:"
msgstr "Afviste forbindelse med værtsnavn \"%s\" fra:"
#: sabnzbd/interface.py
msgid ""
"API Key incorrect, Use the api key from Config->General in your 3rd party "
"program:"
msgstr ""
"Forkert API-nøgle, anvend api-nøglen fra Konfiguration->Generelt i dit "
"tredjepartsprogram:"
#. Warning message
#: sabnzbd/interface.py, sabnzbd/security.py
msgid "Refused connection from:"
msgstr "Afviste forbindelse fra:"
#: sabnzbd/interface.py
msgid ""
"API Key missing, please enter the api key from Config->General into your 3rd"
@@ -794,21 +803,17 @@ msgstr ""
"tredjepartsprogram:"
#: sabnzbd/interface.py
msgid ""
"API Key incorrect, Use the api key from Config->General in your 3rd party "
"program:"
msgid "Too many failed login attempts, try again later."
msgstr ""
#. Warning message
#: sabnzbd/interface.py
msgid "Login attempt refused, too many failures from %s"
msgstr ""
"Forkert API-nøgle, anvend api-nøglen fra Konfiguration->Generelt i dit "
"tredjepartsprogram:"
#: sabnzbd/interface.py
msgid "User logged in to the web interface"
msgstr "Bruger logget på webgrænsefladen"
#. Notification
#: sabnzbd/interface.py, sabnzbd/notifier.py
msgid "User logged in"
msgstr "Bruger logget ind"
msgid "Authentication failed, check username/password."
msgstr "Godkendelse mislykkedes, kontrollere brugernavn/adgangskode."
#. Warning message
#: sabnzbd/interface.py
@@ -870,27 +875,20 @@ msgid ""
msgstr ""
"Kategorimappe kan ikke være en undermappe i den midlertidige download-mappe."
#: sabnzbd/interface.py, sabnzbd/skintext.py
msgid "ERROR:"
msgstr "FEJL:"
#: sabnzbd/interface.py
msgid "Back"
msgstr "Tilbage"
#. Warning message
#: sabnzbd/internetspeed.py
msgid ""
"Internet Bandwidth of IPv4 significantly different from IPv6: %f MB/s versus"
" %f MB/s"
msgstr ""
"Internetbåndbredde for IPv4 afviger markant fra IPv6: %f MB/s mod %f MB/s"
#. Footer: indicator of warnings
#: sabnzbd/macosmenu.py, sabnzbd/skintext.py
msgid "Warnings"
msgstr "Advarsler"
#: sabnzbd/macosmenu.py, sabnzbd/sabtray.py, sabnzbd/sabtraylinux.py,
#: sabnzbd/macosmenu.py, sabnzbd/sabtraylinux.py, sabnzbd/sabtraywin.py,
#: sabnzbd/skintext.py
msgid "Idle"
msgstr "Inaktiv"
@@ -921,8 +919,8 @@ msgstr "Hastighedsbegrænsning"
#. Notification - Pause downloading - Four way switch for duplicates -
#. Config->Scheduling
#: sabnzbd/macosmenu.py, sabnzbd/notifier.py, sabnzbd/sabtray.py,
#: sabnzbd/sabtraylinux.py, sabnzbd/skintext.py
#: sabnzbd/macosmenu.py, sabnzbd/notifier.py, sabnzbd/sabtraylinux.py,
#: sabnzbd/sabtraywin.py, sabnzbd/skintext.py
msgid "Pause"
msgstr "Pause"
@@ -932,8 +930,8 @@ msgid "min"
msgstr "minut"
#. Notification - Resume downloading - Config->Scheduling
#: sabnzbd/macosmenu.py, sabnzbd/notifier.py, sabnzbd/sabtray.py,
#: sabnzbd/sabtraylinux.py, sabnzbd/skintext.py
#: sabnzbd/macosmenu.py, sabnzbd/notifier.py, sabnzbd/sabtraylinux.py,
#: sabnzbd/sabtraywin.py, sabnzbd/skintext.py
msgid "Resume"
msgstr "Genoptag"
@@ -942,7 +940,7 @@ msgstr "Genoptag"
msgid "Scan watched folder"
msgstr "Scan overvåget mappe"
#: sabnzbd/macosmenu.py, sabnzbd/sabtray.py, sabnzbd/sabtraylinux.py
#: sabnzbd/macosmenu.py, sabnzbd/sabtraylinux.py, sabnzbd/sabtraywin.py
msgid "Read all RSS feeds"
msgstr "Læs alle RSS feeds"
@@ -954,17 +952,17 @@ msgstr "Færdig mappe"
msgid "Incomplete Folder"
msgstr "Ufuldstændig mappe"
#: sabnzbd/macosmenu.py, sabnzbd/sabtray.py
#: sabnzbd/macosmenu.py, sabnzbd/sabtraywin.py
msgid "Troubleshoot"
msgstr "Fejlfinding"
#. Config->Scheduling
#: sabnzbd/macosmenu.py, sabnzbd/sabtray.py, sabnzbd/sabtraylinux.py,
#: sabnzbd/macosmenu.py, sabnzbd/sabtraylinux.py, sabnzbd/sabtraywin.py,
#: sabnzbd/skintext.py
msgid "Restart"
msgstr "Genstart"
#: sabnzbd/macosmenu.py, sabnzbd/sabtray.py
#: sabnzbd/macosmenu.py, sabnzbd/sabtraywin.py
msgid "Restart without login"
msgstr "Genstart uden login"
@@ -1161,6 +1159,11 @@ msgstr "Forsøger 7zip med adgangskode \"%s\""
msgid "see logfile"
msgstr "se logfil"
#: sabnzbd/newsunpack.py
msgid "Unpacking skipped, TAR support requires Python 3.12 or later"
msgstr ""
"Udpakning sprunget over, TAR-understøttelse kræver Python 3.12 eller nyere"
#: sabnzbd/newsunpack.py
msgid "Quick Checking"
msgstr "Hurtig kontrollerende"
@@ -1332,6 +1335,11 @@ msgstr "Jobbet misllykedes"
msgid "Queue finished"
msgstr "Kø færdig"
#. Notification
#: sabnzbd/notifier.py
msgid "User logged in"
msgstr "Bruger logget ind"
#. Notification
#: sabnzbd/notifier.py
msgid "Other Messages"
@@ -1343,7 +1351,7 @@ msgid "Open folder"
msgstr "Åbn mappe"
#. Notification action
#: sabnzbd/notifier.py, sabnzbd/sabtray.py, sabnzbd/sabtraylinux.py
#: sabnzbd/notifier.py, sabnzbd/sabtraylinux.py, sabnzbd/sabtraywin.py
msgid "Open complete folder"
msgstr "Åben færdig mappe"
@@ -1482,7 +1490,8 @@ msgstr "PROPAGATING %s min"
msgid "Downloaded in %s at an average of %sB/s"
msgstr "Hentede i %s med et gennemsnit på %sB/s"
#. Job details page, file age column header
#. Config->RSS filter-type selection menu (e.g. ">3d", "<12h") - Job details
#. page, file age column header
#: sabnzbd/nzb/object.py, sabnzbd/skintext.py
msgid "Age"
msgstr "Alder"
@@ -1574,6 +1583,23 @@ msgid ""
"If you get this error message again, please try a different number.<br>"
msgstr "Hvis du får denne fejlmeddelelse igen, prøv et andet portnummer.<br>"
#: sabnzbd/panic.py
msgid ""
"\n"
" SABnzbd is not allowed to use port %s on %s.<br>\n"
" Low port numbers are reserved for privileged processes on most systems,\n"
" so the port itself is available but cannot be claimed.<br>\n"
" <br>\n"
" Either run SABnzbd with the privileges needed for that port, or restart it\n"
" with a port number of 1024 or higher."
msgstr ""
#: sabnzbd/panic.py
msgid ""
"To keep using the low port, put a reverse proxy in front of SABnzbd "
"instead.<br>"
msgstr ""
#: sabnzbd/panic.py
msgid ""
"\n"
@@ -1662,19 +1688,16 @@ msgstr ""
"Kan ikke binde til port %s på %s. En anden software bruger porten eller "
"SABnzbd kører allerede."
#: sabnzbd/panic.py
msgid ""
"Not allowed to bind to port %s on %s. Low ports need elevated privileges."
msgstr ""
#. Warning message
#: sabnzbd/panic.py
msgid "Cannot launch the browser, probably not found"
msgstr "Kan ikke starte browseren, sandsynligvis ikke fundet"
#: sabnzbd/panic.py
msgid "Access denied"
msgstr "Adgang nægtet"
#: sabnzbd/panic.py
msgid "Error %s: You need to provide a valid username and password."
msgstr "Fejl %s: Du skal angive et gyldigt brugernavn og adgangskode."
#. Warning message
#: sabnzbd/postproc.py
msgid "Old queue detected, use Status->Repair to convert the queue"
@@ -1803,6 +1826,10 @@ msgstr "Ingen matchende tidligere rar-fil for %s"
msgid "Removing %s failed"
msgstr "Fjernelse af %s mislykkedes"
#: sabnzbd/postproc.py
msgid "Removed %s files with unwanted extensions"
msgstr ""
#. Error message
#: sabnzbd/powersup.py
msgid "Failed to hibernate system"
@@ -1827,10 +1854,6 @@ msgstr "Modtog en DBus-undtagelse %s"
msgid "Empty RSS entry found (%s)"
msgstr "Tom RSS post blev fundet (%s)"
#: sabnzbd/rss.py
msgid "Incompatible feed"
msgstr "Inkompatibel feed"
#. Error message
#: sabnzbd/rss.py
msgid "Incorrect RSS feed description \"%s\""
@@ -1852,50 +1875,15 @@ msgstr "Mislykkedes at hente RSS fra %s: %s"
msgid "Server %s uses an untrusted HTTPS certificate"
msgstr "Server %s bruger et upålideligt HTTPS-certifikat"
#. Warning message
#: sabnzbd/rss.py
msgid "RSS Feed %s was empty"
msgstr "RSS Feed %s er tom"
#: sabnzbd/sabtray.py, sabnzbd/sabtraylinux.py
msgid "Show interface"
msgstr "Vis grænseflade"
#: sabnzbd/rss.py
msgid "Incompatible feed"
msgstr "Inkompatibel feed"
#. Queue page button or entry box
#: sabnzbd/sabtray.py, sabnzbd/skintext.py
msgid "Pause for"
msgstr "Pause i"
#: sabnzbd/sabtray.py, sabnzbd/skintext.py
msgid "Pause for 5 minutes"
msgstr "Pause 5 minutter"
#: sabnzbd/sabtray.py, sabnzbd/skintext.py
msgid "Pause for 15 minutes"
msgstr "Pause 15 minutter"
#: sabnzbd/sabtray.py, sabnzbd/skintext.py
msgid "Pause for 30 minutes"
msgstr "Pause 30 minutter"
#: sabnzbd/sabtray.py, sabnzbd/skintext.py
msgid "Pause for 1 hour"
msgstr "Pause 1 time"
#: sabnzbd/sabtray.py, sabnzbd/skintext.py
msgid "Pause for 3 hours"
msgstr "Pause 3 timer"
#: sabnzbd/sabtray.py, sabnzbd/skintext.py
msgid "Pause for 6 hours"
msgstr "Pause 6 timer"
#. Config->Scheduling
#: sabnzbd/sabtray.py, sabnzbd/sabtraylinux.py, sabnzbd/skintext.py
msgid "Shutdown"
msgstr "Luk ned"
#: sabnzbd/sabtray.py, sabnzbd/sabtraylinux.py
#: sabnzbd/sabtraylinux.py, sabnzbd/sabtraywin.py
msgid "Remaining"
msgstr "Tilbageværende"
@@ -1903,6 +1891,44 @@ msgstr "Tilbageværende"
msgid "Add NZB"
msgstr "Tilføj NZB"
#: sabnzbd/sabtraylinux.py, sabnzbd/sabtraywin.py
msgid "Show interface"
msgstr "Vis grænseflade"
#. Config->Scheduling
#: sabnzbd/sabtraylinux.py, sabnzbd/sabtraywin.py, sabnzbd/skintext.py
msgid "Shutdown"
msgstr "Luk ned"
#. Queue page button or entry box
#: sabnzbd/sabtraywin.py, sabnzbd/skintext.py
msgid "Pause for"
msgstr "Pause i"
#: sabnzbd/sabtraywin.py, sabnzbd/skintext.py
msgid "Pause for 5 minutes"
msgstr "Pause 5 minutter"
#: sabnzbd/sabtraywin.py, sabnzbd/skintext.py
msgid "Pause for 15 minutes"
msgstr "Pause 15 minutter"
#: sabnzbd/sabtraywin.py, sabnzbd/skintext.py
msgid "Pause for 30 minutes"
msgstr "Pause 30 minutter"
#: sabnzbd/sabtraywin.py, sabnzbd/skintext.py
msgid "Pause for 1 hour"
msgstr "Pause 1 time"
#: sabnzbd/sabtraywin.py, sabnzbd/skintext.py
msgid "Pause for 3 hours"
msgstr "Pause 3 timer"
#: sabnzbd/sabtraywin.py, sabnzbd/skintext.py
msgid "Pause for 6 hours"
msgstr "Pause 6 timer"
#. Warning message
#: sabnzbd/scheduler.py
msgid "Bad schedule %s at %s:%s"
@@ -2238,6 +2264,10 @@ msgstr "Vært"
msgid "Cancel"
msgstr "Annullér"
#: sabnzbd/skintext.py
msgid "Close"
msgstr ""
#: sabnzbd/skintext.py
msgid "Log in"
msgstr "Log in"
@@ -2824,10 +2854,6 @@ msgstr "Port som SABnzbd ska lytte på."
msgid "Web Interface Theme"
msgstr "Tema for webgrænseflade"
#: sabnzbd/skintext.py
msgid "Choose a theme."
msgstr "Vælg et tema."
#: sabnzbd/skintext.py
msgid "SABnzbd Username"
msgstr "SABnzbd brugernavn"
@@ -2972,11 +2998,13 @@ msgstr "Rydningsliste"
#: sabnzbd/skintext.py
msgid ""
"List of file extensions that should be deleted after download.<br />For "
"example: <b>nfo</b> or <b>nfo, sfv</b>"
"List of file extensions, filenames or patterns that should be deleted after "
"download.<br />Examples: <b>nfo, sfv</b> (extensions) or <b>thumbs.db</b> "
"(filenames) or <b>*.tmp, images/*, */test.jpg</b> (patterns)."
msgstr ""
"Listen over filtypenavne, der skal slettes efter download.<br />For "
"eksempel: <b>nfo</b> eller <b>nfo, sfv</b>"
"Liste over filendelser, filnavne eller mønstre, der skal slettes efter "
"download.<br />Eksempler: <b>nfo, sfv</b> (endelser) eller <b>thumbs.db</b> "
"(filnavne) eller <b>*.tmp, images/*, */test.jpg</b> (mønstre)."
#: sabnzbd/skintext.py
msgid "History Retention"
@@ -3356,6 +3384,9 @@ msgid ""
"all jobs will be marked as Completed and moved to the Complete folder even "
"if they are incomplete."
msgstr ""
"Forsøg kun at udpakke jobs, der har bestået verifikationen. Hvis dette er "
"slået fra, markeres alle jobs som Færdig og flyttes til Færdig-mappen, "
"selvom de er ufuldstændige."
#: sabnzbd/skintext.py
msgid "Action when encrypted RAR is downloaded"
@@ -3449,6 +3480,8 @@ msgstr "Aktiver SFV-baseret kontrol"
msgid ""
"If no par2 files are available, use sfv files (if present) to verify files"
msgstr ""
"Hvis ingen par2-filer er tilgængelige, brug da sfv-filer (hvis til stede) "
"til at verificere filer"
#: sabnzbd/skintext.py
msgid "User script can flag job as failed"
@@ -3523,8 +3556,10 @@ msgid "Automatically sort jobs in the queue when a new job is added."
msgstr "Sortér automatisk jobs i køen, når et nyt job tilføjes."
#: sabnzbd/skintext.py
msgid "The queue will resort every 30 seconds if % downloaded is selected."
msgstr "Køen vil sortere hver 30. sekund, hvis % downloadet er valgt."
msgid ""
"The queue will resort every 30 seconds if % downloaded or Remaining Size is "
"selected."
msgstr ""
#: sabnzbd/skintext.py
msgid "Propagation delay"
@@ -3663,8 +3698,8 @@ msgid "Naming"
msgstr "Navngivning"
#: sabnzbd/skintext.py
msgid "How much can be downloaded this month (K/M/G)"
msgstr "Hvor meget der kan downloades i denne måned (K/M/G)"
msgid "How much can be downloaded this Quota period (K/M/G)"
msgstr ""
#. Reset day of the download quota
#: sabnzbd/skintext.py
@@ -3865,7 +3900,7 @@ msgstr "Aktivere"
#: sabnzbd/skintext.py
msgid "Articles per request"
msgstr ""
msgstr "Artikler pr. forespørgsel"
#: sabnzbd/skintext.py
msgid ""
@@ -3873,6 +3908,9 @@ msgid ""
"first.<br />This can improve download speeds, especially on connections with"
" higher latency."
msgstr ""
"Anmod om flere artikler pr. forbindelse uden først at vente på hvert "
"svar.<br />Dette kan forbedre downloadhastighederne, især på forbindelser "
"med højere latens."
#. Button: Remove server
#: sabnzbd/skintext.py
@@ -3961,10 +3999,6 @@ msgstr "Læs Feed"
msgid "Force Download"
msgstr "Gennemtving download"
#: sabnzbd/skintext.py
msgid "Apply filters"
msgstr "Anvend filtre"
#. Config->RSS edit button
#: sabnzbd/skintext.py
msgid "Edit"
@@ -4649,6 +4683,14 @@ msgstr "Slet"
msgid "Filename"
msgstr "Filnavn"
#: sabnzbd/skintext.py
msgid "Toggle SABnzbd navigation menu"
msgstr ""
#: sabnzbd/skintext.py
msgid "Open SABnzbd main menu"
msgstr ""
#: sabnzbd/skintext.py
msgid "Free Space"
msgstr "Ledig diskplads"
@@ -4657,6 +4699,17 @@ msgstr "Ledig diskplads"
msgid "Temp Folder"
msgstr "Midlertidig mappe"
#: sabnzbd/skintext.py
msgid "Clear search"
msgstr ""
#: sabnzbd/skintext.py
msgid ""
"Search by name, or refine with filters <code>cat:</code>, "
"<code>status:</code> and <code>priority:</code> (Queue). For example: "
"<code>my show cat:tv</code>"
msgstr ""
#: sabnzbd/skintext.py
msgid "Multi-Operations"
msgstr "Multi-operationer"
@@ -4665,6 +4718,10 @@ msgstr "Multi-operationer"
msgid "Hold shift key to select a range"
msgstr "Hold Skiftetasten nede for at vælge et område"
#: sabnzbd/skintext.py
msgid "Select job"
msgstr ""
#: sabnzbd/skintext.py
msgid "Check all"
msgstr "Tjek alle"
@@ -4697,6 +4754,10 @@ msgstr "I tilfælde af SABnzbd genstart vil denne skærm forsvinde automatisk!"
msgid "WARNING:"
msgstr "ADVARSEL:"
#: sabnzbd/skintext.py
msgid "ERROR:"
msgstr "FEJL:"
#: sabnzbd/skintext.py
msgid "Fetch"
msgstr "Hent"
@@ -4733,6 +4794,14 @@ msgstr "Ekstra køkolonner"
msgid "Extra history columns"
msgstr "Ekstra historikkolonner"
#: sabnzbd/skintext.py
msgid "Progress"
msgstr "Fremgang"
#: sabnzbd/skintext.py
msgid "Actions"
msgstr "Handlinger"
#: sabnzbd/skintext.py
msgid "page"
msgstr "side"
@@ -4909,6 +4978,10 @@ msgstr ""
msgid "Sort by % downloaded <small>Most&rarr;Least</small>"
msgstr "Sortér efter % downloadet <small>Mest&rarr;Mindst</small>"
#: sabnzbd/skintext.py
msgid "Sort by Remaining Size <small>Smallest&rarr;Largest</small>"
msgstr ""
#: sabnzbd/skintext.py
msgid "Sort by Age <small>Oldest&rarr;Newest</small>"
msgstr "Sortere efter alder <small>Ældst&rarr;Nyeste</small>"
+209 -136
View File
@@ -20,7 +20,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-5.0.0\n"
"Project-Id-Version: SABnzbd-5.2.0\n"
"PO-Revision-Date: 2020-06-27 15:49+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2026\n"
"Language-Team: German (https://app.transifex.com/sabnzbd/teams/111101/de/)\n"
@@ -138,7 +138,6 @@ msgstr "SABnzbd %s gestartet"
msgid "SABnzbd shutdown finished"
msgstr "SABnzbd wurde beendet"
#. Warning message
#: sabnzbd/__init__.py
msgid "Signal %s caught, saving and exiting..."
msgstr "Signal %s erkannt. Wird gespeichert und beendet …"
@@ -171,6 +170,8 @@ msgstr ""
#: sabnzbd/__init__.py
msgid "Windows ARM version of SABnzbd is available from our Downloads page!"
msgstr ""
"Eine Windows-ARM-Version von SABnzbd ist auf unserer Downloads-Seite "
"verfügbar!"
#. Warning message
#: sabnzbd/__init__.py
@@ -274,6 +275,10 @@ msgstr ""
"Unbekanntes SSL-Protokoll: SSL deaktivieren oder alternativen Port "
"versuchen."
#: sabnzbd/api.py
msgid "Could not determine connection result (%s)"
msgstr "Die Verbindung konnte nicht überprüft werden. (%s)"
#: sabnzbd/api.py
msgid "Server requires username and password."
msgstr "Server benötigt ein Benutzername und ein Passwort."
@@ -282,20 +287,13 @@ msgstr "Server benötigt ein Benutzername und ein Passwort."
msgid "Connection Successful!"
msgstr "Verbindung erfolgreich hergestellt!"
#: sabnzbd/api.py, sabnzbd/interface.py
msgid "Authentication failed, check username/password."
msgstr ""
"Authentifizierung fehlgeschlagen. Überprüfen Sie Benutzername und Passwort."
#. Warning message
#: sabnzbd/api.py
msgid "Too many connections, please pause downloading or try again later"
msgid ""
"Server %s took %d ms to respond. On high-latency connections, increasing "
"'Articles per request' to a value between 5 and 10 can improve download "
"speed. See: https://sabnzbd.org/wiki/advanced/nntp-pipelining"
msgstr ""
"Zu viele Verbindungen. Bitte halten Sie die Downloads an oder versuchen Sie "
"es später erneut."
#: sabnzbd/api.py
msgid "Could not determine connection result (%s)"
msgstr "Die Verbindung konnte nicht überprüft werden. (%s)"
#: sabnzbd/api.py
msgid "Resolving address"
@@ -312,12 +310,12 @@ msgid "Default"
msgstr "Standard"
#. Error message
#: sabnzbd/assembler.py
#: sabnzbd/assembler.py, sabnzbd/decoder.py
msgid "Disk full! Forcing Pause"
msgstr "Festplatte voll! Downloads werden angehalten."
#. Error message
#: sabnzbd/assembler.py
#: sabnzbd/assembler.py, sabnzbd/decoder.py
msgid "Disk error on creating file %s"
msgstr "Festplattenfehler beim Anlegen der Datei %s"
@@ -582,8 +580,8 @@ msgid "Resuming"
msgstr "Fortgesetzt"
#. PP status - Priority pick list
#: sabnzbd/downloader.py, sabnzbd/macosmenu.py, sabnzbd/sabtray.py,
#: sabnzbd/sabtraylinux.py, sabnzbd/skintext.py
#: sabnzbd/downloader.py, sabnzbd/macosmenu.py, sabnzbd/sabtraylinux.py,
#: sabnzbd/sabtraywin.py, sabnzbd/skintext.py
msgid "Paused"
msgstr "Angehalten"
@@ -623,26 +621,24 @@ msgid "Fatal error in Downloader"
msgstr "Schwerer Fehler im Downloader"
#: sabnzbd/downloader.py
msgid "Too many connections to server %s [%s]"
msgstr "Zu viele Verbindungen zu Server %s [%s]"
#: sabnzbd/downloader.py
msgid ""
"Login from too many different IP addresses to server %s [%s] - "
"https://sabnzbd.org/multiple-adresses"
msgid "Server %s reported: %s"
msgstr ""
"Von zu vielen unteschiedlichen IP Adressen beim Server eingeloggt %s[%s] "
"https://sabnzbd.org/multiple-adresses"
#: sabnzbd/downloader.py
msgid "Failed login for server %s [%s]"
msgstr "Anmelden beim Server fehlgeschlagen. %s [%s]"
#. Error message
#: sabnzbd/downloader.py
msgid "Connecting %s@%s failed, message=%s"
msgstr "Fehler beim Verbinden mit %s@%s, Meldung = %s"
#. Warning message
#: sabnzbd/downloader.py
msgid ""
"Total number of configured connections (%d) is greater than the operating "
"system limit (512), downloading will crash if all connections become active"
msgstr ""
"Die Gesamtzahl der konfigurierten Verbindungen (%d) ist größer als das Limit"
" des Betriebssystems (512); das Herunterladen stürzt ab, wenn alle "
"Verbindungen aktiv werden"
#: sabnzbd/downloader.py, sabnzbd/skintext.py
msgid "Shutting down"
msgstr "Wird beendet …"
@@ -815,15 +811,27 @@ msgid ""
"files. For details and possible solutions, see: "
"https://sabnzbd.org/wiki/advanced/direct-write"
msgstr ""
#: sabnzbd/interface.py
msgid "Refused connection from:"
msgstr "Abgelehnte Verbindung von:"
"Der Direct-Write-Modus wurde deaktiviert, weil %s keine Sparse-Dateien "
"unterstützt. Einzelheiten und mögliche Lösungen finden Sie unter: "
"https://sabnzbd.org/wiki/advanced/direct-write"
#: sabnzbd/interface.py
msgid "Refused connection with hostname \"%s\" from:"
msgstr "Verbindung vom Host \"%s\" abgelehnt von:"
#: sabnzbd/interface.py
msgid ""
"API Key incorrect, Use the api key from Config->General in your 3rd party "
"program:"
msgstr ""
"API-Schlüssel ungültig. Bitte API-Schlüssel aus Einstellungen->Allgemein in "
"die externe Anwendung eingeben:"
#. Warning message
#: sabnzbd/interface.py, sabnzbd/security.py
msgid "Refused connection from:"
msgstr "Abgelehnte Verbindung von:"
#: sabnzbd/interface.py
msgid ""
"API Key missing, please enter the api key from Config->General into your 3rd"
@@ -833,21 +841,18 @@ msgstr ""
" externe Anwendung eingeben:"
#: sabnzbd/interface.py
msgid ""
"API Key incorrect, Use the api key from Config->General in your 3rd party "
"program:"
msgid "Too many failed login attempts, try again later."
msgstr ""
#. Warning message
#: sabnzbd/interface.py
msgid "Login attempt refused, too many failures from %s"
msgstr ""
"API-Schlüssel ungültig. Bitte API-Schlüssel aus Einstellungen->Allgemein in "
"die externe Anwendung eingeben:"
#: sabnzbd/interface.py
msgid "User logged in to the web interface"
msgstr "Benutzer im Web-Interface angemeldet"
#. Notification
#: sabnzbd/interface.py, sabnzbd/notifier.py
msgid "User logged in"
msgstr "Benutzer angemeldet"
msgid "Authentication failed, check username/password."
msgstr ""
"Authentifizierung fehlgeschlagen. Überprüfen Sie Benutzername und Passwort."
#. Warning message
#: sabnzbd/interface.py
@@ -910,27 +915,21 @@ msgstr ""
"Der Category-Ordner darf kein Unterordner des Temporärer Download-Ordners "
"sein."
#: sabnzbd/interface.py, sabnzbd/skintext.py
msgid "ERROR:"
msgstr "FEHLER:"
#: sabnzbd/interface.py
msgid "Back"
msgstr "Zurück"
#. Warning message
#: sabnzbd/internetspeed.py
msgid ""
"Internet Bandwidth of IPv4 significantly different from IPv6: %f MB/s versus"
" %f MB/s"
msgstr ""
"Internet Bandbreite von IPv4 weicht deutlich von IPv6 ab: %f MB/s gegenüber "
"%f MB/s"
#. Footer: indicator of warnings
#: sabnzbd/macosmenu.py, sabnzbd/skintext.py
msgid "Warnings"
msgstr "Warnungen"
#: sabnzbd/macosmenu.py, sabnzbd/sabtray.py, sabnzbd/sabtraylinux.py,
#: sabnzbd/macosmenu.py, sabnzbd/sabtraylinux.py, sabnzbd/sabtraywin.py,
#: sabnzbd/skintext.py
msgid "Idle"
msgstr "Leerlauf"
@@ -961,8 +960,8 @@ msgstr "Geschwindigkeit begrenzen"
#. Notification - Pause downloading - Four way switch for duplicates -
#. Config->Scheduling
#: sabnzbd/macosmenu.py, sabnzbd/notifier.py, sabnzbd/sabtray.py,
#: sabnzbd/sabtraylinux.py, sabnzbd/skintext.py
#: sabnzbd/macosmenu.py, sabnzbd/notifier.py, sabnzbd/sabtraylinux.py,
#: sabnzbd/sabtraywin.py, sabnzbd/skintext.py
msgid "Pause"
msgstr "Anhalten"
@@ -972,8 +971,8 @@ msgid "min"
msgstr "Minuten"
#. Notification - Resume downloading - Config->Scheduling
#: sabnzbd/macosmenu.py, sabnzbd/notifier.py, sabnzbd/sabtray.py,
#: sabnzbd/sabtraylinux.py, sabnzbd/skintext.py
#: sabnzbd/macosmenu.py, sabnzbd/notifier.py, sabnzbd/sabtraylinux.py,
#: sabnzbd/sabtraywin.py, sabnzbd/skintext.py
msgid "Resume"
msgstr "Fortsetzen"
@@ -982,7 +981,7 @@ msgstr "Fortsetzen"
msgid "Scan watched folder"
msgstr "Überwachter Ordner lesen"
#: sabnzbd/macosmenu.py, sabnzbd/sabtray.py, sabnzbd/sabtraylinux.py
#: sabnzbd/macosmenu.py, sabnzbd/sabtraylinux.py, sabnzbd/sabtraywin.py
msgid "Read all RSS feeds"
msgstr "Alle RSS-Feeds lesen"
@@ -994,17 +993,17 @@ msgstr "Fertige Downloads"
msgid "Incomplete Folder"
msgstr "Unfertige Download"
#: sabnzbd/macosmenu.py, sabnzbd/sabtray.py
#: sabnzbd/macosmenu.py, sabnzbd/sabtraywin.py
msgid "Troubleshoot"
msgstr "Fehler suchen"
#. Config->Scheduling
#: sabnzbd/macosmenu.py, sabnzbd/sabtray.py, sabnzbd/sabtraylinux.py,
#: sabnzbd/macosmenu.py, sabnzbd/sabtraylinux.py, sabnzbd/sabtraywin.py,
#: sabnzbd/skintext.py
msgid "Restart"
msgstr "Neu starten"
#: sabnzbd/macosmenu.py, sabnzbd/sabtray.py
#: sabnzbd/macosmenu.py, sabnzbd/sabtraywin.py
msgid "Restart without login"
msgstr "Neustart ohne Anmeldung"
@@ -1204,6 +1203,11 @@ msgstr "Versuche 7zip mit Passwort \"%s\""
msgid "see logfile"
msgstr "Beachten Sie die Protokolldatei"
#: sabnzbd/newsunpack.py
msgid "Unpacking skipped, TAR support requires Python 3.12 or later"
msgstr ""
"Entpacken übersprungen, TAR-Unterstützung erfordert Python 3.12 oder neuer"
#: sabnzbd/newsunpack.py
msgid "Quick Checking"
msgstr "Schnelle Überprüfung"
@@ -1378,6 +1382,11 @@ msgstr "Auftrag fehlgeschlagen"
msgid "Queue finished"
msgstr "Warteschlange abgearbeitet"
#. Notification
#: sabnzbd/notifier.py
msgid "User logged in"
msgstr "Benutzer angemeldet"
#. Notification
#: sabnzbd/notifier.py
msgid "Other Messages"
@@ -1389,7 +1398,7 @@ msgid "Open folder"
msgstr "Ordner öffnen"
#. Notification action
#: sabnzbd/notifier.py, sabnzbd/sabtray.py, sabnzbd/sabtraylinux.py
#: sabnzbd/notifier.py, sabnzbd/sabtraylinux.py, sabnzbd/sabtraywin.py
msgid "Open complete folder"
msgstr "Öffne Zielverzeichnis"
@@ -1532,7 +1541,8 @@ msgid "Downloaded in %s at an average of %sB/s"
msgstr ""
"Heruntergeladen in %s mit einer Durchschnittsgeschwindigkeit von %sB/s"
#. Job details page, file age column header
#. Config->RSS filter-type selection menu (e.g. ">3d", "<12h") - Job details
#. page, file age column header
#: sabnzbd/nzb/object.py, sabnzbd/skintext.py
msgid "Age"
msgstr "Alter"
@@ -1626,6 +1636,23 @@ msgstr ""
"Wenn Sie diesen Fehler wieder erhalten, probieren Sie eine andere "
"Nummer.<br>"
#: sabnzbd/panic.py
msgid ""
"\n"
" SABnzbd is not allowed to use port %s on %s.<br>\n"
" Low port numbers are reserved for privileged processes on most systems,\n"
" so the port itself is available but cannot be claimed.<br>\n"
" <br>\n"
" Either run SABnzbd with the privileges needed for that port, or restart it\n"
" with a port number of 1024 or higher."
msgstr ""
#: sabnzbd/panic.py
msgid ""
"To keep using the low port, put a reverse proxy in front of SABnzbd "
"instead.<br>"
msgstr ""
#: sabnzbd/panic.py
msgid ""
"\n"
@@ -1714,6 +1741,11 @@ msgstr ""
"Konnte nicht an Port %s auf %s starten. Eine andere Software nutzt diesen "
"Port oder SABnzbd läuft bereits."
#: sabnzbd/panic.py
msgid ""
"Not allowed to bind to port %s on %s. Low ports need elevated privileges."
msgstr ""
#. Warning message
#: sabnzbd/panic.py
msgid "Cannot launch the browser, probably not found"
@@ -1721,15 +1753,6 @@ msgstr ""
"Der Standard-Browser konnte nicht gestartet werden, da er wahrscheinlich "
"nicht gefunden wurde."
#: sabnzbd/panic.py
msgid "Access denied"
msgstr "Zugriff verweigert"
#: sabnzbd/panic.py
msgid "Error %s: You need to provide a valid username and password."
msgstr ""
"Fehler %s: Sie müssen einen gültigen Benutzername und ein Passwort angeben."
#. Warning message
#: sabnzbd/postproc.py
msgid "Old queue detected, use Status->Repair to convert the queue"
@@ -1862,6 +1885,10 @@ msgstr "Keine zugehörige frühere RAR-Datei für %s"
msgid "Removing %s failed"
msgstr "Entfernen von %s fehlgeschlagen"
#: sabnzbd/postproc.py
msgid "Removed %s files with unwanted extensions"
msgstr ""
#. Error message
#: sabnzbd/powersup.py
msgid "Failed to hibernate system"
@@ -1886,10 +1913,6 @@ msgstr "DBus-Ausnahmefehler empfangen %s "
msgid "Empty RSS entry found (%s)"
msgstr "Leerer RSS-Feed gefunden: %s"
#: sabnzbd/rss.py
msgid "Incompatible feed"
msgstr "Inkompatibeler RSS-Feed"
#. Error message
#: sabnzbd/rss.py
msgid "Incorrect RSS feed description \"%s\""
@@ -1911,50 +1934,15 @@ msgstr "Abrufen des RSS-Feeds von %s fehlgeschlagen: %s"
msgid "Server %s uses an untrusted HTTPS certificate"
msgstr "Der Server %s nutzt ein nicht vertrauenswürdiges HTTPS-Zertifikat"
#. Warning message
#: sabnzbd/rss.py
msgid "RSS Feed %s was empty"
msgstr "RSS-Feed %s war leer"
#: sabnzbd/sabtray.py, sabnzbd/sabtraylinux.py
msgid "Show interface"
msgstr "Interface anzeigen"
#: sabnzbd/rss.py
msgid "Incompatible feed"
msgstr "Inkompatibeler RSS-Feed"
#. Queue page button or entry box
#: sabnzbd/sabtray.py, sabnzbd/skintext.py
msgid "Pause for"
msgstr "Anhalten für"
#: sabnzbd/sabtray.py, sabnzbd/skintext.py
msgid "Pause for 5 minutes"
msgstr "5 Minuten anhalten"
#: sabnzbd/sabtray.py, sabnzbd/skintext.py
msgid "Pause for 15 minutes"
msgstr "15 Minuten anhalten"
#: sabnzbd/sabtray.py, sabnzbd/skintext.py
msgid "Pause for 30 minutes"
msgstr "30 Minuten anhalten"
#: sabnzbd/sabtray.py, sabnzbd/skintext.py
msgid "Pause for 1 hour"
msgstr "Eine Stunde anhalten"
#: sabnzbd/sabtray.py, sabnzbd/skintext.py
msgid "Pause for 3 hours"
msgstr "3 Stunden anhalten"
#: sabnzbd/sabtray.py, sabnzbd/skintext.py
msgid "Pause for 6 hours"
msgstr "6 Stunden anhalten"
#. Config->Scheduling
#: sabnzbd/sabtray.py, sabnzbd/sabtraylinux.py, sabnzbd/skintext.py
msgid "Shutdown"
msgstr "Beenden"
#: sabnzbd/sabtray.py, sabnzbd/sabtraylinux.py
#: sabnzbd/sabtraylinux.py, sabnzbd/sabtraywin.py
msgid "Remaining"
msgstr "Verbleibend"
@@ -1962,6 +1950,44 @@ msgstr "Verbleibend"
msgid "Add NZB"
msgstr "NZB hinzufügen"
#: sabnzbd/sabtraylinux.py, sabnzbd/sabtraywin.py
msgid "Show interface"
msgstr "Interface anzeigen"
#. Config->Scheduling
#: sabnzbd/sabtraylinux.py, sabnzbd/sabtraywin.py, sabnzbd/skintext.py
msgid "Shutdown"
msgstr "Beenden"
#. Queue page button or entry box
#: sabnzbd/sabtraywin.py, sabnzbd/skintext.py
msgid "Pause for"
msgstr "Anhalten für"
#: sabnzbd/sabtraywin.py, sabnzbd/skintext.py
msgid "Pause for 5 minutes"
msgstr "5 Minuten anhalten"
#: sabnzbd/sabtraywin.py, sabnzbd/skintext.py
msgid "Pause for 15 minutes"
msgstr "15 Minuten anhalten"
#: sabnzbd/sabtraywin.py, sabnzbd/skintext.py
msgid "Pause for 30 minutes"
msgstr "30 Minuten anhalten"
#: sabnzbd/sabtraywin.py, sabnzbd/skintext.py
msgid "Pause for 1 hour"
msgstr "Eine Stunde anhalten"
#: sabnzbd/sabtraywin.py, sabnzbd/skintext.py
msgid "Pause for 3 hours"
msgstr "3 Stunden anhalten"
#: sabnzbd/sabtraywin.py, sabnzbd/skintext.py
msgid "Pause for 6 hours"
msgstr "6 Stunden anhalten"
#. Warning message
#: sabnzbd/scheduler.py
msgid "Bad schedule %s at %s:%s"
@@ -2297,6 +2323,10 @@ msgstr "Adresse"
msgid "Cancel"
msgstr "Abbrechen"
#: sabnzbd/skintext.py
msgid "Close"
msgstr ""
#: sabnzbd/skintext.py
msgid "Log in"
msgstr "Anmelden"
@@ -2888,10 +2918,6 @@ msgstr "Port, auf dem SABnzbd auf Anfragen warten soll."
msgid "Web Interface Theme"
msgstr "Benutzeroberfläche"
#: sabnzbd/skintext.py
msgid "Choose a theme."
msgstr "Wählen Sie ein Theme."
#: sabnzbd/skintext.py
msgid "SABnzbd Username"
msgstr "SABnzbd-Benutzername"
@@ -3043,11 +3069,14 @@ msgstr "Unerwünschte Dateien"
#: sabnzbd/skintext.py
msgid ""
"List of file extensions that should be deleted after download.<br />For "
"example: <b>nfo</b> or <b>nfo, sfv</b>"
"List of file extensions, filenames or patterns that should be deleted after "
"download.<br />Examples: <b>nfo, sfv</b> (extensions) or <b>thumbs.db</b> "
"(filenames) or <b>*.tmp, images/*, */test.jpg</b> (patterns)."
msgstr ""
"Liste der Dateiendungen, die nach dem Download gelöscht werden sollen.<br "
"/>Zum Beispiel:<b>nfo</b> or <b>nfo,sfv</b>"
"Liste von Dateierweiterungen, Dateinamen oder Mustern, die nach dem Download"
" gelöscht werden sollen.<br />Beispiele: <b>nfo, sfv</b> (Erweiterungen) "
"oder <b>thumbs.db</b> (Dateinamen) oder <b>*.tmp, images/*, */test.jpg</b> "
"(Muster)."
#: sabnzbd/skintext.py
msgid "History Retention"
@@ -3444,6 +3473,10 @@ msgid ""
"all jobs will be marked as Completed and moved to the Complete folder even "
"if they are incomplete."
msgstr ""
"Nur Aufträge entpacken, welche die Überprüfung bestanden haben. Wenn "
"deaktiviert, werden alle Aufträge als Fertiggestellt markiert und in den "
"\"Abgeschlossene Downloads\"-Ordner verschoben, auch wenn sie unvollständig "
"sind."
#: sabnzbd/skintext.py
msgid "Action when encrypted RAR is downloaded"
@@ -3539,6 +3572,8 @@ msgstr "SFV-basierte Überprüfung aktivieren"
msgid ""
"If no par2 files are available, use sfv files (if present) to verify files"
msgstr ""
"Wenn keine par2-Dateien verfügbar sind, sfv-Dateien (falls vorhanden) zur "
"Überprüfung der Dateien verwenden"
#: sabnzbd/skintext.py
msgid "User script can flag job as failed"
@@ -3619,10 +3654,10 @@ msgstr ""
"Warteschlange automatisch sortieren wenn ein neuer Job hinzugefügt wird."
#: sabnzbd/skintext.py
msgid "The queue will resort every 30 seconds if % downloaded is selected."
msgid ""
"The queue will resort every 30 seconds if % downloaded or Remaining Size is "
"selected."
msgstr ""
"Die Warteschlange wird alle 30 Sekunden neu sortiert, wenn % "
"heruntergeladen, ausgewählt ist."
#: sabnzbd/skintext.py
msgid "Propagation delay"
@@ -3765,8 +3800,8 @@ msgid "Naming"
msgstr "Benennung"
#: sabnzbd/skintext.py
msgid "How much can be downloaded this month (K/M/G)"
msgstr "Wie viel kann in diesem Monat heruntergeladen werden (K/M/G)?"
msgid "How much can be downloaded this Quota period (K/M/G)"
msgstr ""
#. Reset day of the download quota
#: sabnzbd/skintext.py
@@ -3973,7 +4008,7 @@ msgstr "Aktivieren"
#: sabnzbd/skintext.py
msgid "Articles per request"
msgstr ""
msgstr "Artikel pro Anfrage"
#: sabnzbd/skintext.py
msgid ""
@@ -3981,6 +4016,9 @@ msgid ""
"first.<br />This can improve download speeds, especially on connections with"
" higher latency."
msgstr ""
"Mehrere Artikel pro Verbindung anfordern, ohne zuerst auf jede Antwort zu "
"warten.<br />Dies kann die Download-Geschwindigkeit verbessern, insbesondere"
" bei Verbindungen mit höherer Latenz."
#. Button: Remove server
#: sabnzbd/skintext.py
@@ -4069,10 +4107,6 @@ msgstr "Feed lesen"
msgid "Force Download"
msgstr "Download erzwingen"
#: sabnzbd/skintext.py
msgid "Apply filters"
msgstr "Filter übernehmen"
#. Config->RSS edit button
#: sabnzbd/skintext.py
msgid "Edit"
@@ -4765,6 +4799,14 @@ msgstr "Löschen"
msgid "Filename"
msgstr "Dateiname"
#: sabnzbd/skintext.py
msgid "Toggle SABnzbd navigation menu"
msgstr ""
#: sabnzbd/skintext.py
msgid "Open SABnzbd main menu"
msgstr ""
#: sabnzbd/skintext.py
msgid "Free Space"
msgstr "Freier Speicherplatz"
@@ -4773,6 +4815,17 @@ msgstr "Freier Speicherplatz"
msgid "Temp Folder"
msgstr "Temporärer Ordner"
#: sabnzbd/skintext.py
msgid "Clear search"
msgstr ""
#: sabnzbd/skintext.py
msgid ""
"Search by name, or refine with filters <code>cat:</code>, "
"<code>status:</code> and <code>priority:</code> (Queue). For example: "
"<code>my show cat:tv</code>"
msgstr ""
#: sabnzbd/skintext.py
msgid "Multi-Operations"
msgstr "Mehrfach-Funktionen"
@@ -4781,6 +4834,10 @@ msgstr "Mehrfach-Funktionen"
msgid "Hold shift key to select a range"
msgstr "Shift-Taste gedrückt halten, um einen ganzen Bereich auszuwählen"
#: sabnzbd/skintext.py
msgid "Select job"
msgstr ""
#: sabnzbd/skintext.py
msgid "Check all"
msgstr "Alle auswählen"
@@ -4813,6 +4870,10 @@ msgstr "Wenn SABnzbd neustartet, wird diese Anzeige automatisch verschwinden!"
msgid "WARNING:"
msgstr "WARNUNG:"
#: sabnzbd/skintext.py
msgid "ERROR:"
msgstr "FEHLER:"
#: sabnzbd/skintext.py
msgid "Fetch"
msgstr "Abrufen"
@@ -4849,6 +4910,14 @@ msgstr "Weitere Spalten bei der Warteschlange"
msgid "Extra history columns"
msgstr "Weitere Spalten bei der Verlaufsliste"
#: sabnzbd/skintext.py
msgid "Progress"
msgstr "Fortschritt"
#: sabnzbd/skintext.py
msgid "Actions"
msgstr "Aktionen"
#: sabnzbd/skintext.py
msgid "page"
msgstr "Seite"
@@ -5026,6 +5095,10 @@ msgstr ""
msgid "Sort by % downloaded <small>Most&rarr;Least</small>"
msgstr "Sortiere nach % heruntergeladen <small>Viel&rarr;Wenig</small>"
#: sabnzbd/skintext.py
msgid "Sort by Remaining Size <small>Smallest&rarr;Largest</small>"
msgstr ""
#: sabnzbd/skintext.py
msgid "Sort by Age <small>Oldest&rarr;Newest</small>"
msgstr "Sortieren nach Alter <small>Älteste&rarr;Neuste</small>"
+209 -132
View File
@@ -9,7 +9,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-5.0.0\n"
"Project-Id-Version: SABnzbd-5.2.0\n"
"PO-Revision-Date: 2020-06-27 15:49+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2026\n"
"Language-Team: Spanish (https://app.transifex.com/sabnzbd/teams/111101/es/)\n"
@@ -127,7 +127,6 @@ msgstr "SABnzbd %s comenzó"
msgid "SABnzbd shutdown finished"
msgstr "Cierre de SABnzbd terminado"
#. Warning message
#: sabnzbd/__init__.py
msgid "Signal %s caught, saving and exiting..."
msgstr "Señal %s capturado, guardando y saliendo..."
@@ -160,6 +159,8 @@ msgstr ""
#: sabnzbd/__init__.py
msgid "Windows ARM version of SABnzbd is available from our Downloads page!"
msgstr ""
"¡La versión Windows ARM de SABnzbd está disponible en nuestra página de "
"descargas!"
#. Warning message
#: sabnzbd/__init__.py
@@ -259,6 +260,10 @@ msgstr ""
"Protocolo SSL desconocido: intente desabilitar el SSL o conectarse a un "
"puerto diferente."
#: sabnzbd/api.py
msgid "Could not determine connection result (%s)"
msgstr "No se pudo determinar el resultado de la conexión (%s)"
#: sabnzbd/api.py
msgid "Server requires username and password."
msgstr "El servidor necesita usuario y contraseña."
@@ -267,18 +272,13 @@ msgstr "El servidor necesita usuario y contraseña."
msgid "Connection Successful!"
msgstr "¡Conexión exitosa!"
#: sabnzbd/api.py, sabnzbd/interface.py
msgid "Authentication failed, check username/password."
msgstr "Autenticación fallida, compruebe el usuario o la contraseña."
#. Warning message
#: sabnzbd/api.py
msgid "Too many connections, please pause downloading or try again later"
msgid ""
"Server %s took %d ms to respond. On high-latency connections, increasing "
"'Articles per request' to a value between 5 and 10 can improve download "
"speed. See: https://sabnzbd.org/wiki/advanced/nntp-pipelining"
msgstr ""
"Demasiadas conexiones; pause las descargas o inténtelo de nuevo más tarde"
#: sabnzbd/api.py
msgid "Could not determine connection result (%s)"
msgstr "No se pudo determinar el resultado de la conexión (%s)"
#: sabnzbd/api.py
msgid "Resolving address"
@@ -295,12 +295,12 @@ msgid "Default"
msgstr "Predeterminado"
#. Error message
#: sabnzbd/assembler.py
#: sabnzbd/assembler.py, sabnzbd/decoder.py
msgid "Disk full! Forcing Pause"
msgstr "Disco lleno! Pausando la cola"
#. Error message
#: sabnzbd/assembler.py
#: sabnzbd/assembler.py, sabnzbd/decoder.py
msgid "Disk error on creating file %s"
msgstr "Error de disco al crear el archivo %s"
@@ -567,8 +567,8 @@ msgid "Resuming"
msgstr "Reanudando"
#. PP status - Priority pick list
#: sabnzbd/downloader.py, sabnzbd/macosmenu.py, sabnzbd/sabtray.py,
#: sabnzbd/sabtraylinux.py, sabnzbd/skintext.py
#: sabnzbd/downloader.py, sabnzbd/macosmenu.py, sabnzbd/sabtraylinux.py,
#: sabnzbd/sabtraywin.py, sabnzbd/skintext.py
msgid "Paused"
msgstr "En pausa"
@@ -608,26 +608,24 @@ msgid "Fatal error in Downloader"
msgstr "Error grave en el descargador"
#: sabnzbd/downloader.py
msgid "Too many connections to server %s [%s]"
msgstr "Demasiadas conexiones con el servidor %s [%s]"
#: sabnzbd/downloader.py
msgid ""
"Login from too many different IP addresses to server %s [%s] - "
"https://sabnzbd.org/multiple-adresses"
msgid "Server %s reported: %s"
msgstr ""
"Inicio de sesión desde demasiadas direcciones IP diferentes en el servidor "
"%s [%s] - https://sabnzbd.org/multiple-adresses"
#: sabnzbd/downloader.py
msgid "Failed login for server %s [%s]"
msgstr "Registraccion fallo para servidor %s [%s]"
#. Error message
#: sabnzbd/downloader.py
msgid "Connecting %s@%s failed, message=%s"
msgstr "Ha fallado la conexión a %s@%s, el mensaje=%s"
#. Warning message
#: sabnzbd/downloader.py
msgid ""
"Total number of configured connections (%d) is greater than the operating "
"system limit (512), downloading will crash if all connections become active"
msgstr ""
"El número total de conexiones configuradas (%d) es mayor que el límite del "
"sistema operativo (512); la descarga se bloqueará si todas las conexiones se"
" activan"
#: sabnzbd/downloader.py, sabnzbd/skintext.py
msgid "Shutting down"
msgstr "Apagando"
@@ -799,15 +797,27 @@ msgid ""
"files. For details and possible solutions, see: "
"https://sabnzbd.org/wiki/advanced/direct-write"
msgstr ""
#: sabnzbd/interface.py
msgid "Refused connection from:"
msgstr "Conexión rechazada de:"
"El modo Direct Write se ha desactivado porque %s no admite archivos "
"dispersos. Para más detalles y posibles soluciones, consulta: "
"https://sabnzbd.org/wiki/advanced/direct-write"
#: sabnzbd/interface.py
msgid "Refused connection with hostname \"%s\" from:"
msgstr "Se deniega la conexión con el nombre de equipo \"%s\" desde:"
#: sabnzbd/interface.py
msgid ""
"API Key incorrect, Use the api key from Config->General in your 3rd party "
"program:"
msgstr ""
"Clave de API erróneo, favor ingresar la clave correcta desde Config->General"
" en tu aplicacion externa:"
#. Warning message
#: sabnzbd/interface.py, sabnzbd/security.py
msgid "Refused connection from:"
msgstr "Conexión rechazada de:"
#: sabnzbd/interface.py
msgid ""
"API Key missing, please enter the api key from Config->General into your 3rd"
@@ -817,21 +827,17 @@ msgstr ""
"aplicacion externa:"
#: sabnzbd/interface.py
msgid ""
"API Key incorrect, Use the api key from Config->General in your 3rd party "
"program:"
msgid "Too many failed login attempts, try again later."
msgstr ""
#. Warning message
#: sabnzbd/interface.py
msgid "Login attempt refused, too many failures from %s"
msgstr ""
"Clave de API erróneo, favor ingresar la clave correcta desde Config->General"
" en tu aplicacion externa:"
#: sabnzbd/interface.py
msgid "User logged in to the web interface"
msgstr "Usuario conectado a la interfaz web"
#. Notification
#: sabnzbd/interface.py, sabnzbd/notifier.py
msgid "User logged in"
msgstr "Usuario conectado"
msgid "Authentication failed, check username/password."
msgstr "Autenticación fallida, compruebe el usuario o la contraseña."
#. Warning message
#: sabnzbd/interface.py
@@ -894,27 +900,21 @@ msgstr ""
"El directorio Categoría no puede ser un subdirectorio del directorio de "
"descargas temporal."
#: sabnzbd/interface.py, sabnzbd/skintext.py
msgid "ERROR:"
msgstr "ERROR:"
#: sabnzbd/interface.py
msgid "Back"
msgstr "Atrás"
#. Warning message
#: sabnzbd/internetspeed.py
msgid ""
"Internet Bandwidth of IPv4 significantly different from IPv6: %f MB/s versus"
" %f MB/s"
msgstr ""
"El ancho de banda de internet de IPv4 difiere significativamente del de "
"IPv6: %f MB/s frente a %f MB/s"
#. Footer: indicator of warnings
#: sabnzbd/macosmenu.py, sabnzbd/skintext.py
msgid "Warnings"
msgstr "Advertencias"
#: sabnzbd/macosmenu.py, sabnzbd/sabtray.py, sabnzbd/sabtraylinux.py,
#: sabnzbd/macosmenu.py, sabnzbd/sabtraylinux.py, sabnzbd/sabtraywin.py,
#: sabnzbd/skintext.py
msgid "Idle"
msgstr "Inactivo"
@@ -945,8 +945,8 @@ msgstr "Limitar Velocidad"
#. Notification - Pause downloading - Four way switch for duplicates -
#. Config->Scheduling
#: sabnzbd/macosmenu.py, sabnzbd/notifier.py, sabnzbd/sabtray.py,
#: sabnzbd/sabtraylinux.py, sabnzbd/skintext.py
#: sabnzbd/macosmenu.py, sabnzbd/notifier.py, sabnzbd/sabtraylinux.py,
#: sabnzbd/sabtraywin.py, sabnzbd/skintext.py
msgid "Pause"
msgstr "Pausar"
@@ -956,8 +956,8 @@ msgid "min"
msgstr "mín"
#. Notification - Resume downloading - Config->Scheduling
#: sabnzbd/macosmenu.py, sabnzbd/notifier.py, sabnzbd/sabtray.py,
#: sabnzbd/sabtraylinux.py, sabnzbd/skintext.py
#: sabnzbd/macosmenu.py, sabnzbd/notifier.py, sabnzbd/sabtraylinux.py,
#: sabnzbd/sabtraywin.py, sabnzbd/skintext.py
msgid "Resume"
msgstr "Reanudar"
@@ -966,7 +966,7 @@ msgstr "Reanudar"
msgid "Scan watched folder"
msgstr "Escanear directorio bajo observación"
#: sabnzbd/macosmenu.py, sabnzbd/sabtray.py, sabnzbd/sabtraylinux.py
#: sabnzbd/macosmenu.py, sabnzbd/sabtraylinux.py, sabnzbd/sabtraywin.py
msgid "Read all RSS feeds"
msgstr "Leer todos los canales RSS"
@@ -978,17 +978,17 @@ msgstr "Carpeta Completa"
msgid "Incomplete Folder"
msgstr "Carpeta Incompleta"
#: sabnzbd/macosmenu.py, sabnzbd/sabtray.py
#: sabnzbd/macosmenu.py, sabnzbd/sabtraywin.py
msgid "Troubleshoot"
msgstr "Resolver un problema"
#. Config->Scheduling
#: sabnzbd/macosmenu.py, sabnzbd/sabtray.py, sabnzbd/sabtraylinux.py,
#: sabnzbd/macosmenu.py, sabnzbd/sabtraylinux.py, sabnzbd/sabtraywin.py,
#: sabnzbd/skintext.py
msgid "Restart"
msgstr "Reiniciar"
#: sabnzbd/macosmenu.py, sabnzbd/sabtray.py
#: sabnzbd/macosmenu.py, sabnzbd/sabtraywin.py
msgid "Restart without login"
msgstr "Reiniciar sin sesión iniciada"
@@ -1192,6 +1192,12 @@ msgstr "Tratando 7zip con la contraseña \"%s\""
msgid "see logfile"
msgstr "ver fichero de log"
#: sabnzbd/newsunpack.py
msgid "Unpacking skipped, TAR support requires Python 3.12 or later"
msgstr ""
"Descompresión omitida, la compatibilidad con TAR requiere Python 3.12 o "
"posterior"
#: sabnzbd/newsunpack.py
msgid "Quick Checking"
msgstr "Chequeo Rápido"
@@ -1368,6 +1374,11 @@ msgstr "Trabajo fallido"
msgid "Queue finished"
msgstr "Cola terminada"
#. Notification
#: sabnzbd/notifier.py
msgid "User logged in"
msgstr "Usuario conectado"
#. Notification
#: sabnzbd/notifier.py
msgid "Other Messages"
@@ -1379,7 +1390,7 @@ msgid "Open folder"
msgstr "Abrir carpeta"
#. Notification action
#: sabnzbd/notifier.py, sabnzbd/sabtray.py, sabnzbd/sabtraylinux.py
#: sabnzbd/notifier.py, sabnzbd/sabtraylinux.py, sabnzbd/sabtraywin.py
msgid "Open complete folder"
msgstr "Abrir todo el folder"
@@ -1522,7 +1533,8 @@ msgstr "PROPAGANDO %s min"
msgid "Downloaded in %s at an average of %sB/s"
msgstr "Descargado en %s a una media de %sB/s"
#. Job details page, file age column header
#. Config->RSS filter-type selection menu (e.g. ">3d", "<12h") - Job details
#. page, file age column header
#: sabnzbd/nzb/object.py, sabnzbd/skintext.py
msgid "Age"
msgstr "Edad"
@@ -1617,6 +1629,23 @@ msgstr ""
"Si obtiene este mensaje de error consecutivamente, por favor inténtelo de "
"nuevo con otro número.<br>"
#: sabnzbd/panic.py
msgid ""
"\n"
" SABnzbd is not allowed to use port %s on %s.<br>\n"
" Low port numbers are reserved for privileged processes on most systems,\n"
" so the port itself is available but cannot be claimed.<br>\n"
" <br>\n"
" Either run SABnzbd with the privileges needed for that port, or restart it\n"
" with a port number of 1024 or higher."
msgstr ""
#: sabnzbd/panic.py
msgid ""
"To keep using the low port, put a reverse proxy in front of SABnzbd "
"instead.<br>"
msgstr ""
#: sabnzbd/panic.py
msgid ""
"\n"
@@ -1705,20 +1734,17 @@ msgstr ""
"No es posible vincular al puerto %s en %s. Otro software está utilizando el "
"puerto o SABnzbd ya se está ejecutando."
#: sabnzbd/panic.py
msgid ""
"Not allowed to bind to port %s on %s. Low ports need elevated privileges."
msgstr ""
#. Warning message
#: sabnzbd/panic.py
msgid "Cannot launch the browser, probably not found"
msgstr ""
"Imposible iniciar el navegador, probablemente no se le haya encontrado"
#: sabnzbd/panic.py
msgid "Access denied"
msgstr "Acceso denegado"
#: sabnzbd/panic.py
msgid "Error %s: You need to provide a valid username and password."
msgstr "Error %s: Necesitas introducir un usuario y contraseña válidos."
#. Warning message
#: sabnzbd/postproc.py
msgid "Old queue detected, use Status->Repair to convert the queue"
@@ -1849,6 +1875,10 @@ msgstr "No hay un archivo rar anterior correspondiente para %s"
msgid "Removing %s failed"
msgstr "Error al eliminar %s"
#: sabnzbd/postproc.py
msgid "Removed %s files with unwanted extensions"
msgstr ""
#. Error message
#: sabnzbd/powersup.py
msgid "Failed to hibernate system"
@@ -1873,10 +1903,6 @@ msgstr "Se ha recibido una excepción DBus %s"
msgid "Empty RSS entry found (%s)"
msgstr "Entrada RSS vacía (%s)"
#: sabnzbd/rss.py
msgid "Incompatible feed"
msgstr "Canal Incorrecto"
#. Error message
#: sabnzbd/rss.py
msgid "Incorrect RSS feed description \"%s\""
@@ -1900,50 +1926,15 @@ msgstr "Error al recuperar RSS desde %s: %s"
msgid "Server %s uses an untrusted HTTPS certificate"
msgstr "El servidor %s utiliza un certificado HTTPS no fiable"
#. Warning message
#: sabnzbd/rss.py
msgid "RSS Feed %s was empty"
msgstr "El canal RSS %s estaba vacío"
#: sabnzbd/sabtray.py, sabnzbd/sabtraylinux.py
msgid "Show interface"
msgstr "Mostrar interfaz"
#: sabnzbd/rss.py
msgid "Incompatible feed"
msgstr "Canal Incorrecto"
#. Queue page button or entry box
#: sabnzbd/sabtray.py, sabnzbd/skintext.py
msgid "Pause for"
msgstr "Pausar durante"
#: sabnzbd/sabtray.py, sabnzbd/skintext.py
msgid "Pause for 5 minutes"
msgstr "Pausar 5 minutos"
#: sabnzbd/sabtray.py, sabnzbd/skintext.py
msgid "Pause for 15 minutes"
msgstr "Pausar 15 minutos"
#: sabnzbd/sabtray.py, sabnzbd/skintext.py
msgid "Pause for 30 minutes"
msgstr "Pausar 30 minutos"
#: sabnzbd/sabtray.py, sabnzbd/skintext.py
msgid "Pause for 1 hour"
msgstr "Pausar 1 hora"
#: sabnzbd/sabtray.py, sabnzbd/skintext.py
msgid "Pause for 3 hours"
msgstr "Pausar 3 horas"
#: sabnzbd/sabtray.py, sabnzbd/skintext.py
msgid "Pause for 6 hours"
msgstr "Pausar 6 horas"
#. Config->Scheduling
#: sabnzbd/sabtray.py, sabnzbd/sabtraylinux.py, sabnzbd/skintext.py
msgid "Shutdown"
msgstr "Apagar"
#: sabnzbd/sabtray.py, sabnzbd/sabtraylinux.py
#: sabnzbd/sabtraylinux.py, sabnzbd/sabtraywin.py
msgid "Remaining"
msgstr "Restante"
@@ -1951,6 +1942,44 @@ msgstr "Restante"
msgid "Add NZB"
msgstr "Añadir NZB"
#: sabnzbd/sabtraylinux.py, sabnzbd/sabtraywin.py
msgid "Show interface"
msgstr "Mostrar interfaz"
#. Config->Scheduling
#: sabnzbd/sabtraylinux.py, sabnzbd/sabtraywin.py, sabnzbd/skintext.py
msgid "Shutdown"
msgstr "Apagar"
#. Queue page button or entry box
#: sabnzbd/sabtraywin.py, sabnzbd/skintext.py
msgid "Pause for"
msgstr "Pausar durante"
#: sabnzbd/sabtraywin.py, sabnzbd/skintext.py
msgid "Pause for 5 minutes"
msgstr "Pausar 5 minutos"
#: sabnzbd/sabtraywin.py, sabnzbd/skintext.py
msgid "Pause for 15 minutes"
msgstr "Pausar 15 minutos"
#: sabnzbd/sabtraywin.py, sabnzbd/skintext.py
msgid "Pause for 30 minutes"
msgstr "Pausar 30 minutos"
#: sabnzbd/sabtraywin.py, sabnzbd/skintext.py
msgid "Pause for 1 hour"
msgstr "Pausar 1 hora"
#: sabnzbd/sabtraywin.py, sabnzbd/skintext.py
msgid "Pause for 3 hours"
msgstr "Pausar 3 horas"
#: sabnzbd/sabtraywin.py, sabnzbd/skintext.py
msgid "Pause for 6 hours"
msgstr "Pausar 6 horas"
#. Warning message
#: sabnzbd/scheduler.py
msgid "Bad schedule %s at %s:%s"
@@ -2286,6 +2315,10 @@ msgstr "Equipo"
msgid "Cancel"
msgstr "Cancelar"
#: sabnzbd/skintext.py
msgid "Close"
msgstr ""
#: sabnzbd/skintext.py
msgid "Log in"
msgstr "Iniciar sesión"
@@ -2872,10 +2905,6 @@ msgstr "Puerto en que SABnzbd debería escuchar"
msgid "Web Interface Theme"
msgstr "Tema de la interfaz web"
#: sabnzbd/skintext.py
msgid "Choose a theme."
msgstr "Elige un tema."
#: sabnzbd/skintext.py
msgid "SABnzbd Username"
msgstr "Usuario SABnzbd"
@@ -3026,11 +3055,14 @@ msgstr "Lista de elementos a limpiar"
#: sabnzbd/skintext.py
msgid ""
"List of file extensions that should be deleted after download.<br />For "
"example: <b>nfo</b> or <b>nfo, sfv</b>"
"List of file extensions, filenames or patterns that should be deleted after "
"download.<br />Examples: <b>nfo, sfv</b> (extensions) or <b>thumbs.db</b> "
"(filenames) or <b>*.tmp, images/*, */test.jpg</b> (patterns)."
msgstr ""
"Lista de extensiones de archivo que se deben eliminar después de la descarga"
" <br /> Por ejemplo : . <b>nfo</ b> o <b>nfo, sfv < /b>"
"Lista de extensiones de archivo, nombres de archivo o patrones que se deben "
"eliminar tras la descarga.<br />Ejemplos: <b>nfo, sfv</b> (extensiones) o "
"<b>thumbs.db</b> (nombres de archivo) o <b>*.tmp, images/*, */test.jpg</b> "
"(patrones)."
#: sabnzbd/skintext.py
msgid "History Retention"
@@ -3420,6 +3452,9 @@ msgid ""
"all jobs will be marked as Completed and moved to the Complete folder even "
"if they are incomplete."
msgstr ""
"Intentar descomprimir solo los trabajos que pasaron la etapa de "
"verificación. Si se desactiva, todos los trabajos se marcarán como "
"Completado y se moverán a la carpeta Completa aunque estén incompletos."
#: sabnzbd/skintext.py
msgid "Action when encrypted RAR is downloaded"
@@ -3515,6 +3550,8 @@ msgstr "Habilitar verificacion basada en SFV"
msgid ""
"If no par2 files are available, use sfv files (if present) to verify files"
msgstr ""
"Si no hay archivos par2 disponibles, usar archivos sfv (si existen) para "
"verificar los archivos"
#: sabnzbd/skintext.py
msgid "User script can flag job as failed"
@@ -3592,8 +3629,10 @@ msgstr ""
"nueva."
#: sabnzbd/skintext.py
msgid "The queue will resort every 30 seconds if % downloaded is selected."
msgstr "La cola se reordenará cada 30 segundos si se selecciona % descargado."
msgid ""
"The queue will resort every 30 seconds if % downloaded or Remaining Size is "
"selected."
msgstr ""
#: sabnzbd/skintext.py
msgid "Propagation delay"
@@ -3737,8 +3776,8 @@ msgid "Naming"
msgstr "Nombrado"
#: sabnzbd/skintext.py
msgid "How much can be downloaded this month (K/M/G)"
msgstr "Cantidad de descarga permitida este mes (K/M/G)"
msgid "How much can be downloaded this Quota period (K/M/G)"
msgstr ""
#. Reset day of the download quota
#: sabnzbd/skintext.py
@@ -3943,7 +3982,7 @@ msgstr "Habilitar"
#: sabnzbd/skintext.py
msgid "Articles per request"
msgstr ""
msgstr "Artículos por solicitud"
#: sabnzbd/skintext.py
msgid ""
@@ -3951,6 +3990,9 @@ msgid ""
"first.<br />This can improve download speeds, especially on connections with"
" higher latency."
msgstr ""
"Solicitar varios artículos por conexión sin esperar primero cada "
"respuesta.<br />Esto puede mejorar la velocidad de descarga, especialmente "
"en conexiones con mayor latencia."
#. Button: Remove server
#: sabnzbd/skintext.py
@@ -4039,10 +4081,6 @@ msgstr "Leer Fuente"
msgid "Force Download"
msgstr "Forzar Descarga"
#: sabnzbd/skintext.py
msgid "Apply filters"
msgstr "Aplicar filtros"
#. Config->RSS edit button
#: sabnzbd/skintext.py
msgid "Edit"
@@ -4734,6 +4772,14 @@ msgstr "Eliminar"
msgid "Filename"
msgstr "Nombre de archivo"
#: sabnzbd/skintext.py
msgid "Toggle SABnzbd navigation menu"
msgstr ""
#: sabnzbd/skintext.py
msgid "Open SABnzbd main menu"
msgstr ""
#: sabnzbd/skintext.py
msgid "Free Space"
msgstr "Espacio libre"
@@ -4742,6 +4788,17 @@ msgstr "Espacio libre"
msgid "Temp Folder"
msgstr "Carpeta temporal"
#: sabnzbd/skintext.py
msgid "Clear search"
msgstr ""
#: sabnzbd/skintext.py
msgid ""
"Search by name, or refine with filters <code>cat:</code>, "
"<code>status:</code> and <code>priority:</code> (Queue). For example: "
"<code>my show cat:tv</code>"
msgstr ""
#: sabnzbd/skintext.py
msgid "Multi-Operations"
msgstr "Op.Múltiples"
@@ -4750,6 +4807,10 @@ msgstr "Op.Múltiples"
msgid "Hold shift key to select a range"
msgstr "Mantén presionada la tecla shift para seleccionar un rango"
#: sabnzbd/skintext.py
msgid "Select job"
msgstr ""
#: sabnzbd/skintext.py
msgid "Check all"
msgstr "Marcar todo"
@@ -4784,6 +4845,10 @@ msgstr ""
msgid "WARNING:"
msgstr "AVISO:"
#: sabnzbd/skintext.py
msgid "ERROR:"
msgstr "ERROR:"
#: sabnzbd/skintext.py
msgid "Fetch"
msgstr "Obtener"
@@ -4820,6 +4885,14 @@ msgstr "Columnas adicionales de la cola"
msgid "Extra history columns"
msgstr "Columnas adicionales del historial"
#: sabnzbd/skintext.py
msgid "Progress"
msgstr "Progreso"
#: sabnzbd/skintext.py
msgid "Actions"
msgstr "Acciones"
#: sabnzbd/skintext.py
msgid "page"
msgstr "página"
@@ -4997,6 +5070,10 @@ msgstr ""
msgid "Sort by % downloaded <small>Most&rarr;Least</small>"
msgstr "Ordenar por % descargado <small>Mayor&rarr;Menor</small>"
#: sabnzbd/skintext.py
msgid "Sort by Remaining Size <small>Smallest&rarr;Largest</small>"
msgstr ""
#: sabnzbd/skintext.py
msgid "Sort by Age <small>Oldest&rarr;Newest</small>"
msgstr "Ordenar por Fecha <small>Más viejo&rarr;Más nuevo</small>"
+182 -126
View File
@@ -6,7 +6,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-5.0.0\n"
"Project-Id-Version: SABnzbd-5.2.0\n"
"PO-Revision-Date: 2020-06-27 15:49+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2026\n"
"Language-Team: Finnish (https://app.transifex.com/sabnzbd/teams/111101/fi/)\n"
@@ -119,7 +119,6 @@ msgstr "SABnzbd %s käynnistetty"
msgid "SABnzbd shutdown finished"
msgstr "SABnzbd sammutus valmis"
#. Warning message
#: sabnzbd/__init__.py
msgid "Signal %s caught, saving and exiting..."
msgstr "Signaali %s kaapattu, tallennetaan ja lopetetaan..."
@@ -239,6 +238,10 @@ msgid ""
msgstr ""
"Tuntematon SSL protokolla: Kokeile ottaa SSL käytöstä tai vaihda porttia."
#: sabnzbd/api.py
msgid "Could not determine connection result (%s)"
msgstr "Yhteystestin lopputulosta ei voitu määrittää (%s)"
#: sabnzbd/api.py
msgid "Server requires username and password."
msgstr "Palvelin vaatii käyttäjänimen ja salasanan."
@@ -247,17 +250,13 @@ msgstr "Palvelin vaatii käyttäjänimen ja salasanan."
msgid "Connection Successful!"
msgstr "Yhdistäminen onnistui!"
#: sabnzbd/api.py, sabnzbd/interface.py
msgid "Authentication failed, check username/password."
msgstr "Varmennus epäonnistui, tarkista käyttäjänimi/salasana."
#. Warning message
#: sabnzbd/api.py
msgid "Too many connections, please pause downloading or try again later"
msgstr "Liikaa yhteyksiä, keskeytä lataaminen tai yritä myöhemmin uudelleen"
#: sabnzbd/api.py
msgid "Could not determine connection result (%s)"
msgstr "Yhteystestin lopputulosta ei voitu määrittää (%s)"
msgid ""
"Server %s took %d ms to respond. On high-latency connections, increasing "
"'Articles per request' to a value between 5 and 10 can improve download "
"speed. See: https://sabnzbd.org/wiki/advanced/nntp-pipelining"
msgstr ""
#: sabnzbd/api.py
msgid "Resolving address"
@@ -274,12 +273,12 @@ msgid "Default"
msgstr "Oletus"
#. Error message
#: sabnzbd/assembler.py
#: sabnzbd/assembler.py, sabnzbd/decoder.py
msgid "Disk full! Forcing Pause"
msgstr "Levy täynnä! Pakotetaan keskeytys"
#. Error message
#: sabnzbd/assembler.py
#: sabnzbd/assembler.py, sabnzbd/decoder.py
msgid "Disk error on creating file %s"
msgstr "Levyvirhe luotaessa tiedostoa %s"
@@ -522,8 +521,8 @@ msgid "Resuming"
msgstr "Jatketaan"
#. PP status - Priority pick list
#: sabnzbd/downloader.py, sabnzbd/macosmenu.py, sabnzbd/sabtray.py,
#: sabnzbd/sabtraylinux.py, sabnzbd/skintext.py
#: sabnzbd/downloader.py, sabnzbd/macosmenu.py, sabnzbd/sabtraylinux.py,
#: sabnzbd/sabtraywin.py, sabnzbd/skintext.py
msgid "Paused"
msgstr "Keskeytetty"
@@ -562,24 +561,21 @@ msgid "Fatal error in Downloader"
msgstr ""
#: sabnzbd/downloader.py
msgid "Too many connections to server %s [%s]"
msgstr "Liikaa yhteyksiä palvelimelle %s [%s]"
#: sabnzbd/downloader.py
msgid ""
"Login from too many different IP addresses to server %s [%s] - "
"https://sabnzbd.org/multiple-adresses"
msgid "Server %s reported: %s"
msgstr ""
#: sabnzbd/downloader.py
msgid "Failed login for server %s [%s]"
msgstr "Kirjautuminen palvelimelle %s epäonnistui [%s]"
#. Error message
#: sabnzbd/downloader.py
msgid "Connecting %s@%s failed, message=%s"
msgstr "Yhdistäminen %s@%s epäonnistui, viesti=%s"
#. Warning message
#: sabnzbd/downloader.py
msgid ""
"Total number of configured connections (%d) is greater than the operating "
"system limit (512), downloading will crash if all connections become active"
msgstr ""
#: sabnzbd/downloader.py, sabnzbd/skintext.py
msgid "Shutting down"
msgstr "Sammutetaan"
@@ -746,11 +742,20 @@ msgid ""
msgstr ""
#: sabnzbd/interface.py
msgid "Refused connection from:"
msgid "Refused connection with hostname \"%s\" from:"
msgstr ""
#: sabnzbd/interface.py
msgid "Refused connection with hostname \"%s\" from:"
msgid ""
"API Key incorrect, Use the api key from Config->General in your 3rd party "
"program:"
msgstr ""
"API avain virheellinen, käytä Asetukset->Yleiset löytyvää api avainta "
"käyttämääsi kolmannen osapuolen ohjelmaan:"
#. Warning message
#: sabnzbd/interface.py, sabnzbd/security.py
msgid "Refused connection from:"
msgstr ""
#: sabnzbd/interface.py
@@ -762,21 +767,17 @@ msgstr ""
"käyttämääsi kolmannen osapuolen ohjelmaan:"
#: sabnzbd/interface.py
msgid ""
"API Key incorrect, Use the api key from Config->General in your 3rd party "
"program:"
msgid "Too many failed login attempts, try again later."
msgstr ""
#. Warning message
#: sabnzbd/interface.py
msgid "Login attempt refused, too many failures from %s"
msgstr ""
"API avain virheellinen, käytä Asetukset->Yleiset löytyvää api avainta "
"käyttämääsi kolmannen osapuolen ohjelmaan:"
#: sabnzbd/interface.py
msgid "User logged in to the web interface"
msgstr "Käyttäjä kirjautui sisään web-käyttöliittymään"
#. Notification
#: sabnzbd/interface.py, sabnzbd/notifier.py
msgid "User logged in"
msgstr "Käyttäjä kirjautui sisään"
msgid "Authentication failed, check username/password."
msgstr "Varmennus epäonnistui, tarkista käyttäjänimi/salasana."
#. Warning message
#: sabnzbd/interface.py
@@ -837,14 +838,6 @@ msgid ""
"Category folder cannot be a subfolder of the Temporary Download Folder."
msgstr ""
#: sabnzbd/interface.py, sabnzbd/skintext.py
msgid "ERROR:"
msgstr "VIRHE:"
#: sabnzbd/interface.py
msgid "Back"
msgstr "Takaisin"
#. Warning message
#: sabnzbd/internetspeed.py
msgid ""
@@ -857,7 +850,7 @@ msgstr ""
msgid "Warnings"
msgstr "Varoitukset"
#: sabnzbd/macosmenu.py, sabnzbd/sabtray.py, sabnzbd/sabtraylinux.py,
#: sabnzbd/macosmenu.py, sabnzbd/sabtraylinux.py, sabnzbd/sabtraywin.py,
#: sabnzbd/skintext.py
msgid "Idle"
msgstr "Toimeton"
@@ -888,8 +881,8 @@ msgstr "Nopeusrajoitus"
#. Notification - Pause downloading - Four way switch for duplicates -
#. Config->Scheduling
#: sabnzbd/macosmenu.py, sabnzbd/notifier.py, sabnzbd/sabtray.py,
#: sabnzbd/sabtraylinux.py, sabnzbd/skintext.py
#: sabnzbd/macosmenu.py, sabnzbd/notifier.py, sabnzbd/sabtraylinux.py,
#: sabnzbd/sabtraywin.py, sabnzbd/skintext.py
msgid "Pause"
msgstr "Keskeytä"
@@ -899,8 +892,8 @@ msgid "min"
msgstr "minuutti"
#. Notification - Resume downloading - Config->Scheduling
#: sabnzbd/macosmenu.py, sabnzbd/notifier.py, sabnzbd/sabtray.py,
#: sabnzbd/sabtraylinux.py, sabnzbd/skintext.py
#: sabnzbd/macosmenu.py, sabnzbd/notifier.py, sabnzbd/sabtraylinux.py,
#: sabnzbd/sabtraywin.py, sabnzbd/skintext.py
msgid "Resume"
msgstr "Jatka"
@@ -909,7 +902,7 @@ msgstr "Jatka"
msgid "Scan watched folder"
msgstr "Tarkista vahdittu kansio"
#: sabnzbd/macosmenu.py, sabnzbd/sabtray.py, sabnzbd/sabtraylinux.py
#: sabnzbd/macosmenu.py, sabnzbd/sabtraylinux.py, sabnzbd/sabtraywin.py
msgid "Read all RSS feeds"
msgstr "Lue kaikki RSS syötteet"
@@ -921,17 +914,17 @@ msgstr "Valmistuneet-kansio"
msgid "Incomplete Folder"
msgstr "Lataukset-kansio"
#: sabnzbd/macosmenu.py, sabnzbd/sabtray.py
#: sabnzbd/macosmenu.py, sabnzbd/sabtraywin.py
msgid "Troubleshoot"
msgstr "Vianmääritys"
#. Config->Scheduling
#: sabnzbd/macosmenu.py, sabnzbd/sabtray.py, sabnzbd/sabtraylinux.py,
#: sabnzbd/macosmenu.py, sabnzbd/sabtraylinux.py, sabnzbd/sabtraywin.py,
#: sabnzbd/skintext.py
msgid "Restart"
msgstr "Käynnistä uudelleen"
#: sabnzbd/macosmenu.py, sabnzbd/sabtray.py
#: sabnzbd/macosmenu.py, sabnzbd/sabtraywin.py
msgid "Restart without login"
msgstr "Käynnistä uudelleen ilman kirjautumista"
@@ -1124,6 +1117,10 @@ msgstr "Yritetään purkaa 7zip arkistoa salasanalla \"%s\""
msgid "see logfile"
msgstr "katso lokitiedosto"
#: sabnzbd/newsunpack.py
msgid "Unpacking skipped, TAR support requires Python 3.12 or later"
msgstr ""
#: sabnzbd/newsunpack.py
msgid "Quick Checking"
msgstr "Pikatarkistetaan"
@@ -1287,6 +1284,11 @@ msgstr "Lataus epäonnistui"
msgid "Queue finished"
msgstr "Jono valmistunut"
#. Notification
#: sabnzbd/notifier.py
msgid "User logged in"
msgstr "Käyttäjä kirjautui sisään"
#. Notification
#: sabnzbd/notifier.py
msgid "Other Messages"
@@ -1298,7 +1300,7 @@ msgid "Open folder"
msgstr ""
#. Notification action
#: sabnzbd/notifier.py, sabnzbd/sabtray.py, sabnzbd/sabtraylinux.py
#: sabnzbd/notifier.py, sabnzbd/sabtraylinux.py, sabnzbd/sabtraywin.py
msgid "Open complete folder"
msgstr "Avaa valmistuneet-kansio"
@@ -1437,7 +1439,8 @@ msgstr "LEVITETÄÄN %s min"
msgid "Downloaded in %s at an average of %sB/s"
msgstr "Ladattiin ajassa %s keskilatausnopeudella %sB/s"
#. Job details page, file age column header
#. Config->RSS filter-type selection menu (e.g. ">3d", "<12h") - Job details
#. page, file age column header
#: sabnzbd/nzb/object.py, sabnzbd/skintext.py
msgid "Age"
msgstr "Ikä"
@@ -1529,6 +1532,23 @@ msgid ""
"If you get this error message again, please try a different number.<br>"
msgstr "Jos saat tämän virhesanoman uudestaan, kokeile toista numeroa.<br>"
#: sabnzbd/panic.py
msgid ""
"\n"
" SABnzbd is not allowed to use port %s on %s.<br>\n"
" Low port numbers are reserved for privileged processes on most systems,\n"
" so the port itself is available but cannot be claimed.<br>\n"
" <br>\n"
" Either run SABnzbd with the privileges needed for that port, or restart it\n"
" with a port number of 1024 or higher."
msgstr ""
#: sabnzbd/panic.py
msgid ""
"To keep using the low port, put a reverse proxy in front of SABnzbd "
"instead.<br>"
msgstr ""
#: sabnzbd/panic.py
msgid ""
"\n"
@@ -1615,19 +1635,16 @@ msgid ""
"SABnzbd is already running."
msgstr ""
#: sabnzbd/panic.py
msgid ""
"Not allowed to bind to port %s on %s. Low ports need elevated privileges."
msgstr ""
#. Warning message
#: sabnzbd/panic.py
msgid "Cannot launch the browser, probably not found"
msgstr "Selainta ei voida käynnistää, todennäköisesti ei löydy"
#: sabnzbd/panic.py
msgid "Access denied"
msgstr "Ei käyttöoikeutta"
#: sabnzbd/panic.py
msgid "Error %s: You need to provide a valid username and password."
msgstr "Virhe %s: Syötä kelvollinen käyttäjänimi ja salasana."
#. Warning message
#: sabnzbd/postproc.py
msgid "Old queue detected, use Status->Repair to convert the queue"
@@ -1756,6 +1773,10 @@ msgstr ""
msgid "Removing %s failed"
msgstr "%s poistaminen epäonnistui"
#: sabnzbd/postproc.py
msgid "Removed %s files with unwanted extensions"
msgstr ""
#. Error message
#: sabnzbd/powersup.py
msgid "Failed to hibernate system"
@@ -1780,10 +1801,6 @@ msgstr ""
msgid "Empty RSS entry found (%s)"
msgstr "Tyhjä RSS kohde löytyi (%s)"
#: sabnzbd/rss.py
msgid "Incompatible feed"
msgstr "Puutteellinen syöte"
#. Error message
#: sabnzbd/rss.py
msgid "Incorrect RSS feed description \"%s\""
@@ -1805,50 +1822,15 @@ msgstr "RSS noutaminen epäonnistui kohteesta %s: %s"
msgid "Server %s uses an untrusted HTTPS certificate"
msgstr "Palvelin %s käyttää epäluotettavaa HTTPS sertifikaattia"
#. Warning message
#: sabnzbd/rss.py
msgid "RSS Feed %s was empty"
msgstr "RSS syöte %s oli tyhjä"
#: sabnzbd/sabtray.py, sabnzbd/sabtraylinux.py
msgid "Show interface"
msgstr "Näytä käyttöliittymä"
#: sabnzbd/rss.py
msgid "Incompatible feed"
msgstr "Puutteellinen syöte"
#. Queue page button or entry box
#: sabnzbd/sabtray.py, sabnzbd/skintext.py
msgid "Pause for"
msgstr "Keskeytä ajaksi"
#: sabnzbd/sabtray.py, sabnzbd/skintext.py
msgid "Pause for 5 minutes"
msgstr "Keskeytä 5:ksi minuutiksi"
#: sabnzbd/sabtray.py, sabnzbd/skintext.py
msgid "Pause for 15 minutes"
msgstr "Keskeytä 15:ksi minuutiksi"
#: sabnzbd/sabtray.py, sabnzbd/skintext.py
msgid "Pause for 30 minutes"
msgstr "Keskeytä 30:ksi minuutiksi"
#: sabnzbd/sabtray.py, sabnzbd/skintext.py
msgid "Pause for 1 hour"
msgstr "Keskeytä tunniksi"
#: sabnzbd/sabtray.py, sabnzbd/skintext.py
msgid "Pause for 3 hours"
msgstr "Keskeytä 3:ksi tunniksi"
#: sabnzbd/sabtray.py, sabnzbd/skintext.py
msgid "Pause for 6 hours"
msgstr "Keskeytä 6:ksi tunniksi"
#. Config->Scheduling
#: sabnzbd/sabtray.py, sabnzbd/sabtraylinux.py, sabnzbd/skintext.py
msgid "Shutdown"
msgstr "Sammuta"
#: sabnzbd/sabtray.py, sabnzbd/sabtraylinux.py
#: sabnzbd/sabtraylinux.py, sabnzbd/sabtraywin.py
msgid "Remaining"
msgstr "Jäljellä"
@@ -1856,6 +1838,44 @@ msgstr "Jäljellä"
msgid "Add NZB"
msgstr "Lisää NZB"
#: sabnzbd/sabtraylinux.py, sabnzbd/sabtraywin.py
msgid "Show interface"
msgstr "Näytä käyttöliittymä"
#. Config->Scheduling
#: sabnzbd/sabtraylinux.py, sabnzbd/sabtraywin.py, sabnzbd/skintext.py
msgid "Shutdown"
msgstr "Sammuta"
#. Queue page button or entry box
#: sabnzbd/sabtraywin.py, sabnzbd/skintext.py
msgid "Pause for"
msgstr "Keskeytä ajaksi"
#: sabnzbd/sabtraywin.py, sabnzbd/skintext.py
msgid "Pause for 5 minutes"
msgstr "Keskeytä 5:ksi minuutiksi"
#: sabnzbd/sabtraywin.py, sabnzbd/skintext.py
msgid "Pause for 15 minutes"
msgstr "Keskeytä 15:ksi minuutiksi"
#: sabnzbd/sabtraywin.py, sabnzbd/skintext.py
msgid "Pause for 30 minutes"
msgstr "Keskeytä 30:ksi minuutiksi"
#: sabnzbd/sabtraywin.py, sabnzbd/skintext.py
msgid "Pause for 1 hour"
msgstr "Keskeytä tunniksi"
#: sabnzbd/sabtraywin.py, sabnzbd/skintext.py
msgid "Pause for 3 hours"
msgstr "Keskeytä 3:ksi tunniksi"
#: sabnzbd/sabtraywin.py, sabnzbd/skintext.py
msgid "Pause for 6 hours"
msgstr "Keskeytä 6:ksi tunniksi"
#. Warning message
#: sabnzbd/scheduler.py
msgid "Bad schedule %s at %s:%s"
@@ -2191,6 +2211,10 @@ msgstr "Isäntä"
msgid "Cancel"
msgstr "Peruuta"
#: sabnzbd/skintext.py
msgid "Close"
msgstr ""
#: sabnzbd/skintext.py
msgid "Log in"
msgstr "Kirjaudu sisään"
@@ -2773,10 +2797,6 @@ msgstr "Portti jota SABnzbdn tulisi kuunnella."
msgid "Web Interface Theme"
msgstr ""
#: sabnzbd/skintext.py
msgid "Choose a theme."
msgstr ""
#: sabnzbd/skintext.py
msgid "SABnzbd Username"
msgstr "SABnzbd käyttäjänimi"
@@ -2917,11 +2937,10 @@ msgstr "Puhdistuslista"
#: sabnzbd/skintext.py
msgid ""
"List of file extensions that should be deleted after download.<br />For "
"example: <b>nfo</b> or <b>nfo, sfv</b>"
"List of file extensions, filenames or patterns that should be deleted after "
"download.<br />Examples: <b>nfo, sfv</b> (extensions) or <b>thumbs.db</b> "
"(filenames) or <b>*.tmp, images/*, */test.jpg</b> (patterns)."
msgstr ""
"Lista tiedostopäätteistä jotka poistetaan latauksen valmistuttua.<br "
"/>Esimerkiksi: <b>nfo</b> tai <b>nfo, sfv</b>"
#: sabnzbd/skintext.py
msgid "History Retention"
@@ -3462,7 +3481,9 @@ msgid "Automatically sort jobs in the queue when a new job is added."
msgstr ""
#: sabnzbd/skintext.py
msgid "The queue will resort every 30 seconds if % downloaded is selected."
msgid ""
"The queue will resort every 30 seconds if % downloaded or Remaining Size is "
"selected."
msgstr ""
#: sabnzbd/skintext.py
@@ -3598,8 +3619,8 @@ msgid "Naming"
msgstr "Nimeäminen"
#: sabnzbd/skintext.py
msgid "How much can be downloaded this month (K/M/G)"
msgstr "Kuinka paljon voidaan ladata tässä kuussa (K/M/G)"
msgid "How much can be downloaded this Quota period (K/M/G)"
msgstr ""
#. Reset day of the download quota
#: sabnzbd/skintext.py
@@ -3881,10 +3902,6 @@ msgstr "Lue syöte"
msgid "Force Download"
msgstr "Pakota lataus"
#: sabnzbd/skintext.py
msgid "Apply filters"
msgstr ""
#. Config->RSS edit button
#: sabnzbd/skintext.py
msgid "Edit"
@@ -4550,6 +4567,14 @@ msgstr "Poista"
msgid "Filename"
msgstr "Tiedostonimi"
#: sabnzbd/skintext.py
msgid "Toggle SABnzbd navigation menu"
msgstr ""
#: sabnzbd/skintext.py
msgid "Open SABnzbd main menu"
msgstr ""
#: sabnzbd/skintext.py
msgid "Free Space"
msgstr "Vapaa tila"
@@ -4558,6 +4583,17 @@ msgstr "Vapaa tila"
msgid "Temp Folder"
msgstr "Väliaikaiskansio"
#: sabnzbd/skintext.py
msgid "Clear search"
msgstr ""
#: sabnzbd/skintext.py
msgid ""
"Search by name, or refine with filters <code>cat:</code>, "
"<code>status:</code> and <code>priority:</code> (Queue). For example: "
"<code>my show cat:tv</code>"
msgstr ""
#: sabnzbd/skintext.py
msgid "Multi-Operations"
msgstr "Monioperaatiot"
@@ -4566,6 +4602,10 @@ msgstr "Monioperaatiot"
msgid "Hold shift key to select a range"
msgstr "Paina vaihto-näppäintä pohjassa valitaksesi alueen"
#: sabnzbd/skintext.py
msgid "Select job"
msgstr ""
#: sabnzbd/skintext.py
msgid "Check all"
msgstr "Valitse kaikki"
@@ -4599,6 +4639,10 @@ msgstr ""
msgid "WARNING:"
msgstr "VAROITUS:"
#: sabnzbd/skintext.py
msgid "ERROR:"
msgstr "VIRHE:"
#: sabnzbd/skintext.py
msgid "Fetch"
msgstr "Nouda"
@@ -4635,6 +4679,14 @@ msgstr ""
msgid "Extra history columns"
msgstr ""
#: sabnzbd/skintext.py
msgid "Progress"
msgstr "Edistyminen"
#: sabnzbd/skintext.py
msgid "Actions"
msgstr "Toiminnot"
#: sabnzbd/skintext.py
msgid "page"
msgstr "sivu"
@@ -4806,6 +4858,10 @@ msgstr ""
msgid "Sort by % downloaded <small>Most&rarr;Least</small>"
msgstr ""
#: sabnzbd/skintext.py
msgid "Sort by Remaining Size <small>Smallest&rarr;Largest</small>"
msgstr ""
#: sabnzbd/skintext.py
msgid "Sort by Age <small>Oldest&rarr;Newest</small>"
msgstr "Järjestä iän mukaan <small>Vanhin&rarr;Uusin</small>"
+216 -137
View File
@@ -7,7 +7,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-5.0.0\n"
"Project-Id-Version: SABnzbd-5.2.0\n"
"PO-Revision-Date: 2020-06-27 15:49+0000\n"
"Last-Translator: Fred L <88com88@gmail.com>, 2026\n"
"Language-Team: French (https://app.transifex.com/sabnzbd/teams/111101/fr/)\n"
@@ -128,7 +128,6 @@ msgstr "SABnzbd %s démarré"
msgid "SABnzbd shutdown finished"
msgstr "Arrêt de SABnzbd terminé"
#. Warning message
#: sabnzbd/__init__.py
msgid "Signal %s caught, saving and exiting..."
msgstr "Signal %s intercepté, enregistrement et fermeture en cours..."
@@ -265,6 +264,10 @@ msgstr ""
"Protocole SSL inconnu: essayez de désactiver SSL ou de vous connecter sur un"
" autre port."
#: sabnzbd/api.py
msgid "Could not determine connection result (%s)"
msgstr "Impossible de déterminer le résultat de la connexion (%s)"
#: sabnzbd/api.py
msgid "Server requires username and password."
msgstr "Le serveur requiert un identifiant et un mot de passe."
@@ -273,19 +276,17 @@ msgstr "Le serveur requiert un identifiant et un mot de passe."
msgid "Connection Successful!"
msgstr "Connexion réussie!"
#: sabnzbd/api.py, sabnzbd/interface.py
msgid "Authentication failed, check username/password."
msgstr "Echec d'authentification, vérifiez les identifiant/mot de passe."
#. Warning message
#: sabnzbd/api.py
msgid "Too many connections, please pause downloading or try again later"
msgid ""
"Server %s took %d ms to respond. On high-latency connections, increasing "
"'Articles per request' to a value between 5 and 10 can improve download "
"speed. See: https://sabnzbd.org/wiki/advanced/nntp-pipelining"
msgstr ""
"Trop de connexions, veuillez mettre en pause le téléchargement ou essayer "
"plus tard"
#: sabnzbd/api.py
msgid "Could not determine connection result (%s)"
msgstr "Impossible de déterminer le résultat de la connexion (%s)"
"Le serveur %s a mis %d ms à répondre. Sur les connexions à forte latence, "
"augmenter le paramètre « Articles par requête » à une valeur comprise entre "
"5 et 10 peut améliorer la vitesse de téléchargement. Voir : "
"https://sabnzbd.org/wiki/advanced/nntp-pipelining"
#: sabnzbd/api.py
msgid "Resolving address"
@@ -302,12 +303,12 @@ msgid "Default"
msgstr "Par défaut"
#. Error message
#: sabnzbd/assembler.py
#: sabnzbd/assembler.py, sabnzbd/decoder.py
msgid "Disk full! Forcing Pause"
msgstr "Disque plein ! Pause forcée"
#. Error message
#: sabnzbd/assembler.py
#: sabnzbd/assembler.py, sabnzbd/decoder.py
msgid "Disk error on creating file %s"
msgstr "Erreur de disque lors de la création du fichier %s"
@@ -573,8 +574,8 @@ msgid "Resuming"
msgstr "Reprise en cours"
#. PP status - Priority pick list
#: sabnzbd/downloader.py, sabnzbd/macosmenu.py, sabnzbd/sabtray.py,
#: sabnzbd/sabtraylinux.py, sabnzbd/skintext.py
#: sabnzbd/downloader.py, sabnzbd/macosmenu.py, sabnzbd/sabtraylinux.py,
#: sabnzbd/sabtraywin.py, sabnzbd/skintext.py
msgid "Paused"
msgstr "En pause"
@@ -614,26 +615,24 @@ msgid "Fatal error in Downloader"
msgstr "Erreur fatale dans le Téléchargeur"
#: sabnzbd/downloader.py
msgid "Too many connections to server %s [%s]"
msgstr "Trop de connexions au serveur %s [%s]"
#: sabnzbd/downloader.py
msgid ""
"Login from too many different IP addresses to server %s [%s] - "
"https://sabnzbd.org/multiple-adresses"
msgstr ""
"Connexion au serveur %s [%s] à partir de trop d'adresses IP différentes - "
"https://sabnzbd.org/multiple-adresses"
#: sabnzbd/downloader.py
msgid "Failed login for server %s [%s]"
msgstr "Échec de la connexion au serveur %s [%s]"
msgid "Server %s reported: %s"
msgstr "Le serveur %s a indiqué : %s"
#. Error message
#: sabnzbd/downloader.py
msgid "Connecting %s@%s failed, message=%s"
msgstr "La connexion à %s@%s a échoué, message=%s"
#. Warning message
#: sabnzbd/downloader.py
msgid ""
"Total number of configured connections (%d) is greater than the operating "
"system limit (512), downloading will crash if all connections become active"
msgstr ""
"Le nombre total de connexions configurées (%d) est supérieur à la limite du "
"système d'exploitation (512) ; le téléchargement va s'interrompre si toutes "
"les connexions deviennent actives"
#: sabnzbd/downloader.py, sabnzbd/skintext.py
msgid "Shutting down"
msgstr "Arrêt en cours..."
@@ -810,14 +809,23 @@ msgstr ""
"fichiers fragmentés. Pour plus d'informations et les solutions possibles, "
"consultez : https://sabnzbd.org/wiki/advanced/direct-write"
#: sabnzbd/interface.py
msgid "Refused connection from:"
msgstr "Connexion refusée de:"
#: sabnzbd/interface.py
msgid "Refused connection with hostname \"%s\" from:"
msgstr "Connexion refusée avec le nom d'hôte \"%s\" à partir de :"
#: sabnzbd/interface.py
msgid ""
"API Key incorrect, Use the api key from Config->General in your 3rd party "
"program:"
msgstr ""
"Clé API incorrecte, utilisez la clé API de la configuration générale dans "
"votre application tierce :"
#. Warning message
#: sabnzbd/interface.py, sabnzbd/security.py
msgid "Refused connection from:"
msgstr "Connexion refusée de:"
#: sabnzbd/interface.py
msgid ""
"API Key missing, please enter the api key from Config->General into your 3rd"
@@ -827,21 +835,17 @@ msgstr ""
" application tierce :"
#: sabnzbd/interface.py
msgid ""
"API Key incorrect, Use the api key from Config->General in your 3rd party "
"program:"
msgstr ""
"Clé API incorrecte, utilisez la clé API de la configuration générale dans "
"votre application tierce :"
msgid "Too many failed login attempts, try again later."
msgstr "Trop de tentatives de connexion échouées, réessayer ultérieurement."
#. Warning message
#: sabnzbd/interface.py
msgid "Login attempt refused, too many failures from %s"
msgstr "Tentative de connexion refusée, trop d'échecs de %s"
#: sabnzbd/interface.py
msgid "User logged in to the web interface"
msgstr "Utilisateur connecté à l'interface web"
#. Notification
#: sabnzbd/interface.py, sabnzbd/notifier.py
msgid "User logged in"
msgstr "Utilisateur connecté"
msgid "Authentication failed, check username/password."
msgstr "Echec d'authentification, vérifiez les identifiant/mot de passe."
#. Warning message
#: sabnzbd/interface.py
@@ -904,14 +908,6 @@ msgstr ""
"Le dossier de catégorie ne peut pas être un sous-dossier du dossier de "
"téléchargement temporaire."
#: sabnzbd/interface.py, sabnzbd/skintext.py
msgid "ERROR:"
msgstr "ERREUR:"
#: sabnzbd/interface.py
msgid "Back"
msgstr "Retour"
#. Warning message
#: sabnzbd/internetspeed.py
msgid ""
@@ -926,7 +922,7 @@ msgstr ""
msgid "Warnings"
msgstr "Avertissements"
#: sabnzbd/macosmenu.py, sabnzbd/sabtray.py, sabnzbd/sabtraylinux.py,
#: sabnzbd/macosmenu.py, sabnzbd/sabtraylinux.py, sabnzbd/sabtraywin.py,
#: sabnzbd/skintext.py
msgid "Idle"
msgstr "A l'arrêt"
@@ -957,8 +953,8 @@ msgstr "Limiter la vitesse"
#. Notification - Pause downloading - Four way switch for duplicates -
#. Config->Scheduling
#: sabnzbd/macosmenu.py, sabnzbd/notifier.py, sabnzbd/sabtray.py,
#: sabnzbd/sabtraylinux.py, sabnzbd/skintext.py
#: sabnzbd/macosmenu.py, sabnzbd/notifier.py, sabnzbd/sabtraylinux.py,
#: sabnzbd/sabtraywin.py, sabnzbd/skintext.py
msgid "Pause"
msgstr "Mettre en pause"
@@ -968,8 +964,8 @@ msgid "min"
msgstr "min"
#. Notification - Resume downloading - Config->Scheduling
#: sabnzbd/macosmenu.py, sabnzbd/notifier.py, sabnzbd/sabtray.py,
#: sabnzbd/sabtraylinux.py, sabnzbd/skintext.py
#: sabnzbd/macosmenu.py, sabnzbd/notifier.py, sabnzbd/sabtraylinux.py,
#: sabnzbd/sabtraywin.py, sabnzbd/skintext.py
msgid "Resume"
msgstr "Reprendre"
@@ -978,7 +974,7 @@ msgstr "Reprendre"
msgid "Scan watched folder"
msgstr "Analyser le dossier surveillé"
#: sabnzbd/macosmenu.py, sabnzbd/sabtray.py, sabnzbd/sabtraylinux.py
#: sabnzbd/macosmenu.py, sabnzbd/sabtraylinux.py, sabnzbd/sabtraywin.py
msgid "Read all RSS feeds"
msgstr "Lire tous les flux RSS"
@@ -990,17 +986,17 @@ msgstr "Dossier des téléchargements terminés"
msgid "Incomplete Folder"
msgstr "Dossier incomplet"
#: sabnzbd/macosmenu.py, sabnzbd/sabtray.py
#: sabnzbd/macosmenu.py, sabnzbd/sabtraywin.py
msgid "Troubleshoot"
msgstr "Résoudre les problèmes"
#. Config->Scheduling
#: sabnzbd/macosmenu.py, sabnzbd/sabtray.py, sabnzbd/sabtraylinux.py,
#: sabnzbd/macosmenu.py, sabnzbd/sabtraylinux.py, sabnzbd/sabtraywin.py,
#: sabnzbd/skintext.py
msgid "Restart"
msgstr "Redémarrer"
#: sabnzbd/macosmenu.py, sabnzbd/sabtray.py
#: sabnzbd/macosmenu.py, sabnzbd/sabtraywin.py
msgid "Restart without login"
msgstr "Redémarrer sans se connecter"
@@ -1203,6 +1199,12 @@ msgstr "Tentative 7zip avec le mot de passe \"%s\""
msgid "see logfile"
msgstr "voir le journal"
#: sabnzbd/newsunpack.py
msgid "Unpacking skipped, TAR support requires Python 3.12 or later"
msgstr ""
"Décompression ignorée, la prise en charge des fichiers TAR nécessite Python "
"3.12 ou une version ultérieure"
#: sabnzbd/newsunpack.py
msgid "Quick Checking"
msgstr "Contrôle rapide"
@@ -1373,6 +1375,11 @@ msgstr "La tâche a échoué"
msgid "Queue finished"
msgstr "File d'attente terminée"
#. Notification
#: sabnzbd/notifier.py
msgid "User logged in"
msgstr "Utilisateur connecté"
#. Notification
#: sabnzbd/notifier.py
msgid "Other Messages"
@@ -1384,7 +1391,7 @@ msgid "Open folder"
msgstr "Ouvrir le dossier"
#. Notification action
#: sabnzbd/notifier.py, sabnzbd/sabtray.py, sabnzbd/sabtraylinux.py
#: sabnzbd/notifier.py, sabnzbd/sabtraylinux.py, sabnzbd/sabtraywin.py
msgid "Open complete folder"
msgstr "Ouvrir le dossier des complets"
@@ -1523,7 +1530,8 @@ msgstr "PROPAGATION %s min"
msgid "Downloaded in %s at an average of %sB/s"
msgstr "Téléchargé en %s à %sO/s de moyenne"
#. Job details page, file age column header
#. Config->RSS filter-type selection menu (e.g. ">3d", "<12h") - Job details
#. page, file age column header
#: sabnzbd/nzb/object.py, sabnzbd/skintext.py
msgid "Age"
msgstr "Âge"
@@ -1617,6 +1625,32 @@ msgstr ""
"Si vous obtenez ce message d'erreur à nouveau, veuillez essayer un nombre "
"différent.<br>"
#: sabnzbd/panic.py
msgid ""
"\n"
" SABnzbd is not allowed to use port %s on %s.<br>\n"
" Low port numbers are reserved for privileged processes on most systems,\n"
" so the port itself is available but cannot be claimed.<br>\n"
" <br>\n"
" Either run SABnzbd with the privileges needed for that port, or restart it\n"
" with a port number of 1024 or higher."
msgstr ""
"\n"
" SABnzbd n'est pas autorisé à utiliser le port %s sur %s.<br>\n"
" Sur la plupart des systèmes, les numéros de port faibles sont réservés aux processus privilégiés.\n"
" le port est donc disponible, mais ne peut pas être utilisé.<br>\n"
" <br>\n"
" Lancez SABnzbd avec les privilèges requis pour ce port, ou redémarrez-le\n"
" en lui attribuant un numéro de port supérieur ou égal à 1024."
#: sabnzbd/panic.py
msgid ""
"To keep using the low port, put a reverse proxy in front of SABnzbd "
"instead.<br>"
msgstr ""
"Pour continuer à utiliser un port privilégié, mettez un proxy inverse devant"
" SABnzbd.<br>"
#: sabnzbd/panic.py
msgid ""
"\n"
@@ -1705,21 +1739,18 @@ msgstr ""
"Impossible de lier le port %s sur %s. Un autre logiciel utilise le port ou "
"SABnzbd est déjà en cours d'exécution."
#: sabnzbd/panic.py
msgid ""
"Not allowed to bind to port %s on %s. Low ports need elevated privileges."
msgstr ""
"Impossible de se lier au port %s sur %s. Les ports inférieurs à 1024 "
"demandent des privilèges élevés."
#. Warning message
#: sabnzbd/panic.py
msgid "Cannot launch the browser, probably not found"
msgstr "Impossible de lancer le navigateur web, probablement pas trouvé"
#: sabnzbd/panic.py
msgid "Access denied"
msgstr "Accès refusé"
#: sabnzbd/panic.py
msgid "Error %s: You need to provide a valid username and password."
msgstr ""
"Erreur %s: vous devez fournir un nom d'utilisateur et un mot de passe "
"valide."
#. Warning message
#: sabnzbd/postproc.py
msgid "Old queue detected, use Status->Repair to convert the queue"
@@ -1852,6 +1883,10 @@ msgstr "Aucun fichier rar antérieur correspondant pour %s"
msgid "Removing %s failed"
msgstr "Échec de la suppression de %s"
#: sabnzbd/postproc.py
msgid "Removed %s files with unwanted extensions"
msgstr "Suppression de %s fichiers comportant des extensions indésirables"
#. Error message
#: sabnzbd/powersup.py
msgid "Failed to hibernate system"
@@ -1876,10 +1911,6 @@ msgstr "Exception DBus reçue %s"
msgid "Empty RSS entry found (%s)"
msgstr "Entrée vide de flux RSS trouvée (%s)"
#: sabnzbd/rss.py
msgid "Incompatible feed"
msgstr "Flux incompatible"
#. Error message
#: sabnzbd/rss.py
msgid "Incorrect RSS feed description \"%s\""
@@ -1902,50 +1933,15 @@ msgstr "Échec de la récupération RSS de %s : %s"
msgid "Server %s uses an untrusted HTTPS certificate"
msgstr "Le serveur %s utilise un certificat de sécurité HTTPS non authentifié"
#. Warning message
#: sabnzbd/rss.py
msgid "RSS Feed %s was empty"
msgstr "Le flux RSS %s était vide"
#: sabnzbd/sabtray.py, sabnzbd/sabtraylinux.py
msgid "Show interface"
msgstr "Afficher linterface"
#: sabnzbd/rss.py
msgid "Incompatible feed"
msgstr "Flux incompatible"
#. Queue page button or entry box
#: sabnzbd/sabtray.py, sabnzbd/skintext.py
msgid "Pause for"
msgstr "Pause de"
#: sabnzbd/sabtray.py, sabnzbd/skintext.py
msgid "Pause for 5 minutes"
msgstr "Mettre en pause pendant 5 minutes"
#: sabnzbd/sabtray.py, sabnzbd/skintext.py
msgid "Pause for 15 minutes"
msgstr "Mettre en pause pendant 15 minutes"
#: sabnzbd/sabtray.py, sabnzbd/skintext.py
msgid "Pause for 30 minutes"
msgstr "Mettre en pause pendant 30 minutes"
#: sabnzbd/sabtray.py, sabnzbd/skintext.py
msgid "Pause for 1 hour"
msgstr "Mettre en pause pendant 1 heure"
#: sabnzbd/sabtray.py, sabnzbd/skintext.py
msgid "Pause for 3 hours"
msgstr "Mettre en pause pendant 3 heures"
#: sabnzbd/sabtray.py, sabnzbd/skintext.py
msgid "Pause for 6 hours"
msgstr "Mettre en pause pendant 6 heures"
#. Config->Scheduling
#: sabnzbd/sabtray.py, sabnzbd/sabtraylinux.py, sabnzbd/skintext.py
msgid "Shutdown"
msgstr "Arrêter"
#: sabnzbd/sabtray.py, sabnzbd/sabtraylinux.py
#: sabnzbd/sabtraylinux.py, sabnzbd/sabtraywin.py
msgid "Remaining"
msgstr "Restant"
@@ -1953,6 +1949,44 @@ msgstr "Restant"
msgid "Add NZB"
msgstr "Ajouter NZB"
#: sabnzbd/sabtraylinux.py, sabnzbd/sabtraywin.py
msgid "Show interface"
msgstr "Afficher linterface"
#. Config->Scheduling
#: sabnzbd/sabtraylinux.py, sabnzbd/sabtraywin.py, sabnzbd/skintext.py
msgid "Shutdown"
msgstr "Arrêter"
#. Queue page button or entry box
#: sabnzbd/sabtraywin.py, sabnzbd/skintext.py
msgid "Pause for"
msgstr "Pause de"
#: sabnzbd/sabtraywin.py, sabnzbd/skintext.py
msgid "Pause for 5 minutes"
msgstr "Mettre en pause pendant 5 minutes"
#: sabnzbd/sabtraywin.py, sabnzbd/skintext.py
msgid "Pause for 15 minutes"
msgstr "Mettre en pause pendant 15 minutes"
#: sabnzbd/sabtraywin.py, sabnzbd/skintext.py
msgid "Pause for 30 minutes"
msgstr "Mettre en pause pendant 30 minutes"
#: sabnzbd/sabtraywin.py, sabnzbd/skintext.py
msgid "Pause for 1 hour"
msgstr "Mettre en pause pendant 1 heure"
#: sabnzbd/sabtraywin.py, sabnzbd/skintext.py
msgid "Pause for 3 hours"
msgstr "Mettre en pause pendant 3 heures"
#: sabnzbd/sabtraywin.py, sabnzbd/skintext.py
msgid "Pause for 6 hours"
msgstr "Mettre en pause pendant 6 heures"
#. Warning message
#: sabnzbd/scheduler.py
msgid "Bad schedule %s at %s:%s"
@@ -2288,6 +2322,10 @@ msgstr "Hôte"
msgid "Cancel"
msgstr "Annuler"
#: sabnzbd/skintext.py
msgid "Close"
msgstr "Fermer"
#: sabnzbd/skintext.py
msgid "Log in"
msgstr "Se connecter"
@@ -2879,10 +2917,6 @@ msgstr "Port que SABnzbd doit surveiller."
msgid "Web Interface Theme"
msgstr "Thème de l'interface web"
#: sabnzbd/skintext.py
msgid "Choose a theme."
msgstr "Choisissez un thème."
#: sabnzbd/skintext.py
msgid "SABnzbd Username"
msgstr "Identifiant SABnzbd"
@@ -3033,11 +3067,14 @@ msgstr "Nettoyer la liste"
#: sabnzbd/skintext.py
msgid ""
"List of file extensions that should be deleted after download.<br />For "
"example: <b>nfo</b> or <b>nfo, sfv</b>"
"List of file extensions, filenames or patterns that should be deleted after "
"download.<br />Examples: <b>nfo, sfv</b> (extensions) or <b>thumbs.db</b> "
"(filenames) or <b>*.tmp, images/*, */test.jpg</b> (patterns)."
msgstr ""
"Liste des extensions de fichiers qui doivent être supprimés après le "
"téléchargement.<br /><Par exemple: <b>nfo</b> ou <b>nfo, sfv</b>"
"Liste des extensions, noms de fichiers ou modèles à supprimer après le "
"téléchargement.<br />Exemples : <b>nfo, sfv</b> (extensions) ou "
"<b>thumbs.db</b> (noms de fichiers) ou <b>*.tmp, images/*, */test.jpg</b> "
"(modèles)."
#: sabnzbd/skintext.py
msgid "History Retention"
@@ -3612,10 +3649,12 @@ msgstr ""
"ajoutée."
#: sabnzbd/skintext.py
msgid "The queue will resort every 30 seconds if % downloaded is selected."
msgid ""
"The queue will resort every 30 seconds if % downloaded or Remaining Size is "
"selected."
msgstr ""
"La file d'attente sera triée toutes les 30 secondes si le % de "
"téléchargement est sélectionné."
"La file d'attente sera retriée toutes les 30 secondes si % téléchargé ou "
"Taille restante est sélectionné."
#: sabnzbd/skintext.py
msgid "Propagation delay"
@@ -3759,8 +3798,10 @@ msgid "Naming"
msgstr "Appellation"
#: sabnzbd/skintext.py
msgid "How much can be downloaded this month (K/M/G)"
msgstr "Combien peut-être télécharger ce mois (K/M/G)"
msgid "How much can be downloaded this Quota period (K/M/G)"
msgstr ""
"Quantité de données pouvant être téléchargées durant ce cycle de quota "
"(K/M/G)"
#. Reset day of the download quota
#: sabnzbd/skintext.py
@@ -4066,10 +4107,6 @@ msgstr "Lire le flux RSS"
msgid "Force Download"
msgstr "Forcer le téléchargement"
#: sabnzbd/skintext.py
msgid "Apply filters"
msgstr "Appliquer les filtres"
#. Config->RSS edit button
#: sabnzbd/skintext.py
msgid "Edit"
@@ -4763,6 +4800,14 @@ msgstr "Supprimer"
msgid "Filename"
msgstr "Nom de fichier"
#: sabnzbd/skintext.py
msgid "Toggle SABnzbd navigation menu"
msgstr "Afficher/masquer le menu de navigation de SABnzbd"
#: sabnzbd/skintext.py
msgid "Open SABnzbd main menu"
msgstr "Ouvrir le menu principal de SABnzbd"
#: sabnzbd/skintext.py
msgid "Free Space"
msgstr "Espace libre"
@@ -4771,6 +4816,20 @@ msgstr "Espace libre"
msgid "Temp Folder"
msgstr "Dossier temporaire"
#: sabnzbd/skintext.py
msgid "Clear search"
msgstr "Effacer la recherche"
#: sabnzbd/skintext.py
msgid ""
"Search by name, or refine with filters <code>cat:</code>, "
"<code>status:</code> and <code>priority:</code> (Queue). For example: "
"<code>my show cat:tv</code>"
msgstr ""
"Rechercher par nom, ou affiner avec des filtres <code>cat :</code>, "
"<code>statut :</code> et <code>priorité :</code> (file d'attente). Par "
"exemple : <code>mon show cat:tv</code>"
#: sabnzbd/skintext.py
msgid "Multi-Operations"
msgstr "Multi-Operations"
@@ -4779,6 +4838,10 @@ msgstr "Multi-Operations"
msgid "Hold shift key to select a range"
msgstr "Maintenez la touche Maj pour sélectionner une plage"
#: sabnzbd/skintext.py
msgid "Select job"
msgstr "Sélectionner la tâche"
#: sabnzbd/skintext.py
msgid "Check all"
msgstr "Tout sélectionner"
@@ -4812,6 +4875,10 @@ msgstr ""
msgid "WARNING:"
msgstr "AVERTISSEMENT :"
#: sabnzbd/skintext.py
msgid "ERROR:"
msgstr "ERREUR:"
#: sabnzbd/skintext.py
msgid "Fetch"
msgstr "Charger"
@@ -4848,6 +4915,14 @@ msgstr "Colonnes de file d'attente supplémentaires"
msgid "Extra history columns"
msgstr "Colonnes d'historique supplémentaires"
#: sabnzbd/skintext.py
msgid "Progress"
msgstr "Avancement"
#: sabnzbd/skintext.py
msgid "Actions"
msgstr "Actions"
#: sabnzbd/skintext.py
msgid "page"
msgstr "page"
@@ -5027,6 +5102,10 @@ msgstr ""
msgid "Sort by % downloaded <small>Most&rarr;Least</small>"
msgstr "Trier par % téléchargé <small>Most&rarr;Least</small>"
#: sabnzbd/skintext.py
msgid "Sort by Remaining Size <small>Smallest&rarr;Largest</small>"
msgstr "Trier par Taille restante <small>Plus petit&rarr;Plus grand</small>"
#: sabnzbd/skintext.py
msgid "Sort by Age <small>Oldest&rarr;Newest</small>"
msgstr "Trier par Age <small>Moins récent&rarr;Plus récent</small>"
Loaded 100 of 292 files, more files were not shown because too many files have changed in this diff. Show more