Compare commits

..
Author SHA1 Message Date
Safihre 8bd39e4c12 Refactor pre-queue script
[skip ci]
2023-11-22 16:17:19 +01:00
344 changed files with 25884 additions and 49265 deletions

No files matched your search

-43
View File
@@ -1,43 +0,0 @@
# `git blame` master ignore list.
#
# This file contains a list of git hashes of revisions to be ignored
# by `git blame`. These revisions are considered "unimportant" in
# that they are unlikely to be what you are interested in when blaming.
# They are typically expected to be formatting-only changes.
#
# It can be used for `git blame` using `--ignore-revs-file` or by
# setting `blame.ignoreRevsFile` in the `git config`[1].
#
# Ignore these commits when reporting with blame. Calling
#
# git blame --ignore-revs-file .git-blame-ignore-revs
#
# will tell `git blame` to ignore changes made by these revisions when
# assigning blame, as if the change never happened.
#
# You can enable this as a default for your local repository by
# running
#
# git config blame.ignoreRevsFile .git-blame-ignore-revs
#
# This will probably be automatically picked by your IDE
# (VSCode+GitLens and JetBrains products are confirmed to do this).
#
# Important: if you are switching to a branch without this file,
# `git blame` will fail with an error.
#
# GitHub also excludes the commits listed below from its "Blame"
# views[2][3].
#
# [1]: https://git-scm.com/docs/git-blame#Documentation/git-blame.txt-blameignoreRevsFile
# [2]: https://github.blog/changelog/2022-03-24-ignore-commits-in-the-blame-view-beta/
# [3]: https://docs.github.com/en/repositories/working-with-files/using-files/viewing-a-file#ignore-commits-in-the-blame-view
# Black changes
465a88154152fb0607a63fa24c8446bff43ec886
f06891926661986fff52d6eb4b4cb120c71972d1
9bcbcaefdfecc85aedfd8e2f8aaa1ca7f959404e
433dcab02b29f7bd3827e237434034deecc1b549
9f6a9f991222efccc87b45a701086c95629c67b6
f89114ca7e1b20bf8e645ecd0b52b707ec857aa9
+1 -3
View File
@@ -3,8 +3,7 @@ description: >
Did you discover a bug in SABnzbd? Report it here!
If you are not 100% certain this is a bug please go to our forums, Reddit or Discord server first.
labels:
- Support
type: bug
- Bug
body:
- type: input
attributes:
@@ -22,7 +21,6 @@ body:
options:
- linuxserver
- hotio
- binhex
- Other
- type: textarea
attributes:
+1 -1
View File
@@ -4,7 +4,7 @@ contact_links:
url: https://forums.sabnzbd.org/
about: Support questions can be asked on our forums, Reddit or Discord server.
- name: Discord
url: https://discord.sabnzbd.org
url: https://discord.gg/KQzDe7fvNU
about: Support questions can be asked on our forums, Reddit or Discord server.
- name: Reddit - r/sabnzbd
url: https://www.reddit.com/r/sabnzbd
+1 -2
View File
@@ -1,8 +1,7 @@
name: Feature request
description: What new feature would you like to have added to SABnzbd?
labels:
- Support
type: feature
- Feature request
body:
- type: textarea
attributes:
+6
View File
@@ -0,0 +1,6 @@
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
+7 -36
View File
@@ -7,53 +7,24 @@
"schedule": [
"before 8am on Monday"
],
"minimumReleaseAge": "14 days",
"baseBranches": ["develop"],
"ignorePaths": [
".github/workflows/**"
],
"pip_requirements": {
"fileMatch": [
"requirements.txt",
"tests/requirements.txt",
"builder/requirements.txt",
"builder/release-requirements.txt"
"builder/release-requirements.txt",
"builder/osx/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",
"tempora",
"cryptography"
"sabctools",
"werkzeug"
],
"packageRules": [
{
"matchManagers": ["github-actions"],
"matchPackageNames": ["windows", "macos"],
"enabled": false
},
{
"matchPackagePatterns": [
"*"
+109 -138
View File
@@ -2,150 +2,128 @@ name: Build binaries and source distribution
on: [push, pull_request]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
# Setting PYTHONNODEBUGRANGES reduces binary size
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.6"
jobs:
build_windows:
name: Build Windows binary (${{ matrix.architecture }})
strategy:
fail-fast: false
matrix:
include:
- architecture: x64
runs-on: windows-2022
- architecture: arm64
runs-on: windows-11-arm
runs-on: ${{ matrix.runs-on }}
timeout-minutes: 15
name: Build Windows binary
runs-on: windows-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v7
- name: Set up Python
uses: actions/setup-python@v7
- uses: actions/checkout@v3
- name: Set up Python 3.12 (64bit)
uses: actions/setup-python@v4
with:
python-version: ${{ env.PYTHON_VERSION }}
architecture: ${{ matrix.architecture }}
cache: pip
cache-dependency-path: "**/requirements.txt"
- name: Install Python dependencies
python-version: "3.12"
architecture: "x64"
- name: Cache Python virtualenv (64bit)
uses: syphar/restore-virtualenv@v1.3
id: cache-virtualenv-64bit
with:
custom_virtualenv_dir: "venv64"
custom_cache_key_element: "release"
requirement_files: "**/requirements.txt"
- name: Install Python dependencies (64bit)
if: steps.cache-virtualenv-64bit.outputs.cache-hit != 'true'
# Without dependencies to make sure everything is covered in the requirements.txt
# Special settings:
# cryptography see https://github.com/pyca/cryptography/pull/14216
run: |
python --version
python -m pip install --upgrade pip wheel
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
uses: signpath/github-action-submit-signing-request@v2
if: contains(github.ref, 'refs/tags/')
with:
api-token: ${{ secrets.SIGNPATH_API_TOKEN }}
organization-id: ${{ secrets.SIGNPATH_ORG_ID }}
project-slug: "sabnzbd"
artifact-configuration-slug: "sabnzbd-binary"
signing-policy-slug: "release-signing"
github-artifact-id: ${{ steps.upload-unsigned-binary.outputs.artifact-id }}
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/*-win*-bin.zip"
- name: Build Windows installer
if: matrix.architecture == 'x64'
pip install --upgrade -r requirements.txt --no-dependencies
pip install --upgrade -r builder/requirements.txt --no-dependencies
- name: Build Windows standalone binary and installer (64bit)
run: python builder/package.py installer
- name: Upload Windows installer
if: matrix.architecture == 'x64'
uses: actions/upload-artifact@v7
id: upload-unsigned-installer
- name: Upload Windows standalone binary (64bit)
uses: actions/upload-artifact@v3
with:
path: "*-win64-bin.zip"
name: Windows Windows standalone binary (64bit)
- name: Upload Windows installer (64bit)
uses: actions/upload-artifact@v3
with:
archive: ${{ contains(github.ref, 'refs/tags/') }}
path: "*-win-setup.exe"
name: Windows installer (${{ matrix.architecture }})
- name: Sign Windows installer
if: matrix.architecture == 'x64' && contains(github.ref, 'refs/tags/')
uses: signpath/github-action-submit-signing-request@v2
name: Windows installer
- name: Set up Python 3.8 (32bit and legacy)
uses: actions/setup-python@v4
with:
api-token: ${{ secrets.SIGNPATH_API_TOKEN }}
organization-id: ${{ secrets.SIGNPATH_ORG_ID }}
project-slug: "sabnzbd"
artifact-configuration-slug: "sabnzbd-installer"
signing-policy-slug: "release-signing"
github-artifact-id: ${{ steps.upload-unsigned-installer.outputs.artifact-id }}
wait-for-completion: true
output-artifact-directory: "signed"
- name: Upload Windows installer (signed)
if: matrix.architecture == 'x64' && contains(github.ref, 'refs/tags/')
uses: actions/upload-artifact@v7
python-version: "3.8"
architecture: "x86"
- name: Cache Python virtualenv (32bit and legacy)
uses: syphar/restore-virtualenv@v1.3
id: cache-virtualenv-32bit
with:
archive: false
path: "signed/*-win-setup.exe"
custom_virtualenv_dir: "venv32"
custom_cache_key_element: "release"
requirement_files: "**/requirements.txt"
- name: Install Python dependencies (32bit and legacy)
if: steps.cache-virtualenv-32bit.outputs.cache-hit != 'true'
# We do not care about the extra dependencies for the legacy build
run: |
python --version
python -m pip install --upgrade pip wheel
pip install --upgrade -r requirements.txt --no-dependencies
pip install --upgrade -r builder/requirements.txt --no-dependencies
- name: Build Windows standalone binary (32bit and legacy)
run: python builder/package.py binary
- name: Upload Windows standalone binary (32bit and legacy)
uses: actions/upload-artifact@v3
with:
path: "*-win32-bin.zip"
name: Windows Windows standalone binary (32bit and legacy)
build_macos:
name: Build macOS binary
runs-on: macos-14
timeout-minutes: 15
runs-on: macos-11
timeout-minutes: 30
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
MACOSX_DEPLOYMENT_TARGET: "10.15"
PYTHON_VERSION: "3.12.0"
MACOSX_DEPLOYMENT_TARGET: "10.9"
# We need to force compile for universal2 support
CFLAGS: -arch x86_64 -arch arm64
ARCHFLAGS: -arch x86_64 -arch arm64
steps:
- uses: actions/checkout@v7
- name: Set up Python
# Only use this for the caching of pip packages!
uses: actions/setup-python@v7
with:
python-version: "3.14"
cache: pip
cache-dependency-path: "**/requirements.txt"
- uses: actions/checkout@v3
- name: Cache Python download
id: cache-python-download
uses: actions/cache@v6
uses: actions/cache@v3
with:
path: ~/python.pkg
key: cache-macOS-Python-${{ env.PYTHON_VERSION }}
- name: Get Python from python.org
- name: Get Python
if: steps.cache-python-download.outputs.cache-hit != 'true'
run: curl https://www.python.org/ftp/python/${PYTHON_VERSION}/python-${PYTHON_VERSION}-macos11.pkg -o ~/python.pkg
- name: Install Python
run: sudo installer -pkg ~/python.pkg -target /
- name: Cache Python virtualenv
uses: syphar/restore-virtualenv@v1.3
id: cache-virtualenv
with:
custom_cache_key_element: "release"
requirement_files: "**/requirements.txt"
- name: Install Python dependencies
# We have to manually compile some modules as they don't automatically fetch universal2 binaries
# We have to manually take a few steps:
# 1. Because building cryptography is hard, and we cannot force pip to fetch universal2 version we
# first install the x86 version (and it's dependencies) and then manually fetch the universal2 build
# https://github.com/pypa/pip/issues/5453
# 2. We need to build the PyInstaller bootloader:
# https://github.com/pyinstaller/pyinstaller/issues/6235
if: steps.cache-virtualenv.outputs.cache-hit != 'true'
run: |
python3 --version
pip3 install --upgrade pip wheel
pip3 install -r requirements.txt --no-binary cffi,CT3,PyYAML --no-dependencies
pip3 install -r builder/requirements.txt --no-dependencies
pip3 install --upgrade -r requirements.txt --no-binary cffi --no-dependencies
pip3 uninstall cryptography -y
pip3 download -r builder/osx/requirements.txt --platform macosx_10_12_universal2 --only-binary :all: --no-dependencies --dest .
pip3 install -r builder/osx/requirements.txt --no-cache-dir --no-index --find-links .
PYINSTALLER_COMPILE_BOOTLOADER=1 pip3 install --upgrade -r builder/requirements.txt --no-binary pyinstaller --no-dependencies
- name: Import macOS codesign certificates
# Taken from https://github.com/Apple-Actions/import-codesign-certs/pull/27 (comments)
env:
@@ -155,8 +133,8 @@ jobs:
if: env.CERTIFICATES_P12
run: |
echo $CERTIFICATES_P12 | base64 --decode > certificate.p12
security create-keychain -p "$MACOS_KEYCHAIN_TEMP_PASSWORD" build.keychain
security default-keychain -s build.keychain
security create-keychain -p "$MACOS_KEYCHAIN_TEMP_PASSWORD" build.keychain
security default-keychain -s build.keychain
security unlock-keychain -p "$MACOS_KEYCHAIN_TEMP_PASSWORD" build.keychain
security set-keychain-settings -lut 21600 build.keychain
security import certificate.p12 -k build.keychain -P "$CERTIFICATES_P12_PASSWORD" -T /usr/bin/codesign -T /usr/bin/productsign -T /usr/bin/xcrun
@@ -165,58 +143,51 @@ jobs:
# Run this on macOS so the line endings are correct by default
run: python builder/package.py source
- name: Upload source distribution
uses: actions/upload-artifact@v7
uses: actions/upload-artifact@v3
with:
archive: false
path: "*-src.tar.gz"
name: Source distribution
- name: Build macOS binary
env:
SIGNING_AUTH: ${{ secrets.SIGNING_AUTH }}
NOTARIZATION_USER: ${{ secrets.NOTARIZATION_USER }}
NOTARIZATION_PASS: ${{ secrets.NOTARIZATION_PASS }}
run: |
python3 builder/package.py app dmg
python3 builder/package.py app
python3 builder/make_dmg.py
- name: Upload macOS binary
uses: actions/upload-artifact@v7
uses: actions/upload-artifact@v3
with:
archive: false
path: "*-macos.dmg"
path: "*-osx.dmg"
name: macOS binary (not notarized)
release:
name: Prepare Release
name: Publish Release
runs-on: ubuntu-latest
needs: [build_windows, build_macos]
if: contains(github.ref, 'refs/tags/')
environment: release
if: github.event_name == 'push' && contains(github.ref, 'refs/tags/')
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v7
uses: actions/setup-python@v4
with:
python-version: "3.14"
cache: pip
cache-dependency-path: "builder/release-requirements.txt"
- 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
python-version: "3.x"
- name: Download all artifacts
uses: actions/download-artifact@v3
with:
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:
pattern: "*signed*"
merge-multiple: true
path: dist
- name: Move all artifacts to main folder
run: find dist -type f -exec mv {} . \;
- 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 --no-dependencies
pip3 install -r builder/release-requirements.txt
python3 builder/release.py
- name: Release latest available Snap
env:
SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.SNAP_TOKEN }}
run: |
sudo snap install snapcraft --classic
python3 snap/local/release_snap.py
-91
View File
@@ -1,91 +0,0 @@
name: Build and publish Snap packages
on:
push:
paths:
- 'snap/**'
- 'requirements.txt'
- '.github/workflows/build_snaps.yml'
pull_request:
paths:
- 'snap/**'
- 'requirements.txt'
- '.github/workflows/build_snaps.yml'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: read
jobs:
build-snap:
name: Build Snap Packages (${{ matrix.linux_arch }})
timeout-minutes: 30
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
linux_arch: x64
- os: ubuntu-24.04-arm
linux_arch: arm64
steps:
- uses: actions/checkout@v7
- name: Cache par2cmdline-turbo tarball
uses: actions/cache@v6
id: cache-par2cmdline
# Clearing the cache in case of new version requires manual clearing in GitHub!
with:
path: snap/par2cmdline.tar.gz
key: cache-par2cmdline
- name: Download par2cmdline-turbo tarball
if: steps.cache-par2cmdline.outputs.cache-hit != 'true'
run: |
PAR2_TARBALL=$(curl -sL https://api.github.com/repos/animetosho/par2cmdline-turbo/releases/latest | jq -r '.tarball_url')
curl -o snap/par2cmdline.tar.gz -L "$PAR2_TARBALL"
- uses: snapcore/action-build@v1
name: Build snap
id: snapcraft
- name: Test snap installation
run: |
sudo snap install --dangerous *.snap
sudo snap connect sabnzbd:removable-media
# 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
with:
name: Snap package (${{ matrix.linux_arch }})
path: ${{ steps.snapcraft.outputs.snap }}
- name: Determine snap channel
if: contains(github.ref, 'refs/tags/')
id: snap-channel
run: |
TAG="${GITHUB_REF#refs/tags/}"
if [[ "$TAG" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "channel=stable,beta" >> "$GITHUB_OUTPUT"
else
echo "channel=beta" >> "$GITHUB_OUTPUT"
fi
- name: Publish snap
uses: snapcore/action-publish@v1
if: contains(github.ref, 'refs/tags/')
env:
SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.SNAP_TOKEN }}
with:
store_login: ${{ secrets.SNAP_TOKEN }}
snap: ${{ steps.snapcraft.outputs.snap }}
release: ${{ steps.snap-channel.outputs.channel }}
-26
View File
@@ -1,26 +0,0 @@
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
- uses: anthropics/claude-code-action@v1
with:
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
github_token: ${{ secrets.GITHUB_TOKEN }}
+43 -38
View File
@@ -2,34 +2,27 @@ name: CI Tests
on: [push, pull_request]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: read
jobs:
black-and-ruff:
name: Black and Ruff checks
black:
name: Black Code Formatter
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- run: pip install ruff black
- name: Black code formatter
run: >
black
SABnzbd.py
sabnzbd
scripts
builder
builder/SABnzbd.spec
tests
tools
--check
--diff
- name: Ruff checks
run: ruff check
- uses: actions/checkout@v3
- name: Black Code Formatter
uses: lgeiger/black-action@master
with:
args: >
SABnzbd.py
sabnzbd
scripts
tools
builder
builder/SABnzbd.spec
tests
--line-length=120
--target-version=py38
--check
--diff
test:
name: Test ${{ matrix.name }} - Python ${{ matrix.python-version }}
@@ -38,34 +31,46 @@ jobs:
strategy:
fail-fast: false
matrix:
# 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" ]
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]
python-architecture: ["x64"]
name: ["Linux"]
os: [ubuntu-latest]
os: [ubuntu-20.04]
include:
- name: macOS
os: macos-latest
python-version: "3.14"
python-version: "3.12"
python-architecture: "x64"
- name: Windows
os: windows-2022
python-version: "3.14"
os: windows-latest
python-version: "3.12"
python-architecture: "x64"
- name: Windows (32bit)
os: windows-latest
python-version: "3.8"
python-architecture: "x86"
steps:
- uses: actions/checkout@v7
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v7
- uses: actions/checkout@v3
- name: Set up Python ${{ matrix.python-version }} ${{ matrix.python-architecture }}
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
cache: pip
cache-dependency-path: "**/requirements.txt"
architecture: ${{ matrix.python-architecture }}
- name: Install system dependencies
if: runner.os == 'Linux'
run: sudo apt-get install unrar 7zip par2 libxml2-dev libxslt-dev
run: sudo apt-get install unrar p7zip-full par2
- name: Cache Python virtualenv
uses: syphar/restore-virtualenv@v1.3
id: cache-virtualenv
with:
custom_cache_key_element: ci-${{ matrix.python-architecture }}
requirement_files: "**/requirements.txt"
- name: Install Python dependencies
if: steps.cache-virtualenv.outputs.cache-hit != 'true'
run: |
python --version
python -m pip install --upgrade pip wheel
pip install -r requirements.txt --no-dependencies
pip install --upgrade -r requirements.txt
pip install --upgrade -r tests/requirements.txt
- name: Test SABnzbd
run: pytest -s
+3 -7
View File
@@ -4,17 +4,13 @@ on:
- cron: "30 1 * * *"
workflow_dispatch:
permissions:
issues: write
pull-requests: write
jobs:
stale:
name: "Close stale issues"
if: github.repository_owner == 'sabnzbd'
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v10
- uses: actions/stale@v8
with:
days-before-stale: 21
days-before-close: 7
@@ -23,14 +19,14 @@ 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.
only-issue-labels: "Support"
exempt-issue-labels: "Feature request, Work in progress, Bug"
lock:
name: "Lock old issues"
if: github.repository_owner == 'sabnzbd'
runs-on: ubuntu-latest
steps:
- uses: dessant/lock-threads@v6
- uses: dessant/lock-threads@v4
with:
log-output: true
issue-inactive-days: 60
+16 -30
View File
@@ -5,13 +5,6 @@ on:
branches:
- develop
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: write
jobs:
translations:
name: Update translatable texts
@@ -19,39 +12,32 @@ jobs:
env:
TX_TOKEN: ${{ secrets.TX_TOKEN }}
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v3
with:
# Need the parent commit too so we can diff HEAD and detect local translation edits
fetch-depth: 2
token: ${{ secrets.AUTOMATION_GITHUB_TOKEN }}
- name: Generate translatable texts
run: |
python3 tools/extract_pot.py
- name: Push/pull Transifex translations
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!
# 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.
# 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!
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
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}
curl -o- https://raw.githubusercontent.com/transifex/cli/master/install.sh | bash
./tx push --source
./tx pull --all --force
- name: Compile translations to validate them
run: |
python3 tools/make_mo.py
- name: Push translatable and translated texts back to repo
uses: stefanzweifel/git-auto-commit-action@v4.16.0
if: env.TX_TOKEN
run: |
git config user.name "SABnzbd Automation"
git config user.email "bugs@sabnzbd.org"
git add po/*.pot po/*.po *.nsi
git diff --cached --quiet || git commit -m "Update translatable texts" -m "[skip ci]"
git push
with:
commit_message: |
Update translatable texts
[skip ci]
commit_user_name: SABnzbd Automation
commit_user_email: bugs@sabnzbd.org
commit_author: SABnzbd Automation <bugs@sabnzbd.org>
file_pattern: "po/*.pot po/*.po"
push_options: --force
-201
View File
@@ -1,201 +0,0 @@
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: ${{ 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: ${{ 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: ${{ 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
+10 -4
View File
@@ -29,8 +29,15 @@ SABnzbd-*/
# WingIDE/PyCharm project files
*.wp[ru]
.idea
# VScode
.vscode/
# Testing folders
.cache
.xprocess
.pytest*
tests/cache
# General junk
@@ -38,10 +45,9 @@ tests/cache
*.bak
*.log
# Ignore all hidden directories at repo root, except used ones
.*/
!.github/
!.tx/
# Some people use Emacs as an editor
\#*
.\#*
# Apple
.DS_Store
+18 -5
View File
@@ -1,11 +1,10 @@
(c) Copyright 2007-2026 by The SABnzbd-Team (sabnzbd.org)
(c) Copyright 2007-2023 by The SABnzbd-Team (sabnzbd.org)
The SABnzbd-Team is:
Active team:
Safihre
mnightingale
sanderjo
jcfp
inpheaux
@@ -18,8 +17,21 @@ Sleeping members:
Honorary member (and original author):
Gregor Kaufmann
The contributors and moderators of the translations are credited in the
header of each translation file in the po/ directory.
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
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
@@ -32,6 +44,7 @@ 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, see <https://www.gnu.org/licenses/>.
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
See accompanying files GPL2.txt and GPL3.txt.
+110
View File
@@ -0,0 +1,110 @@
SABnzbd
-------------------------------------------------------------------------------
0) LICENSE
-------------------------------------------------------------------------------
(c) Copyright 2007-2023 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.8 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.
+1 -1
View File
@@ -1,4 +1,4 @@
(c) Copyright 2007-2026 by The SABnzbd-Team (sabnzbd.org)
(c) Copyright 2007-2023 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
+4 -13
View File
@@ -2,7 +2,7 @@ SABnzbd - The automated Usenet download tool
============================================
[![License](https://img.shields.io/badge/License-GPL%20v2-blue.svg)](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html)
[![Join our Discord](https://img.shields.io/discord/976737547558461480?color=7289DA&label=Discord&logo=Discord&logoColor=white)](https://discord.sabnzbd.org)
[![Join our Discord](https://img.shields.io/discord/976737547558461480?color=7289DA&label=Discord&logo=Discord&logoColor=white)](https://discord.gg/KQzDe7fvNU)
SABnzbd is an Open Source Binary Newsreader written in Python.
@@ -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.10 and above, often called `python3`)
- Python modules listed in `requirements.txt`. Install with `python3 -m pip install -r requirements.txt`
- `python` (Python 3.8 and above, often called `python3`)
- Python modules listed in `requirements.txt`. Install with `python3 -m pip install -r requirements.txt -U`
- `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://sabnzbd.org/wiki/installation/install-off-modules).
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).
## Running SABnzbd from source
@@ -66,12 +66,3 @@ Conditions:
- Bugfixes created specifically for a release branch are done there (because they are specific, they're not cherry-picked to `develop`).
- Bugfixes done on `develop` may be cherry-picked to a release branch.
- We will not release a 1.0.2 if a 1.1.0 has already been released.
## Privacy Policy
This program will not transfer any information to other networked systems unless
specifically requested by the user or the person installing or operating it.
## Code Signing Policy
For our Windows release, free code signing is provided by [SignPath.io](https://signpath.io), certificate by [SignPath Foundation](https://signpath.org).
+23 -116
View File
@@ -1,126 +1,33 @@
Release Notes - SABnzbd 5.1.3
Release Notes - SABnzbd 4.2.0 Alpha 2
=========================================================
## Security fixes
## Changes since 4.1.0
- Numerous smaller performance improvements were made.
- Reduced recursive unpacking to 2 levels, instead of 5.
- IPv6 addresses are preferred during server address selection.
- Stricter check if `Complete Folder` is inside `Download Folder`.
- Windows: Reduced size of installer.
- Windows/macOS: Updated to Python 3.12.
SABnzbd 5.1.1, 5.1.2 and 5.1.3 resolve a number of security vulnerabilities.
Over the past releases, several security researchers have put a lot of time and effort
examining SABnzbd, responsibly reporting their findings, and helping us verify the fixes.
We are grateful for their hard work, it has made SABnzbd meaningfully safer for everyone.
For most users the risk is limited: you are only affected if your setup is exposed to
untrusted parties. The web interface issues only apply if it can be reached by someone
you do not trust. By default, SABnzbd is only accessible from your own device and
`External internet access` is set to `No access`. If either of those is still at its
default, or if you use a proxy service for authentication, you are not affected.
The download-processing issues only apply if SABnzbd handles NZBs from a source you
do not fully trust, such as a public indexer.
If none of that describes your setup, you were never at risk.
The safest action is simply to update to 5.1.3.
Full details, affected versions, actions and mitigations for each vulnerability are described at:
https://sabnzbd.org/5-1-vulnerabilities
Vulnerabilities resolved:
* GHSA-q326-jpxx-jmjc: `__wrapped__` dispatch bypass allows unauthenticated API access, fixed in 5.1.3.
* GHSA-mjwj-v5mr-cmcg: PAR2 symlink bypass allows pickle remote code execution, fixed in 5.1.3.
* GHSA-rgqj-28c2-gxwp: Critical remote code execution via authentication bypass on configuration endpoints, fixed in 5.1.2.
* GHSA-75g3-96fr-7p2r: High-severity path traversal during post-processing via crafted PAR2/SFV files, fixed in 5.1.2.
* GHSA-xrfq-jhgh-wqch: Critical authentication bypass allowing a valid session without credentials, fixed in 5.1.1.
If you rely on the SABnzbd username and password to keep out untrusted parties, and your
setup was exposed, it is recommended that you rotate your SABnzbd username/password and
API-key, Usenet server passwords, indexer API-keys used in RSS-feeds, and notification
service credentials after updating. See the page above for the full guidance.
## Other changes and bug fixes in 5.1.3
* Tighten Compact display mode.
* Directly show usenet server errors instead of parsing them.
* Prevent `database is locked` error when re-evaluating stored RSS jobs.
* Added tooltip to explain search filters in Queue and History.
* Windows and macOS: Updated 7zip to 26.03.
## Other bug fixes in 5.1.2
* RSS filters were not applied directly.
## Other bug fixes in 5.1.1
* Styling cache issues could occur after updating.
* RSS items were removed too eagerly from RSS-history after download.
* The RSS-feed `Clear Downloaded` button did not do anything.
## Changelog 5.1.0
This release brings a fundamental improvement to "Retry": instead of
re-downloading any files with missing data, only the articles that were actually
missing are fetched again. RSS got an overhaul under the hood, the interface is
refreshed, and we added quite a long list of long-requested features and bugfixes.
## New features in 5.1.0
* 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.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.
* Don't log Warning during clean shutdowns.
* 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.
* macOS: Included par2cmdline-turbo will now use all available CPU-features.
## Bugfixes since 4.1.0
- Multi-select in the queue was broken for some users.
- Prevent crash during saving of configuration.
- Removing a failed download from the history could break active downloads.
## Upgrade notices
* You can directly upgrade from version 3.0.0 and newer.
* Upgrading from older versions will require performing a `Queue repair`.
- Direct upgrade is possible from version 3.0.0 and newer.
Upgrading from older versions will require `Queue repair`.
- Downgrading from version 4.2.0 or newer to 3.7.2 or older will
require `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
Windows code signing is provided by SignPath.io using a SignPath Foundation certificate.
- Read the file "ISSUES.txt"
## About
SABnzbd is an open-source cross-platform binary newsreader.
It simplifies the process of downloading from Usenet dramatically, thanks
to its web-based user interface and advanced built-in post-processing options
that automatically verify, repair, extract and clean up posts downloaded
from Usenet.
SABnzbd is an open-source cross-platform binary newsreader.
It simplifies the process of downloading from Usenet dramatically, thanks to its web-based
user interface and advanced built-in post-processing options that automatically verify, repair,
extract and clean up posts downloaded from Usenet.
(c) Copyright 2007-2026 by The SABnzbd-Team (sabnzbd.org)
(c) Copyright 2007-2023 by The SABnzbd-Team (sabnzbd.org)
+294 -187
View File
File diff suppressed because it is too large. Load diff
+49 -54
View File
@@ -1,5 +1,6 @@
# -*- mode: python -*-
import os
import re
import sys
from PyInstaller.building.api import EXE, COLLECT, PYZ
@@ -7,14 +8,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.common import EXTRA_FILES, EXTRA_FOLDERS, RELEASE_VERSION, RELEASE_VERSION_BASE, RELEASE_VERSION_TUPLE
from builder.constants import EXTRA_FILES, EXTRA_FOLDERS, RELEASE_VERSION
# Add extra files in the PyInstaller-spec
extra_pyinstaller_files = []
# Add hidden imports
extra_hiddenimports = ["Cheetah.DummyTransaction", "cheroot.ssl.builtin", "certifi"]
extra_hiddenimports.extend(collect_submodules("apprise"))
extra_hiddenimports.extend(collect_submodules("babelfish.converters"))
extra_hiddenimports.extend(collect_submodules("guessit.data"))
@@ -22,11 +22,11 @@ extra_hiddenimports.extend(collect_submodules("guessit.data"))
if sys.platform == "darwin":
extra_hiddenimports.extend(["objc", "PyObjCTools"])
# macOS folders
EXTRA_FOLDERS += ["macos/par2/", "macos/unrar/", "macos/7zip/"]
EXTRA_FOLDERS += ["osx/par2/", "osx/unrar/", "osx/7zip/"]
# Add NZB-icon file
extra_pyinstaller_files.append(("builder/macos/image/nzbfile.icns", "."))
extra_pyinstaller_files.append(("builder/osx/image/nzbfile.icns", "."))
# Version information is set differently on macOS
version_info_console = version_info = None
version_info = None
else:
# Build would fail on non-Windows
from PyInstaller.utils.win32.versioninfo import (
@@ -40,49 +40,47 @@ else:
)
# Windows
extra_hiddenimports.extend(["win32timezone", "winrt.windows.foundation.collections"])
EXTRA_FOLDERS += ["win/par2/", "win/unrar/", "win/7zip/"]
extra_hiddenimports.append("win32timezone")
EXTRA_FOLDERS += ["win/multipar/", "win/par2/", "win/unrar/", "win/7zip/"]
EXTRA_FILES += ["portable.cmd"]
# Parse the version info
version_regexed = re.search(r"(\d+)\.(\d+)\.(\d+)([a-zA-Z]*)(\d*)", RELEASE_VERSION)
version_tuple = (int(version_regexed.group(1)), int(version_regexed.group(2)), int(version_regexed.group(3)), 0)
# Detailed instructions are in the PyInstaller documentation
# We don't include the alpha/beta/rc in the counters
def make_version_info(filename):
return VSVersionInfo(
ffi=FixedFileInfo(
filevers=RELEASE_VERSION_TUPLE,
prodvers=RELEASE_VERSION_TUPLE,
mask=0x3F,
flags=0x0,
OS=0x40004,
fileType=0x1,
subtype=0x0,
date=(0, 0),
version_info = VSVersionInfo(
ffi=FixedFileInfo(
filevers=version_tuple,
prodvers=version_tuple,
mask=0x3F,
flags=0x0,
OS=0x40004,
fileType=0x1,
subtype=0x0,
date=(0, 0),
),
kids=[
StringFileInfo(
[
StringTable(
"040904B0",
[
StringStruct("Comments", f"SABnzbd {RELEASE_VERSION}"),
StringStruct("CompanyName", "The SABnzbd-Team"),
StringStruct("FileDescription", f"SABnzbd {RELEASE_VERSION}"),
StringStruct("FileVersion", RELEASE_VERSION),
StringStruct("LegalCopyright", "The SABnzbd-Team"),
StringStruct("ProductName", f"SABnzbd {RELEASE_VERSION}"),
StringStruct("ProductVersion", RELEASE_VERSION),
],
)
]
),
kids=[
StringFileInfo(
[
StringTable(
"040904B0",
[
StringStruct("Comments", f"SABnzbd {RELEASE_VERSION}"),
StringStruct("CompanyName", "The SABnzbd-Team"),
StringStruct("FileDescription", f"SABnzbd {RELEASE_VERSION}"),
StringStruct("FileVersion", RELEASE_VERSION),
StringStruct("InternalName", "SABnzbd"),
StringStruct("LegalCopyright", "The SABnzbd-Team"),
StringStruct("OriginalFilename", filename),
StringStruct("ProductName", f"SABnzbd {RELEASE_VERSION}"),
StringStruct("ProductVersion", RELEASE_VERSION),
],
)
]
),
VarFileInfo([VarStruct("Translation", [1033, 1200])]),
],
)
version_info = make_version_info("SABnzbd.exe")
version_info_console = make_version_info("SABnzbd-console.exe")
VarFileInfo([VarStruct("Translation", [1033, 1200])]),
],
)
# Process the extra-files and folders
for file_item in EXTRA_FILES:
@@ -93,18 +91,15 @@ for folder_item in EXTRA_FOLDERS:
# Add babelfish data files
extra_pyinstaller_files.extend(collect_data_files("babelfish"))
extra_pyinstaller_files.extend(collect_data_files("guessit"))
extra_pyinstaller_files.extend(collect_data_files("apprise"))
extra_pyinstaller_files.extend(collect_data_files("dateutil"))
pyi_analysis = Analysis(
["SABnzbd.py"],
datas=extra_pyinstaller_files,
hiddenimports=extra_hiddenimports,
excludes=["ujson", "FixTk", "tcl", "tk", "_tkinter", "tkinter", "Tkinter", "pydoc", "pydoc_data.topics"],
module_collection_mode={"apprise.plugins": "py"},
)
pyz = PYZ(pyi_analysis.pure)
pyz = PYZ(pyi_analysis.pure, pyi_analysis.zipped_data)
codesign_identity = os.environ.get("SIGNING_AUTH")
if not codesign_identity:
@@ -124,30 +119,31 @@ exe = EXE(
contents_directory=".",
version=version_info,
target_arch="universal2",
entitlements_file="builder/macos/entitlements.plist",
entitlements_file="builder/osx/entitlements.plist",
codesign_identity=codesign_identity,
)
coll = COLLECT(exe, pyi_analysis.binaries, pyi_analysis.datas, name="SABnzbd")
coll = COLLECT(exe, pyi_analysis.binaries, pyi_analysis.zipfiles, pyi_analysis.datas, name="SABnzbd")
# We need to run again for the console-app
if sys.platform == "win32":
# Enable console=True for this one
console_exe = EXE(
pyz,
pyi_analysis.scripts,
[],
exclude_binaries=True,
name="SABnzbd-console",
console=True,
append_pkg=False,
icon="icons/sabnzbd.ico",
contents_directory=".",
version=version_info_console,
version=version_info,
)
console_coll = COLLECT(
console_exe,
pyi_analysis.binaries,
pyi_analysis.zipfiles,
pyi_analysis.datas,
name="SABnzbd-console",
)
@@ -158,7 +154,6 @@ if sys.platform == "darwin":
"NSUIElement": 1,
"NSPrincipalClass": "NSApplication",
"CFBundleShortVersionString": RELEASE_VERSION,
"CFBundleVersion": RELEASE_VERSION_BASE,
"NSHumanReadableCopyright": "The SABnzbd-Team",
"CFBundleIdentifier": "org.sabnzbd.sabnzbd",
"CFBundleDocumentTypes": [
@@ -172,14 +167,14 @@ if sys.platform == "darwin":
"NSPersistentStoreTypeKey": "Binary",
}
],
"LSMinimumSystemVersion": "10.15",
"LSMinimumSystemVersion": "10.9",
"LSEnvironment": {"LANG": "en_US.UTF-8", "LC_ALL": "en_US.UTF-8"},
}
app = BUNDLE(
coll,
name="SABnzbd.app",
icon="builder/macos/image/sabnzbdplus.icns",
icon="builder/osx/image/sabnzbdplus.icns",
bundle_identifier="org.sabnzbd.sabnzbd",
info_plist=info_plist,
)
+10 -32
View File
@@ -1,5 +1,5 @@
#!/usr/bin/python3 -OO
# Copyright 2008-2026 by The SABnzbd-Team (sabnzbd.org)
# Copyright 2008-2017 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
@@ -16,8 +16,6 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
import os
import platform
import re
# Constants
VERSION_FILE = "sabnzbd/version.py"
@@ -32,33 +30,24 @@ with open(VERSION_FILE) as version_file:
exec(version_file.read())
RELEASE_VERSION = __version__
# 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))
# 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
# Define release name
RELEASE_NAME = "SABnzbd-%s" % RELEASE_VERSION
RELEASE_TITLE = "SABnzbd %s" % RELEASE_VERSION
RELEASE_SRC = RELEASE_NAME + "-src.tar.gz"
RELEASE_WIN_BIN_X64 = RELEASE_NAME + "-win64-bin.zip"
RELEASE_WIN_BIN_ARM64 = RELEASE_NAME + "-win-arm64-bin.zip"
RELEASE_WIN_INSTALLER = RELEASE_NAME + "-win-setup.exe"
RELEASE_MACOS = RELEASE_NAME + "-macos.dmg"
RELEASE_BINARY_32 = RELEASE_NAME + "-win32-bin.zip"
RELEASE_BINARY_64 = RELEASE_NAME + "-win64-bin.zip"
RELEASE_INSTALLER = RELEASE_NAME + "-win-setup.exe"
RELEASE_MACOS = RELEASE_NAME + "-osx.dmg"
RELEASE_README = "README.mkd"
# Detect architecture
RELEASE_WIN_BIN = RELEASE_WIN_BIN_X64
if platform.machine() == "ARM64":
RELEASE_WIN_BIN = RELEASE_WIN_BIN_ARM64
# Used in package.py and SABnzbd.spec
EXTRA_FILES = [
RELEASE_README,
"README.html",
"README.txt",
"INSTALL.txt",
"LICENSE.txt",
"GPL2.txt",
"GPL3.txt",
@@ -73,17 +62,6 @@ EXTRA_FOLDERS = [
"interfaces/Glitter/",
"interfaces/wizard/",
"interfaces/Config/",
"scripts/",
"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
+206
View File
@@ -0,0 +1,206 @@
#!/usr/bin/python3 -OO
# Copyright 2008-2017 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.
import os
from constants import RELEASE_VERSION
# We need to call dmgbuild from command-line, so here we can setup how
if __name__ == "__main__":
# Check for DMGBuild
try:
import dmgbuild
except:
print("Requires dmgbuild-module, use pip install dmgbuild")
exit()
# Make sure we are in the src folder
if not os.path.exists("builder"):
raise FileNotFoundError("Run from the main SABnzbd source folder: python builder/package.py")
# Check if signing is possible
authority = os.environ.get("SIGNING_AUTH")
# Extract version info and set DMG path
# Create sub-folder to upload later
release = RELEASE_VERSION
prod = "SABnzbd-" + release
fileDmg = prod + "-osx.dmg"
# Path to app file
apppath = "dist/SABnzbd.app"
# Copy Readme
readmepath = os.path.join(apppath, "Contents/Resources/README.txt")
# Path to background and the icon
backgroundpath = "builder/osx/image/sabnzbd_new_bg.png"
iconpath = "builder/osx/image/sabnzbdplus.icns"
# Make DMG
print("Building DMG")
dmgbuild.build_dmg(
filename=fileDmg,
volume_name=prod,
settings_file="builder/make_dmg.py",
defines={"app": apppath, "readme": readmepath, "background": backgroundpath, "iconpath": iconpath},
)
# Resign APP
if authority:
print("Siging DMG")
os.system('codesign --deep -f -i "org.sabnzbd.SABnzbd" -s "%s" "%s"' % (authority, fileDmg))
print("Signed!")
else:
print("Signing skipped, missing SIGNING_AUTH.")
exit()
### START OF DMGBUILD SETTINGS
### COPIED AND MODIFIED FROM THE EXAMPLE ONLINE
application = defines.get("app", "AppName.app")
readme = defines.get("readme", "ReadMe.rtf")
appname = os.path.basename(application)
# .. Basics ....................................................................
# Volume format (see hdiutil create -help)
format = defines.get("format", "UDBZ")
# Volume size (must be large enough for your files)
size = defines.get("size", "100M")
# Files to include
files = [application, readme]
# Symlinks to create
symlinks = {"Applications": "/Applications"}
# Volume icon
#
# You can either define icon, in which case that icon file will be copied to the
# image, *or* you can define badge_icon, in which case the icon file you specify
# will be used to badge the system's Removable Disk icon
#
badge_icon = defines.get("iconpath", "")
# Where to put the icons
icon_locations = {readme: (70, 160), appname: (295, 220), "Applications": (510, 220)}
# .. Window configuration ......................................................
# Window position in ((x, y), (w, h)) format
window_rect = ((100, 100), (660, 360))
# Background
#
# This is a STRING containing any of the following:
#
# #3344ff - web-style RGB color
# #34f - web-style RGB color, short form (#34f == #3344ff)
# rgb(1,0,0) - RGB color, each value is between 0 and 1
# hsl(120,1,.5) - HSL (hue saturation lightness) color
# hwb(300,0,0) - HWB (hue whiteness blackness) color
# cmyk(0,1,0,0) - CMYK color
# goldenrod - X11/SVG named color
# builtin-arrow - A simple built-in background with a blue arrow
# /foo/bar/baz.png - The path to an image file
#
# Other color components may be expressed either in the range 0 to 1, or
# as percentages (e.g. 60% is equivalent to 0.6).
background = defines.get("background", "builtin-arrow")
show_status_bar = False
show_tab_view = False
show_toolbar = False
show_pathbar = False
show_sidebar = False
sidebar_width = 0
# Select the default view; must be one of
#
# 'icon-view'
# 'list-view'
# 'column-view'
# 'coverflow'
#
default_view = "icon-view"
# General view configuration
show_icon_preview = False
# Set these to True to force inclusion of icon/list view settings (otherwise
# we only include settings for the default view)
include_icon_view_settings = "auto"
include_list_view_settings = "auto"
# .. Icon view configuration ...................................................
arrange_by = None
grid_offset = (0, 0)
grid_spacing = 50
scroll_position = (0, 0)
label_pos = "bottom" # or 'right'
text_size = 16
icon_size = 64
# .. List view configuration ...................................................
# Column names are as follows:
#
# name
# date-modified
# date-created
# date-added
# date-last-opened
# size
# kind
# label
# version
# comments
#
list_icon_size = 16
list_text_size = 12
list_scroll_position = (0, 0)
list_sort_by = "name"
list_use_relative_dates = True
list_calculate_all_sizes = (False,)
list_columns = ("name", "date-modified", "size", "kind", "date-added")
list_column_widths = {
"name": 300,
"date-modified": 181,
"date-created": 181,
"date-added": 181,
"date-last-opened": 181,
"size": 97,
"kind": 115,
"label": 100,
"version": 75,
"comments": 300,
}
list_column_sort_directions = {
"name": "ascending",
"date-modified": "descending",
"date-created": "descending",
"date-added": "descending",
"date-last-opened": "descending",
"size": "descending",
"kind": "ascending",
"label": "ascending",
"version": "ascending",
"comments": "ascending",
}
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
+3
View File
@@ -0,0 +1,3 @@
# Special requirements for macOS universal2 binary release
# This way dependabot can auto-update them
cryptography==41.0.5
+83 -219
View File
@@ -1,5 +1,5 @@
#!/usr/bin/python3 -OO
# Copyright 2008-2026 by The SABnzbd-Team (sabnzbd.org)
# Copyright 2008-2017 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
@@ -16,7 +16,7 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
import glob
import importlib.util
import platform
import re
import sys
import os
@@ -28,27 +28,21 @@ import tarfile
import urllib.request
import urllib.error
import configobj
import markdown
import packaging.version
import xml.etree.ElementTree as ET
from typing import List
from common import (
from constants import (
RELEASE_VERSION,
RELEASE_VERSION_TUPLE,
VERSION_FILE,
RELEASE_README,
RELEASE_NAME,
RELEASE_MACOS,
RELEASE_WIN_BIN,
RELEASE_WIN_INSTALLER,
RELEASE_BINARY_32,
RELEASE_BINARY_64,
RELEASE_INSTALLER,
ON_GITHUB_ACTIONS,
RELEASE_THIS,
RELEASE_SRC,
EXTRA_FILES,
EXTRA_FOLDERS,
APPDATA_FILE,
RELEASE_VERSION_BASE,
pe_has_authenticode_signature,
)
@@ -76,9 +70,9 @@ def delete_files_glob(glob_pattern: str, allow_no_matches: bool = False):
raise FileNotFoundError(f"No files found that match '{glob_pattern}'")
def run_external_command(command: list[str], print_output: bool = True, **kwargs):
def run_external_command(command: List[str], print_output: bool = True):
"""Wrapper to ease the use of calling external programs"""
process = subprocess.Popen(command, text=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, **kwargs)
process = subprocess.Popen(command, text=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
output, _ = process.communicate()
ret = process.wait()
if (output and print_output) or ret != 0:
@@ -90,7 +84,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):
@@ -115,89 +109,6 @@ def patch_version_file(release_name):
ver.write(version_file)
def verify_appdata():
"""Verify that appdata file is updated"""
if not isinstance(
ET.parse(APPDATA_FILE).find(f"./releases/release[@version='{RELEASE_VERSION_BASE}']"),
ET.Element,
):
release_missing = f"Could not find {RELEASE_VERSION_BASE} in {APPDATA_FILE}"
if RELEASE_THIS:
raise RuntimeError(release_missing)
elif ON_GITHUB_ACTIONS:
print(f"::warning file={APPDATA_FILE},title=Missing release::{release_missing}")
else:
print(release_missing)
def build_readme_html(output_path: str):
"""Convert the release notes Markdown to a styled HTML file."""
with open(RELEASE_README, "r", encoding="utf-8") as f:
readme_html = markdown.markdown(f.read(), extensions=["nl2br"])
# Linkify bare URLs not already inside an HTML attribute
readme_html = re.sub(
r'(?<![="\'(])https?://[^\s<>"\']+',
lambda m: f'<a href="{m.group()}" target="_blank">{m.group()}</a>',
readme_html,
)
with open(output_path, "w", encoding="utf-8") as f:
f.write(
f'<!DOCTYPE html><html><head><meta charset="utf-8">'
f"<title>SABnzbd {RELEASE_VERSION} Release Notes</title>"
f'<link rel="icon" href="icons/logo-arrow.svg">'
f"<style>body{{font-family:system-ui,sans-serif;max-width:800px;margin:40px auto;padding:0 20px}}</style>"
f"</head><body>{readme_html}</body></html>"
)
def test_macos_min_version(binary_path: str):
# Skip check if nothing was set
if macos_min_version := os.environ.get("MACOSX_DEPLOYMENT_TARGET"):
# Skip any arm64 specific files
if "arm64" in binary_path:
print(f"Skipping arm64 binary {binary_path}")
return
# Check minimum macOS version is at least mac OS10.13
# We only check the x86_64 since for arm64 it's always macOS 11+
print(f"Checking if binary supports macOS {macos_min_version} and above: {binary_path}")
otool_output = run_external_command(
[
"otool",
"-arch",
"x86_64",
"-l",
binary_path,
],
print_output=False,
)
# Parse the output for LC_BUILD_VERSION minos
# The output is very large, so that's why we enumerate over it
req_version = packaging.version.parse(macos_min_version)
bin_version = None
lines = otool_output.split("\n")
for line_nr, line in enumerate(lines):
if "LC_VERSION_MIN_MACOSX" in line:
# Display the version in the next lines
bin_version = packaging.version.parse(lines[line_nr + 2].split()[1])
elif "minos" in line:
bin_version = packaging.version.parse(line.split()[1])
if bin_version and bin_version > req_version:
raise ValueError(f"{binary_path} requires {bin_version}, we want {req_version}")
else:
# We got the information we need
break
else:
print(lines)
raise RuntimeError(f"Could not determine minimum macOS version for {binary_path}")
else:
print("Skipping macOS version check, MACOSX_DEPLOYMENT_TARGET not set")
def test_sab_binary(binary_path: str):
"""Wrapper to have a simple start-up test for the binary"""
with tempfile.TemporaryDirectory() as config_dir:
@@ -214,7 +125,7 @@ def test_sab_binary(binary_path: str):
try:
urllib.request.urlopen(base_url, timeout=1).read()
break
except Exception:
except:
time.sleep(1)
else:
# Print console output and give some time to print
@@ -270,18 +181,17 @@ if __name__ == "__main__":
raise FileNotFoundError("Run from the main SABnzbd source folder: python builder/package.py")
# Check if we have the needed certificates
if importlib.util.find_spec("certifi") is None:
try:
import certifi
except ImportError:
raise FileNotFoundError("Need certifi module")
# Check if we have correct appdata file
verify_appdata()
# Patch release file
patch_version_file(RELEASE_VERSION)
# Convert release notes to HTML for the installer finish page
safe_remove("README.html")
build_readme_html("README.html")
# Rename release notes file
safe_remove("README.txt")
shutil.copyfile(RELEASE_README, "README.txt")
# Compile translations
if not os.path.exists("locale"):
@@ -291,21 +201,30 @@ if __name__ == "__main__":
if not os.path.exists("locale"):
raise FileNotFoundError("Failed to compile language files")
if "binary" in sys.argv:
# Make sure we remove any existing build-folders
safe_remove("build")
safe_remove("dist")
safe_remove(RELEASE_NAME)
# Copy the specification
shutil.copyfile("builder/SABnzbd.spec", "SABnzbd.spec")
if "binary" in sys.argv or "installer" in sys.argv:
# Must be run on Windows
if sys.platform != "win32":
raise RuntimeError("Binary should be created on Windows")
# Make sure we remove any existing build-folders
safe_remove("build")
safe_remove("dist")
# Check what architecture we are on
RELEASE_BINARY = RELEASE_BINARY_32
BUILDING_64BIT = False
if platform.architecture()[0] == "64bit":
RELEASE_BINARY = RELEASE_BINARY_64
BUILDING_64BIT = True
# Remove any leftovers
safe_remove(RELEASE_NAME)
safe_remove(RELEASE_WIN_BIN)
safe_remove(RELEASE_BINARY)
# Run PyInstaller and check output
shutil.copyfile("builder/SABnzbd.spec", "SABnzbd.spec")
run_external_command([sys.executable, "-O", "-m", "PyInstaller", "SABnzbd.spec"])
shutil.copytree("dist/SABnzbd-console", "dist/SABnzbd", dirs_exist_ok=True)
@@ -313,53 +232,47 @@ if __name__ == "__main__":
# Remove unwanted DLL's
shutil.rmtree("dist/SABnzbd/Pythonwin")
delete_files_glob("dist/SABnzbd/api-ms-win*.dll", allow_no_matches=True)
delete_files_glob("dist/SABnzbd/ucrtbase.dll", allow_no_matches=True)
if BUILDING_64BIT:
# These are only present on 64bit (Python 3.9+)
delete_files_glob("dist/SABnzbd/api-ms-win*.dll", allow_no_matches=True)
delete_files_glob("dist/SABnzbd/ucrtbase.dll", allow_no_matches=True)
# Test the release
test_sab_binary("dist/SABnzbd/SABnzbd.exe")
# Remove 32bit external executables
delete_files_glob("dist/SABnzbd/win/par2/par2.exe")
delete_files_glob("dist/SABnzbd/win/multipar/par2j.exe")
delete_files_glob("dist/SABnzbd/win/unrar/UnRAR.exe")
if "installer" in sys.argv:
# Needs to be run on 64 bit
if not BUILDING_64BIT:
raise RuntimeError("Installer should be created on 64bit Python")
# Compile NSIS translations
safe_remove("NSIS_Installer.nsi")
safe_remove("NSIS_Installer.nsi.tmp")
shutil.copyfile("builder/win/NSIS_Installer.nsi", "NSIS_Installer.nsi")
run_external_command([sys.executable, "tools/make_mo.py", "nsis"])
# Run NSIS to build installer
run_external_command(
[
"makensis.exe",
"/V3",
"/DSAB_PRODUCT=%s" % RELEASE_NAME,
"/DSAB_VERSION=%s" % RELEASE_VERSION,
"/DSAB_FILE=%s" % RELEASE_INSTALLER,
"NSIS_Installer.nsi.tmp",
]
)
# Rename the folder
shutil.copytree("dist/SABnzbd", RELEASE_NAME)
# Create the archive
run_external_command(["win/7zip/7za.exe", "a", RELEASE_WIN_BIN, "SABnzbd"], cwd="dist")
shutil.move(f"dist/{RELEASE_WIN_BIN}", RELEASE_WIN_BIN)
run_external_command(["win/7zip/7za.exe", "a", RELEASE_BINARY, RELEASE_NAME])
if "installer" in sys.argv:
# Check if we have the dist folder
if not os.path.exists("dist/SABnzbd/SABnzbd.exe"):
raise FileNotFoundError("SABnzbd executable not found, run binary creation first")
# Check if we have a signed version
if os.path.exists(f"signed/{RELEASE_WIN_BIN}"):
print("Using signed version of SABnzbd binaries")
safe_remove("dist/SABnzbd")
run_external_command(["win/7zip/7za.exe", "x", "-odist", f"signed/{RELEASE_WIN_BIN}"])
# 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:
print("Using unsigned version of SABnzbd binaries")
# Run NSIS to build installer
run_external_command(
[
"makensis.exe",
"/V3",
"/NOCD", # No not change working directory
"/DSAB_VERSION=%s" % RELEASE_VERSION,
"/DSAB_VERSIONKEY=%s" % ".".join(map(str, RELEASE_VERSION_TUPLE)),
"/DSAB_FILE=%s" % RELEASE_WIN_INSTALLER,
"builder/win/NSIS_Installer.nsi",
]
)
# Test the release, as the very last step to not mess with any release code
test_sab_binary("dist/SABnzbd/SABnzbd.exe")
if "app" in sys.argv:
# Must be run on macOS
@@ -375,17 +288,14 @@ if __name__ == "__main__":
# Otherwise the signature of the main application becomes invalid
if authority:
files_to_sign = [
"macos/par2/par2",
"macos/unrar/unrar",
"macos/unrar/arm64/unrar",
"macos/7zip/7zz",
"osx/par2/par2-turbo",
"osx/par2/arm64/par2-turbo",
"osx/unrar/unrar",
"osx/unrar/arm64/unrar",
"osx/7zip/7zz",
]
for file_to_sign in files_to_sign:
# Make sure it supports the macOS versions we want first
test_macos_min_version(file_to_sign)
# Then sign in
print("Signing %s with hardened runtime" % file_to_sign)
print("Signing %s with hardended runtime" % file_to_sign)
run_external_command(
[
"codesign",
@@ -395,7 +305,7 @@ if __name__ == "__main__":
"--options",
"runtime",
"--entitlements",
"builder/macos/entitlements.plist",
"builder/osx/entitlements.plist",
"-s",
authority,
file_to_sign,
@@ -405,22 +315,17 @@ if __name__ == "__main__":
print("Signed %s!" % file_to_sign)
# Run PyInstaller and check output
shutil.copyfile("builder/SABnzbd.spec", "SABnzbd.spec")
run_external_command([sys.executable, "-O", "-m", "PyInstaller", "SABnzbd.spec"])
# Make sure we created a fully universal2 release when releasing or during CI
# PyInstaller also includes a builtin check for this, but we double-check every executable
if RELEASE_THIS or ON_GITHUB_ACTIONS:
for bin_to_check in glob.glob("dist/SABnzbd.app/**/*.so", recursive=True):
for bin_to_check in glob.glob("dist/SABnzbd.app/Contents/MacOS/**/*.so", recursive=True):
print("Checking if binary is universal2: %s" % bin_to_check)
file_output = run_external_command(["file", bin_to_check], print_output=False)
# Make sure we have both arm64 and x86
if not ("x86_64" in file_output and "arm64" in file_output):
raise RuntimeError("Non-universal2 binary found!")
# Make sure it supports the macOS versions we want
test_macos_min_version(bin_to_check)
# Only continue if we can sign
if authority:
# We use PyInstaller to sign the main SABnzbd executable and the SABnzbd.app
@@ -450,8 +355,8 @@ if __name__ == "__main__":
if authority not in sign_result or "adhoc" in sign_result or "invalid" in sign_result:
raise RuntimeError("Signature of %s seems invalid!" % file_to_check)
# Always notarize, as newer macOS versions don't allow any code without it
if notarization_user and notarization_pass:
# Only notarize for real builds that we want to deploy
if notarization_user and notarization_pass and RELEASE_THIS:
# Prepare zip to upload to notarization service
print("Creating zip to send to Apple notarization service")
# We need to use ditto, otherwise the signature gets lost!
@@ -485,6 +390,8 @@ if __name__ == "__main__":
# Staple the notarization!
print("Approved! Stapling the result to the app")
run_external_command(["xcrun", "stapler", "staple", "dist/SABnzbd.app"])
elif notarization_user and notarization_pass:
print("Notarization skipped, tag commit to trigger notarization!")
else:
print("Notarization skipped, NOTARIZATION_USER or NOTARIZATION_PASS missing.")
else:
@@ -493,46 +400,6 @@ if __name__ == "__main__":
# Test the release, as the very last step to not mess with any release code
test_sab_binary("dist/SABnzbd.app/Contents/MacOS/SABnzbd")
if "dmg" in sys.argv:
# Must be run on macOS
if sys.platform != "darwin":
raise RuntimeError("DMG should be created on macOS")
# Only available for macOS
import dmgbuild
apppath = "dist/SABnzbd.app"
readmepath = os.path.join(apppath, "Contents/Resources/README.html")
print("Building DMG")
dmgbuild.build_dmg(
filename=RELEASE_MACOS,
volume_name=RELEASE_NAME,
settings={
"files": [apppath, readmepath],
"symlinks": {"Applications": "/Applications"},
"icon": "builder/macos/image/sabnzbdplus.icns",
"background": "builder/macos/image/sabnzbd_new_bg.png",
"icon_locations": {
os.path.basename(readmepath): (70, 160),
os.path.basename(apppath): (295, 220),
"Applications": (510, 220),
},
"window_rect": ((100, 100), (660, 360)),
"sidebar_width": 0,
"grid_spacing": 50,
"icon_size": 64,
},
callback=lambda info: print("dmgbuild", info),
)
if authority := os.environ.get("SIGNING_AUTH"):
print("Signing DMG")
run_external_command(["codesign", "-f", "-i", "org.sabnzbd.sabnzbd", "-s", authority, RELEASE_MACOS])
print("Signed!")
else:
print("Signing skipped, missing SIGNING_AUTH.")
if "source" in sys.argv:
# Prepare Source distribution package.
# We assume the sources are freshly cloned from the repo
@@ -557,10 +424,7 @@ if __name__ == "__main__":
shutil.copyfile(source_file, os.path.join(src_folder, source_file))
# Make sure all line-endings are correct
# Skip test data directories which contain binary files with misleading extensions
for input_filename in glob.glob("%s/**/*.*" % src_folder, recursive=True):
if os.path.join("tests", "data", "") in input_filename:
continue
base, ext = os.path.splitext(input_filename)
if ext.lower() not in (".py", ".txt", ".css", ".js", ".tmpl", ".sh", ".cmd"):
continue
@@ -585,7 +449,7 @@ if __name__ == "__main__":
tarinfo.uid = 0
tarinfo.gid = 0
if _file in ("SABnzbd.py", "Sample-PostProc.sh", "make_mo.py", "msgfmt.py"):
# Force Linux/macOS scripts as executable
# Force Linux/OSX scripts as executable
tarinfo.mode = 0o755
else:
tarinfo.mode = 0o644
+2 -27
View File
@@ -1,27 +1,2 @@
# All sub-dependencies are listed below and installed with --no-dependencies for security
PyGithub==2.9.1
praw==8.0.2
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.4.9
idna==3.18
certifi==2026.6.17
# cryptography (via pyjwt[crypto]) dependencies
cryptography==49.0.0
cffi==2.1.0
pycparser==3.0
PyGithub==2.1.1
praw==7.7.1
+71 -87
View File
@@ -1,5 +1,5 @@
#!/usr/bin/python3 -OO
# Copyright 2008-2026 by The SABnzbd-Team (sabnzbd.org)
# Copyright 2008-2017 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
@@ -15,63 +15,76 @@
# 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 xml.etree.ElementTree as ET
import github
import praw
import requests
from common import (
from constants import (
RELEASE_VERSION,
PRERELEASE,
RELEASE_SRC,
RELEASE_WIN_BIN_X64,
RELEASE_WIN_BIN_ARM64,
RELEASE_WIN_INSTALLER,
RELEASE_BINARY_32,
RELEASE_BINARY_64,
RELEASE_INSTALLER,
RELEASE_MACOS,
RELEASE_README,
RELEASE_THIS,
RELEASE_TITLE,
pe_has_authenticode_signature,
APPDATA_FILE,
)
# Verify we have all assets
files_to_check = (
RELEASE_SRC,
RELEASE_WIN_BIN_X64,
RELEASE_WIN_BIN_ARM64,
RELEASE_WIN_INSTALLER,
RELEASE_BINARY_32,
RELEASE_BINARY_64,
RELEASE_INSTALLER,
RELEASE_MACOS,
RELEASE_README,
)
for file_to_check in files_to_check:
if not os.path.exists(file_to_check):
raise RuntimeError("Release file %s is missing!" % file_to_check)
raise RuntimeError("Not all release files are present!")
print("All release files are present")
# 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)
# Verify that appdata file is updated
if not PRERELEASE:
if not isinstance(ET.parse(APPDATA_FILE).find(f"./releases/release[@version='{RELEASE_VERSION}']"), ET.Element):
raise RuntimeError(f"Could not find {RELEASE_VERSION} in {APPDATA_FILE}")
# 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("----")
# Check if tagged as release and check for token
gh_token = os.environ.get("AUTOMATION_GITHUB_TOKEN", "")
if RELEASE_THIS and gh_token:
gh_obj = github.Github(auth=github.Auth.Token(gh_token))
gh_obj = github.Github(gh_token)
gh_repo = gh_obj.get_repo("sabnzbd/sabnzbd")
# Read the release notes (reused for the Reddit post below)
# Read the release notes
with open(RELEASE_README, "r") as readme_file:
readme_data = readme_file.read()
# 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:
# 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.title)
break
else:
# Did not find it, so create the release, use the GitHub tag we got as input
print("Creating GitHub release SABnzbd %s" % RELEASE_VERSION)
gh_release = gh_repo.create_git_release(
tag=RELEASE_VERSION,
@@ -81,26 +94,38 @@ if RELEASE_THIS and gh_token:
prerelease=PRERELEASE,
)
# 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)
# Fetch existing assets, as overwriting is not allowed by GitHub
gh_assets = gh_release.get_assets()
# 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,
)
# 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.title))
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,
)
# Update the website
gh_repo_web = gh_obj.get_repo("sabnzbd/sabnzbd.github.io")
@@ -187,20 +212,6 @@ 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):
@@ -217,11 +228,12 @@ if RELEASE_THIS and gh_token:
subreddit_sabnzbd = reddit.subreddit("sabnzbd")
subreddit_usenet = reddit.subreddit("usenet")
# Reuse the release notes read earlier, split into lines
readme_lines = readme_data.splitlines(keepends=True)
# Read the release notes
with open(RELEASE_README, "r") as readme_file:
readme_lines = readme_file.readlines()
# Put the download link after the title
readme_lines[2] = "## https://sabnzbd.org/downloads\n\n"
readme_lines[2] = "## https://sabnzbd.org/downloads\n"
# Use the header in the readme as title
title = readme_lines[0]
@@ -248,34 +260,6 @@ 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.")
+24 -19
View File
@@ -1,23 +1,28 @@
# All sub-dependencies are listed below and installed with --no-dependencies for security
pyinstaller==6.21.0
packaging==26.2
pyinstaller-hooks-contrib==2026.6
altgraph==0.17.5
wrapt==2.2.2
setuptools==83.0.0
markdown==3.10.2
pefile==2024.8.26
# Basic build requirements
# Note that not all sub-dependencies are listed, but only ones we know could cause trouble
pyinstaller==6.2.0
packaging==23.2
pyinstaller-hooks-contrib==2023.10
altgraph==0.17.4
wrapt==1.16.0
setuptools==68.2.2
certifi
# Required on 32bit Windows, exclude it based on Python-version
importlib_metadata==6.8.0; python_version < '3.10'
importlib_resources==6.1.1; python_version < '3.10'
zipp==3.17.0; python_version < '3.10'
# orjson does not support 32bit Windows, also exclude based on Python-version
orjson==3.9.10; python_version > '3.8'
# For the Windows build
pywin32-ctypes==0.2.3; sys_platform == 'win32'
pefile==2023.2.7; sys_platform == 'win32'
pywin32-ctypes==0.2.2; 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.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'
dmgbuild==1.6.1; sys_platform == 'darwin'
mac-alias==2.2.2; sys_platform == 'darwin'
macholib==1.16.3; sys_platform == 'darwin'
ds-store==1.3.1; sys_platform == 'darwin'
PyNaCl==1.5.0; sys_platform == 'darwin'
+113 -410
View File
@@ -1,4 +1,4 @@
; -*- coding: utf-8 -*-
; -*- coding: utf-8 -*-
;
; Copyright 2008-2015 The SABnzbd-Team (sabnzbd.org)
;
@@ -18,10 +18,6 @@
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
@@ -33,7 +29,6 @@ SetCompressor /SOLID lzma
!include "nsProcess.nsh"
!include "x64.nsh"
!include "servicelib.nsh"
!include "StdUtils.nsh"
;------------------------------------------------------------------
;
@@ -47,49 +42,16 @@ SetCompressor /SOLID lzma
RMDir /r "${idir}"
!macroend
!define RemovePrevShortcuts "!insertmacro RemovePrevShortcuts"
!macro RemovePrevShortcuts
; Remove shortcuts, starting with current user ones (from old installs)
SetShellVarContext current
!insertmacro MUI_STARTMENU_GETFOLDER Application $MUI_TEMP
Delete "$SMPROGRAMS\$MUI_TEMP\SABnzbd.lnk"
Delete "$SMPROGRAMS\$MUI_TEMP\Uninstall.lnk"
Delete "$SMPROGRAMS\$MUI_TEMP\SABnzbd - SafeMode.lnk"
Delete "$SMPROGRAMS\$MUI_TEMP\SABnzbd - Documentation.url"
RMDir "$SMPROGRAMS\$MUI_TEMP"
Delete "$SMPROGRAMS\Startup\SABnzbd.lnk"
Delete "$DESKTOP\SABnzbd.lnk"
SetShellVarContext all
!insertmacro MUI_STARTMENU_GETFOLDER Application $MUI_TEMP
Delete "$SMPROGRAMS\$MUI_TEMP\SABnzbd.lnk"
Delete "$SMPROGRAMS\$MUI_TEMP\Uninstall.lnk"
Delete "$SMPROGRAMS\$MUI_TEMP\SABnzbd - SafeMode.lnk"
Delete "$SMPROGRAMS\$MUI_TEMP\SABnzbd - Documentation.url"
RMDir "$SMPROGRAMS\$MUI_TEMP"
Delete "$SMPROGRAMS\Startup\SABnzbd.lnk"
Delete "$DESKTOP\SABnzbd.lnk"
!macroend
;------------------------------------------------------------------
; Define names of the product
Name "SABnzbd ${SAB_VERSION}"
VIProductVersion "${SAB_VERSIONKEY}"
VIFileVersion "${SAB_VERSIONKEY}"
VIAddVersionKey "Comments" "SABnzbd ${SAB_VERSION}"
VIAddVersionKey "CompanyName" "The SABnzbd-Team"
VIAddVersionKey "FileDescription" "SABnzbd ${SAB_VERSION}"
VIAddVersionKey "FileVersion" "${SAB_VERSION}"
VIAddVersionKey "LegalCopyright" "The SABnzbd-Team"
VIAddVersionKey "ProductName" "SABnzbd ${SAB_VERSION}"
VIAddVersionKey "ProductVersion" "${SAB_VERSION}"
Name "${SAB_PRODUCT}"
OutFile "${SAB_FILE}"
InstallDir "$PROGRAMFILES\SABnzbd"
;------------------------------------------------------------------
; 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)
@@ -143,10 +105,10 @@ SetCompressor /SOLID lzma
!insertmacro MUI_PAGE_STARTMENU Application $STARTMENU_FOLDER
!insertmacro MUI_PAGE_INSTFILES
!define MUI_FINISHPAGE_RUN
!define MUI_FINISHPAGE_RUN_FUNCTION PageFinishRun
!define MUI_FINISHPAGE_RUN_TEXT $(MsgRunSAB)
!define MUI_FINISHPAGE_SHOWREADME "$INSTDIR\README.html"
; !define MUI_FINISHPAGE_RUN
; !define MUI_FINISHPAGE_RUN_FUNCTION PageFinishRun
; !define MUI_FINISHPAGE_RUN_TEXT $(MsgRunSAB)
!define MUI_FINISHPAGE_SHOWREADME "$INSTDIR\README.txt"
!define MUI_FINISHPAGE_SHOWREADME_TEXT $(MsgShowRelNote)
!define MUI_FINISHPAGE_LINK $(MsgSupportUs)
!define MUI_FINISHPAGE_LINK_LOCATION "https://sabnzbd.org/donate"
@@ -154,25 +116,16 @@ SetCompressor /SOLID lzma
!insertmacro MUI_PAGE_FINISH
!insertmacro MUI_UNPAGE_CONFIRM
!define MUI_UNPAGE_COMPONENTSPAGE_NODESC
!insertmacro MUI_UNPAGE_COMPONENTS
!insertmacro MUI_UNPAGE_INSTFILES
;------------------------------------------------------------------
; Run as user-level at end of install
Function PageFinishRun
; Check if SABnzbd service is installed
!insertmacro SERVICE "installed" "SABnzbd" ""
Pop $0 ;response
${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" "" ""
${EndIf}
FunctionEnd
; DOES NOT WORK
; Function PageFinishRun
; !insertmacro UAC_AsUser_ExecShell "" "$INSTDIR\SABnzbd.exe" "" "" ""
; FunctionEnd
;------------------------------------------------------------------
@@ -188,19 +141,18 @@ FunctionEnd
!insertmacro MUI_LANGUAGE "Polish"
!insertmacro MUI_LANGUAGE "Swedish"
!insertmacro MUI_LANGUAGE "Danish"
!insertmacro MUI_LANGUAGE "Italian"
!insertmacro MUI_LANGUAGE "Norwegian"
!insertmacro MUI_LANGUAGE "Romanian"
!insertmacro MUI_LANGUAGE "Spanish"
!insertmacro MUI_LANGUAGE "PortugueseBR"
!insertmacro MUI_LANGUAGE "Serbian"
!insertmacro MUI_LANGUAGE "Turkish"
!insertmacro MUI_LANGUAGE "Hebrew"
!insertmacro MUI_LANGUAGE "Russian"
!insertmacro MUI_LANGUAGE "Czech"
!insertmacro MUI_LANGUAGE "SimpChinese"
;------------------------------------------------------------------
;Reserve Files
;If you are using solid compression, files that are required before
@@ -218,38 +170,9 @@ Section "SABnzbd" SecDummy
SetOutPath "$INSTDIR"
SetShellVarContext all
DetailPrint $(MsgShutting)
;------------------------------------------------------------------
; 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
@@ -277,16 +200,9 @@ Section "SABnzbd" SecDummy
WriteRegStr HKEY_LOCAL_MACHINE "Software\Microsoft\Windows\CurrentVersion\Uninstall\SABnzbd" "URLUpdateInfo" 'https://sabnzbd.org/'
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'
; 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" "EstimatedSize" 25674
WriteRegDWORD HKEY_LOCAL_MACHINE "Software\Microsoft\Windows\CurrentVersion\Uninstall\SABnzbd" "NoRepair" -1
WriteRegDWORD HKEY_LOCAL_MACHINE "Software\Microsoft\Windows\CurrentVersion\Uninstall\SABnzbd" "NoModify" -1
WriteRegStr HKEY_CURRENT_USER "Software\Classes\AppUserModelId\SABnzbd" "DisplayName" "SABnzbd"
WriteRegStr HKEY_CURRENT_USER "Software\Classes\AppUserModelId\SABnzbd" "IconUri" '$INSTDIR\icons\sabnzbd16_32.ico'
; write out uninstaller
WriteUninstaller "$INSTDIR\Uninstall.exe"
@@ -319,23 +235,18 @@ Function .onInit
${If} ${RunningX64}
StrCpy $INSTDIR "$PROGRAMFILES64\SABnzbd"
${Else}
MessageBox MB_OK|MB_ICONSTOP $(MsgOnly64bit)
MessageBox MB_OK $(MsgOnly64bit)
ExecShell "open" "https://sabnzbd.org/downloads"
Abort
${EndIf}
; Python 3.9 no longer supports Windows 7
${If} ${AtMostWin8}
MessageBox MB_OK|MB_ICONSTOP $(MsgNoWin7)
MessageBox MB_OK $(MsgNoWin7)
ExecShell "open" "https://sabnzbd.org/downloads"
Abort
${EndIf}
; Inform ARM64 users that a native ARM64 build is available
${If} ${IsNativeARM64}
MessageBox MB_OKCANCEL|MB_ICONINFORMATION $(MsgARM64Notice) IDOK continueARM64
Abort
continueARM64:
${EndIf}
;------------------------------------------------------------------
; Change settings based on if SAB was already installed
ReadRegStr $PREV_INST_DIR HKEY_LOCAL_MACHINE "SOFTWARE\SABnzbd" ""
@@ -350,9 +261,8 @@ Function .onInit
;------------------------------------------------------------------
; Check what the user has currently set for install options
SetShellVarContext current
IfFileExists "$SMPROGRAMS\Startup\SABnzbd.lnk" 0 endCheckStartupCurrent
IfFileExists "$SMPROGRAMS\Startup\SABnzbd.lnk" 0 endCheckStartup
SectionSetFlags ${startup} 1
endCheckStartupCurrent:
SetShellVarContext all
IfFileExists "$SMPROGRAMS\Startup\SABnzbd.lnk" 0 endCheckStartup
SectionSetFlags ${startup} 1
@@ -360,13 +270,13 @@ Function .onInit
SetShellVarContext current
IfFileExists "$DESKTOP\SABnzbd.lnk" endCheckDesktop 0
; If not present for current user, first check all user folder
SetShellVarContext all
IfFileExists "$DESKTOP\SABnzbd.lnk" endCheckDesktop 0
SectionSetFlags ${desktop} 0 ; SAB is installed but desktop-icon not, so uncheck it
endCheckDesktop:
SectionSetFlags ${desktop} 0 ; SAB is installed but desktop-icon not, so uncheck it
SetShellVarContext all
IfFileExists "$DESKTOP\SABnzbd.lnk" endCheckDesktop 0
SectionSetFlags ${desktop} 0 ; SAB is installed but desktop-icon not, so uncheck it
endCheckDesktop:
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
@@ -376,70 +286,113 @@ Function .onInit
; Display language chooser
!insertmacro MUI_LANGDLL_DISPLAY
;------------------------------------------------------------------
; make sure user terminates sabnzbd.exe or else abort
;
loop:
${nsProcess::FindProcess} "SABnzbd.exe" $R0
StrCmp $R0 0 0 endcheck
MessageBox MB_OKCANCEL|MB_ICONEXCLAMATION $(MsgCloseSab) IDOK loop IDCANCEL exitinstall
exitinstall:
${nsProcess::Unload}
Abort
endcheck:
;------------------------------------------------------------------
; make sure both services aren't running
;
!insertmacro SERVICE "running" "SABnzbd" ""
Pop $0 ;response
!insertmacro SERVICE "running" "SABHelper" ""
Pop $1
${If} $0 == true
${OrIf} $1 == true
MessageBox MB_OKCANCEL|MB_ICONEXCLAMATION $(MsgCloseSab) IDOK loop IDCANCEL exitinstall
; exitinstall already defined above
${EndIf}
;------------------------------------------------------------------
; Tell users about the service change
;
!insertmacro SERVICE "installed" "SABHelper" ""
Pop $0 ;response
${If} $0 == true
MessageBox MB_OKCANCEL|MB_ICONEXCLAMATION $(MsgServChange) IDOK removeservices IDCANCEL exitinstall
; exitinstall already defined above
removeservices:
!insertmacro SERVICE "delete" "SABHelper" ""
!insertmacro SERVICE "delete" "SABnzbd" ""
${EndIf}
FunctionEnd
;------------------------------------------------------------------
; Show the shortcuts at end of install so user can start SABnzbd
; This is instead of us trying to run SAB from the installer
;
Function .onInstSuccess
ExecShell "open" "$SMPROGRAMS\$STARTMENU_FOLDER"
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}
DetailPrint "Process Killed"
; add delete commands to delete whatever files/registry keys/etc you installed here.
Delete "$INSTDIR\uninstall.exe"
DeleteRegKey HKEY_LOCAL_MACHINE "SOFTWARE\SABnzbd"
DeleteRegKey HKEY_LOCAL_MACHINE "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\SABnzbd"
DeleteRegKey HKEY_CURRENT_USER "Software\Classes\AppUserModelId\SABnzbd"
DeleteRegKey HKEY_CURRENT_USER "Software\SABnzbd"
; Only remove the directory if it actually contains a SABnzbd installation
IfFileExists "$INSTDIR\SABnzbd.exe" 0 skipRemoveDir
${RemovePrev} "$INSTDIR"
skipRemoveDir:
${RemovePrevShortcuts}
${RemovePrev} "$INSTDIR"
; Remove firewall entries
liteFirewallW::RemoveRule "$INSTDIR\SABnzbd.exe" "SABnzbd"
liteFirewallW::RemoveRule "$INSTDIR\SABnzbd-console.exe" "SABnzbd-console"
SetShellVarContext all
!insertmacro MUI_STARTMENU_GETFOLDER Application $MUI_TEMP
Delete "$SMPROGRAMS\$MUI_TEMP\SABnzbd.lnk"
Delete "$SMPROGRAMS\$MUI_TEMP\Uninstall.lnk"
Delete "$SMPROGRAMS\$MUI_TEMP\SABnzbd - SafeMode.lnk"
Delete "$SMPROGRAMS\$MUI_TEMP\SABnzbd - Documentation.url"
RMDir "$SMPROGRAMS\$MUI_TEMP"
Delete "$SMPROGRAMS\Startup\SABnzbd.lnk"
Delete "$DESKTOP\SABnzbd.lnk"
SetShellVarContext current
!insertmacro MUI_STARTMENU_GETFOLDER Application $MUI_TEMP
Delete "$SMPROGRAMS\$MUI_TEMP\SABnzbd.lnk"
Delete "$SMPROGRAMS\$MUI_TEMP\Uninstall.lnk"
Delete "$SMPROGRAMS\$MUI_TEMP\SABnzbd - SafeMode.lnk"
Delete "$SMPROGRAMS\$MUI_TEMP\SABnzbd - Documentation.url"
RMDir "$SMPROGRAMS\$MUI_TEMP"
Delete "$SMPROGRAMS\Startup\SABnzbd.lnk"
Delete "$DESKTOP\SABnzbd.lnk"
DeleteRegKey HKEY_CURRENT_USER "Software\SABnzbd"
${unregisterExtension} ".nzb" "NZB File"
${RefreshShellIcons}
SectionEnd ; end of uninstall section
Section /o "un.$(MsgDelSettings)" DelSettings
; 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"
DetailPrint "Uninstall settings $LOCALAPPDATA"
Delete "$LOCALAPPDATA\sabnzbd\sabnzbd.ini"
RMDir /r "$LOCALAPPDATA\sabnzbd"
SectionEnd
@@ -448,284 +401,34 @@ SectionEnd
;--------------------------------
;Language strings
LangString MsgShowRelNote ${LANG_ENGLISH} "Show Release Notes"
LangString MsgShowRelNote ${LANG_CZECH} "Zobrazit poznámky k vydání"
LangString MsgShowRelNote ${LANG_DANISH} "Vis udgivelsesbemærkninger"
LangString MsgShowRelNote ${LANG_GERMAN} "Versionshinweise anzeigen"
LangString MsgShowRelNote ${LANG_SPANISH} "Mostrar notas de la versión"
LangString MsgShowRelNote ${LANG_FINNISH} "Näytä julkaisutiedot"
LangString MsgShowRelNote ${LANG_FRENCH} "Afficher les notes de version"
LangString MsgShowRelNote ${LANG_HEBREW} "הראה הערות שחרור"
LangString MsgShowRelNote ${LANG_ITALIAN} "Mostra note di rilascio"
LangString MsgShowRelNote ${LANG_NORWEGIAN} "Vis versjonsmerknader"
LangString MsgShowRelNote ${LANG_DUTCH} "Toon opmerkingen bij deze uitgave"
LangString MsgShowRelNote ${LANG_POLISH} "Pokaż informacje o wydaniu"
LangString MsgShowRelNote ${LANG_PORTUGUESEBR} "Mostrar Notas de Lançamento"
LangString MsgShowRelNote ${LANG_ROMANIAN} "Arată Notele de Publicare"
LangString MsgShowRelNote ${LANG_RUSSIAN} "Показать заметки о выпуске"
LangString MsgShowRelNote ${LANG_SERBIAN} "Прикажи белешке о издању"
LangString MsgShowRelNote ${LANG_SWEDISH} "Visa releasenoteringar"
LangString MsgShowRelNote ${LANG_TURKISH} "Yayın Notlarını Göster"
LangString MsgShowRelNote ${LANG_SIMPCHINESE} "显示版本说明"
LangString MsgRunSAB ${LANG_ENGLISH} "Run SABnzbd"
LangString MsgRunSAB ${LANG_CZECH} "Spustit SABnzbd"
LangString MsgRunSAB ${LANG_DANISH} "Kør SABnzbd"
LangString MsgRunSAB ${LANG_GERMAN} "SABnzbd starten"
LangString MsgRunSAB ${LANG_SPANISH} "Ejecutar SABnzbd"
LangString MsgRunSAB ${LANG_FINNISH} "Käynnistä SABnzbd"
LangString MsgRunSAB ${LANG_FRENCH} "Lancer SABnzbd"
LangString MsgRunSAB ${LANG_HEBREW} "הפעל את SABnzbd"
LangString MsgRunSAB ${LANG_ITALIAN} "Avvia SABnzbd"
LangString MsgRunSAB ${LANG_NORWEGIAN} "Kjør SABnzbd"
LangString MsgRunSAB ${LANG_DUTCH} "SABnzbd starten"
LangString MsgRunSAB ${LANG_POLISH} "Uruchom SABnzbd"
LangString MsgRunSAB ${LANG_PORTUGUESEBR} "Executar o SABnzbd"
LangString MsgRunSAB ${LANG_ROMANIAN} "Rulați SABnzbd"
LangString MsgRunSAB ${LANG_RUSSIAN} "Запустить SABnzbd"
LangString MsgRunSAB ${LANG_SERBIAN} "Покрени SABnzbd"
LangString MsgRunSAB ${LANG_SWEDISH} "Kör SABnzbd"
LangString MsgRunSAB ${LANG_TURKISH} "SABnzbd'yi çalıştır"
LangString MsgRunSAB ${LANG_SIMPCHINESE} "运行 SABnzbd"
LangString MsgSupportUs ${LANG_ENGLISH} "Support the project, Donate!"
LangString MsgSupportUs ${LANG_CZECH} "Podpořte projekt!"
LangString MsgSupportUs ${LANG_DANISH} "Støt projektet, donér!"
LangString MsgSupportUs ${LANG_GERMAN} "Bitte unterstützen Sie das Projekt durch eine Spende!"
LangString MsgSupportUs ${LANG_SPANISH} "¡Apoye el proyecto, haga una donación!"
LangString MsgSupportUs ${LANG_FINNISH} "Tue projektia, lahjoita!"
LangString MsgSupportUs ${LANG_FRENCH} "Soutenez le projet, faites un don !"
LangString MsgSupportUs ${LANG_HEBREW} "תמוך במיזם, תרום!"
LangString MsgSupportUs ${LANG_ITALIAN} "Sostieni il progetto, dona!"
LangString MsgSupportUs ${LANG_NORWEGIAN} "Støtt prosjektet, donèr!"
LangString MsgSupportUs ${LANG_DUTCH} "Steun het project, doneer!"
LangString MsgSupportUs ${LANG_POLISH} "Wspomóż projekt!"
LangString MsgSupportUs ${LANG_PORTUGUESEBR} "Apoie o projeto. Faça uma doação!"
LangString MsgSupportUs ${LANG_ROMANIAN} "Susţine proiectul, Donează!"
LangString MsgSupportUs ${LANG_RUSSIAN} "Поддержите проект. Сделайте пожертвование!"
LangString MsgSupportUs ${LANG_SERBIAN} "Подржите пројекат, дајте добровољан прилог!"
LangString MsgSupportUs ${LANG_SWEDISH} "Donera och stöd detta projekt!"
LangString MsgSupportUs ${LANG_TURKISH} "Projeye destek olun, Bağış Yapın!"
LangString MsgSupportUs ${LANG_SIMPCHINESE} "支持该项目,捐助!"
LangString MsgOnly64bit ${LANG_ENGLISH} "SABnzbd only supports 64-bit Windows."
LangString MsgOnly64bit ${LANG_CZECH} "SABnzbd podporuje pouze 64bitové Windows."
LangString MsgOnly64bit ${LANG_DANISH} "SABnzbd understøtter kun 64-bit Windows."
LangString MsgOnly64bit ${LANG_GERMAN} "SABnzbd unterstützt nur 64-Bit-Windows."
LangString MsgOnly64bit ${LANG_SPANISH} "SABnzbd solo es compatible con Windows de 64 bits."
LangString MsgOnly64bit ${LANG_FINNISH} "SABnzbd tukee vain 64-bittistä Windowsia."
LangString MsgOnly64bit ${LANG_FRENCH} "SABnzbd n'est compatible qu'avec Windows 64 bits."
LangString MsgOnly64bit ${LANG_HEBREW} "SABnzbd תומך רק במערכות Windows מסוג 64 סיביות."
LangString MsgOnly64bit ${LANG_ITALIAN} "SABnzbd supporta solo Windows a 64 bit."
LangString MsgOnly64bit ${LANG_NORWEGIAN} "SABnzbd støtter kun 64-bit Windows."
LangString MsgOnly64bit ${LANG_DUTCH} "SABnzbd ondersteund alleen 64bit Windows."
LangString MsgOnly64bit ${LANG_POLISH} "SABnzbd obsługuje tylko system Windows 64-bit."
LangString MsgOnly64bit ${LANG_PORTUGUESEBR} "O SABnzbd oferece suporte apenas ao Windows de 64 bits."
LangString MsgOnly64bit ${LANG_ROMANIAN} "SABnzbd acceptă doar Windows pe 64 de biți."
LangString MsgOnly64bit ${LANG_RUSSIAN} "SABnzbd поддерживает только 64-битные версии Windows."
LangString MsgOnly64bit ${LANG_SERBIAN} "SABnzbd подржава само 64‑битни Windows."
LangString MsgOnly64bit ${LANG_SWEDISH} "SABnzbd stöder endast 64-bitars Windows."
LangString MsgOnly64bit ${LANG_TURKISH} "SABnzbd sadece 64 bit Windows'u destekler."
LangString MsgOnly64bit ${LANG_SIMPCHINESE} "SABnzbd 仅支持 64 位 Windows。"
LangString MsgCloseSab ${LANG_ENGLISH} "Please close $\"SABnzbd.exe$\" first"
LangString MsgNoWin7 ${LANG_ENGLISH} "SABnzbd only supports Windows 8.1 and above."
LangString MsgNoWin7 ${LANG_CZECH} "SABnzbd podporuje pouze Windows 8.1 a novější."
LangString MsgNoWin7 ${LANG_DANISH} "SABnzbd understøtter kun Windows 8.1 og nyere."
LangString MsgNoWin7 ${LANG_GERMAN} "SABnzbd unterstützt nur Windows 8.1 und höher."
LangString MsgNoWin7 ${LANG_SPANISH} "SABnzbd solo es compatible con Windows 8.1 y superiores."
LangString MsgNoWin7 ${LANG_FINNISH} "SABnzbd tukee vain Windows 8.1:tä ja uudempia."
LangString MsgNoWin7 ${LANG_FRENCH} "SABnzbd n'est compatible qu'avec Windows 8.1 et plus."
LangString MsgNoWin7 ${LANG_HEBREW} "SABnzbd תומך רק במערכות Windows 8.1 ומעלה."
LangString MsgNoWin7 ${LANG_ITALIAN} "SABnzbd supporta solo Windows 8.1 e versioni successive."
LangString MsgNoWin7 ${LANG_NORWEGIAN} "SABnzbd støtter kun Windows 8.1 og nyere."
LangString MsgNoWin7 ${LANG_DUTCH} "SABnzbd ondersteund alleen Windows 8.1 en hoger."
LangString MsgNoWin7 ${LANG_POLISH} "SABnzbd obsługuje tylko Windows 8.1 i nowsze."
LangString MsgNoWin7 ${LANG_PORTUGUESEBR} "O SABnzbd oferece suporte apenas ao Windows 8.1 e superior."
LangString MsgNoWin7 ${LANG_ROMANIAN} "SABnzbd acceptă doar Windows 8.1 și versiunile ulterioare."
LangString MsgNoWin7 ${LANG_RUSSIAN} "SABnzbd поддерживает только Windows 8.1 и более новые."
LangString MsgNoWin7 ${LANG_SERBIAN} "SABnzbd подржава само Windows 8.1 и новије."
LangString MsgNoWin7 ${LANG_SWEDISH} "SABnzbd stöder endast Windows 8.1 och senare."
LangString MsgNoWin7 ${LANG_TURKISH} "SABnzbd sadece Windows 8.1 ve üzerini destekler."
LangString MsgNoWin7 ${LANG_SIMPCHINESE} "SABnzbd 仅支持 Windows 8.1 及更高版本。"
LangString MsgServChange ${LANG_ENGLISH} "The SABnzbd Windows Service changed in SABnzbd 3.0.0. $\nYou will need to reinstall the SABnzbd service. $\n$\nClick `OK` to remove the existing services or `Cancel` to cancel this upgrade."
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} "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} "גרסת 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."
LangString MsgARM64Notice ${LANG_PORTUGUESEBR} "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_ROMANIAN} "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_RUSSIAN} "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_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} "我们的下载页面提供 SABnzbd 的 ARM 版本。此安装程序仅包含常规版本。$\n按“确定”继续,或按“取消”退出。"
LangString MsgOnly64bit ${LANG_ENGLISH} "The installer only supports 64-bit Windows, use the standalone version to run on 32-bit Windows."
LangString MsgShutting ${LANG_ENGLISH} "Shutting down SABnzbd"
LangString MsgShutting ${LANG_CZECH} "Vypínání SABnzbd"
LangString MsgShutting ${LANG_DANISH} "Lukker SABnzbd"
LangString MsgShutting ${LANG_GERMAN} "Beende SABnzbd"
LangString MsgShutting ${LANG_SPANISH} "Apagando SABnzbd"
LangString MsgShutting ${LANG_FINNISH} "Sammutetaan SABnzbd"
LangString MsgShutting ${LANG_FRENCH} "Arrêt de SABnzbd"
LangString MsgShutting ${LANG_HEBREW} "מכבה את SABnzbd"
LangString MsgShutting ${LANG_ITALIAN} "Arresto di SABnzbd in corso"
LangString MsgShutting ${LANG_NORWEGIAN} "Slår av SABnzbd"
LangString MsgShutting ${LANG_DUTCH} "SABnzbd wordt afgesloten"
LangString MsgShutting ${LANG_POLISH} "Zamykanie SABnzbd"
LangString MsgShutting ${LANG_PORTUGUESEBR} "Encerrando o SABnzbd"
LangString MsgShutting ${LANG_ROMANIAN} "Se oprește SABnzbd"
LangString MsgShutting ${LANG_RUSSIAN} "Завершение работы SABnzbd"
LangString MsgShutting ${LANG_SERBIAN} "Искључивање SABnzbd"
LangString MsgShutting ${LANG_SWEDISH} "Stänger av SABnzbd."
LangString MsgShutting ${LANG_TURKISH} "SABnzbd kapatılıyor"
LangString MsgShutting ${LANG_SIMPCHINESE} "正在关闭 SABnzbd"
LangString MsgNoWin7 ${LANG_ENGLISH} "The installer only supports Windows 8.1 and above, use the standalone legacy version to run on older Windows version."
LangString MsgUninstall ${LANG_ENGLISH} "This will uninstall SABnzbd from your system"
LangString MsgUninstall ${LANG_CZECH} "Tímto odinstalujete SABnzbd z vašeho systému"
LangString MsgUninstall ${LANG_DANISH} "Dette vil afinstallere SABnzbd fra dit system"
LangString MsgUninstall ${LANG_GERMAN} "Dies entfernt SABnzbd von Ihrem System"
LangString MsgUninstall ${LANG_SPANISH} "Esto desinstalará SABnzbd de su sistema"
LangString MsgUninstall ${LANG_FINNISH} "Tämä poistaa SABnzbd:n tietokoneestasi"
LangString MsgUninstall ${LANG_FRENCH} "Ceci désinstallera SABnzbd de votre système"
LangString MsgUninstall ${LANG_HEBREW} "זה יסיר את SABnzbd מהמערכת שלך"
LangString MsgUninstall ${LANG_ITALIAN} "Questo disinstallerà SABnzbd dal tuo sistema"
LangString MsgUninstall ${LANG_NORWEGIAN} "Dette vil avinstallere SABnzbd fra ditt system"
LangString MsgUninstall ${LANG_DUTCH} "Dit verwijdert SABnzbd van je systeem"
LangString MsgUninstall ${LANG_POLISH} "To odinstaluje SABnzbd z systemu"
LangString MsgUninstall ${LANG_PORTUGUESEBR} "Isso irá desinstalar SABnzbd de seu sistema"
LangString MsgUninstall ${LANG_ROMANIAN} "Acest lucru va dezinstala SABnzbd din sistem"
LangString MsgUninstall ${LANG_RUSSIAN} "Приложение SABnzbd будет удалено из вашей системы"
LangString MsgUninstall ${LANG_SERBIAN} "Ово ће уклонити САБнзбд са вашег система"
LangString MsgUninstall ${LANG_SWEDISH} "Detta kommer att avinstallera SABnzbd från systemet"
LangString MsgUninstall ${LANG_TURKISH} "Bu, SABnzbd'yi sisteminizden kaldıracaktır"
LangString MsgUninstall ${LANG_SIMPCHINESE} "这将从您的系统中卸载 SABnzbd"
LangString MsgRunAtStart ${LANG_ENGLISH} "Run at startup"
LangString MsgRunAtStart ${LANG_CZECH} "Spouštět při startu systému"
LangString MsgRunAtStart ${LANG_DANISH} "Kør ved opstart"
LangString MsgRunAtStart ${LANG_GERMAN} "Beim Systemstart ausführen"
LangString MsgRunAtStart ${LANG_SPANISH} "Ejecutar al inicio"
LangString MsgRunAtStart ${LANG_FINNISH} "Suorita käynnistyksen yhteydessä"
LangString MsgRunAtStart ${LANG_FRENCH} "Lancer au démarrage"
LangString MsgRunAtStart ${LANG_HEBREW} "הרץ בהזנק"
LangString MsgRunAtStart ${LANG_ITALIAN} "Esegui all'avvio"
LangString MsgRunAtStart ${LANG_NORWEGIAN} "Kjør ved oppstart"
LangString MsgRunAtStart ${LANG_DUTCH} "Starten met Windows"
LangString MsgRunAtStart ${LANG_POLISH} "Uruchom wraz z systemem"
LangString MsgRunAtStart ${LANG_PORTUGUESEBR} "Executar na inicialização"
LangString MsgRunAtStart ${LANG_ROMANIAN} "Executare la pornire"
LangString MsgRunAtStart ${LANG_RUSSIAN} "Запускать вместе с системой"
LangString MsgRunAtStart ${LANG_SERBIAN} "Покрени са системом"
LangString MsgRunAtStart ${LANG_SWEDISH} "Kör vid uppstart"
LangString MsgRunAtStart ${LANG_TURKISH} "Başlangıçta Çalıştır"
LangString MsgRunAtStart ${LANG_SIMPCHINESE} "启动时运行"
LangString MsgIcon ${LANG_ENGLISH} "Desktop Icon"
LangString MsgIcon ${LANG_CZECH} "Ikona na ploše"
LangString MsgIcon ${LANG_DANISH} "Skrivebordsikon"
LangString MsgIcon ${LANG_GERMAN} "Desktop-Symbol"
LangString MsgIcon ${LANG_SPANISH} "Icono del escritorio"
LangString MsgIcon ${LANG_FINNISH} "Työpöydän kuvake"
LangString MsgIcon ${LANG_FRENCH} "Icône sur le Bureau"
LangString MsgIcon ${LANG_HEBREW} "צור קיצור דרך בשולחן העבודה"
LangString MsgIcon ${LANG_ITALIAN} "Icona sul desktop"
LangString MsgIcon ${LANG_NORWEGIAN} "Skrivebordsikon"
LangString MsgIcon ${LANG_DUTCH} "Bureaubladpictogram"
LangString MsgIcon ${LANG_POLISH} "Ikona pulpitu"
LangString MsgIcon ${LANG_PORTUGUESEBR} "Ícone na Área de Trabalho"
LangString MsgIcon ${LANG_ROMANIAN} "Icoană Desktop"
LangString MsgIcon ${LANG_RUSSIAN} "Значок на рабочем столе"
LangString MsgIcon ${LANG_SERBIAN} "Иконица радне површи"
LangString MsgIcon ${LANG_SWEDISH} "Skrivbordsikon"
LangString MsgIcon ${LANG_TURKISH} "Masaüstü İkonu"
LangString MsgIcon ${LANG_SIMPCHINESE} "桌面图标"
LangString MsgAssoc ${LANG_ENGLISH} "NZB File association"
LangString MsgAssoc ${LANG_CZECH} "Přiřazení souborů NZB"
LangString MsgAssoc ${LANG_DANISH} "NZB-filtilknytning"
LangString MsgAssoc ${LANG_GERMAN} "Mit NZB-Dateien verknüpfen"
LangString MsgAssoc ${LANG_SPANISH} "Asociación de archivos NZB"
LangString MsgAssoc ${LANG_FINNISH} "NZB tiedostosidos"
LangString MsgAssoc ${LANG_FRENCH} "Association des fichiers NZB"
LangString MsgAssoc ${LANG_HEBREW} "NZB שייך קבצי"
LangString MsgAssoc ${LANG_ITALIAN} "Associazione file NZB"
LangString MsgAssoc ${LANG_NORWEGIAN} "NZB-filassosiering"
LangString MsgAssoc ${LANG_DUTCH} "NZB-bestanden openen met SABnzbd"
LangString MsgAssoc ${LANG_POLISH} "powiązanie pliku NZB"
LangString MsgAssoc ${LANG_PORTUGUESEBR} "Associação com Arquivos NZB"
LangString MsgAssoc ${LANG_ROMANIAN} "Asociere cu Fişierele NZB"
LangString MsgAssoc ${LANG_RUSSIAN} "Ассоциировать с файлами NZB"
LangString MsgAssoc ${LANG_SERBIAN} "Придруживање НЗБ датотеке"
LangString MsgAssoc ${LANG_SWEDISH} "NZB Filassosication"
LangString MsgAssoc ${LANG_TURKISH} "NZB Dosya ilişkilendirmesi"
LangString MsgAssoc ${LANG_SIMPCHINESE} "NZB 文件关联"
LangString MsgDelProgram ${LANG_ENGLISH} "Delete Program"
LangString MsgDelProgram ${LANG_CZECH} "Odstranit program"
LangString MsgDelProgram ${LANG_DANISH} "Slet program"
LangString MsgDelProgram ${LANG_GERMAN} "Programm löschen"
LangString MsgDelProgram ${LANG_SPANISH} "Eliminar programa"
LangString MsgDelProgram ${LANG_FINNISH} "Poista sovellus"
LangString MsgDelProgram ${LANG_FRENCH} "Supprimer le programme"
LangString MsgDelProgram ${LANG_HEBREW} "מחק תוכנית"
LangString MsgDelProgram ${LANG_ITALIAN} "Elimina programma"
LangString MsgDelProgram ${LANG_NORWEGIAN} "Fjern program"
LangString MsgDelProgram ${LANG_DUTCH} "Programma verwijderen"
LangString MsgDelProgram ${LANG_POLISH} "Usuń program"
LangString MsgDelProgram ${LANG_PORTUGUESEBR} "Excluir o Programa"
LangString MsgDelProgram ${LANG_ROMANIAN} "Şterge Program"
LangString MsgDelProgram ${LANG_RUSSIAN} "Удалить программу"
LangString MsgDelProgram ${LANG_SERBIAN} "Обриши програм"
LangString MsgDelProgram ${LANG_SWEDISH} "Radera programmet"
LangString MsgDelProgram ${LANG_TURKISH} "Programı Sil"
LangString MsgDelProgram ${LANG_SIMPCHINESE} "删除程序"
LangString MsgDelSettings ${LANG_ENGLISH} "Delete Settings"
LangString MsgDelSettings ${LANG_CZECH} "Smazat nastavení"
LangString MsgDelSettings ${LANG_DANISH} "Slet indstillinger"
LangString MsgDelSettings ${LANG_GERMAN} "Einstellungen löschen"
LangString MsgDelSettings ${LANG_SPANISH} "Eliminar Ajustes"
LangString MsgDelSettings ${LANG_FINNISH} "Poista asetukset"
LangString MsgDelSettings ${LANG_FRENCH} "Supprimer les paramètres"
LangString MsgDelSettings ${LANG_HEBREW} "מחק הגדרות"
LangString MsgDelSettings ${LANG_ITALIAN} "Elimina impostazioni"
LangString MsgDelSettings ${LANG_NORWEGIAN} "Slett innstillinger"
LangString MsgDelSettings ${LANG_DUTCH} "Verwijder alle instellingen"
LangString MsgDelSettings ${LANG_POLISH} "Skasuj obecne ustawienia"
LangString MsgDelSettings ${LANG_PORTUGUESEBR} "Apagar Configurações"
LangString MsgDelSettings ${LANG_ROMANIAN} "Ştergeţi Setări"
LangString MsgDelSettings ${LANG_RUSSIAN} "Удалить параметры"
LangString MsgDelSettings ${LANG_SERBIAN} "Обриши подешавања"
LangString MsgDelSettings ${LANG_SWEDISH} "Radera inställningar"
LangString MsgDelSettings ${LANG_TURKISH} "Ayarları Sil"
LangString MsgDelSettings ${LANG_SIMPCHINESE} "删除设置"
LangString MsgRemoveOld ${LANG_ENGLISH} "You cannot overwrite an existing installation. $\n$\nClick `OK` to remove the previous version or `Cancel` to cancel this upgrade."
LangString MsgRemoveOld2 ${LANG_ENGLISH} "Your settings and data will be preserved."
LangString MsgLangCode ${LANG_ENGLISH} "en"
LangString MsgLangCode ${LANG_CZECH} "cs"
LangString MsgLangCode ${LANG_DANISH} "da"
LangString MsgLangCode ${LANG_GERMAN} "de"
LangString MsgLangCode ${LANG_SPANISH} "es"
LangString MsgLangCode ${LANG_FINNISH} "fi"
LangString MsgLangCode ${LANG_FRENCH} "fr"
LangString MsgLangCode ${LANG_HEBREW} "he"
LangString MsgLangCode ${LANG_ITALIAN} "it"
LangString MsgLangCode ${LANG_NORWEGIAN} "nb"
LangString MsgLangCode ${LANG_DUTCH} "nl"
LangString MsgLangCode ${LANG_POLISH} "pl"
LangString MsgLangCode ${LANG_PORTUGUESEBR} "pt_BR"
LangString MsgLangCode ${LANG_ROMANIAN} "ro"
LangString MsgLangCode ${LANG_RUSSIAN} "ru"
LangString MsgLangCode ${LANG_SERBIAN} "sr"
LangString MsgLangCode ${LANG_SWEDISH} "sv"
LangString MsgLangCode ${LANG_TURKISH} "tr"
LangString MsgLangCode ${LANG_SIMPCHINESE} "zh_CN"
Function un.onInit
!insertmacro MUI_UNGETLANGUAGE
-501
View File
@@ -1,501 +0,0 @@
#################################################################################
# StdUtils plug-in for NSIS
# Copyright (C) 2004-2018 LoRd_MuldeR <MuldeR2@GMX.de>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library 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
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
#
# http://www.gnu.org/licenses/lgpl-2.1.txt
#################################################################################
# DEVELOPER NOTES:
# - Please see "https://github.com/lordmulder/stdutils/" for news and updates!
# - Please see "Docs\StdUtils\StdUtils.html" for detailed function descriptions!
# - Please see "Examples\StdUtils\StdUtilsTest.nsi" for usage examples!
#################################################################################
# FUNCTION DECLARTIONS
#################################################################################
!ifndef ___STDUTILS__NSH___
!define ___STDUTILS__NSH___
!define StdUtils.Time '!insertmacro _StdU_Time' #time(), as in C standard library
!define StdUtils.GetMinutes '!insertmacro _StdU_GetMinutes' #GetSystemTimeAsFileTime(), returns the number of minutes
!define StdUtils.GetHours '!insertmacro _StdU_GetHours' #GetSystemTimeAsFileTime(), returns the number of hours
!define StdUtils.GetDays '!insertmacro _StdU_GetDays' #GetSystemTimeAsFileTime(), returns the number of days
!define StdUtils.Rand '!insertmacro _StdU_Rand' #rand(), as in C standard library
!define StdUtils.RandMax '!insertmacro _StdU_RandMax' #rand(), as in C standard library, with maximum value
!define StdUtils.RandMinMax '!insertmacro _StdU_RandMinMax' #rand(), as in C standard library, with minimum/maximum value
!define StdUtils.RandList '!insertmacro _StdU_RandList' #rand(), as in C standard library, with list support
!define StdUtils.RandBytes '!insertmacro _StdU_RandBytes' #Generates random bytes, returned as Base64-encoded string
!define StdUtils.FormatStr '!insertmacro _StdU_FormatStr' #sprintf(), as in C standard library, one '%d' placeholder
!define StdUtils.FormatStr2 '!insertmacro _StdU_FormatStr2' #sprintf(), as in C standard library, two '%d' placeholders
!define StdUtils.FormatStr3 '!insertmacro _StdU_FormatStr3' #sprintf(), as in C standard library, three '%d' placeholders
!define StdUtils.ScanStr '!insertmacro _StdU_ScanStr' #sscanf(), as in C standard library, one '%d' placeholder
!define StdUtils.ScanStr2 '!insertmacro _StdU_ScanStr2' #sscanf(), as in C standard library, two '%d' placeholders
!define StdUtils.ScanStr3 '!insertmacro _StdU_ScanStr3' #sscanf(), as in C standard library, three '%d' placeholders
!define StdUtils.TrimStr '!insertmacro _StdU_TrimStr' #Remove whitspaces from string, left and right
!define StdUtils.TrimStrLeft '!insertmacro _StdU_TrimStrLeft' #Remove whitspaces from string, left side only
!define StdUtils.TrimStrRight '!insertmacro _StdU_TrimStrRight' #Remove whitspaces from string, right side only
!define StdUtils.RevStr '!insertmacro _StdU_RevStr' #Reverse a string, e.g. "reverse me" <-> "em esrever"
!define StdUtils.ValidFileName '!insertmacro _StdU_ValidFileName' #Test whether string is a valid file name - no paths allowed
!define StdUtils.ValidPathSpec '!insertmacro _StdU_ValidPathSpec' #Test whether string is a valid full(!) path specification
!define StdUtils.ValidDomainName '!insertmacro _StdU_ValidDomain' #Test whether string is a valid host name or domain name
!define StdUtils.StrToUtf8 '!insertmacro _StdU_StrToUtf8' #Convert string from Unicode (UTF-16) or ANSI to UTF-8 bytes
!define StdUtils.StrFromUtf8 '!insertmacro _StdU_StrFromUtf8' #Convert string from UTF-8 bytes to Unicode (UTF-16) or ANSI
!define StdUtils.SHFileMove '!insertmacro _StdU_SHFileMove' #SHFileOperation(), using the FO_MOVE operation
!define StdUtils.SHFileCopy '!insertmacro _StdU_SHFileCopy' #SHFileOperation(), using the FO_COPY operation
!define StdUtils.AppendToFile '!insertmacro _StdU_AppendToFile' #Append contents of an existing file to another file
!define StdUtils.ExecShellAsUser '!insertmacro _StdU_ExecShlUser' #ShellExecute() as NON-elevated user from elevated installer
!define StdUtils.InvokeShellVerb '!insertmacro _StdU_InvkeShlVrb' #Invokes a "shell verb", e.g. for pinning items to the taskbar
!define StdUtils.ExecShellWaitEx '!insertmacro _StdU_ExecShlWaitEx' #ShellExecuteEx(), returns the handle of the new process
!define StdUtils.WaitForProcEx '!insertmacro _StdU_WaitForProcEx' #WaitForSingleObject(), e.g. to wait for a running process
!define StdUtils.GetParameter '!insertmacro _StdU_GetParameter' #Get the value of a specific command-line option
!define StdUtils.TestParameter '!insertmacro _StdU_TestParameter' #Test whether a specific command-line option has been set
!define StdUtils.ParameterCnt '!insertmacro _StdU_ParameterCnt' #Get number of command-line tokens, similar to argc in main()
!define StdUtils.ParameterStr '!insertmacro _StdU_ParameterStr' #Get the n-th command-line token, similar to argv[i] in main()
!define StdUtils.GetAllParameters '!insertmacro _StdU_GetAllParams' #Get complete command-line, but without executable name
!define StdUtils.GetRealOSVersion '!insertmacro _StdU_GetRealOSVer' #Get the *real* Windows version number, even on Windows 8.1+
!define StdUtils.GetRealOSBuildNo '!insertmacro _StdU_GetRealOSBld' #Get the *real* Windows build number, even on Windows 8.1+
!define StdUtils.GetRealOSName '!insertmacro _StdU_GetRealOSStr' #Get the *real* Windows version, as a "friendly" name
!define StdUtils.GetOSEdition '!insertmacro _StdU_GetOSEdition' #Get the Windows edition, i.e. "workstation" or "server"
!define StdUtils.GetOSReleaseId '!insertmacro _StdU_GetOSRelIdNo' #Get the Windows release identifier (on Windows 10)
!define StdUtils.GetOSReleaseName '!insertmacro _StdU_GetOSRelIdStr' #Get the Windows release (on Windows 10), as a "friendly" name
!define StdUtils.VerifyOSVersion '!insertmacro _StdU_VrfyRealOSVer' #Compare *real* operating system to an expected version number
!define StdUtils.VerifyOSBuildNo '!insertmacro _StdU_VrfyRealOSBld' #Compare *real* operating system to an expected build number
!define StdUtils.HashText '!insertmacro _StdU_HashText' #Compute hash from text string (CRC32, MD5, SHA1/2/3, BLAKE2)
!define StdUtils.HashFile '!insertmacro _StdU_HashFile' #Compute hash from file (CRC32, MD5, SHA1/2/3, BLAKE2)
!define StdUtils.NormalizePath '!insertmacro _StdU_NormalizePath' #Simplifies the path to produce a direct, well-formed path
!define StdUtils.GetParentPath '!insertmacro _StdU_GetParentPath' #Get parent path by removing the last component from the path
!define StdUtils.SplitPath '!insertmacro _StdU_SplitPath' #Split the components of the given path
!define StdUtils.GetDrivePart '!insertmacro _StdU_GetDrivePart' #Get drive component of path
!define StdUtils.GetDirectoryPart '!insertmacro _StdU_GetDirPart' #Get directory component of path
!define StdUtils.GetFileNamePart '!insertmacro _StdU_GetFNamePart' #Get file name component of path
!define StdUtils.GetExtensionPart '!insertmacro _StdU_GetExtnPart' #Get file extension component of path
!define StdUtils.TimerCreate '!insertmacro _StdU_TimerCreate' #Create a new event-timer that will be triggered periodically
!define StdUtils.TimerDestroy '!insertmacro _StdU_TimerDestroy' #Destroy a running timer created with TimerCreate()
!define StdUtils.ProtectStr '!insertmacro _StdU_PrtctStr' #Protect a given String using Windows' DPAPI
!define StdUtils.UnprotectStr '!insertmacro _StdU_UnprtctStr' #Unprotect a string that was protected via ProtectStr()
!define StdUtils.GetLibVersion '!insertmacro _StdU_GetLibVersion' #Get the current StdUtils library version (for debugging)
!define StdUtils.SetVerbose '!insertmacro _StdU_SetVerbose' #Enable or disable "verbose" mode (for debugging)
#################################################################################
# MACRO DEFINITIONS
#################################################################################
!macro _StdU_Time out
StdUtils::Time /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_GetMinutes out
StdUtils::GetMinutes /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_GetHours out
StdUtils::GetHours /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_GetDays out
StdUtils::GetDays /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_Rand out
StdUtils::Rand /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_RandMax out max
push ${max}
StdUtils::RandMax /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_RandMinMax out min max
push ${min}
push ${max}
StdUtils::RandMinMax /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_RandList count max
push ${max}
push ${count}
StdUtils::RandList /NOUNLOAD
!macroend
!macro _StdU_RandBytes out count
push ${count}
StdUtils::RandBytes /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_FormatStr out format val
push `${format}`
push ${val}
StdUtils::FormatStr /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_FormatStr2 out format val1 val2
push `${format}`
push ${val1}
push ${val2}
StdUtils::FormatStr2 /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_FormatStr3 out format val1 val2 val3
push `${format}`
push ${val1}
push ${val2}
push ${val3}
StdUtils::FormatStr3 /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_ScanStr out format input default
push `${format}`
push `${input}`
push ${default}
StdUtils::ScanStr /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_ScanStr2 out1 out2 format input default1 default2
push `${format}`
push `${input}`
push ${default1}
push ${default2}
StdUtils::ScanStr2 /NOUNLOAD
pop ${out1}
pop ${out2}
!macroend
!macro _StdU_ScanStr3 out1 out2 out3 format input default1 default2 default3
push `${format}`
push `${input}`
push ${default1}
push ${default2}
push ${default3}
StdUtils::ScanStr3 /NOUNLOAD
pop ${out1}
pop ${out2}
pop ${out3}
!macroend
!macro _StdU_TrimStr var
push ${var}
StdUtils::TrimStr /NOUNLOAD
pop ${var}
!macroend
!macro _StdU_TrimStrLeft var
push ${var}
StdUtils::TrimStrLeft /NOUNLOAD
pop ${var}
!macroend
!macro _StdU_TrimStrRight var
push ${var}
StdUtils::TrimStrRight /NOUNLOAD
pop ${var}
!macroend
!macro _StdU_RevStr var
push ${var}
StdUtils::RevStr /NOUNLOAD
pop ${var}
!macroend
!macro _StdU_ValidFileName out test
push `${test}`
StdUtils::ValidFileName /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_ValidPathSpec out test
push `${test}`
StdUtils::ValidPathSpec /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_ValidDomain out test
push `${test}`
StdUtils::ValidDomainName /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_StrToUtf8 out str
push `${str}`
StdUtils::StrToUtf8 /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_StrFromUtf8 out trnc str
push ${trnc}
push `${str}`
StdUtils::StrFromUtf8 /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_SHFileMove out from to hwnd
push `${from}`
push `${to}`
push ${hwnd}
StdUtils::SHFileMove /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_SHFileCopy out from to hwnd
push `${from}`
push `${to}`
push ${hwnd}
StdUtils::SHFileCopy /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_AppendToFile out from dest offset maxlen
push `${from}`
push `${dest}`
push ${offset}
push ${maxlen}
StdUtils::AppendToFile /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_ExecShlUser out file verb args
push `${file}`
push `${verb}`
push `${args}`
StdUtils::ExecShellAsUser /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_InvkeShlVrb out path file verb_id
push "${path}"
push "${file}"
push ${verb_id}
StdUtils::InvokeShellVerb /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_ExecShlWaitEx out_res out_val file verb args
push `${file}`
push `${verb}`
push `${args}`
StdUtils::ExecShellWaitEx /NOUNLOAD
pop ${out_res}
pop ${out_val}
!macroend
!macro _StdU_WaitForProcEx out handle
push `${handle}`
StdUtils::WaitForProcEx /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_GetParameter out name default
push `${name}`
push `${default}`
StdUtils::GetParameter /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_TestParameter out name
push `${name}`
StdUtils::TestParameter /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_ParameterCnt out
StdUtils::ParameterCnt /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_ParameterStr out index
push ${index}
StdUtils::ParameterStr /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_GetAllParams out truncate
push `${truncate}`
StdUtils::GetAllParameters /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_GetRealOSVer out_major out_minor out_spack
StdUtils::GetRealOsVersion /NOUNLOAD
pop ${out_major}
pop ${out_minor}
pop ${out_spack}
!macroend
!macro _StdU_GetRealOSBld out
StdUtils::GetRealOsBuildNo /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_GetRealOSStr out
StdUtils::GetRealOsName /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_VrfyRealOSVer out major minor spack
push `${major}`
push `${minor}`
push `${spack}`
StdUtils::VerifyRealOsVersion /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_VrfyRealOSBld out build
push `${build}`
StdUtils::VerifyRealOsBuildNo /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_GetOSEdition out
StdUtils::GetOsEdition /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_GetOSRelIdNo out
StdUtils::GetOsReleaseId /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_GetOSRelIdStr out
StdUtils::GetOsReleaseName /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_HashText out type text
push `${type}`
push `${text}`
StdUtils::HashText /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_HashFile out type file
push `${type}`
push `${file}`
StdUtils::HashFile /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_NormalizePath out path
push `${path}`
StdUtils::NormalizePath /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_GetParentPath out path
push `${path}`
StdUtils::GetParentPath /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_SplitPath out_drive out_dir out_fname out_ext path
push `${path}`
StdUtils::SplitPath /NOUNLOAD
pop ${out_drive}
pop ${out_dir}
pop ${out_fname}
pop ${out_ext}
!macroend
!macro _StdU_GetDrivePart out path
push `${path}`
StdUtils::GetDrivePart /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_GetDirPart out path
push `${path}`
StdUtils::GetDirectoryPart /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_GetFNamePart out path
push `${path}`
StdUtils::GetFileNamePart /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_GetExtnPart out path
push `${path}`
StdUtils::GetExtensionPart /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_TimerCreate out callback interval
GetFunctionAddress ${out} ${callback}
push ${out}
push ${interval}
StdUtils::TimerCreate /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_TimerDestroy out timer_id
push ${timer_id}
StdUtils::TimerDestroy /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_PrtctStr out dpsc salt text
push `${dpsc}`
push `${salt}`
push `${text}`
StdUtils::ProtectStr /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_UnprtctStr out trnc salt data
push `${trnc}`
push `${salt}`
push `${data}`
StdUtils::UnprotectStr /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_GetLibVersion out_ver out_tst
StdUtils::GetLibVersion /NOUNLOAD
pop ${out_ver}
pop ${out_tst}
!macroend
!macro _StdU_SetVerbose enable
Push ${enable}
StdUtils::SetVerboseMode /NOUNLOAD
!macroend
#################################################################################
# MAGIC NUMBERS
#################################################################################
!define StdUtils.Const.ShellVerb.PinToTaskbar 0
!define StdUtils.Const.ShellVerb.UnpinFromTaskbar 1
!define StdUtils.Const.ShellVerb.PinToStart 2
!define StdUtils.Const.ShellVerb.UnpinFromStart 3
!endif # !___STDUTILS__NSH___
Binary file not shown.
-39
View File
@@ -1,39 +0,0 @@
## Download flow (Downloader + NewsWrapper)
1. **Job ingestion**
- NZBs arrive via UI/API/URL; `urlgrabber.py` fetches remote NZBs, `nzbparser.py` turns them into `NzbObject`s, and `nzbqueue.NzbQueue` stores ordered jobs with priorities and categories.
2. **Queue to articles**
- When servers need work, `NzbQueue.get_articles` (called from `Server.get_article` in `downloader.py`) hands out batches of `Article`s per server, respecting retention, priority, and forced/paused items.
3. **Downloader setup**
- `Downloader` thread loads server configs (`config.get_servers`), instantiates `Server` objects (per host/port/SSL/threads), and spawns `NewsWrapper` instances per configured connection.
- A `selectors.DefaultSelector` watches all sockets; `BPSMeter` tracks throughput and speed limits; timers manage server penalties/restarts.
4. **Connection establishment (NewsWrapper.init_connect → NNTP.connect)**
- `Server.request_addrinfo` resolves fastest address; `NewsWrapper` builds an `NNTP` socket, wraps SSL if needed, sets non-blocking, and registers with the selector.
- First server greeting (200/201) is queued; `finish_connect` drives the login handshake (`AUTHINFO USER/PASS`) and handles temporary (480) or permanent (400/502) errors.
5. **Request scheduling & pipelining**
- `write()` chooses the next article command (`STAT/HEAD` for precheck, `BODY` or `ARTICLE` otherwise).
- Concurrency is limited by `server.pipelining_requests`; commands are queued and sent with `sock.sendall`, so there is no local send buffer.
- Sockets stay registered for `EVENT_WRITE`: without write readiness events, a temporarily full kernel send buffer could stall queued commands when there is nothing to read, so WRITE interest is needed to resume sending promptly.
6. **Receiving data**
- Selector events route to `process_nw_read`; `NewsWrapper.read` pulls bytes (SSL optimized via sabctools), parses NNTP responses, and calls `on_response`.
- Successful BODY/ARTICLE (220/222) updates per-server stats; missing/500 variants toggle capability flags (BODY/STAT support).
7. **Decoding and caching**
- `Downloader.decode` hands responses to `decoder.decode`, which yEnc/UU decodes, CRC-checks, and stores payloads in `ArticleCache` (memory or disk spill).
- Articles with DMCA/bad data trigger retry on other servers until `max_art_tries` is exceeded.
8. **Assembly to files**
- `Assembler` worker consumes decoded pieces, writes to the target file, updates CRC, and cleans admin markers. It guards disk space (`diskspace_check`) and schedules direct unpack or PAR2 handling when files finish.
9. **Queue bookkeeping**
- `NzbQueue.register_article` records success/failure; completed files advance NZF/NZO state. If all files done, the job moves to post-processing (`PostProcessor.process`), which runs `newsunpack`, scripts, sorting, etc.
10. **Control & resilience**
- Pausing/resuming (`Downloader.pause/resume`), bandwidth limiting, and sleep tuning happen in the main loop.
- Errors/timeouts lead to `reset_nw` (close socket, return article, maybe penalize server). Optional servers can be temporarily disabled; required ones schedule resumes.
- Forced disconnect/shutdown drains sockets, refreshes DNS, and exits cleanly.
-32
View File
@@ -1,32 +0,0 @@
## Repo layout
- Entry points & metadata
- `SABnzbd.py`: starts the app.
- `README.md` / `README.mkd`: release notes and overview.
- `requirements.txt`: runtime deps.
- Core application package `sabnzbd/`
- Download engine: `downloader.py` (main loop), `newswrapper.py` (NNTP connections), `urlgrabber.py`, `nzbqueue.py` (queue), `nzbparser.py` (parse NZB), `assembler.py` (writes decoded parts), `decoder.py` (yEnc/UU decode), `articlecache.py` (in-memory/on-disk cache).
- Post-processing: `newsunpack.py`, `postproc.py`, `directunpacker.py`, `sorting.py`, `deobfuscate_filenames.py`.
- Config/constants/utilities: `cfg.py`, `config.py`, `constants.py`, `misc.py`, `filesystem.py`, `encoding.py`, `lang.py`, `scheduler.py`, `notifier.py`, `emailer.py`, `rss.py`.
- UI plumbing: `interface.py`, `skintext.py`, `version.py`, platform helpers (`macosmenu.py`, `sabtray*.py`).
- Subpackages: `sabnzbd/nzb/` (NZB model objects), `sabnzbd/utils/` (helpers).
- Web interfaces & assets
- `interfaces/Glitter`, `interfaces/Config`, `interfaces/wizard`: HTML/JS/CSS skins.
- `icons/`: tray/web icons.
- `locale/`, `po/`, `tools/`: translation sources and helper scripts (`make_mo.py`, etc.).
- Testing & samples
- `tests/`: pytest suite plus `data/` fixtures and `test_utils/`.
- `scripts/`: sample post-processing hooks (`Sample-PostProc.*`).
- 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/common.py`, `licenses/`: release and licensing support files.
- Documentation
- Documentation website source is stored in the `sabnzbd.github.io` repo.
- This repo is most likely located 1 level up from the root folder of this repo.
- Documentation is split per SABnzbd version, in the `wiki` folder.
+1 -1
View File
@@ -1,7 +1,7 @@
##
## Bad URL Fetch Email template for SABnzbd
## This a Cheetah template
## Documentation: https://sabnzbd.org/wiki/extra/email-templates
## Documentation: http://sabnzbd.wikidot.com/email-templates
##
## Newlines and whitespace are significant!
##
+1 -1
View File
@@ -1,7 +1,7 @@
##
## Default Email template for SABnzbd
## This a Cheetah template
## Documentation: https://sabnzbd.org/wiki/extra/email-templates
## Documentation: http://sabnzbd.wikidot.com/email-templates
##
## Newlines and whitespace are significant!
##
+1 -1
View File
@@ -1,7 +1,7 @@
##
## RSS Email template for SABnzbd
## This a Cheetah template
## Documentation: https://sabnzbd.org/wiki/extra/email-templates
## Documentation: http://sabnzbd.wikidot.com/email-templates
##
## Newlines and whitespace are significant!
##
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -12,13 +12,13 @@
<div class="modal-body">
</div>
<div class="modal-footer">
<!--#if not $windows#-->
<div class="checkbox">
<label>
<input type="checkbox" id="show_hidden_folders"> <span>$T('hiddenFolders')</span>
</label>
</div>
<!--#end if#-->
<button type="button" class="btn btn-danger" data-dismiss="modal"><span class="glyphicon glyphicon-remove"></span> $T('cancel')</button>
<button type="button" class="btn btn-default" id="filebrowser_modal_accept"><span class="glyphicon glyphicon-ok"></span> $T('rss-accept')</button>
</div>
+34 -14
View File
@@ -14,7 +14,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 == "Notify" then $T('cmenu-notif') else ""#
#if $pane == "Email" 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 ""#
@@ -31,14 +31,14 @@
<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="stylesheet" type="text/css" href="${root}staticcfg/bootstrap/css/bootstrap.min.css?v=$cache_buster" />
<link rel="stylesheet" type="text/css" href="${root}staticcfg/css/chartist.min.css?v=$cache_buster" />
<link rel="stylesheet" type="text/css" href="${root}staticcfg/css/style.css?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=$cache_buster"/>
<link rel="stylesheet" type="text/css" href="${root}staticcfg/css/${color_scheme}.css?v=$version"/>
<!--#end if#-->
<link rel="shortcut icon" href="${root}staticcfg/ico/favicon.ico?v=$cache_buster" />
<link rel="shortcut icon" href="${root}staticcfg/ico/favicon.ico?v=$version" />
<script type="text/javascript">
// Keeping track of the form state
@@ -65,12 +65,32 @@
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=$cache_buster"></script>
<script type="text/javascript" src="${root}staticcfg/bootstrap/js/bootstrap.min.js?v=$cache_buster"></script>
<script type="text/javascript" src="${root}staticcfg/js/script.js?v=$cache_buster"></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">
// Set default functions for the autocomplete everywhere
jQuery.extend(jQuery.fn.typeahead.defaults, {
source: function (query, process) {
// If there's no separator, it must be a relative path
if(query.split(folderSeperator).length < 2 && this.\$element.data('initialdir')) {
query = this.\$element.data('initialdir') + folderSeperator + query;
}
// Get info from the API
return jQuery.get(folderBrowseUrl + '&compact=1&term=' + query, function (data) {
return process(data);
});
},
updater: function(item) {
// Is it a relative path?
if(item.indexOf(this.\$element.data('initialdir')) === 0) {
// Remove start
return item.replace(this.\$element.data('initialdir')+folderSeperator, '');
}
// Full path
return item
}
})
// to top right away
if(window.location.hash) {
@@ -92,7 +112,7 @@
<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="${root}" title="$T('Home')">
#include $webdir + "/staticcfg/images/logo-small.svg"#
</a>
</div>
@@ -135,7 +155,7 @@
</a>
</li>
<li>
<a href="${root}config/notify/" #if $pane == "Notify" then 'class="active"' else ""#>
<a href="${root}config/notify/" #if $pane == "Email" then 'class="active"' else ""#>
<span class="glyphicon glyphicon-comment"></span>
<strong>$T('cmenu-notif')</strong>
</a>
@@ -170,14 +190,14 @@
</a>
</li>
<li class="dropdown" id="search-menu">
<a data-toggle="dropdown" href="#" aria-label="$T('cmenu-search')" aria-controls="search-dropdown" aria-haspopup="true" aria-expanded="false">
<a data-toggle="dropdown" href="#">
<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')" aria-label="$T('cmenu-search')" aria-controls="search-dropdown" onkeyup="doConfigSearch(this)">
<input type="text" name="config-search" id="search-box" placeholder="$T('cmenu-search')" onkeyup="doConfigSearch(this)">
</form>
</li>
</ul>
+15 -5
View File
@@ -29,8 +29,18 @@
</tr>
<tr>
<th scope="row">OpenSSL:</th>
<td>$ssl_version</td>
<td>
$ssl_version
</td>
</tr>
<!--#if not $certificate_validation#-->
<tr>
<th scope="row"></th>
<td>
<span class="label label-danger">$T('warning')</span> $T('explain-nosslcontext')
</td>
</tr>
<!--#end if#-->
<!--#if not $windows and not $macos#-->
<tr>
<th scope="row">Par2cmdline-turbo:</th>
@@ -78,8 +88,8 @@
<td><a href="https://github.com/sabnzbd/sabnzbd" target="_blank">https://github.com/sabnzbd/sabnzbd</a></td>
</tr>
<tr>
<th scope="row">$T('menu-live-chat') </th>
<td><a href="https://sabnzbd.org/live-chat/" target="_blank">https://sabnzbd.org/live-chat/</a> (IRC &amp; Discord)</td>
<th scope="row">$T('menu-irc') </th>
<td><a href="irc://irc.synirc.net/#sabnzbd"><i>#sabnzbd</i> on <i>irc.synirc.net</i></a> $T('or') (<a href="http://sabnzbd.org/live-chat/" target="_blank">webchat</a>)</td>
</tr>
<tr>
<th scope="row">$T('menu-issues') </th>
@@ -95,8 +105,8 @@
</div>
<div class="colmask">
<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>
<div class="padding">
<h5 class="copyright">Copyright &copy; 2007-2023 The SABnzbd-Team (<a href="https://sabnzbd.org/" target="_blank">sabnzbd.org</a>)</h5>
<p class="copyright"><small>$T('yourRights')</small></p>
</div>
@@ -77,7 +77,7 @@
</div>
<div class="field-pair advanced-settings">
<label class="config" for="password_file">$T('opt-password_file')</label>
<input type="text" name="password_file" id="password_file" value="$password_file" class="fileBrowserField" data-initialdir="$my_home" data-files="1" />
<input type="text" name="password_file" id="password_file" value="$password_file" />
<span class="desc">$T('explain-password_file')</span>
</div>
<div class="field-pair">
@@ -133,7 +133,7 @@
<script type="text/javascript">
jQuery(document).ready(function() {
// Add autocomplete and file-browser
jQuery('.col1 input[name$="_dir"], #password_file').typeahead().fileBrowser();
jQuery('.col1 input[name$="_dir"]').typeahead().fileBrowser();
jQuery('#purge_log_files').click(function () {
if ( confirm("$T('confirm')") ) {
+11 -13
View File
@@ -26,7 +26,7 @@
</div>
<div class="field-pair">
<label class="config" for="port">$T('opt-port')</label>
<input type="number" name="port" id="port" value="$port" size="8" data-original="$port" min="0" max="65535" />
<input type="number" name="port" id="port" value="$port" size="8" data-original="$port" />
<span class="desc">$T('explain-port')</span>
</div>
<div class="field-pair">
@@ -35,7 +35,7 @@
<span class="desc">$T('explain-enable_https')</span>
<span class="desc"><span class="label label-warning">$T('warning').upper()</span> $T('explain-enable_https_warning')</span>
</div>
<div class="field-pair">
<div class="field-pair advanced-settings">
<label class="config" for="web_dir">$T('opt-web_dir')</label>
<select name="web_dir" id="web_dir">
<!--#for $webline in $web_list#-->
@@ -69,12 +69,12 @@
</div>
<div class="field-pair advanced-settings">
<label class="config" for="https_port">$T('opt-https_port')</label>
<input type="number" name="https_port" id="https_port" value="$https_port" size="8" data-original="$https_port" min="0" max="65535" />
<input type="number" name="https_port" id="https_port" value="$https_port" size="8" data-original="$https_port" />
<span class="desc">$T('explain-https_port')</span>
</div>
<div class="field-pair advanced-settings">
<label class="config" for="https_cert">$T('opt-https_cert')</label>
<input type="text" name="https_cert" id="https_cert" value="$https_cert" class="fileBrowserField" data-initialdir="$my_lcldata" data-files="1" />
<input type="text" name="https_cert" id="https_cert" value="$https_cert" />
<button class="btn btn-default generate_cert" title="$T('explain-new-cert')">
<span class="glyphicon glyphicon-repeat"></span>
</button>
@@ -82,7 +82,7 @@
</div>
<div class="field-pair advanced-settings">
<label class="config" for="https_key">$T('opt-https_key')</label>
<input type="text" name="https_key" id="https_key" value="$https_key" class="fileBrowserField" data-initialdir="$my_lcldata" data-files="1" />
<input type="text" name="https_key" id="https_key" value="$https_key" />
<button class="btn btn-default generate_cert" title="$T('explain-new-cert')">
<span class="glyphicon glyphicon-repeat"></span>
</button>
@@ -90,7 +90,7 @@
</div>
<div class="field-pair advanced-settings">
<label class="config" for="https_chain">$T('opt-https_chain')</label>
<input type="text" name="https_chain" id="https_chain" value="$https_chain" class="fileBrowserField" data-initialdir="$my_lcldata" data-files="1" />
<input type="text" name="https_chain" id="https_chain" value="$https_chain" />
<span class="desc">$T('explain-https_chain')</span>
</div>
<div class="field-pair">
@@ -136,14 +136,14 @@
</div>
<div class="field-pair">
<label class="config" for="apikey_display">$T('opt-apikey')</label>
<input type="text" id="apikey_display" value="$apikey" readonly />
<input type="text" id="apikey_display" class="fileBrowserField" value="$apikey" readonly />
<button class="btn btn-default show_qrcode" title="$T('explain-qr-code')" rel="$apikey" ><span class="glyphicon glyphicon-qrcode"></span></button>
<button class="btn btn-default generate_key" id="generate_new_apikey" title="$T('button-apikey')"><span class="glyphicon glyphicon-repeat"></span></button>
<span class="desc">$T('explain-apikey')</span>
</div>
<div class="field-pair">
<label class="config" for="nzbkey">$T('opt-nzbkey')</label>
<input type="text" id="nzbkey" value="$nzb_key" readonly />
<input type="text" id="nzbkey" class="fileBrowserField" value="$nzb_key" readonly />
<button class="btn btn-default show_qrcode" title="$T('explain-qr-code')" rel="$nzb_key" ><span class="glyphicon glyphicon-qrcode"></span></button>
<button class="btn btn-default generate_key" id="generate_new_nzbkey" title="$T('button-apikey')"><span class="glyphicon glyphicon-repeat"></span></button>
<span class="desc">$T('explain-nzbkey')</span>
@@ -172,10 +172,11 @@
<option value="1" <!--#if $check_new_rel == 1 then 'selected="selected"' else ""#--> >$T('on')</option>
<option value="2" <!--#if $check_new_rel == 2 then 'selected="selected"' else ""#--> >$T('also-test')</option>
</select>
<span class="desc">$T('explain-check_new_rel')</span>
</div>
<div class="field-pair advanced-settings">
<div class="field-pair advanced-settings <!--#if int($certificate_validation) == 0 then "disabled" else ""#-->">
<label class="config" for="enable_https_verification">$T('opt-enable_https_verification')</label>
<input type="checkbox" name="enable_https_verification" id="enable_https_verification" value="1" <!--#if int($enable_https_verification) > 0 then 'checked="checked"' else ""#--> />
<input type="checkbox" name="enable_https_verification" id="enable_https_verification" value="1" <!--#if int($enable_https_verification) > 0 then 'checked="checked"' else ""#--> <!--#if int($certificate_validation) == 0 then "disabled=\"disabled\"" else ""#--> />
<span class="desc">$T('explain-enable_https_verification')</span>
</div>
<div class="field-pair advanced-settings">
@@ -341,9 +342,6 @@ jQuery(document).ready(function(){
}
});
// Add autocomplete and file-browser
jQuery('.fileBrowserField').typeahead().fileBrowser();
jQuery('.show_qrcode').click(function (e) {
// Show in modal
jQuery('#modal_qr .modal-dialog').width(330)
+61 -109
View File
@@ -1,23 +1,20 @@
<!--#set global $pane="Notify"#-->
<!--#set global $pane="Email"#-->
<!--#set global $help_uri = $confighelpuri + "notifications"#-->
<!--#include $webdir + "/_inc_header_uc.tmpl"#-->
<!--#import apprise#-->
<!--#def show_notify_checkboxes($section_label)#-->
<!--#for $type in $notify_types#-->
<div class="field-pair">
<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">
<label class="config wide" 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 int($getVar($section_label + '_prio_' + $type)) > 0 then 'checked="checked"' else ""#--> />
</div>
<!--#end for#-->
<!--#end def#-->
<!--#def show_cat_box($section_label)#-->
<div class="col2-cats" <!--#if $getVar($section_label + '_enable') then '' else 'style="display:none"'#-->>
<div class="col2-cats" <!--#if int($getVar($section_label + '_enable')) > 0 then '' else 'style="display:none"'#-->>
<hr>
<b>$T('affectedCat')</b><br/>
<select name="${section_label}_cats" multiple="multiple" class="multiple_cats" size="$len($categories)">
@@ -59,12 +56,12 @@
</div>
<div class="field-pair">
<label class="config" for="email_full">$T('opt-email_full')</label>
<input type="checkbox" name="email_full" id="email_full" value="1" <!--#if $email_full then 'checked="checked"' else ""#--> />
<input type="checkbox" name="email_full" id="email_full" value="1" <!--#if int($email_full) > 0 then 'checked="checked"' else ""#--> />
<span class="desc">$T('explain-email_full')</span>
</div>
<div class="field-pair">
<label class="config" for="email_rss">$T('opt-email_rss')</label>
<input type="checkbox" name="email_rss" id="email_rss" value="1" <!--#if $email_rss then 'checked="checked"' else ""#--> />
<input type="checkbox" name="email_rss" id="email_rss" value="1" <!--#if int($email_rss) > 0 then 'checked="checked"' else ""#--> />
<span class="desc">$T('explain-email_rss')</span>
</div>
<div class="field-pair">
@@ -95,9 +92,9 @@
<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 class="result-box">
<div class="alert"></div>
</div>
</div>
<div class="field-pair result-box">
<div class="alert"></div>
</div>
</fieldset>
</div>
@@ -108,20 +105,20 @@
<h3>$T('section-NC')</h3>
<table>
<tr>
<td><input type="checkbox" name="ncenter_enable" id="ncenter_enable" value="1" <!--#if $ncenter_enable then 'checked="checked"' else ""#--> /></td>
<td><input type="checkbox" name="ncenter_enable" id="ncenter_enable" value="1" <!--#if int($ncenter_enable) > 0 then 'checked="checked"' else ""#--> /></td>
<td><label for="ncenter_enable"> $T('opt-ncenter_enable')</label></td>
</tr>
</table>
</div>
<div class="col1" <!--#if $ncenter_enable then '' else 'style="display:none"'#-->>
<div class="col1" <!--#if int($ncenter_enable) > 0 then '' else 'style="display:none"'#-->>
<fieldset>
$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 class="result-box">
<div class="alert"></div>
</div>
</div>
<div class="field-pair result-box">
<div class="alert"></div>
</div>
</fieldset>
</div>
@@ -133,21 +130,21 @@
<h3>$T('section-AC')</h3>
<table>
<tr>
<td><input type="checkbox" name="acenter_enable" id="acenter_enable" value="1" <!--#if $acenter_enable then 'checked="checked"' else ""#--> /></td>
<td><input type="checkbox" name="acenter_enable" id="acenter_enable" value="1" <!--#if int($acenter_enable) > 0 then 'checked="checked"' else ""#--> /></td>
<td><label for="acenter_enable"> $T('opt-acenter_enable')</label></td>
</tr>
</table>
$show_cat_box('acenter')
</div>
<div class="col1" <!--#if $acenter_enable then '' else 'style="display:none"'#-->>
<div class="col1" <!--#if int($acenter_enable) > 0 then '' else 'style="display:none"'#-->>
<fieldset>
$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 class="result-box">
<div class="alert"></div>
</div>
</div>
<div class="field-pair result-box">
<div class="alert"></div>
</div>
</fieldset>
</div>
@@ -159,85 +156,39 @@
<h3>$T('section-OSD') <a href="$help_uri#toc4" target="_blank"><span class="glyphicon glyphicon-question-sign"></span></a></h3>
<table>
<tr>
<td><input type="checkbox" name="ntfosd_enable" id="ntfosd_enable" value="1" <!--#if $ntfosd_enable then 'checked="checked"' else ""#--> /></td>
<td><input type="checkbox" name="ntfosd_enable" id="ntfosd_enable" value="1" <!--#if int($ntfosd_enable) > 0 then 'checked="checked"' else ""#--> /></td>
<td><label for="ntfosd_enable"> $T('opt-ntfosd_enable')</label></td>
</tr>
</table>
$show_cat_box('ntfosd')
</div>
<div class="col1" <!--#if $ntfosd_enable then '' else 'style="display:none"'#-->>
<div class="col1" <!--#if int($ntfosd_enable) > 0 then '' else 'style="display:none"'#-->>
<fieldset>
$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 class="result-box">
<div class="alert"></div>
</div>
</div>
<div class="field-pair result-box">
<div class="alert"></div>
</div>
</fieldset>
</div>
</div>
<!--#end if#-->
<div class="section" id="apprise">
<div class="col2">
<h3>Apprise <a href="$help_uri#apprise" target="_blank"><span class="glyphicon glyphicon-question-sign"></span></a></h3>
<table>
<tr>
<td><input type="checkbox" name="apprise_enable" id="apprise_enable" value="1" <!--#if $apprise_enable then 'checked="checked"' else ""#--> /></td>
<td><label for="apprise_enable"> $T('opt-apprise_enable')</label></td>
</tr>
</table>
<p>$T('explain-apprise_enable')</p>
<p><a href="https://appriseit.com/" target="_blank">Apprise documentation</a></p>
<p>$T('version'): ${apprise.__version__}</p>
$show_cat_box('apprise')
</div>
<div class="col1" <!--#if $apprise_enable then '' else 'style="display:none"'#-->>
<fieldset>
<div class="field-pair">
<label class="config" for="apprise_urls">$T('opt-apprise_urls')</label>
<input type="text" name="apprise_urls" id="apprise_urls" value="$apprise_urls" />
<span class="desc">$T('explain-apprise_urls')</span>
</div>
<div class="field-pair">
<span class="desc">$T('explain-apprise_extra_urls')</span>
</div>
<!--#set $section_label = 'apprise'#-->
<!--#for $type in $notify_types#-->
<div class="field-pair">
<label class="config" for="${section_label}_target_${type}">
$T($notify_types[$type]).replace('/', ' / ')
</label>
<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 class="result-box">
<div class="alert"></div>
</div>
</div>
</fieldset>
</div>
</div>
<div class="section" id="nscript">
<div class="col2">
<h3>$T('section-NScript') <a href="$help_uri#nscript" target="_blank"><span class="glyphicon glyphicon-question-sign"></span></a></h3>
<table>
<tr>
<td><input type="checkbox" name="nscript_enable" id="nscript_enable" value="1" <!--#if $nscript_enable then 'checked="checked"' else ""#--> /></td>
<td><input type="checkbox" name="nscript_enable" id="nscript_enable" value="1" <!--#if int($nscript_enable) > 0 then 'checked="checked"' else ""#--> /></td>
<td><label for="nscript_enable"> $T('opt-nscript_enable')</label></td>
</tr>
</table>
<em>$T('explain-nscript_enable')</em><br><a href="$help_uri#nscript" target="_blank">$T('readwiki')</a>
$show_cat_box('nscript')
</div>
<div class="col1" <!--#if $nscript_enable then '' else 'style="display:none"'#-->>
<div class="col1" <!--#if int($nscript_enable) > 0 then '' else 'style="display:none"'#-->>
<fieldset>
<div class="field-pair">
<label class="config" for="nscript_script">$T('opt-nscript_script')</label>
@@ -251,15 +202,15 @@
<div class="field-pair">
<label class="config" for="nscript_parameters">$T('opt-nscript_parameters')</label>
<input type="text" name="nscript_parameters" id="nscript_parameters" value="$nscript_parameters" />
<span class="desc">$T('Optional') - $T('readwiki')</span>
<span class="desc">$T('Optional') - $T('explain-nscript_parameters')</span>
</div>
$show_notify_checkboxes('nscript')
<div class="field-pair no-field-pair-bg">
<button class="btn btn-default saveButton"><span class="glyphicon glyphicon-ok"></span> $T('button-saveChanges')</button>
<button class="btn btn-default" type="button" id="test_nscript"><span class="glyphicon glyphicon-comment"></span> $T('testNotify')</button>
<div class="result-box">
<div class="alert"></div>
</div>
</div>
<div class="field-pair result-box">
<div class="alert"></div>
</div>
</fieldset>
</div>
@@ -269,14 +220,14 @@
<h3>$T('section-Prowl')</h3>
<table>
<tr>
<td><input type="checkbox" name="prowl_enable" id="prowl_enable" value="1" <!--#if $prowl_enable then 'checked="checked"' else ""#--> /></td>
<td><input type="checkbox" name="prowl_enable" id="prowl_enable" value="1" <!--#if int($prowl_enable) > 0 then 'checked="checked"' else ""#--> /></td>
<td><label for="prowl_enable"> $T('opt-prowl_enable')</label></td>
</tr>
</table>
<em>$T('explain-prowl_enable')</em>
$show_cat_box('prowl')
</div>
<div class="col1" <!--#if $prowl_enable then '' else 'style="display:none"'#-->>
<div class="col1" <!--#if int($prowl_enable) > 0 then '' else 'style="display:none"'#-->>
<fieldset>
<div class="field-pair">
<label class="config" for="prowl_apikey">$T('opt-prowl_apikey')</label>
@@ -290,21 +241,21 @@
$T($notify_types[$type]).replace('/', ' / ')
</label>
<select name="${section_label}_prio_$type" id="${section_label}_prio_$type">
<option value="-3" <!--#if $getVar($section_label + '_prio_' + $type) == -3 then 'selected="selected"' else ""#--> >$T('prowl-off')</option>
<option value="-2" <!--#if $getVar($section_label + '_prio_' + $type) == -2 then 'selected="selected"' else ""#--> >$T('prowl-very-low')</option>
<option value="-1" <!--#if $getVar($section_label + '_prio_' + $type) == -1 then 'selected="selected"' else ""#--> >$T('prowl-moderate')</option>
<option value="0" <!--#if $getVar($section_label + '_prio_' + $type) == 0 then 'selected="selected"' else ""#--> >$T('prowl-normal')</option>
<option value="1" <!--#if $getVar($section_label + '_prio_' + $type) == 1 then 'selected="selected"' else ""#--> >$T('prowl-high')</option>
<option value="2" <!--#if $getVar($section_label + '_prio_' + $type) == 2 then 'selected="selected"' else ""#--> >$T('prowl-emergency')</option>
<option value="-3" <!--#if $getVar($section_label + '_prio_' + $type) == "-3" then 'selected="selected"' else ""#--> >$T('prowl-off')</option>
<option value="-2" <!--#if $getVar($section_label + '_prio_' + $type) == "-2" then 'selected="selected"' else ""#--> >$T('prowl-very-low')</option>
<option value="-1" <!--#if $getVar($section_label + '_prio_' + $type) == "-1" then 'selected="selected"' else ""#--> >$T('prowl-moderate')</option>
<option value="0" <!--#if $getVar($section_label + '_prio_' + $type) == "0" then 'selected="selected"' else ""#--> >$T('prowl-normal')</option>
<option value="1" <!--#if $getVar($section_label + '_prio_' + $type) == "1" then 'selected="selected"' else ""#--> >$T('prowl-high')</option>
<option value="2" <!--#if $getVar($section_label + '_prio_' + $type) == "2" then 'selected="selected"' else ""#--> >$T('prowl-emergency')</option>
</select>
</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_prowl"><span class="glyphicon glyphicon-comment"></span> $T('testNotify')</button>
<div class="result-box">
<div class="alert"></div>
</div>
</div>
<div class="field-pair result-box">
<div class="alert"></div>
</div>
</fieldset>
</div>
@@ -315,14 +266,14 @@
<h3>$T('section-Pushover')</h3>
<table>
<tr>
<td><input type="checkbox" name="pushover_enable" id="pushover_enable" value="1" <!--#if $pushover_enable then 'checked="checked"' else ""#--> /></td>
<td><input type="checkbox" name="pushover_enable" id="pushover_enable" value="1" <!--#if int($pushover_enable) > 0 then 'checked="checked"' else ""#--> /></td>
<td><label for="pushover_enable"> $T('opt-pushover_enable')</label></td>
</tr>
</table>
<em>$T('explain-pushover_enable')</em>
$show_cat_box('pushover')
</div>
<div class="col1" <!--#if $pushover_enable then '' else 'style="display:none"'#-->>
<div class="col1" <!--#if int($pushover_enable) > 0 then '' else 'style="display:none"'#-->>
<fieldset>
<div class="field-pair">
<label class="config" for="pushover_token">$T('opt-pushover_token')</label>
@@ -356,21 +307,21 @@
$T($notify_types[$type]).replace('/', ' / ')
</label>
<select name="${section_label}_prio_$type" id="${section_label}_prio_$type">
<option value="-3" <!--#if $getVar($section_label + '_prio_' + $type) == -3 then 'selected="selected"' else ""#--> >$T('pushover-off')</option>
<option value="-2" <!--#if $getVar($section_label + '_prio_' + $type) == -2 then 'selected="selected"' else ""#--> >$T('prowl-very-low')</option>
<option value="-1" <!--#if $getVar($section_label + '_prio_' + $type) == -1 then 'selected="selected"' else ""#--> >$T('pushover-low')</option>
<option value="0" <!--#if $getVar($section_label + '_prio_' + $type) == 0 then 'selected="selected"' else ""#--> >$T('prowl-normal')</option>
<option value="1" <!--#if $getVar($section_label + '_prio_' + $type) == 1 then 'selected="selected"' else ""#--> >$T('pushover-high')</option>
<option value="2" <!--#if $getVar($section_label + '_prio_' + $type) == 2 then 'selected="selected"' else ""#--> >$T('prowl-emergency')</option>
<option value="-3" <!--#if $getVar($section_label + '_prio_' + $type) == "-3" then 'selected="selected"' else ""#--> >$T('pushover-off')</option>
<option value="-2" <!--#if $getVar($section_label + '_prio_' + $type) == "-2" then 'selected="selected"' else ""#--> >$T('prowl-very-low')</option>
<option value="-1" <!--#if $getVar($section_label + '_prio_' + $type) == "-1" then 'selected="selected"' else ""#--> >$T('pushover-low')</option>
<option value="0" <!--#if $getVar($section_label + '_prio_' + $type) == "0" then 'selected="selected"' else ""#--> >$T('prowl-normal')</option>
<option value="1" <!--#if $getVar($section_label + '_prio_' + $type) == "1" then 'selected="selected"' else ""#--> >$T('pushover-high')</option>
<option value="2" <!--#if $getVar($section_label + '_prio_' + $type) == "2" then 'selected="selected"' else ""#--> >$T('prowl-emergency')</option>
</select>
</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_pushover"><span class="glyphicon glyphicon-comment"></span> $T('testNotify')</button>
<div class="result-box">
<div class="alert"></div>
</div>
</div>
<div class="field-pair result-box">
<div class="alert"></div>
</div>
</fieldset>
</div>
@@ -380,14 +331,14 @@
<h3>$T('section-Pushbullet')</h3>
<table>
<tr>
<td><input type="checkbox" name="pushbullet_enable" id="pushbullet_enable" value="1" <!--#if $pushbullet_enable then 'checked="checked"' else ""#--> /></td>
<td><input type="checkbox" name="pushbullet_enable" id="pushbullet_enable" value="1" <!--#if int($pushbullet_enable) > 0 then 'checked="checked"' else ""#--> /></td>
<td><label for="pushbullet_enable"> $T('opt-pushbullet_enable')</label></td>
</tr>
</table>
<em>$T('explain-pushbullet_enable')</em>
$show_cat_box('pushbullet')
</div>
<div class="col1" <!--#if $pushbullet_enable then '' else 'style="display:none"'#-->>
<div class="col1" <!--#if int($pushbullet_enable) > 0 then '' else 'style="display:none"'#-->>
<fieldset>
<div class="field-pair">
<label class="config" for="pushbullet_apikey">$T('opt-pushbullet_apikey')</label>
@@ -403,11 +354,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 class="result-box">
<div class="alert"></div>
</div>
<button class="btn btn-default saveButton"><span class="glyphicon glyphicon-ok"></span> $T('button-saveChanges')</button>
<button class="btn btn-default" type="button" id="test_pushbullet"><span class="glyphicon glyphicon-comment"></span> $T('testNotify')</button>
</div>
<div class="field-pair result-box">
<div class="alert"></div>
</div>
</fieldset>
</div>
@@ -427,6 +378,7 @@ 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) {
@@ -474,7 +426,7 @@ jQuery(document).ready(function(){
}
})
}
jQuery('#test_email, #test_notif, #test_windows, #test_apprise, #test_pushbullet, #test_pushover, #test_prowl, #test_osd, #test_nscript').click(function () {
jQuery('#test_email, #test_notif, #test_windows, #test_pushbullet, #test_pushover, #test_prowl, #test_osd, #test_nscript').click(function () {
testNotification(this)
})
});
+147 -141
View File
@@ -156,8 +156,7 @@
<option value=">"> $T('rss-atleast')</option>
<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>
<option value="F"> $T('rss-from-show') ($T('rss-accept'))</option>
</select>
</td>
<td>
@@ -231,7 +230,6 @@
<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>
@@ -308,7 +306,6 @@
<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>
@@ -371,148 +368,151 @@
<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>
<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="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" />
<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>
<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" />
<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>
</div>
<!-- /colmask -->
@@ -564,7 +564,7 @@ jQuery(document).ready(function(){
}
})
jQuery('.nav-tabs a').click(function (e) {
jQuery('.tabs a').click(function (e) {
e.preventDefault()
jQuery(this).tab('show')
})
@@ -660,6 +660,12 @@ jQuery(document).ready(function(){
theForm.attr("action", "clean_rss_jobs").submit();
});
jQuery('.evalFeed').click(function(){
setActiveIcon(this)
var theForm = jQuery(this).closest("form");
theForm.attr("action", "eval_rss_feed").submit();
});
jQuery('.delFilter').click(function(){
var theForm = jQuery(this).closest("form");
theForm.attr("action", "del_rss_filter").submit();
@@ -23,11 +23,11 @@ else:
<fieldset>
<div class="field-pair">
<label class="config" for="hour">$T('hour').capitalize() : $T('minute').capitalize()</label>
<select name="hour" id="hour" aria-label="$T('hour').capitalize()">
<select name="hour" id="hour">
<!--#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" aria-label="$T('minute').capitalize()">
</select>&nbsp;<b>:</b>&nbsp;<select name="minute" id="minute">
<!--#for $i in range(60)#-->
<option value="$i"> $str($i).zfill(2) </option>
<!--#end for#-->
@@ -92,11 +92,11 @@ else:
<!--#set $odd = not $odd#-->
<form action="delSchedule" method="post">
<input type="hidden" name="apikey" value="$apikey"/>
<input type="hidden" name="line" value="$line"/>
<input type="hidden" name="line" id="line" value="$line"/>
<div class="field-pair infoTableSeperator <!--#if $odd then "" else " alt"#-->">
<input type="checkbox" name="schedenabled" value="$line" 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">
<input type="checkbox" name="schedenabled" value="$line" <!--#if int($taskinfo[$schednum][5]) > 0 then 'checked="checked"' else ""#-->>
<button class="btn btn-default float-left"><span class="glyphicon glyphicon-trash"></span></button>
<div class="scheduleEntry">
<span class="time">$taskinfo[$schednum][1]:$taskinfo[$schednum][2]</span><span class="frequency">$taskinfo[$schednum][3]</span> <span class="darkred">$taskinfo[$schednum][4]</span>
</div>
</div>
+46 -67
View File
@@ -28,7 +28,7 @@
</label>
<div class="advanced-buttonSeperator"></div>
<div class="chart-selector-container" title="$T('selectedDates')" data-placement="bottom">
<div class="chart-selector-container" title="$T('selectedDates')">
<span class="glyphicon glyphicon-signal"></span>
<!--#set today = datetime.date.today()#-->
<input type="date" name="chart-start" id="chart-start" value="<!--#echo (today-datetime.timedelta(days=30)).strftime('%Y-%m-%d')#-->"> -
@@ -47,6 +47,7 @@
<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>
@@ -58,7 +59,7 @@
</div>
<div class="field-pair advanced-settings">
<label class="config" for="port">$T('srv-port')</label>
<input type="number" name="port" id="port" size="8" value="563" min="0" max="65535" />
<input type="number" name="port" id="port" size="8" value="563" min="0" />
</div>
<div class="field-pair">
<label class="config" for="ssl">$T('srv-ssl')</label>
@@ -76,7 +77,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="16" required />
<input type="number" name="connections" id="connections" min="1" max="500" value="8" required />
</div>
<div class="field-pair">
<label class="config" for="priority">$T('srv-priority')</label>
@@ -90,15 +91,14 @@
<label class="config" for="timeout">$T('srv-timeout')</label>
<input type="number" name="timeout" id="timeout" min="20" max="240" /> <i>$T('seconds')</i>
</div>
<div class="field-pair advanced-settings">
<div class="field-pair <!--#if int($certificate_validation) == 0 then "disabled" else ""#--> advanced-settings">
<label class="config" for="ssl_verify">$T('opt-ssl_verify')</label>
<select name="ssl_verify" id="ssl_verify">
<option value="3" selected>$T('ssl_verify-strict')</option>
<option value="2">$T('ssl_verify-medium')</option>
<option value="1">$T('ssl_verify-minimal')</option>
<select name="ssl_verify" id="ssl_verify" <!--#if int($certificate_validation) == 0 then "disabled=\"disabled\"" else ""#-->>
<option value="2" selected>$T('ssl_verify-strict')</option>
<option value="1">$T('ssl_verify-normal')</option>
<option value="0">$T('ssl_verify-disabled')</option>
</select>
<span class="desc">$T('explain-ssl_verify').replace('-', '<br/>-')</span>
<span class="desc">$T('explain-ssl_verify').replace('. ', '.<br/>')</span>
</div>
<div class="field-pair advanced-settings">
<label class="config" for="ssl_ciphers">$T('opt-ssl_ciphers')</label>
@@ -106,6 +106,11 @@
<span class="desc">$T('explain-ssl_ciphers') <br>$T('readwiki')
<a href="https://sabnzbd.org/wiki/advanced/ssl-ciphers" target="_blank">https://sabnzbd.org/wiki/advanced/ssl-ciphers</a></span>
</div>
<div class="field-pair advanced-settings">
<label class="config" for="send_group">$T('srv-send_group')</label>
<input type="checkbox" name="send_group" id="send_group" value="1" />
<span class="desc">$T('srv-explain-send_group')</span>
</div>
<div class="field-pair advanced-settings">
<label class="config" for="required">$T('srv-required')</label>
<input type="checkbox" name="required" id="required" value="1" />
@@ -116,12 +121,6 @@
<input type="checkbox" name="optional" id="optional" value="1" />
<span class="desc">$T('explain-optional')</span>
</div>
<div class="field-pair advanced-settings">
<label class="config" for="pipelining_requests">$T('srv-pipelining_requests')</label>
<input type="number" name="pipelining_requests" id="pipelining_requests" min="1" max="20" value="2" />
<span class="desc">$T('explain-pipelining_requests')<br>$T('readwiki')
<a href="https://sabnzbd.org/wiki/advanced/nntp-pipelining" target="_blank">https://sabnzbd.org/wiki/advanced/nntp-pipelining</a></span>
</div>
<div class="field-pair advanced-settings">
<label class="config" for="expire_date">$T('srv-expire_date')</label>
<input type="date" name="expire_date" id="expire_date" />
@@ -137,19 +136,18 @@
<textarea name="notes" id="notes" rows="3" cols="50"></textarea>
</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"><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 class="result-box">
<div class="alert"></div>
</div>
</div>
<div class="field-pair result-box">
<div class="alert"></div>
</div>
</fieldset>
</form>
</div>
</div>
<!--#set $prio_colors = ["#59cc33", "#26a69a", "#3366cc", "#7f33cc", "#cc33a6", "#f39c12", "#cc3333", "#8d6e63"] #-->
<!--#set $prio_colors = ["#59cc33", "#3366cc","#7f33cc", "#cc33a6", "#cc3333"] #-->
<!--#set $cur_prio_color = -1 #-->
<!--#set $last_prio = -1 #-->
<!--#for $cur, $server in enumerate($servers) #-->
@@ -178,9 +176,6 @@
</table>
<button type="button" class="btn btn-default showserver"><span class="glyphicon glyphicon-pencil"></span> $T('showDetails')</button>
<button type="button" class="btn btn-default clrServer"><span class="glyphicon glyphicon-remove"></span> $T('button-clrServer')</button>
<!--#if $server['notes'] #-->
<p>$server['notes'].replace('\n', '<br>')</p>
<!--#end if#-->
</div>
<div class="col1" style="display:none;">
<input type="hidden" name="enable" id="enable$cur" value="$int($server['enable'])" />
@@ -195,7 +190,7 @@
</div>
<div class="field-pair advanced-settings">
<label class="config" for="port$cur">$T('srv-port')</label>
<input type="number" name="port" id="port$cur" value="$server['port']" size="8" min="0" max="65535" required />
<input type="number" name="port" id="port$cur" value="$server['port']" size="8" min="0" required />
</div>
<div class="field-pair">
<label class="config" for="ssl$cur">$T('srv-ssl')</label>
@@ -228,15 +223,14 @@
<input type="number" name="timeout" id="timeout$cur" value="$server['timeout']" min="20" max="240" required /> <i>$T('seconds')</i>
</div>
<div class="field-pair advanced-settings">
<div class="field-pair <!--#if int($certificate_validation) == 0 then "disabled" else ""#--> advanced-settings">
<label class="config" for="ssl_verify$cur">$T('opt-ssl_verify')</label>
<select name="ssl_verify" id="ssl_verify$cur">
<option value="3" <!--#if $server['ssl_verify'] == 3 then 'selected="selected"' else ""#--> >$T('ssl_verify-strict')</option>
<option value="2" <!--#if $server['ssl_verify'] == 2 then 'selected="selected"' else ""#--> >$T('ssl_verify-medium')</option>
<option value="1" <!--#if $server['ssl_verify'] == 1 then 'selected="selected"' else ""#--> >$T('ssl_verify-minimal')</option>
<select name="ssl_verify" id="ssl_verify$cur" <!--#if int($certificate_validation) == 0 then "disabled=\"disabled\"" else ""#-->>
<option value="2" <!--#if $server['ssl_verify'] == 2 then 'selected="selected"' else ""#--> >$T('ssl_verify-strict')</option>
<option value="1" <!--#if $server['ssl_verify'] == 1 then 'selected="selected"' else ""#--> >$T('ssl_verify-normal')</option>
<option value="0" <!--#if $server['ssl_verify'] == 0 then 'selected="selected"' else ""#--> >$T('ssl_verify-disabled')</option>
</select>
<span class="desc">$T('explain-ssl_verify').replace('-', '<br/>-')</span>
<span class="desc">$T('explain-ssl_verify').replace('. ', '.<br/>')</span>
</div>
<div class="field-pair advanced-settings">
<label class="config" for="ssl_ciphers$cur">$T('opt-ssl_ciphers')</label>
@@ -255,10 +249,9 @@
<span class="desc">$T('explain-optional')</span>
</div>
<div class="field-pair advanced-settings">
<label class="config" for="pipelining_requests$cur">$T('srv-pipelining_requests')</label>
<input type="number" name="pipelining_requests" id="pipelining_requests$cur" value="$server['pipelining_requests']" min="1" max="20" required />
<span class="desc">$T('explain-pipelining_requests')<br>$T('readwiki')
<a href="https://sabnzbd.org/wiki/advanced/nntp-pipelining" target="_blank">https://sabnzbd.org/wiki/advanced/nntp-pipelining</a></span>
<label class="config" for="send_group$cur">$T('srv-send_group')</label>
<input type="checkbox" name="send_group" id="send_group$cur" value="1" <!--#if int($server['send_group']) != 0 then 'checked="checked"' else ""#--> />
<span class="desc">$T('srv-explain-send_group')</span>
</div>
<div class="field-pair advanced-settings">
<label class="config" for="expire_date$cur">$T('srv-expire_date')</label>
@@ -278,14 +271,13 @@
<button class="btn btn-default saveButton"><span class="glyphicon glyphicon-ok"></span> $T('button-saveChanges')</button>
<button class="btn btn-default testServer" type="button"><span class="glyphicon glyphicon-sort"></span> $T('button-testServer')</button>
<button class="btn btn-default delServer"><span class="glyphicon glyphicon-trash"></span> $T('button-delServer')</button>
<div class="result-box">
<div class="alert"></div>
</div>
</div>
<div class="field-pair result-box">
<div class="alert"></div>
</div>
</fieldset>
</div>
<div class="col1 padding" style="display:block;">
<div class="col1" style="display:block;">
<!--#if 'amounts' in $server#-->
<div class="server-amounts-text">
<p>
@@ -306,7 +298,7 @@
<p><b>$T('srv-expire_date'):</b> $(server['expire_date'])</p>
<!--#end if#-->
<!--#if $server['quota']#-->
<p><b>$T('quota-left'):</b> $(server['quota_left'])</p>
<p><b>$T('quota-left'):</b> $(server['quota_left'])B</p>
<!--#end if#-->
</div>
<div class="server-chart" data-serverid="${cur}">
@@ -477,14 +469,14 @@
When finished loading
**/
jQuery(document).ready(function(){
// Initialize tooltips
jQuery('[data-toggle="tooltip"]').tooltip()
// Reload form 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('')
// Exception when change of priority, reload
jQuery('input[name="priority"], input[name="displayname"]').on('change', function() {
jQuery('.fullform').submit(function() {
// No ajax this time
jQuery('input[name="ajax"]').val('')
// Skip the fancy stuff, just submit
this.submit()
})
})
/**
@@ -518,10 +510,14 @@
// 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() {
@@ -568,26 +564,9 @@
if(data.value.result) {
resultBox.addClass('alert-success')
resultBox.prepend('<span class="glyphicon glyphicon-ok-sign"></span> ')
// Allow adding the new server if we are in the new-server section
if(theButton.parents("form[action='addServer']").length) {
jQuery(".addNewServer").removeAttr("disabled")
jQuery(".addNewServer").removeAttr("data-toggle")
jQuery(".addNewServer").removeAttr("title")
jQuery(".addNewServer").tooltip("destroy")
}
} else {
resultBox.addClass('alert-danger')
resultBox.prepend('<span class="glyphicon glyphicon-exclamation-sign"></span> ')
// Disable the adding of new server, just to be sure
if(theButton.parents("form[action='addServer']").length) {
jQuery(".addNewServer").attr("disabled", "disabled")
jQuery(".addNewServer").attr("data-toggle", "tooltip")
jQuery(".addNewServer").attr("data-placement", "top")
jQuery(".addNewServer").attr("title", "$T('wizard-test-server-required')")
jQuery(".addNewServer").tooltip()
}
}
});
});
@@ -644,7 +623,7 @@
})
}
if(event.data === 'hide_server') {
// Hide and don't load anymore until the next release
// Hide and don't load anymore untill the next release
jQuery('.Servers .server-frame').hide()
localStorage.setItem("server-frame-hide-$version", "hide")
}
@@ -71,7 +71,7 @@
<div class="field-pair">
<label class="config" for="field_sort_string_$cur">$T('sortString')</label>
<input type="text" name="sort_string" id="field_sort_string_$cur" value="$slot.sort_string" required="required" />
<button type="button" class="btn btn-default patternKey" onclick="jQuery('#pattern_explainer_$cur').toggle(); window.scrollBy(0, 500);">
<button type="button" title="$T('sort-legenda')" class="btn btn-default patternKey" onclick="jQuery('#pattern_explainer_$cur').toggle(); window.scrollBy(0, 500);">
<span class="glyphicon glyphicon-list-alt" aria-hidden="true"></span> $T('sort-legenda')
</button>
</div>
@@ -442,6 +442,8 @@
jQuery('.addSorter').click(function(){
jQuery('#sorter_0').toggle();
// Add coloring
addRowColor()
});
jQuery('.showSorter').click(function () {
@@ -453,6 +455,8 @@
} else {
theSwitch.html(theSwitch.html().replace("$T('hideDetails')", "$T('showDetails')"));
}
// Add coloring
addRowColor()
});
// Make the configured sorters sortable
@@ -69,7 +69,7 @@
</div>
<div class="field-pair">
<label class="config" for="propagation_delay">$T('opt-propagation_delay')</label>
<input type="number" name="propagation_delay" id="propagation_delay" value="$propagation_delay" min="0" /> <i>$T('minutes')</i>
<input type="number" name="propagation_delay" id="propagation_delay" value="$propagation_delay" /> <i>$T('minutes')</i>
<span class="desc">$T('explain-propagation_delay')</span>
</div>
<div class="field-pair advanced-settings">
@@ -93,39 +93,33 @@
<option value="0" <!--#if int($no_dupes) == 0 then 'selected="selected"' else ""#--> >$T('nodupes-off')</option>
<option value="4" <!--#if int($no_dupes) == 4 then 'selected="selected"' else ""#--> >$T('nodupes-tag')</option>
<option value="2" <!--#if int($no_dupes) == 2 then 'selected="selected"' else ""#--> >$T('nodupes-pause')</option>
<option value="3" <!--#if int($no_dupes) == 3 then 'selected="selected"' else ""#--> >$T('fail-to-history')</option>
<option value="3" <!--#if int($no_dupes) == 3 then 'selected="selected"' else ""#--> >$T('nodupes-fail')</option>
<option value="1" <!--#if int($no_dupes) == 1 then 'selected="selected"' else ""#--> >$T('nodupes-ignore')</option>
</select>
<span class="desc">
$T('explain-no_dupes')<br>
<a href="https://sabnzbd.org/wiki/duplicate-detection" target="_blank">https://sabnzbd.org/wiki/duplicate-detection</a>
</span>
<span class="desc">$T('explain-no_dupes')</span>
</div>
<div class="field-pair">
<label class="config" for="no_smart_dupes">$T('opt-no_smart_dupes')</label>
<select name="no_smart_dupes" id="no_smart_dupes">
<option value="0" <!--#if int($no_smart_dupes) == 0 then 'selected="selected"' else ""#--> >$T('nodupes-off')</option>
<option value="4" <!--#if int($no_smart_dupes) == 4 then 'selected="selected"' else ""#--> >$T('nodupes-tag')</option>
<option value="2" <!--#if int($no_smart_dupes) == 2 then 'selected="selected"' else ""#--> >$T('nodupes-pause')</option>
<option value="3" <!--#if int($no_smart_dupes) == 3 then 'selected="selected"' else ""#--> >$T('fail-to-history')</option>
<option value="1" <!--#if int($no_smart_dupes) == 1 then 'selected="selected"' else ""#--> >$T('nodupes-ignore')</option>
<label class="config" for="no_series_dupes">$T('opt-no_series_dupes')</label>
<select name="no_series_dupes" id="no_series_dupes">
<option value="0" <!--#if int($no_series_dupes) == 0 then 'selected="selected"' else ""#--> >$T('nodupes-off')</option>
<option value="4" <!--#if int($no_series_dupes) == 4 then 'selected="selected"' else ""#--> >$T('nodupes-tag')</option>
<option value="2" <!--#if int($no_series_dupes) == 2 then 'selected="selected"' else ""#--> >$T('nodupes-pause')</option>
<option value="3" <!--#if int($no_series_dupes) == 3 then 'selected="selected"' else ""#--> >$T('nodupes-fail')</option>
<option value="1" <!--#if int($no_series_dupes) == 1 then 'selected="selected"' else ""#--> >$T('nodupes-ignore')</option>
</select>
<span class="desc">
$T('explain-no_smart_dupes')<br>
<a href="https://sabnzbd.org/wiki/duplicate-detection" target="_blank">https://sabnzbd.org/wiki/duplicate-detection</a>
</span>
<span class="desc">$T('explain-no_series_dupes')</span>
</div>
<div class="field-pair advanced-settings">
<label class="config" for="dupes_propercheck">$T('opt-dupes_propercheck')</label>
<input type="checkbox" name="dupes_propercheck" id="dupes_propercheck" value="1" <!--#if int($dupes_propercheck) > 0 then 'checked="checked"' else ""#--> />
<span class="desc">$T('explain-dupes_propercheck')</span>
<label class="config" for="series_propercheck">$T('opt-series_propercheck')</label>
<input type="checkbox" name="series_propercheck" id="series_propercheck" value="1" <!--#if int($series_propercheck) > 0 then 'checked="checked"' else ""#--> />
<span class="desc">$T('explain-series_propercheck')</span>
</div>
<div class="field-pair">
<label class="config" for="pause_on_pwrar">$T('opt-pause_on_pwrar')</label>
<select name="pause_on_pwrar" id="pause_on_pwrar">
<option value="0" <!--#if int($pause_on_pwrar) == 0 then 'selected="selected"' else ""#--> >$T('nodupes-off')</option>
<option value="1" <!--#if int($pause_on_pwrar) == 1 then 'selected="selected"' else ""#--> >$T('nodupes-pause')</option>
<option value="2" <!--#if int($pause_on_pwrar) == 2 then 'selected="selected"' else ""#--> >$T('fail-to-history')</option>
<option value="2" <!--#if int($pause_on_pwrar) == 2 then 'selected="selected"' else ""#--> >$T('abort')</option>
</select>
<span class="desc">$T('explain-pause_on_pwrar')</span>
</div>
@@ -143,7 +137,7 @@
<select name="action_on_unwanted_extensions" id="action_on_unwanted_extensions">
<option value="0" <!--#if int($action_on_unwanted_extensions) == 0 then 'selected="selected"' else ""#--> >$T('nodupes-off')</option>
<option value="1" <!--#if int($action_on_unwanted_extensions) == 1 then 'selected="selected"' else ""#--> >$T('nodupes-pause')</option>
<option value="2" <!--#if int($action_on_unwanted_extensions) == 2 then 'selected="selected"' else ""#--> >$T('fail-to-history')</option>
<option value="2" <!--#if int($action_on_unwanted_extensions) == 2 then 'selected="selected"' else ""#--> >$T('abort')</option>
</select>
<span class="desc">$T('explain-action_on_unwanted_extensions')</span>
</div>
@@ -152,7 +146,6 @@
<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>
@@ -194,12 +187,12 @@
<div class="field-pair advanced-settings <!--#if not $have_nice then "disabled" else "" #-->">
<label class="config" for="nice">$T('opt-nice')</label>
<input type="text" name="nice" id="nice" value="$nice" <!--#if not $have_nice then 'readonly="readonly" disabled="disabled"' else "" #--> />
<span class="desc">$T('readwiki')</span>
<span class="desc">$T('explain-nice')</span>
</div>
<div class="field-pair advanced-settings <!--#if not $have_ionice then "disabled" else "" #-->">
<label class="config" for="ionice">$T('opt-ionice')</label>
<input type="text" name="ionice" id="ionice" value="$ionice" <!--#if not $have_ionice then 'readonly="readonly" disabled="disabled"' else "" #--> />
<span class="desc">$T('readwiki')</span>
<span class="desc">$T('explain-ionice')</span>
</div>
<!--#else#-->
<div class="field-pair advanced-settings">
@@ -211,13 +204,13 @@
<option value="2" <!--#if int($win_process_prio) == 2 then 'selected="selected"' else ""#-->>$T('win_process_prio-low')</option>
<option value="1" <!--#if int($win_process_prio) == 1 then 'selected="selected"' else ""#-->>$T('win_process_prio-idle')</option>
</select>
<span class="desc">$T('readwiki')</span>
<span class="desc">$T('explain-win_process_prio')</span>
</div>
<!--#end if#-->
<div class="field-pair advanced-settings">
<label class="config" for="par_option">$T('opt-par_option')</label>
<input type="text" name="par_option" id="par_option" value="$par_option" />
<span class="desc">$T('readwiki')</span>
<span class="desc">$T('explain-par_option')</span>
</div>
<div class="field-pair advanced-settings">
<label class="config" for="sfv_check">$T('opt-sfv_check')</label>
@@ -260,17 +253,16 @@
<span class="desc">$T('explain-cleanup_list')</span>
</div>
<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 $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>
<option value="days-delete" <!--#if $history_retention_option == "days-delete" then 'selected="selected"' else ""#-->>$T('history_retention-days-delete')</option>
<option value="all-archive" <!--#if $history_retention_option == "all-archive" then 'selected="selected"' else ""#-->>$T('history_retention-archive')</option>
<option value="all-delete" <!--#if $history_retention_option == "all-delete" then 'selected="selected"' else ""#-->>$T('history_retention-none')</option>
<label class="config" for="history_retention_select">$T('opt-history_retention')</label>
<input type="hidden" name="history_retention" id="history_retention" value="$history_retention">
<select name="history_retention_select" id="history_retention_select">
<option value="0">$T('history_retention-all')</option>
<option value="n">$T('history_retention-number')</option>
<option value="d">$T('history_retention-days')</option>
<option value="-1">$T('history_retention-none')</option>
</select>
<input type="number" id="history_retention_number" name="history_retention_number" min="1" value="$history_retention_number">
<input type="number" id="history_retention_number" name="history_retention_number" min="1">
<span class="desc">$T('explain-history_retention').replace('. ', '.<br/>')</span>
</div>
<div class="field-pair">
<button class="btn btn-default saveButton"><span class="glyphicon glyphicon-ok"></span> $T('button-saveChanges')</button>
@@ -361,24 +353,52 @@
<script type="text/javascript">
jQuery(document).ready(function() {
jQuery('#history_retention_option').on('change', updateHistoryRetention)
jQuery('#history_retention_select, #history_retention_number').on('change', updateHistoryRetention)
function updateHistoryRetention() {
var retention_option = jQuery('#history_retention_option').val()
var retention_setting = jQuery('#history_retention')
var retention_select = jQuery('#history_retention_select').val()
var retention_number = jQuery('#history_retention_number')
if(retention_option === "number-archive" || retention_option === "number-delete") {
retention_number.show()
retention_number.attr('placeholder', '$T('history_retention-limit')')
} else if(retention_option === "days-archive" || retention_option === "days-delete") {
retention_number.show()
retention_number.attr('placeholder', '$T('days').capitalize()')
} else {
// Keep all or keep none
if(retention_select === "0" || retention_select === "-1") {
retention_number.hide()
retention_number.val('')
retention_number.attr('placeholder', '')
retention_setting.val(retention_select)
} else {
retention_number.show()
// Days or number?
if(retention_select.indexOf("d") !== -1) {
retention_number.attr('placeholder', '$T('days').capitalize()')
if(retention_number.val()) {
retention_setting.val(retention_number.val() + 'd')
} else if(parseInt(retention_setting.val()) > 0) {
retention_number.val(parseInt(retention_setting.val()))
}
} else {
retention_number.attr('placeholder', '$T('history_retention-limit')')
if(retention_number.val()) {
retention_setting.val(retention_number.val())
} else if(parseInt(retention_setting.val()) > 0) {
retention_number.val(parseInt(retention_setting.val()))
}
}
}
}
updateHistoryRetention()
// Set the history-retention settig
var retention_setting_value = jQuery('#history_retention').val()
if(parseInt(retention_setting_value) > 0) {
// Days or number?
if(retention_setting_value.indexOf("d") !== -1) {
jQuery('#history_retention_select').val("d")
} else {
jQuery('#history_retention_select').val("n")
}
jQuery('#history_retention_number').val(parseInt(retention_setting_value))
} else {
// Keep all or keep none
jQuery('#history_retention_select').val(retention_setting_value)
jQuery('#history_retention_number').hide()
}
jQuery('.restoreDefaults').click(function(e) {
// Get section name
+13 -14
View File
@@ -1,4 +1,3 @@
<!DOCTYPE HTML>
<html lang="$active_lang">
<head>
<title>SABnzbd - $T('login')</title>
@@ -12,24 +11,24 @@
<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=$cache_buster" />
<link rel="shortcut icon" href="../staticcfg/ico/favicon.ico?v=$version" />
<link rel="stylesheet" type="text/css" href="../staticcfg/bootstrap/css/bootstrap.min.css?v=$cache_buster" />
<link rel="stylesheet" type="text/css" href="../staticcfg/css/login.css?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=$cache_buster"/>
<link rel="stylesheet" type="text/css" href="../staticcfg/css/${color_scheme}.css?v=$version"/>
<!--#end if#-->
<script type="text/javascript" src="../staticcfg/js/jquery-3.5.1.min.js?v=$cache_buster"></script>
<script type="text/javascript" src="../staticcfg/bootstrap/js/bootstrap.min.js?v=$cache_buster"></script>
<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>
</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" aria-label="$T('menu-help')">
<span class="glyphicon glyphicon-question-sign" aria-hidden="true"></span>
<a href="https://sabnzbd.org/wiki/faq#why-login" target="_blank">
<span class="glyphicon glyphicon-question-sign"></span>
</a>
</div>
<form class="form-signin" action="./" method="post">
@@ -37,14 +36,14 @@
<div class="alert alert-danger" role="alert">$error</div>
<!--#end if#-->
<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>
<input type="text" class="form-control" name="username" placeholder="$T('srv-username')" required autofocus>
<input type="password" class="form-control" name="password" placeholder="$T('srv-password')" required>
<button class="btn btn-default"><span class="glyphicon glyphicon-circle-arrow-right"></span> $T('login') </button>
<div class="text-center" data-toggle="tooltip" data-placement="bottom" title="$T('explain-sessionExpire')">
<div class="checkbox text-center" data-toggle="tooltip" data-placement="bottom" title="$T('explain-sessionExpire')">
<label>
$T('rememberme') <input type="checkbox" name="remember_me" value="1">
<input type="checkbox" name="remember_me" value="1"> $T('rememberme')
</label>
</div>
</form>
@@ -63,4 +62,4 @@
} catch(err) { }
</script>
</body>
</html>
</html>
@@ -1 +1 @@
@import url('Night.css?lastmajorupdate=510') screen and (prefers-color-scheme: dark);
@import url('Night.css') screen and (prefers-color-scheme: dark);
@@ -1,61 +1,45 @@
/*
* Night theme — overrides :root color tokens defined in style.css.
* Only structural/behavioural rules that can't be expressed as color tokens remain below.
*/
:root {
--clr-page-bg: black;
--clr-surface: #303030;
--clr-surface-alt: #444444;
--clr-surface-hover: #666666;
--clr-border: #555555;
--clr-border-subtle: #252525;
--clr-text: #EBEBEB;
--clr-text-muted: #c7c7c7;
--clr-error: #ff3333;
--clr-link: #63a7e1;
--clr-input-bg: #555555;
--clr-input-border: #252525;
--clr-dropdown-bg: #444444;
--clr-modal-bg: #727272;
--clr-modal-header-bg: #3C3C3C;
--clr-table-bg: #555555;
--clr-table-row-alt: #444444;
--clr-toggle-inactive: #666666;
body {
background-color: black;
color: #EBEBEB !important;
}
/* Links use a custom colour in dark mode */
a:not(.btn) {
color: var(--clr-link);
color: #63a7e1;
}
/* Buttons get a subtle white shadow in dark mode for depth */
.btn {
box-shadow: 1px 1px 1px rgba(255, 255, 255, .1);
box-shadow: 1px 1px 1px rgba(255, 255, 255, .1) !important;
}
/* Borders on interactive controls */
.btn:not(.btn-danger),
.btn-default,
input,
select,
textarea,
.advanced-button,
.list-group-item {
border-color: var(--clr-input-border) !important;
.list-group-item {
border-color: #252525 !important;
}
.advanced-button {
background-color: var(--clr-surface-alt);
#addFeed,
#addFeedContent,
.section {
border-bottom: 1px solid #555555;
}
.col2 p,
.col2-cats {
color: #AAA;
}
/* Sidebar section header */
.col2 h3 {
background: none repeat scroll 0 0 var(--clr-surface-alt);
background: none repeat scroll 0 0 #555555;
}
/* Various elements that need the alt surface */
.catTable,
.dropdown-menu,
.dropdown-menu .divider,
.even,
.Key tr:nth-child(odd),
.language:hover,
.navbar-default .navbar-nav>.open>a,
@@ -63,9 +47,9 @@ textarea,
.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,
.selected,
.server-disabled,
#serverResponse,
.table>tbody>tr:nth-child(odd),
@@ -73,32 +57,41 @@ textarea,
ul.tabs li.active a,
select[disabled],
select:hover {
background-color: var(--clr-surface-alt) !important;
color: var(--clr-text) !important;
background-color: #444444 !important;
color: #EBEBEB !important;
}
/* Button hovers */
.btn:not(.btn-danger):hover,
.btn:not(.btn-danger):active:focus {
background-color: var(--clr-surface-hover);
.correct {
border: 2px solid #00cc22 !important;
}
.btn:not(.btn-danger):active {
color: var(--clr-text);
.failed,
.required-star,
.error-text {
color: #ff3333 !important;
}
/* Error / failed colour */
.failed {
color: var(--clr-error) !important;
.unselected,
.selected {
border: 1px solid #EBEBEB !important;
color: #EBEBEB !important;
}
.incorrect {
border: 2px solid #ff3333 !important;
}
.disabled-text {
color: #777 !important;
}
/* Secondary text */
#rightGreyText,
small {
color: var(--clr-text-muted) !important;
color: #c7c7c7 !important;
}
/* Controls and form elements using input bg */
.Categories form.sorting-row:nth-child(2n-1) tr,
.advanced-button,
.advanced-buttonSeperator,
.alt,
.infoTableSeperator.alt,
@@ -128,16 +121,52 @@ input[type="date"],
input[type="number"],
input[type="password"],
input[disabled],
textarea,
select {
background-color: var(--clr-input-bg);
color: var(--clr-text);
background-color: #555555;
color: #EBEBEB;
}
.Categories form.sorting-row:nth-child(2n-1) tr {
background-color: var(--clr-surface);
.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;
}
/* Focus / active border */
.btn-default:focus,
.form-control:focus,
input:focus,
@@ -148,57 +177,46 @@ select:focus {
box-shadow: 0 0 0 0.25rem rgba(255, 255, 255, 0.3) !important;
}
/* Modal backdrop */
.modal-backdrop {
background-color: #262626 !important;
}
/* Table structural */
.Key tr,
.Config .table {
.Key tr {
border: none;
}
.table>tbody>tr>td,
.table>tbody>tr>th,
.infoTableSeperator,
.modal-footer,
.data-row {
border-top: none;
}
.modal-footer {
border-top: 1px solid rgb(131 131 131);
border-top: 1px solid #555555;
}
hr {
border-top: 1px solid var(--clr-border);
border-top: 1px solid #555555;
}
/* Danger button dark border */
.btn-danger {
border-color: #7b2b28;
}
/* Section borders */
.tab-content .catTable tbody,
ul.tabs a,
.section,
#addFeed,
#addFeedContent,
.colmask,
#subscriptions,
.RSS form[action="add_rss_feed"] tr:nth-child(even) {
border: 1px solid var(--clr-border);
.RSS form[action="add_rss_feed"] tr:nth-child(even),
.Config .table {
border: 1px solid #555555 !important;
}
/* Hover states */
.Categories form:first-of-type tr:last-of-type,
.default,
.dropdown-menu>li>a:focus,
.dropdown-menu>li>a:hover {
background-color: var(--clr-surface-hover);
background-color: #696969;
}
/* Elements using primary text colour */
.activeRSS,
.activeRSS a,
.activeRSS a:visited,
@@ -218,94 +236,84 @@ ul.tabs a,
a.wizard-advanced-settings,
.quoteBlock a,
a.main-helplink,
.col2 h3 a,
col2 h3 a,
.text-center a,
.text-center a:hover {
color: var(--clr-text);
color: #EBEBEB;
}
/* Remove backgrounds on container/content wrappers */
.container,
#content {
background-color: unset !important;
}
/* Server chart lines */
#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: var(--clr-text);
stroke: #EBEBEB;
}
/* Sections/cards use dark surface */
#inner,
.section,
#addFeed,
#addFeedContent {
background-color: var(--clr-surface);
box-shadow: 0 2px 8px rgba(0,0,0,0.3);
.colmask {
background-color: #303030;
}
/* Active tab uses card surface */
.nav.nav-tabs li a {
color: var(--clr-text);
.modal-header {
background-color: #3C3C3C;
}
.nav.nav-tabs li a:hover {
background-color: var(--clr-surface-alt);
border-color: var(--clr-border);
border-bottom: none;
.modal-content,
.modal-body,
.modal-footer {
background-color: #727272;
}
.nav.nav-tabs li.active a,
.nav.nav-tabs li.active a:hover {
border-color: var(--clr-border);
border-bottom: none;
background-color: var(--clr-surface);
color: var(--clr-text);
}
/* QR modal needs light background */
#modal_qr .modal-body {
background-color: #EBEBEB;
}
/* RSS icon */
.form-signin .btn.btn-default {
color: black;
}
.rss-icon-svg {
fill: white;
}
.rss-symbol {
fill: var(--clr-input-bg);
fill: #555555;
}
/* Placeholder */
::placeholder {
color: var(--clr-text) !important;
opacity: 0.5;
/* Placeholders - Will not work if grouped! */
::-webkit-input-placeholder {
color: #EBEBEB !important;
}
::-moz-placeholder {
color: #EBEBEB !important;
opacity: 1 !important;
}
:-ms-input-placeholder {
color: #EBEBEB !important;
}
/* Tooltip — inverted (light tooltip on dark page) */
.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,19 +1,18 @@
:root {
/* Light mode defaults — overridden by Night.css */
--clr-page-bg: #E4E4E4;
--clr-surface: #FFFFFF;
--clr-input-bg: #FFFFFF;
--clr-border: rgba(0, 0, 0, 0.15);
--clr-text: #000000;
--clr-text-muted: #636363;
--radius-lg: 8px;
--clr-toggle-active: #f9ca33;
--clr-toggle-inactive: #aaaaaa;
}
body {
overflow-y: scroll;
background-color: var(--clr-page-bg);
background-color: #E4E4E4;
}
* {
border-radius: 0 !important;
}
.btn,
.btn:hover,
.btn:active,
.btn:focus {
box-shadow: 1px 1px 1px rgba(0,0,0,.1) !important;
background-color: white !important;
}
.logo-header {
@@ -26,34 +25,30 @@ body {
}
.account-wall {
width: 400px;
width: 300px;
position: absolute;
top: 30%;
left: 50%;
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);
transform: translate(-50%, -30%);
}
.text-center a {
position: absolute;
right: 13px;
top: 12px;
color: var(--clr-text-muted);
color: #636363;
font-size: 1.2em
}
.text-center a:hover {
color: var(--clr-text);
color: black;
}
.form-signin .alert {
margin: 5px 0px;
}
.form-signin input {
margin-top: 5px
}
@@ -71,43 +66,12 @@ body {
font-size: 1.2em;
}
/* Toggle switch — text on the left, switch on the right */
label {
margin-top: 10px;
color: var(--clr-text-muted);
}
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;
width: 16px;
height: 16px;
margin-top: 2px;
}
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;
}
label {
color: #636363;
}
@@ -1,35 +1,6 @@
:root {
/* Border radius scale */
--radius-sm: 4px; /* dropdowns */
--radius-md: 4px; /* inputs, buttons, section headers */
--radius-lg: 6px; /* cards, modals */
/* Color palette — light mode defaults; override in Night.css */
--clr-page-bg: #E4E4E4; /* body/page background */
--clr-surface: #FFFFFF; /* cards/sections (.section, #addFeed, etc.) */
--clr-surface-alt: #F8F8F8; /* alternating rows, .alt elements */
--clr-surface-hover: #f6f6f6; /* hovered rows and buttons */
--clr-border: #dfdede; /* card/section borders */
--clr-border-subtle: #E5E5E5; /* inner dividers (#subscriptions, data-row) */
--clr-text: #000000; /* primary body text */
--clr-text-muted: #666666; /* secondary text (.col2 p, .feed-row td) */
--clr-error: red; /* errors, failed items */
--clr-link: inherit; /* links (Bootstrap default in light) */
--clr-input-bg: #FFFFFF; /* form inputs, buttons */
--clr-input-border: #d4d0c8; /* input borders */
--clr-toggle-active: #f9ca33; /* accent color (toggles, focus rings) */
--clr-toggle-inactive: #aaaaaa; /* toggle track when off */
--clr-dropdown-bg: #FFFFFF; /* dropdown menus */
--clr-modal-bg: #FFFFFF; /* modal body/footer */
--clr-modal-header-bg: #3C3C3C; /* modal header (dark in both themes) */
--clr-table-bg: #FFFFFF; /* table background */
--clr-table-row-alt: #F8F8F8; /* alternating table row background */
}
body {
overflow-y: scroll;
background-color: var(--clr-page-bg);
color: var(--clr-text);
background-color: #E4E4E4;
}
#logo {
display: block;
@@ -37,7 +8,7 @@ body {
}
#content {
color: var(--clr-text);
color: #000;
padding: 65px 20px 20px;
font-size: 13px;
}
@@ -47,7 +18,9 @@ body {
clear: both;
float: left;
overflow: visible;
width: 100%;
border: 1px solid #dfdede;
background-color: #FFF;
width: 100%
}
.advanced-button {
float: right;
@@ -57,26 +30,21 @@ body {
color: inherit;
height: auto;
padding-bottom: 5px;
line-height: 1.5em;
}
#advanced-settings-button {
float: right;
margin: 0px 0px 0px 7px;
float: left;
margin: 2px 7px 0px 0px;
}
.advanced-buttonSeperator {
width: 1px;
height: 34px;
background-color: var(--clr-border);
background-color: #dfdede;
float: right;
margin: 0px 10px;
}
.section, #addFeed, #addFeedContent {
overflow: hidden !important;
background-color: var(--clr-surface);
border: 1px solid var(--clr-border);
border-radius: var(--radius-lg);
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
margin-bottom: 12px;
border-bottom: 1px solid #dfdede;
overflow: auto;
}
.col2 {
z-index: 10;
@@ -89,10 +57,7 @@ body {
z-index: 11;
width: 100%;
float: none;
padding: 2px 0;
}
.col1 .field-pair {
position: relative;
padding: 12px 0 0;
}
.col2 h3 {
background: none repeat scroll 0 0 #666;
@@ -105,7 +70,6 @@ body {
white-space: nowrap;
text-overflow: ellipsis;
position: relative;
border-radius: var(--radius-md);
}
.col2 h3 a {
display: none;
@@ -155,7 +119,7 @@ label.config,
display: block;
text-align: left;
margin-right: 10px;
margin-left: 5px;
margin-left: 20px;
padding-top: 5px;
}
label.config-hover {
@@ -176,7 +140,7 @@ label.wide,
.desc {
display: block;
margin: 0 0 0 24%;
padding: 3px 0 0 16px;
padding: 3px 0 0 31px;
font-size: 12px;
font-style: italic;
}
@@ -197,26 +161,20 @@ input[type="checkbox"]+.desc {
.col2 p,
.col2-cats {
font-size: 12px;
color: var(--clr-text-muted);
color: #666;
margin: 1em 0;
}
.col2 p {
margin-left: 3px;
}
.field-pair {
padding: 10px 5px;
padding: 6px;
clear: both;
float: none;
overflow: hidden;
min-width: 555px;
border-bottom: 1px solid rgb(131 131 131 / 0.4);
}
.field-pair:last-child {
border-bottom: none;
}
.Key tr:nth-child(odd),
.tab-pane tr:nth-child(odd) {
background-color: var(--clr-table-row-alt);
.tab-pane tr:nth-child(odd),
.even {
background-color: #F8F8F8;
}
.field-pair:last-child,
.field-pair.no-field-pair-bg {
@@ -224,7 +182,7 @@ input[type="checkbox"]+.desc {
}
.alt,
.infoTableSeperator.alt {
background-color: var(--clr-surface-alt);
background-color: #F8F8F8;
}
.success {
color: #5cb85c !important;
@@ -233,8 +191,6 @@ input[type="checkbox"]+.desc {
padding: 0;
min-width: 300px;
margin: 0;
border-radius: var(--radius-sm);
box-shadow: 0 4px 12px rgba(0,0,0,0.12);
}
.typeahead.dropdown-menu>li>a {
padding-left: 12px;
@@ -251,23 +207,9 @@ input[type="checkbox"]+.desc {
outline: 0;
min-width: 350px;
margin: 0;
padding: 0 12px 0 6px;
padding: 0 12px 12px 6px;
}
textarea,
select,
input[type="date"],
input[type="datetime"],
input[type="datetime-local"],
input[type="email"],
input[type="month"],
input[type="number"],
input[type="password"],
input[type="search"],
input[type="tel"],
input[type="text"],
input[type="time"],
input[type="url"],
input[type="week"] {
textarea, select, input[type="date"], input[type="datetime"], input[type="datetime-local"], input[type="email"], input[type="month"], input[type="number"], input[type="password"], input[type="search"], input[type="tel"], input[type="text"], input[type="time"], input[type="url"], input[type="week"] {
padding-left: 5px;
border: 1px solid #d4d0c8;
}
@@ -311,57 +253,22 @@ input[type="text"]:focus,
input[type="time"]:focus,
input[type="url"]:focus,
input[type="week"]:focus {
background-color: var(--clr-surface-hover);
color: var(--clr-text);
background-color: #fffff0;
border: 1px solid #aaa;
}
/* Bootstrap's .checkbox reserves only 20px for the input, but our toggle is 36px.
Override to give the label enough padding so the text doesn't overlap the toggle. */
.checkbox label {
padding-left: 46px;
.col1 input[type='checkbox'] {
position: absolute;
top: auto!important;
height: 16px;
width: 16px;
margin-left: 0;
}
.checkbox input[type='checkbox'] {
margin-left: -46px;
.col2 input[type='checkbox'] {
height: 16px;
width: 16px;
margin-top: 0;
}
input[type='checkbox'] {
appearance: none;
display: inline-block;
vertical-align: middle;
flex-shrink: 0;
width: 36px;
height: 20px;
/* Track colour + white circle drawn as a radial gradient tile */
background-color: var(--clr-toggle-inactive);
background-image:
/* white circle, 7px radius (14px diameter) — on top */
radial-gradient(circle at 10px 10px, #ffffff 6.5px, rgba(255,255,255,0) 7px),
/* soft drop-shadow 2px below the circle */
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;
}
.readonly {
background-color: #eff;
}
@@ -502,6 +409,11 @@ tr.separator {
.RSS .catTable select {
max-width: 120px;
}
.RSS input[type="checkbox"] {
width:16px;
height: 16px;
margin-top: 0px;
}
.RSS form[action="add_rss_feed"] tr:nth-child(even) {
border: 1px solid #E5E5E5;
}
@@ -538,9 +450,7 @@ tr.separator {
}
.Config .table {
margin: 0;
}
.Config .table {
border: 1px solid #ddd;
border: 1px solid #ddd
}
.Config .colmask {
margin-bottom: 12px;
@@ -597,8 +507,6 @@ tr.separator {
display: none;
border: 1px solid #ccc;
width: 425px;
border-radius: var(--radius-md);
overflow: hidden;
}
.Key th {
background: none repeat scroll 0 0 #666;
@@ -627,26 +535,22 @@ tr.separator {
width: 160px;
}
.infoTableSeperator {
border-top: 1px solid var(--clr-border-subtle);
border-top: 1px solid #ddd;
}
.icon {
padding-left: 5px;
}
.modal-content {
background-color: var(--clr-modal-bg);
box-shadow: 0 8px 30px rgba(0,0,0,0.2);
border-radius: var(--radius-lg);
overflow: hidden;
box-shadow: none;
}
.modal-backdrop {
background-color: var(--clr-page-bg);
background-color: white;
}
.modal-backdrop.in {
opacity: 0.8;
}
.modal-body {
padding-bottom: 0;
background-color: var(--clr-modal-bg);
}
.modal-dialog {
padding: 3px;
@@ -655,7 +559,7 @@ tr.separator {
word-break: break-all;
}
.modal-header {
background-color: var(--clr-modal-header-bg);
background-color: #3C3C3C;
color: white;
border: 0;
padding:11px 12px 8px;
@@ -694,11 +598,6 @@ tr.separator {
.btn-default.clearBtn {
margin-left: -1px !important;
box-shadow: 1px 0px 1px rgba(0,0,0,.1) !important;
border-radius: 0 var(--radius-md) var(--radius-md) 0 !important;
}
input:has(+ .fileBrowser),
input:has(+ .clearBtn) {
border-radius: var(--radius-md) 0 0 var(--radius-md) !important;
}
#filebrowser_modal .list-group-item {
font-weight: bold;
@@ -717,7 +616,6 @@ input:has(+ .clearBtn) {
#filebrowser_modal .checkbox input+span {
opacity: 0.6;
font-weight: bold;
line-height: 1.5em;
}
#filebrowser_modal .checkbox input:checked+span,
#filebrowser_modal .checkbox span:hover {
@@ -777,11 +675,11 @@ h2.activeRSS {
color: red !important;
}
#subscriptions {
border: 1px solid var(--clr-border-subtle);
border: 1px solid #E5E5E5;
width: 100%;
}
.data-row {
border-top: 1px solid var(--clr-border-subtle);
border-top: 1px solid #E5E5E5;
}
#subscriptions td {
border: 0 none;
@@ -819,7 +717,7 @@ h2.activeRSS {
white-space: nowrap;
}
.feed-row td {
color: var(--clr-text-muted);
color: #777;
max-width: 515px;
padding: 0 0 .5em;
}
@@ -835,7 +733,7 @@ h2.activeRSS {
text-align: left;
}
.tab-content .catTable tbody {
border: 1px solid var(--clr-border);
border: 1px solid #ddd;
}
.tab-content .catTable td {
line-height: 2em;
@@ -844,7 +742,7 @@ h2.activeRSS {
word-break: break-all;
}
.tab-content .catTable tr:hover td {
background-color: var(--clr-surface-hover);
background-color: #F2F2F2;
}
span.count {
background-color: #6E6E6E;
@@ -853,18 +751,47 @@ span.count {
padding: 1px 10px;
}
a.current .count {
background-color: #777 !important;
background-color: #777!important;
}
a.current:hover .count {
background-color: #333 !important;
background-color: #333!important;
}
.nav-tabs {
margin: 10px 10px 0 10px;
border: none;
ul.tabs {
margin: 0!important;
padding: 0;
height: 30px;
}
.nav-tabs>li>a {
ul.tabs li {
float: left;
padding: 0;
margin: 0;
list-style-type: none;
}
ul.tabs a {
float: left;
font-size: 13px;
display: block;
padding: 5px 30px;
text-decoration: none;
border: 1px solid #dfdede;
border-bottom: 0;
color: black;
margin-right: 2px;
position: relative;
top: 1px;
outline: 0;
}
ul.tabs a:hover {
background-color: #eee;
color: #333;
}
ul.tabs li.active a {
background-color: #eee;
border-bottom: 1px solid #f8f4e7;
color: #000;
cursor: default;
}
.checkbox-days {
float: left;
}
@@ -881,6 +808,9 @@ a.current:hover .count {
}
/** EDITS 2015 **/
* {
border-radius: 0 !important;
}
.collapsing {
-webkit-transition: none;
@@ -894,7 +824,7 @@ input[type=radio] {
input[type="button"],
input[type="submit"] {
color: var(--clr-text);
color: #333;
display:inline-block;
padding:6px 12px;
margin-bottom: 0;
@@ -905,17 +835,16 @@ input[type="submit"] {
white-space:nowrap;
vertical-align:middle;
cursor:pointer;
background: var(--clr-input-bg) none;
border:1px solid var(--clr-input-border);
background: #fff none;
border:1px solid #ccc;
height: 34px;
border-radius: var(--radius-md);
}
input[type="button"]:hover,
input[type="submit"]:hover {
color: var(--clr-text);
background-color: var(--clr-surface-hover);
border-color: var(--clr-input-border);
color: #333;
background-color: #e6e6e6;
border-color: #adadad;
}
input[type="text"],
@@ -928,7 +857,7 @@ textarea,
select {
line-height:1.42857143;
padding:6px 12px;
border: 1px solid var(--clr-input-border);
border: 1px solid #d4d0c8;
box-shadow: inset 0 1px 1px rgba(0,0,0,.075);
display:inline-block;
vertical-align:middle;
@@ -936,9 +865,7 @@ select {
min-height: 34px;
min-width: 55px;
font-size: 13px;
background-color: var(--clr-input-bg);
color: var(--clr-text);
border-radius: var(--radius-md);
background-color: white;
}
input[type="text"]:not([size]),
@@ -954,8 +881,7 @@ input[type="text"].smaller_input {
.btn {
font-size: 13px;
height: 34px;
box-shadow: 1px 1px 1px rgba(0,0,0,.1);
border-radius: var(--radius-md);
box-shadow: 1px 1px 1px rgba(0,0,0,.1) !important;
}
.btn .glyphicon {
@@ -1031,7 +957,6 @@ input[type="checkbox"] {
background-color: #FFF;
border-bottom: 1px solid rgba(0, 0, 0, 0.2);
margin-bottom: 10px;
box-shadow: 0 2px 6px rgba(0,0,0,0.1);
}
.navbar-fixed-top {
@@ -1072,10 +997,20 @@ input[type="checkbox"] {
height: 40px;
width: 40px;
}
/*
.navbar-logo-wide {
display: none;
}
.navbar-logo-wide {
padding-top: 9px;
}
.navbar-logo-wide svg {
height: 36px;
width: 130px;
}
*/
.navbar-default .navbar-nav>li>a {
color: black;
outline: none;
}
.navbar-default .navbar-nav>li>a:hover,
@@ -1109,7 +1044,7 @@ input[type="checkbox"] {
}
.col2 table {
margin: 5px 0px 10px;
margin: 10px 0px;
}
.Servers .server-frame {
@@ -1139,7 +1074,7 @@ input[type="checkbox"] {
}
.Servers .col2 .label {
margin-top: 12px;
margin-top: 8px;
font-size: 0.85em;
float: right;
line-height: 1.1em;
@@ -1153,10 +1088,6 @@ input[type="checkbox"] {
opacity: 0.7;
}
.Servers .col1.padding {
padding: 12px;
}
.Servers .server-amounts-text {
width: 20%;
float: left;
@@ -1210,8 +1141,8 @@ input[type="checkbox"] {
}
.Servers .col2 label,
.Notify .col2 label {
margin: 6px 0 0 6px;
.Email .col2 label {
margin: 2px 0 0 4px;
cursor: pointer;
}
@@ -1220,7 +1151,6 @@ input[type="checkbox"] {
}
.result-box .alert {
margin-top: 10px;
margin-bottom: 0px;
display: none;
}
@@ -1256,11 +1186,6 @@ input[type="checkbox"] {
top: 1px;
}
.Notify .notify-checkboxes {
float: left;
margin-right: 8px;
}
.main-restarting.in {
text-align: center;
opacity: 0.9;
@@ -1297,6 +1222,7 @@ input[type="checkbox"] {
}
.value-and-select select {
min-width: 30px;
margin-top: 1px;
}
.dotOne, .dotTwo, .dotThree {
@@ -1333,58 +1259,10 @@ input[type="checkbox"] {
100% { transform: rotate(359deg); }
}
/***
Theme-aware rules
These use CSS variables so Night.css only needs to override :root.
Most color properties are already applied at each selector's first declaration above;
only rules with no earlier home live here.
***/
/* Dropdown menus */
.dropdown-menu {
background-color: var(--clr-dropdown-bg);
}
.dropdown-menu>li>a {
color: var(--clr-text);
}
.dropdown-menu>li>a:hover,
.dropdown-menu>li>a:focus {
background-color: var(--clr-surface-hover);
color: var(--clr-text);
}
/* Modal footer (modal-body bg is set at its first declaration above) */
.modal-footer {
background-color: var(--clr-modal-bg);
}
/* Table rows */
.table>tbody>tr:nth-child(odd),
.table-striped>tbody>tr:nth-child(odd) {
background-color: var(--clr-table-row-alt);
color: var(--clr-text);
}
.table>tbody>tr:nth-child(even),
.table-striped>tbody>tr:nth-child(even) {
background-color: var(--clr-table-bg);
color: var(--clr-text);
}
/* Placeholder text */
::placeholder {
color: var(--clr-text-muted) !important;
}
/***
RTL Fixes
***/
html[dir="rtl"] .col1 input[type='checkbox'] {
right: auto;
left: 10px;
}
html[dir="rtl"] .col1 input[type='checkbox'],
html[dir="rtl"] .col2 h3 a {
left: 5px;
}
@@ -1446,6 +1324,7 @@ html[dir="rtl"] .Scheduling form[action="addSchedule"] input[type="checkbox"] {
@media screen and (max-width: 1000px) {
.col1 {
float: right;
padding: 5px 0px;
}
.col1 h5 {
@@ -60,7 +60,6 @@
// Initialize
this.element = $(element);
this.initialDir = null;
this.showFiles = false;
this.currentBrowserPath = null;
this.currentRequest = null;
this.fileBrowserDialog = $('#filebrowser_modal .modal-body');
@@ -100,11 +99,6 @@
this.initialDir = this.element.data('initialdir') + folderSeperator + this.element.val();
}
// Are we selecting files or folders
if(this.element.data('files')) {
this.showFiles = true
}
// Browse
this.browse(this.initialDir , folderBrowseUrl);
@@ -150,20 +144,12 @@
// Still loading
if (this.currentRequest) this.currentRequest.abort();
// Show hidden folders
var params = { name: path}
if($('#show_hidden_folders').is(':checked')) {
params['show_hidden_folders'] = "1"
}
// Show files?
if(this.showFiles) {
params['show_files'] = "1"
}
// Show hidden folders on Linux?
var extraHidden = $('#show_hidden_folders').is(':checked') ? '&show_hidden_folders=1' : '';
// Get current folders
this.currentBrowserPath = path;
this.currentRequest = $.getJSON(endpoint, params, function (data) {
this.currentRequest = $.getJSON(endpoint + extraHidden, { name: path }, function (data) {
// Clean
self.fileBrowserDialog.empty();
@@ -177,21 +163,11 @@
}
// Regular link
link = $('<a class="list-group-item" href="javascript:void(0)" />').click(function () {
// Are we looking for files and did we select a file?
if(self.showFiles && !entry.dir) {
// Trigger selection
self.currentBrowserPath = entry.path
$('#filebrowser_modal_accept').click()
} else {
self.browse(entry.path, endpoint);
}
}).text(entry.name);
self.browse(entry.path, endpoint); }
).text(entry.name);
// Back image
if(entry.name === '..') {
$('<span class="glyphicon glyphicon-arrow-left"></span> ').prependTo(link);
} else if(!entry.dir) {
$('<span class="glyphicon glyphicon-file"></span> ').prependTo(link);
} else {
$('<span class="glyphicon glyphicon-folder-open"></span> ').prependTo(link);
}
@@ -262,10 +238,9 @@ function do_restart() {
// Show overlay
$('.main-restarting').show()
// Check if we need redirect
// Uses == on purpose, because val() returns string and data() returns int!
// What template
var switchedHTTPS = ($('#enable_https').is(':checked') === ($('#enable_https').data('original') === undefined))
var portsUnchanged = ($('#port').val() == $('#port').data('original')) && ($('#https_port').val() == $('#https_port').data('original'))
var portsUnchanged = ($('#port').val() === $('#port').data('original')) && ($('#https_port').val() === $('#https_port').data('original'))
// Are we on settings page or did nothing change?
if(!$('body').hasClass('General') || (!switchedHTTPS && portsUnchanged)) {
@@ -332,7 +307,7 @@ function do_restart() {
});
}
// Remove obfuscation
// Remove obfusication
function removeObfuscation() {
$('input[data-hide]').each(function(index, objInput) {
$(objInput).attr('name', $(objInput).data('hide'))
@@ -340,35 +315,13 @@ function removeObfuscation() {
return true
}
// Set default functions for the autocomplete everywhere
jQuery.extend(jQuery.fn.typeahead.defaults, {
source: function (query, process) {
// If there's no separator, it must be a relative path
if(query.split(folderSeperator).length < 2 && this.$element.data('initialdir')) {
query = this.$element.data('initialdir') + folderSeperator + query;
}
var params = { compact: "1", name: query }
if($('#show_hidden_folders').is(':checked')) {
params['show_hidden_folders'] = "1"
}
if(this.$element.data('files')) {
params['show_files'] = "1"
}
// Get info from the API
return jQuery.get(folderBrowseUrl, params, function (data) {
return process(data["paths"]);
});
},
updater: function(item) {
// Is it a relative path?
if(item.indexOf(this.$element.data('initialdir')) === 0) {
// Remove start
return item.replace(this.$element.data('initialdir') + folderSeperator, '');
}
// Full path
return item
}
})
// 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')
})
}
$(document).ready(function () {
/**
@@ -474,16 +427,14 @@ $(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()
}
// 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()
addRowColor()
});
/*
@@ -503,7 +454,6 @@ 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
@@ -514,45 +464,6 @@ $(document).ready(function() {
});
});
}
}).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
@@ -578,46 +489,11 @@ $(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 = $.trim($(value).val())
var searchTerm = $(value).val().replace('"', '\"')
var searchOutput = $('#search-dropdown')
// Build new search
@@ -628,24 +504,14 @@ 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(function() {
return $(this).text().toUpperCase().indexOf(searchTerm.toUpperCase()) >= 0
})
var subResults = results.filter(':contains("'+searchTerm+'")')
if(subResults.length > 0) {
// Add the section
searchOutput.append($('<li class="divider"></li>'))
searchOutput.append($('<li class="dropdown-header"></li>').text(configTranslate.searchPages[page_index]))
searchOutput.append('<li class="divider"></li>')
searchOutput.append('<li class="dropdown-header">'+configTranslate.searchPages[page_index]+'</li>')
$.each(subResults, function(index, result) {
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())
)
)
searchOutput.append('<li><a href="'+rootURL + 'config/' + arrPages[page_index] + '/#' + $(result).attr('for') +'">'+$(result).text()+'</a></li>')
})
}
})
@@ -1,7 +1,7 @@
<div class="history" id="history-tab">
<div class="history" id="history-tab" data-bind="visible: hasHistory() || displayTabbed()" style="display: none">
<div class="history-header">
<h2>$T('menu-history') <small data-bind="visible: history.showArchive()">($T('archive'))</small></h2>
<a href="#" aria-label="$T('Glitter-multiOperations')" data-bind="click: history.showMultiEdit, visible: hasHistory()">
<h2>$T('menu-history')</h2>
<a href="#" data-bind="click: history.showMultiEdit">
<span class="glyphicon glyphicon-tasks" data-tooltip="true" data-placement="left" title="$T('Glitter-multiOperations')"></span>
</a>
</div>
@@ -10,7 +10,7 @@
<tr>
<th style="width: 25px;"></th>
<th></th>
<th class="table-status-header" data-bind="css: { 'table-header-status-smaller' : extraHistoryColumns().length || displayCompact() }"></th>
<th class="table-status-header" data-bind="css: { 'table-header-status-smaller' : extraHistoryColumns().length }"></th>
<!-- ko foreach: extraHistoryColumns -->
<th class="table-header-extra"></th>
<!-- /ko -->
@@ -18,16 +18,7 @@
<th style="width: 60px;"></th>
</tr>
</thead>
<!-- ko if: !hasHistory() -->
<tbody class="no-downloads">
<tr>
<td colspan="6" data-bind="attr: { 'colspan': 5 + extraHistoryColumns().length }">
<span>$T('empty')</span>
</td>
</tr>
</tbody>
<!-- /ko -->
<tbody data-bind="foreach: history.historyItems, visible: hasHistory()" style="display: none;">
<tbody data-bind="foreach: history.historyItems">
<tr class="history-item" data-bind="css: {'history-failed-download':failed()}">
<td>
<div data-bind="visible: processingWaiting()">
@@ -45,12 +36,12 @@
<span class="glyphicon glyphicon-ok"></span>
</div>
<div data-bind="visible: failed() && canRetry()">
<a class="retry-button" href="#" aria-label="$T('button-retry')" data-bind="click: retry">
<a class="retry-button" href="#" data-bind="click: retry">
<span class="glyphicon glyphicon-exclamation-sign"></span>
</a>
</div>
<div data-bind="visible: failed() && !canRetry()">
<span class="retry-button-inactive" role="img" aria-label="$T('post-Failed')">
<span class="retry-button-inactive">
<span class="glyphicon glyphicon-exclamation-sign"></span>
</span>
</div>
@@ -70,10 +61,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')" aria-label="$T('Glitter-selectJob')" data-bind="click: parent.addMultiEdit, attr: { 'id': 'multiedit_' + id } " />
<input type="checkbox" name="multiedit" title="$T('Glitter-multiSelect')" data-bind="click: parent.addMultiEdit, attr: { 'id': 'multiedit_' + id } " />
</label>
<div class="dropdown" data-bind="visible: !parent.isMultiEditing()">
<a href="#" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" aria-label="$T('nzoDetails')" data-bind="click: updateAllHistoryInfo">
<a href="#" data-toggle="dropdown" data-bind="click: updateAllHistoryInfo">
<span class="caret"></span>
</a>
<!-- ko if: hasDropdown() -->
@@ -83,19 +74,13 @@
<div class="col-sm-2">$T('name')</div>
<div class="col-sm-10" data-bind="text: historyStatus.name"></div>
</div>
<div class="row" data-bind="visible: historyStatus.time_added">
<div class="col-sm-2">$T('rss-added')</div>
<div class="col-sm-10" data-bind="text: timeAdded(), attr: { 'data-timestamp': historyStatus.time_added }"></div>
</div>
<div class="row">
<div class="col-sm-2">$T('post-Completed')</div>
<div class="col-sm-10" data-bind="text: completedOn, attr: { 'data-timestamp': completed }"></div>
</div>
<div class="row">
<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>
</div>
<div class="col-sm-10" data-bind="text: glitterTranslate.status[historyStatus.status()] ? glitterTranslate.status[historyStatus.status()] : statusText()"></div>
</div>
<div class="row">
<div class="col-sm-2">$T('size')</div>
@@ -119,14 +104,11 @@
<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, attr: { 'aria-label': processingDownload() == 2 ? '$T('abort')' : '$T('nzo-delete')' }">
<a href="#" data-bind="click: deleteSlot">
<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>
@@ -134,43 +116,34 @@
</tbody>
</table>
<div class="history-footer">
<div class="input-group search-box">
<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')' }">
<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 }">
<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 }">
<!-- 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 data-bind="css: { active: isCurrent, disabled: isDots }, click: onclick">
<span data-bind="text: page"></span>
</li>
</ul>
<div class="multioperations-selector" id="history-options">
<a href="#" class="hover-button history-archive" title="$T('showArchive') / $T('showAllHis')" data-tooltip="true" data-placement="top" data-bind="click: history.toggleShowArchive, css: { 'history-options-show-failed': history.showArchive }"><svg viewBox="6 6 36 36" height="14" width="14" class="archive-icon"><path d="M41.09 10.45l-2.77-3.36c-.56-.66-1.39-1.09-2.32-1.09h-24c-.93 0-1.76.43-2.31 1.09l-2.77 3.36c-.58.7-.92 1.58-.92 2.55v25c0 2.21 1.79 4 4 4h28c2.21 0 4-1.79 4-4v-25c0-.97-.34-1.85-.91-2.55zm-17.09 24.55l-11-11h7v-4h8v4h7l-11 11zm-13.75-25l1.63-2h24l1.87 2h-27.5z"/></svg></a>
<a href="#" class="hover-button" title="$T('showFailedHis') / $T('showAllHis')" data-tooltip="true" data-placement="top" data-bind="click: history.toggleShowFailed, css: { 'history-options-show-failed': history.showFailed }"><span class="glyphicon glyphicon-exclamation-sign"></span></a>
<a href="#" class="hover-button" title="$T('link-retryAll')" data-tooltip="true" data-placement="top" data-bind="click: history.retryAllFailed"><span class="glyphicon glyphicon-repeat"></span></a>
<a href="#" class="hover-button" title="$T('button-mark-completed')" data-bind="visible: (history.isMultiEditing() && hasHistory()), click: history.doMultiMarkCompleted" data-tooltip="true" data-placement="top"><span class="glyphicon glyphicon-ok"></span></a>
<a href="#" class="hover-button" title="$T('link-retryAll')" data-tooltip="true" data-placement="left" data-bind="click: history.retryAllFailed"><span class="glyphicon glyphicon-repeat"></span></a>
<a href="#" class="hover-button" title="$T('showAllHis') / $T('showFailedHis')" data-tooltip="true" data-placement="left" data-bind="click: history.toggleShowFailed, css: { 'history-options-show-failed': history.showFailed }"><span class="glyphicon glyphicon-exclamation-sign"></span></a>
<div data-bind="visible: (history.isMultiEditing() && hasHistory())">
<div data-bind="visible: history.isMultiEditing()">
<span class="label label-default" data-bind="text: history.multiEditItems().length">0</span>
<label for="multiedit-checkall-history">
<input type="checkbox" name="multieditCheckAll" id="multiedit-checkall-history" title="$T('Glitter-checkAll')" data-bind="click: history.checkAllJobs" data-tooltip="true" data-placement="top" />
</label>
</div>
<a href="#" class="hover-button" title="$T('nzo-delete')" data-bind="visible: (history.isMultiEditing() && hasHistory()), click: history.doMultiDelete" data-tooltip="true" data-placement="top">
<a href="#" class="hover-button" data-bind="visible: history.isMultiEditing(), click: history.doMultiDelete">
<span class="glyphicon glyphicon-trash"></span>
</a>
<a href="#modal-purge-history" class="hover-button" title="$T('purgeHist')" data-bind="visible: !(history.isMultiEditing() && hasHistory())" data-toggle="modal" data-tooltip="true" data-placement="top">
<a href="#modal-purge-history" class="hover-button" title="$T('purgeHist')" data-bind="visible: !history.isMultiEditing()" data-toggle="modal" data-tooltip="true" data-placement="left">
<span class="glyphicon glyphicon-trash"></span>
</a>
</div>
@@ -181,6 +154,10 @@
<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>
<div class="info-container history-info" data-bind="visible: !hasHistory() && !displayTabbed()" style="display: none">
<span class="glyphicon glyphicon-save"></span>
<span data-bind="text: history.downloadedToday"></span>B $T('Glitter-today')
<span data-bind="text: history.downloadedMonth"></span>B $T('Glitter-thisMonth')
<span data-bind="text: history.downloadedTotal"></span>B $T('Glitter-total')
</div>
+15 -15
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" aria-label="$T('Glitter-toggleNavigation')" aria-controls="navbar-collapse" aria-expanded="false">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar-collapse">
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-logo" href="./" aria-label="SABnzbd $T('menu-queue')">
<a class="navbar-logo" href="./">
#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')', 'aria-label': 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')' }">
<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" aria-label="$T('pauseFor')" aria-haspopup="true" aria-expanded="false">
<button type="button" class="btn btn-default navbar-btn dropdown-toggle" data-toggle="dropdown">
<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,13 +37,13 @@
<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" aria-haspopup="true">
<button type="button" class="btn btn-default navbar-btn navbar-speed" data-toggle="dropdown">
<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)" aria-hidden="true" tabindex="-1">
<button type="button" class="btn btn-default navbar-btn dropdown-toggle" data-toggle="dropdown" onclick="keepOpen(this)">
<span class="caret"></span>
</button>
<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;">
<a href="#" class="max-speed-input-clear hover-button" 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">
@@ -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" aria-label="$T('Glitter-addNZB')"><span class="glyphicon glyphicon-plus-sign"></span></a>
<a href="#modal-add-nzb" data-toggle="modal"><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" aria-label="$T('Glitter-statusInterfaceOptions')"><span class="glyphicon glyphicon-wrench"></span></a>
<a href="#modal-options" data-toggle="modal"><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/" aria-label="$T('cmenu-rss')">
<a href="./config/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/" aria-label="SABnzbd $T('menu-config')"><span class="glyphicon glyphicon-cog"></span></a>
<a href="./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)" aria-label="$T('Glitter-mainMenu')" aria-haspopup="true" aria-expanded="false">
<a href="#" data-toggle="dropdown" onclick="keepOpen(this)">
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
@@ -102,7 +102,7 @@
<li class="divider"></li>
<li class="dropdown-header"><span class="glyphicon glyphicon-off"></span> $T('Glitter-onFinish'):</li>
<li>
<select data-bind="value: finishaction, event: { change: setOnQueueFinish }" class="form-control" aria-label="$T('Glitter-onFinish')">
<select data-bind="value: finishaction, event: { change: setOnQueueFinish }" class="form-control">
<option value=""></option>
<option value="shutdown_program">$T('shutdownSab')</option>
<!--#if $power_options#-->
@@ -116,4 +116,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" aria-label="$T('warnings')">
<table class="table table-hover table-messages">
<thead>
<tr>
<th class="table-messages-header-label"><span class="sr-only">$T('warnings')</span></th>
<th class="table-messages-header-label"></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" aria-label="$T('button-clear')"><span class="glyphicon glyphicon-remove" aria-hidden="true"></span></a>
<a href="#" class="hover-button"><span class="glyphicon glyphicon-remove"></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" aria-label="$T('button-clear')"><span class="glyphicon glyphicon-remove" aria-hidden="true"></span></a>
<a href="#" data-bind="click: clear" class="hover-button"><span class="glyphicon glyphicon-remove"></span></a>
</td>
<!-- /ko -->
</tr>
@@ -45,4 +45,4 @@
<!-- /ko -->
</tbody>
</table>
</div>
</div>
@@ -6,12 +6,8 @@
<span class="glyphicon glyphicon-open"></span> $T('Glitter-notification-uploading') <span class="main-notification-box-file-count"></span>
</div>
<div class="main-notification-box-uploading-failed">
<span class="glyphicon glyphicon-exclamation-sign"></span> $T('Glitter-notification-upload-failed').replace('%s', '') <span class="main-notification-box-file-count"></span>
</div>
<div class="main-notification-box-queue-repair">
<span class="glyphicon glyphicon-wrench"></span> $T('Glitter-repairQueue')
<span class="glyphicon glyphicon glyphicon-wrench"></span> $T('Glitter-repairQueue')
</div>
<div class="main-notification-box-disconnect">
@@ -138,7 +134,7 @@
<div class="col-sm-6">$T('dashboard-systemPerformance') &nbsp; </div>
<div class="col-sm-6 col-dot-overflow" data-bind="visible: hasPerformanceInfo">
<span data-bind="text: statusInfo.pystone"></span>
<a href="#" class="diskspeed-button" data-bind="click: loadStatusInfo" data-tooltip="true" data-placement="right" title="$T('dashboard-repeatTest') (~10 $T('seconds'))"><span class="glyphicon glyphicon-repeat"></span></a>
<a href="#" class="diskspeed-button" data-bind="click: loadStatusInfo" data-tooltip="true" data-placement="right" title="$T('dashboard-repeatTest')"><span class="glyphicon glyphicon-repeat"></span></a>
<small title="$cpumodel $cpusimd" data-tooltip="true">$cpumodel $cpusimd</small>
</div>
<div class="col-sm-6 col-loading" data-bind="visible: !hasPerformanceInfo()">$T('Glitter-loading')<span class="loader-dot-one">.</span><span class="loader-dot-two">.</span><span class="loader-dot-three">.</span></div>
@@ -147,7 +143,7 @@
<div class="col-sm-6">$T('dashboard-downloadDirSpeed') &nbsp; </div>
<div class="col-sm-6 col-dot-overflow" data-bind="visible: hasPerformanceInfo">
<span data-bind="text: statusInfo.downloaddirspeed()"></span> MB/s
<a href="#" class="diskspeed-button" data-bind="click: loadStatusInfo" data-tooltip="true" data-placement="right" title="$T('dashboard-repeatTest') (~10 $T('seconds'))"><span class="glyphicon glyphicon-repeat"></span></a>
<a href="#" class="diskspeed-button" data-bind="click: loadStatusInfo" data-tooltip="true" data-placement="right" title="$T('dashboard-repeatTest')"><span class="glyphicon glyphicon-repeat"></span></a>
<small data-bind="text: statusInfo.downloaddir, attr: { 'data-original-title': statusInfo.downloaddir }" data-tooltip="true"></small>
</div>
<div class="col-sm-6 col-loading" data-bind="visible: !hasPerformanceInfo()">$T('Glitter-loading')<span class="loader-dot-one">.</span><span class="loader-dot-two">.</span><span class="loader-dot-three">.</span></div>
@@ -156,7 +152,7 @@
<div class="col-sm-6">$T('dashboard-completeDirSpeed') &nbsp; </div>
<div class="col-sm-6 col-dot-overflow" data-bind="visible: hasPerformanceInfo">
<span data-bind="text: statusInfo.completedirspeed()"></span> MB/s
<a href="#" class="diskspeed-button" data-bind="click: loadStatusInfo" data-tooltip="true" data-placement="right" title="$T('dashboard-repeatTest') (~10 $T('seconds'))"><span class="glyphicon glyphicon-repeat"></span></a>
<a href="#" class="diskspeed-button" data-bind="click: loadStatusInfo" data-tooltip="true" data-placement="right" title="$T('dashboard-repeatTest')"><span class="glyphicon glyphicon-repeat"></span></a>
<small data-bind="text: statusInfo.completedir, attr: { 'data-original-title': statusInfo.completedir }" data-tooltip="true"></small>
</div>
<div class="col-sm-6 col-loading" data-bind="visible: !hasPerformanceInfo()">$T('Glitter-loading')<span class="loader-dot-one">.</span><span class="loader-dot-two">.</span><span class="loader-dot-three">.</span></div>
@@ -165,17 +161,11 @@
<div class="col-sm-6">$T('dashboard-internetBandwidth') &nbsp; </div>
<div class="col-sm-6" data-bind="visible: hasPerformanceInfo">
<span data-bind="text: statusInfo.internetbandwidth()"></span> MB/s
<a href="#" class="diskspeed-button" data-bind="click: loadStatusInfo" data-tooltip="true" data-placement="right" title="$T('dashboard-repeatTest') (~10 $T('seconds'))"><span class="glyphicon glyphicon-repeat"></span></a>
<a href="#" class="diskspeed-button" data-bind="click: loadStatusInfo" data-tooltip="true" data-placement="right" title="$T('dashboard-repeatTest')"><span class="glyphicon glyphicon-repeat"></span></a>
<small><span data-bind="text: statusInfo.internetbandwidth()*8"></span> Mbps</small>
</div>
<div class="col-sm-6 col-loading" data-bind="visible: !hasPerformanceInfo()">$T('Glitter-loading')<span class="loader-dot-one">.</span><span class="loader-dot-two">.</span><span class="loader-dot-three">.</span></div>
</div>
<div class="row">
<div class="col-sm-6">$T('platform') &nbsp; </div>
<div class="col-sm-6">
$platform
</div>
</div>
<div class="row test-download">
<div class="col-sm-6">$T('dashboard-testDownload') &nbsp; </div>
<div class="col-sm-6">
@@ -274,7 +264,7 @@
</div>
<!-- ko if: serverconnections().length > 0 -->
<table class="table table-hover table-server-connections" aria-label="$T('connections')" data-bind="visible: \$root.showActiveConnections()">
<table class="table table-hover table-server-connections" data-bind="visible: \$root.showActiveConnections()">
<thead>
<tr>
<th><strong>$T('article-id')</strong></th>
@@ -304,7 +294,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" aria-label="$T('Glitter-orphanedJobs')">
<table class="table table-hover table-striped">
<thead>
<tr>
<th style="width: 30px;"></th>
@@ -333,7 +323,7 @@
</label>
</div>
<div class="form-group">
<label class="col-sm-6 control-label" for="refreshRate-option">$T('Glitter-interfaceRefresh')</label>
<label class="col-sm-6 control-label">$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 +341,9 @@
</div>
</div>
<div class="form-group">
<label class="col-sm-6 control-label" for="queue-pagination-limit">$T('Glitter-queueItemLimit')</label>
<label class="col-sm-6 control-label">$T('Glitter-queueItemLimit')</label>
<div class="col-sm-4">
<select name="queue-pagination-limit" id="queue-pagination-limit" class="form-control" data-bind="value: queue.paginationLimit">
<select name="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 +356,9 @@
</div>
</div>
<div class="form-group">
<label class="col-sm-6 control-label" for="history-pagination-limit">$T('Glitter-historyItemLimit')</label>
<label class="col-sm-6 control-label">$T('Glitter-historyItemLimit')</label>
<div class="col-sm-4">
<select name="history-pagination-limit" id="history-pagination-limit" class="form-control" data-bind="value: history.paginationLimit">
<select name="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 +371,9 @@
</div>
</div>
<div class="form-group">
<label class="col-sm-6 control-label" for="general-date-format">$T('Glitter-dateFormat')</label>
<label class="col-sm-6 control-label">$T('Glitter-dateFormat')</label>
<div class="col-sm-4">
<select name="general-date-format" id="general-date-format" class="form-control" data-bind="value: dateFormat">
<select name="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 +389,12 @@
</div>
</div>
<div class="form-group">
<label class="col-sm-6 control-label" for="general-extra-queue-columns">
<label class="col-sm-6 control-label">
$T('Glitter-showExtraQueueColumn')<br />
<span class="label label-warning">&gt; 1200 pixels</span>
</label>
<div class="col-sm-4">
<select name="general-extra-column" id="general-extra-queue-columns" class="form-control-multiselect form-control" data-bind="selectedOptions: extraQueueColumns" multiple="true">
<select name="general-extra-column" 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 +404,12 @@
</div>
</div>
<div class="form-group">
<label class="col-sm-6 control-label" for="general-extra-history-columns">
<label class="col-sm-6 control-label">
$T('Glitter-showExtraHistoryColumn')<br />
<span class="label label-warning">&gt; 1200 pixels</span>
</label>
<div class="col-sm-4">
<select name="general-extra-column" id="general-extra-history-columns" class="form-control-multiselect form-control" data-bind="selectedOptions: extraHistoryColumns" multiple="true">
<select name="general-extra-column" 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 +417,53 @@
</div>
</div>
<div class="form-group form-checkbox">
<label class="col-sm-6 control-label" for="displayCompact">
<label class="col-sm-6 control-label">
$T("Glitter-displayCompact")
</label>
<div class="col-sm-4">
<input type="checkbox" name="displayCompact" id="displayCompact" value="true" data-bind="checked: displayCompact" />
<input type="checkbox" name="displayCompact" value="true" data-bind="checked: displayCompact" />
</div>
</div>
<div class="form-group form-checkbox">
<label class="col-sm-6 control-label" for="displayFullWidth">
<label class="col-sm-6 control-label">
$T("Glitter-displayFullWidth")
</label>
<div class="col-sm-4">
<input type="checkbox" name="displayFullWidth" id="displayFullWidth" value="true" data-bind="checked: displayFullWidth" />
<input type="checkbox" name="displayFullWidth" value="true" data-bind="checked: displayFullWidth" />
</div>
</div>
<div class="form-group form-checkbox">
<label class="col-sm-6 control-label" for="displayTabbed">
<label class="col-sm-6 control-label">
$T("Glitter-displayTabbed")
</label>
<div class="col-sm-4">
<input type="checkbox" name="displayTabbed" id="displayTabbed" value="true" data-bind="checked: displayTabbed" />
<input type="checkbox" name="displayTabbed" value="true" data-bind="checked: displayTabbed" />
</div>
</div>
<div class="form-group form-checkbox">
<label class="col-sm-6 control-label" for="confirmDeleteQueue">
<label class="col-sm-6 control-label">
$T("Glitter-confirmDeleteQueue")
</label>
<div class="col-sm-4">
<input type="checkbox" name="confirmDeleteQueue" id="confirmDeleteQueue" value="true" data-bind="checked: confirmDeleteQueue" />
<input type="checkbox" name="confirmDeleteQueue" value="true" data-bind="checked: confirmDeleteQueue" />
</div>
</div>
<div class="form-group form-checkbox">
<label class="col-sm-6 control-label" for="confirmDeleteHistory">
<label class="col-sm-6 control-label">
$T("Glitter-confirmDeleteHistory")
</label>
<div class="col-sm-4">
<input type="checkbox" name="confirmDeleteHistory" id="confirmDeleteHistory" value="true" data-bind="checked: confirmDeleteHistory" />
<input type="checkbox" name="confirmDeleteHistory" value="true" data-bind="checked: confirmDeleteHistory" />
</div>
</div>
<div class="form-group form-checkbox">
<label class="col-sm-6 control-label" for="keyboardShortcuts">
<label class="col-sm-6 control-label">
$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" id="keyboardShortcuts" value="true" data-bind="checked: keyboardShortcuts" />
<input type="checkbox" name="keyboardShortcuts" value="true" data-bind="checked: keyboardShortcuts" />
</div>
</div>
</form>
@@ -524,30 +514,30 @@
<div class="clearfix"></div>
<hr />
<div class="form-group">
<label class="col-sm-4 control-label" for="nzbname">$T('name')</label>
<label class="col-sm-4 control-label">$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" for="password">$T('srv-password')</label>
<label class="col-sm-4 control-label">$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" for="add-nzb-category">$T('category')</label>
<label class="col-sm-4 control-label">$T('category')</label>
<div class="col-sm-6">
<select name="Category" id="add-nzb-category" class="form-control" data-bind="options: queue.categoriesList, optionsValue: 'catValue', optionsText: 'catText', optionsCaption: ''"></select>
<select name="Category" class="form-control" data-bind="options: queue.categoriesList, optionsValue: 'catValue', optionsText: 'catText'"></select>
<span class="glyphicon glyphicon-tag"></span>
</div>
</div>
<div class="form-group">
<label class="col-sm-4 control-label" for="add-nzb-priority">$T('priority')</label>
<label class="col-sm-4 control-label">$T('priority')</label>
<div class="col-sm-6">
<!-- This list is different from the one during download! -->
<select name="Priority" id="add-nzb-priority" class="form-control">
<option value=""></option>
<select name="Priority" class="form-control">
<option value="-100">$T('default')</option>
<option value="2">$T('pr-force')</option>
<option value="1">$T('pr-high')</option>
<option value="0">$T('pr-normal')</option>
@@ -558,16 +548,16 @@
</div>
</div>
<div class="form-group">
<label class="col-sm-4 control-label" for="add-nzb-processing">$T('swtag-pp')</label>
<label class="col-sm-4 control-label">$T('swtag-pp')</label>
<div class="col-sm-6">
<select name="Processing" id="add-nzb-processing" class="form-control" data-bind="options: queue.processingOptions, optionsValue: 'value', optionsText: 'name', optionsCaption: ''"></select>
<select name="Processing" class="form-control" data-bind="options: queue.processingOptions, optionsValue: 'value', optionsText: 'name', optionsCaption: '$T('default')'"></select>
<span class="glyphicon glyphicon-check"></span>
</div>
</div>
<div class="form-group">
<label class="col-sm-4 control-label" for="add-nzb-script">$T('eoq-scripts')</label>
<label class="col-sm-4 control-label">$T('eoq-scripts')</label>
<div class="col-sm-6">
<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>
<select name="Post-processing" class="form-control" data-bind="options: queue.scriptsList, optionsCaption: '$T('default')', optionsValue: 'scriptValue', optionsText: 'scriptText', enable: (queue.scriptsList().length > 1)"></select>
<span class="glyphicon glyphicon-flash"></span>
</div>
</div>
@@ -645,59 +635,6 @@
</div>
</div>
<div id="modal-delete-queue-job" class="modal modal-delete-job fade" tabindex="-1">
<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>
</div>
<form data-bind="submit: queue.removeDownloads">
<div class="modal-body">
$T('confirm-delete')
<ul data-bind="foreach: queue.deleteItems">
<li data-bind="text: name"></li>
</ul>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">$T('cancel')</button>
<button type="submit" class="btn btn-danger"><span class="glyphicon glyphicon-trash"></span> $T('nzo-delete')</button>
</div>
</form>
</div>
</div>
</div>
<div id="modal-delete-history-job" class="modal modal-delete-job fade" tabindex="-1">
<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>
</div>
<form data-bind="submit: history.removeDownloads">
<div class="modal-body">
$T('confirm-delete')
<ul data-bind="foreach: history.deleteItems">
<li data-bind="text: historyStatus.name"></li>
</ul>
</div>
<div class="modal-footer">
<div class="checkbox" data-bind="visible: !history.showArchive()">
<label>
<input type="checkbox" data-bind="checked: history.permanentlyDelete"> <span>$T('permanently-delete')</span>
</label>
</div>
<button type="button" class="btn btn-default" data-dismiss="modal">$T('cancel')</button>
<button type="submit" class="btn btn-danger"><span class="glyphicon glyphicon-trash"></span> $T('nzo-delete')</button>
</div>
</form>
</div>
</div>
</div>
<div id="modal-retry-job" class="modal modal-small fade" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
@@ -728,9 +665,6 @@
</div>
</div>
</fieldset>
<fieldset>
$T('Glitter-retryNoChecks')
</fieldset>
</div>
<div class="modal-footer">
<button class="btn btn-default"><span class="glyphicon glyphicon-repeat"></span> $T('button-retry')</button>
@@ -778,13 +712,13 @@
<td><a href="https://github.com/sabnzbd/sabnzbd" target="_blank">https://github.com/sabnzbd/sabnzbd/</a></td>
</tr>
<tr>
<td><strong>$T('menu-live-chat'):</strong></td>
<td><strong>$T('menu-irc'):</strong></td>
<td><a href="https://sabnzbd.org/live-chat" target="_blank">https://sabnzbd.org/live-chat</a></td>
</tr>
</tbody>
</table>
<hr/>
<p><small>Copyright &copy; 2007-2026 by The SABnzbd-Team (<a href="https://sabnzbd.org/" target="_blank">sabnzbd.org</a>)<br/>$T('yourRights') </small></p>
<p><small>Copyright (C) 2007-2021 The SABnzbd-Team (<a href="https://sabnzbd.org/" target="_blank">sabnzbd.org</a>)<br/>$T('yourRights') </small></p>
</div>
</div>
</div>
@@ -803,18 +737,11 @@
<button type="button" class="btn btn-danger" data-bind="click: history.emptyHistory" data-action="history-purge-completed"><span class="glyphicon glyphicon-floppy-saved"></span> $T('purgeCompl')</button><hr />
<button type="button" class="btn btn-danger" data-bind="click: history.emptyHistory" data-action="history-purge-page"><span class="glyphicon glyphicon-check"></span> $T('purgePage') <span class="label label-default" data-bind="text: history.historyItems().length"></span></button>
</div>
<div class="modal-footer">
<div class="checkbox">
<label>
<input type="checkbox" data-bind="checked: history.permanentlyDelete"> <span>$T('permanently-delete')</span>
</label>
</div>
</div>
</div>
</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">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
+33 -61
View File
@@ -15,10 +15,10 @@
<span data-bind="text: queueDataLeft"></span> $T('Glitter-left')
</div>
<!-- /ko -->
<!-- ko if: (parseInt(quotaLimit()) != 0) -->
<!-- ko if: (quotaLimit() != 0) -->
<div class="info-container-box">
<span data-bind="css: { 'queue-error-info' : (parseInt(quotaLimitLeft())<=0) }">
<span class="glyphicon glyphicon-log-in"></span> <span data-bind="text: quotaLimitLeft"></span> / <span data-bind="text: quotaLimit"></span> $T('quota-left')
<span class="glyphicon glyphicon-log-in"></span> <span data-bind="text: quotaLimitLeft"></span>B / <span data-bind="text: quotaLimit"></span>B $T('quota-left')
</span>
</div>
<!-- /ko -->
@@ -47,7 +47,6 @@
</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>
@@ -66,7 +65,7 @@
<!-- ko foreach: extraQueueColumns -->
<th class="table-header-extra"></th>
<!-- /ko -->
<th class="table-header-progress" data-bind="css: { 'table-header-progress-smaller' : extraQueueColumns().length || displayCompact() }"></th>
<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>
</tr>
@@ -85,7 +84,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')', 'aria-label': pausedStatus() ? '$T('link-resume')' : '$T('link-pause')' }">
<a href="#" data-bind="click: pauseToggle, attr: { 'title': pausedStatus() ? '$T('link-resume')' : '$T('link-pause')' }">
<span class="hover-button glyphicon" data-bind="css: queueIcon"></span>
</a>
</td>
@@ -103,36 +102,22 @@
<!-- /ko -->
</div>
<form data-bind="submit: editingNameSubmit">
<input type="text" aria-label="$T('Glitter-rename')" data-bind="value: nameForEdit, visible: editingName(), hasfocus: editingName" />
<input type="text" 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')" 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>
<a href="#" data-bind="click: \$parent.queue.moveButton" class="hover-button buttonMoveToTop" title="$T('Glitter-top')"><span class="glyphicon glyphicon-chevron-up"></span></a>
<a href="#" data-bind="click: \$parent.queue.moveButton" class="hover-button buttonMoveToBottom" title="$T('Glitter-bottom')"><span class="glyphicon glyphicon-chevron-down"></span></a>
<a href="#" data-bind="click: editName" class="hover-button" title="$T('Glitter-rename')"><span class="glyphicon glyphicon-pencil"></span></a>
<a href="#" data-bind="click: showFiles" class="hover-button" title="$T('nzoDetails') - $T('srv-password')"><span class="glyphicon glyphicon-folder-open"></span></a>
<small data-bind="text: avg_age"></small>
</div>
</td>
<!-- 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">
@@ -147,106 +132,93 @@
<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')" aria-label="$T('Glitter-selectJob')" data-bind="click: parent.addMultiEdit, attr: { 'id': 'multiedit_' + id } " />
<input type="checkbox" name="multiedit" title="$T('Glitter-multiSelect')" data-bind="click: parent.addMultiEdit, attr: { 'id': 'multiedit_' + id } " />
</label>
<!-- ko if: !isGrabbing() -->
<div class="dropdown" data-bind="visible: !parent.isMultiEditing()">
<a href="#" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" aria-label="$T('nzoDetails')" data-bind="click: toggleDropdown">
<a href="#" data-toggle="dropdown" 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" aria-label="$T('category')" data-bind="options: parent.categoriesList, optionsValue: 'catValue', optionsText: 'catText', value: category, valueAllowUnset: true, event: { change: changeCat }"></select>
<select name="Category" class="form-control" data-bind="options: parent.categoriesList, optionsValue: 'catValue', optionsText: 'catText', value: category, 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" aria-label="$T('priority')" data-bind="options: parent.priorityOptions, optionsValue: 'value', optionsText: 'name', value: priority, valueAllowUnset: true, event: { change: changePriority }"></select>
<select name="Priority" class="form-control" data-bind="options: parent.priorityOptions, optionsValue: 'value', optionsText: 'name', value: priority, 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" aria-label="$T('swtag-pp')" data-bind="options: parent.processingOptions, optionsValue: 'value', optionsText: 'name', value: unpackopts, valueAllowUnset: true, event: { change: changeProcessing }"></select>
<select name="Processing" class="form-control" data-bind="options: parent.processingOptions, optionsValue: 'value', optionsText: 'name', value: unpackopts, 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" 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>
<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>
</li>
</ul>
<!-- /ko -->
</div>
<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>
<!-- /ko -->
<a href="#" class="hover-button" title="$T('removeNZB-Files')" data-bind="click: removeDownload"><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')" aria-label="$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')" data-bind="click: queue.checkAllJobs" data-tooltip="true" data-placement="top" />
</label>
<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">
<a href="#" class="hover-button" data-bind="click: queue.doMultiDelete">
<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" aria-label="$T('link-resume')" data-bind="event: { change: queue.doMultiEditUpdate }" />
<label for="multiedit-play">
<input type="radio" name="multiedit-status" value="resume" id="multiedit-play" 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" aria-label="$T('link-pause')" data-bind="event: { change: queue.doMultiEditUpdate }" />
<label for="multiedit-pause">
<input type="radio" name="multiedit-status" value="pause" id="multiedit-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')" 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')" 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>
</div>
<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" aria-label="$T('category')" data-bind="options: queue.categoriesList, optionsValue: 'catValue', optionsText: 'catText', optionsCaption: '', event: { change: queue.doMultiEditUpdate }"></select>
<select name="Category" class="form-control" 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" aria-label="$T('priority')" data-bind="options: queue.priorityOptions, optionsValue: 'value', optionsText: 'name', optionsCaption: '', event: { change: queue.doMultiEditUpdate }"></select>
<select name="Priority" class="form-control" 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" aria-label="$T('swtag-pp')" data-bind="options: queue.processingOptions, optionsValue: 'value', optionsText: 'name', optionsCaption: '', event: { change: queue.doMultiEditUpdate }"></select>
<select name="Processing" class="form-control" 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" aria-label="$T('eoq-scripts')" data-bind="options: queue.scriptsList, optionsValue: 'scriptValue', optionsText: 'scriptText', optionsCaption: '', event: { change: queue.doMultiEditUpdate }"></select>
<select name="Post-processing" class="form-control" 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')" 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')' }">
<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 }">
<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 }">
<!-- 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 data-bind="css: { active: isCurrent, disabled: isDots }, click: onclick">
<span data-bind="text: page"></span>
</li>
</ul>
</div><!-- /.queue-footer -->
</div>
</div>
+25 -24
View File
@@ -31,13 +31,13 @@
<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=$cache_buster" data-bind="attr: { 'href': SABIcon }" />
<link rel="shortcut icon" type="image/ico" href="./staticcfg/ico/favicon.ico?v=$version" data-bind="attr: { 'href': SABIcon }" />
<link rel="stylesheet" type="text/css" href="./static/bootstrap/css/bootstrap.min.css?v=$cache_buster" />
<link rel="stylesheet" type="text/css" href="./static/stylesheets/glitter.css?v=$cache_buster" />
<link rel="stylesheet" type="text/css" href="./static/stylesheets/glitter.mobile.css?v=$cache_buster" media="all and (max-width: 768px)" />
<link rel="stylesheet" type="text/css" href="./static/bootstrap/css/bootstrap.min.css?v=$version" />
<link rel="stylesheet" type="text/css" href="./static/stylesheets/glitter.css?v=$version" />
<link rel="stylesheet" type="text/css" href="./static/stylesheets/glitter.mobile.css?v=$version" media="all and (max-width: 768px)" />
<!--#if $color_scheme not in ('Light', '') #-->
<link rel="stylesheet" type="text/css" href="./static/stylesheets/colorschemes/${color_scheme}.css?v=$cache_buster"/>
<link rel="stylesheet" type="text/css" href="./static/stylesheets/colorschemes/${color_scheme}.css?v=$version"/>
<!--#end if#-->
<!-- Make translations available in scripts -->
@@ -52,15 +52,16 @@
var glitterTranslate = new Object();
glitterTranslate.paused = "$T('post-Paused')";
glitterTranslate.left = "$T('Glitter-left')";
glitterTranslate.clearOrphanWarning = "$T('Glitter-clearOrphanWarning')";
glitterTranslate.clearWarn = "$T('confirm')";
glitterTranslate.pausePromptFail = "$T('Glitter-pausePromptFail')"
glitterTranslate.pauseFor = "$T('pauseFor')"
glitterTranslate.minutes = "$T('mins')"
glitterTranslate.shutdown = "$T('shutdownOK?')";
glitterTranslate.restart = "$T('explain-Restart') $T('explain-needNewLogin')".replace(/\<br(\s*\/|)\>/g, '\n');
glitterTranslate.repair = "$T('explain-Repair')".replace(/<br \/>/g, "\n").replace(/&quot;/g,'"');
glitterTranslate.confirm = "$T('confirm')";
glitterTranslate.markComplete = "$T('button-mark-completed')";
glitterTranslate.deleteMsg = "$T('nzo-delete')";
glitterTranslate.removeDown = "$T('confirm')";
glitterTranslate.removeDow1 = "$T('confirm')";
glitterTranslate.renameAbort = "$T('Glitter-confirmAbortDirectUnpack')\n$T('confirm')";
glitterTranslate.retryAll = "$T('link-retryAll')?";
glitterTranslate.fetch = "$T('Glitter-fetch')";
@@ -76,6 +77,7 @@
glitterTranslate.defaultText = "$T('default')";
glitterTranslate.noneText = "$T('None')";
glitterTranslate.moreText = "$T('Glitter-more')";
glitterTranslate.status = [];
glitterTranslate.status['DirectUnpack'] = "$T('opt-direct_unpack')";
glitterTranslate.status['Completed'] = "$T('post-Completed')";
@@ -95,7 +97,6 @@
glitterTranslate.status['Unpack'] = "$T('stage-unpack')";
glitterTranslate.status['Deobfuscate'] = "$T('stage-deobfuscate')";
glitterTranslate.status['Script'] = "$T('stage-script')";
glitterTranslate.status['RSS'] = "$T('stage-rss')";
glitterTranslate.status['Source'] = "$T('stage-source')";
glitterTranslate.status['Servers'] = "$T('stage-servers')";
glitterTranslate.status['INFO'] = "$T('log-info')".replace('+', '').toUpperCase();
@@ -130,7 +131,7 @@
<!--#include $webdir + "/static/javascripts/glitter.js"#-->
</script>
<!--#if active_lang != 'en'#-->
<script type="text/javascript" src="./static/javascripts/momentjs_locale/${active_lang}.js?v=$cache_buster"></script>
<script type="text/javascript" src="./static/javascripts/momentjs_locale/${active_lang}.js"></script>
<!--#end if#-->
</head>
<body>
@@ -138,21 +139,21 @@
<!--#include $webdir + "/include_menu.tmpl"#-->
</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">
<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 main-content" data-bind="css: { 'main-content-loaded': isLoaded }">
<!--#include $webdir + "/include_queue.tmpl"#-->
<!--#include $webdir + "/include_messages.tmpl"#-->
<!--#include $webdir + "/include_history.tmpl"#-->
@@ -9,13 +9,10 @@ function HistoryListModel(parent) {
self.lastUpdate = 0;
self.historyItems = ko.observableArray([])
self.showFailed = ko.observable(false).extend({ persist: 'historyShowFailed' });
self.showArchive = ko.observable(false).extend({ persist: 'historyShowArchive' });
self.permanentlyDelete = ko.observable(false).extend({ persist: 'permanentlyDelete' });
self.isLoading = ko.observable(false).extend({ rateLimit: 100 });
self.searchTerm = ko.observable('').extend({ rateLimit: { timeout: 400, method: "notifyWhenChangesStop" } });
self.paginationLimit = ko.observable(10).extend({ persist: 'historyPaginationLimit' });
self.totalItems = ko.observable(0);
self.deleteItems = ko.observableArray([]);
self.ppItems = ko.observable(0);
self.pagination = new paginationModel(self);
self.isMultiEditing = ko.observable(false).extend({ persist: 'historyIsMultiEditing' });
@@ -121,27 +118,6 @@ function HistoryListModel(parent) {
self.parent.refresh(true)
});
self.triggerRemoveDownload = function(items) {
// Show and fill modal
self.deleteItems.removeAll()
// Single or multiple items?
if(items.length) {
ko.utils.arrayPushAll(self.deleteItems, items)
} else {
self.deleteItems.push(items)
}
// Show modal or delete right away
if(self.parent.confirmDeleteHistory()) {
// Open modal if desired
$('#modal-delete-history-job').modal("show")
} else {
// Otherwise just submit right away
$('#modal-delete-history-job form').submit()
}
}
// Retry a job
self.retryJob = function(form) {
// Adding a extra retry file happens through this special function
@@ -184,15 +160,15 @@ function HistoryListModel(parent) {
// Clear searchterm
self.clearSearchTerm = function(data, event) {
// Was it escape key or click?
if(event.type === 'click' || (event.keyCode && event.keyCode === 27)) {
if(event.type === 'mousedown' || (event.keyCode && event.keyCode === 27)) {
// Set the loader so it doesn't flicker and then switch
self.isLoading(true)
self.searchTerm('');
}
// Was it click and the field is empty? Then we focus on the field
if(event.type === 'click' && self.searchTerm() === '') {
if(event.type === 'mousedown' && self.searchTerm() === '') {
$(event.target).parents('.search-box').find('input[type="text"]').focus()
return false;
return;
}
// Need to return true to allow typing
return true;
@@ -200,17 +176,10 @@ function HistoryListModel(parent) {
// Toggle showing failed
self.toggleShowFailed = function(data, event) {
// Set the loader so it doesn't flicker and then switch
self.isLoading(true)
self.showFailed(!self.showFailed())
// Force hide tooltip so it doesn't linger
$('#history-options a').tooltip('hide')
// Force refresh
self.parent.refresh(true)
}
// Toggle showing archive
self.toggleShowArchive = function(data, event) {
self.showArchive(!self.showArchive())
// Force hide tooltip so it doesn't linger
// Forde hide tooltip so it doesn't linger
$('#history-options a').tooltip('hide')
// Force refresh
self.parent.refresh(true)
@@ -232,9 +201,11 @@ function HistoryListModel(parent) {
// Empty history options
self.emptyHistory = function(data, event) {
// Make sure no flickering
self.isLoading(true)
// What event?
var whatToRemove = $(event.target).data('action');
var skipArchive = $('#modal-purge-history input[type="checkbox"]').prop("checked")
var del_files, value;
// Purge failed
@@ -267,7 +238,6 @@ function HistoryListModel(parent) {
mode: 'history',
name: 'delete',
del_files: 1,
archive: (!skipArchive) * 1,
value: strIDs
}).then(function() {
// Clear search, refresh and hide
@@ -282,9 +252,8 @@ function HistoryListModel(parent) {
callAPI({
mode: 'history',
name: 'delete',
del_files: del_files,
archive: (!skipArchive) * 1,
value: value
value: value,
del_files: del_files
}).then(function() {
self.parent.refresh();
$("#modal-purge-history").modal('hide');
@@ -359,108 +328,43 @@ function HistoryListModel(parent) {
return true;
}
// Remove downloads from history
self.removeDownloads = function(form) {
// Hide modal and show notification
$('#modal-delete-history-job').modal("hide")
showNotification('.main-notification-box-removing')
var strIDsPP = '';
var strIDsHistory = '';
$.each(self.deleteItems(), function(index) {
// Split in jobs that need post-processing aborted, and jobs that need to be deleted
if(this.processingDownload() === 2) {
strIDsPP = strIDsPP + this.id + ',';
// These items should not be listed in the deletedItems later on
// as active post-processing aren't removed from the history output
self.deleteItems.remove(this)
} else {
strIDsHistory = strIDsHistory + this.id + ',';
}
})
// Trigger post-processing aborting
if(strIDsPP !== "") {
callAPI({
mode: 'cancel_pp',
value: strIDsPP
}).then(function(response) {
// Only hide and refresh
self.parent.refresh();
hideNotification()
});
}
if(strIDsHistory !== "") {
var skipArchive = $('#modal-delete-history-job input[type="checkbox"]').prop("checked")
// Permanently delete if we are on the Archive page
if(self.showArchive()) skipArchive = true
callAPI({
mode: 'history',
name: 'delete',
del_files: 1,
archive: (!skipArchive) * 1,
value: strIDsHistory
}).then(function(response) {
self.historyItems.removeAll(self.deleteItems());
self.multiEditItems.removeAll(self.deleteItems())
self.parent.refresh();
hideNotification()
});
}
};
// Delete all selected
self.doMultiDelete = function() {
// Anything selected?
if(self.multiEditItems().length < 1) return;
// Trigger modal
self.triggerRemoveDownload(self.multiEditItems())
}
// Mark jobs as completed
self.markAsCompleted = function(items) {
// Confirm
if(!confirm(glitterTranslate.markComplete)) {
return
}
// Single or multiple items?
var strIDs = '';
if(items.length) {
$.each(items, function(index) {
// Need confirm
if(!self.parent.confirmDeleteHistory() || confirm(glitterTranslate.removeDown)) {
// List all the ID's
var strIDs = '';
$.each(self.multiEditItems(), function(index) {
strIDs = strIDs + this.id + ',';
})
} else {
strIDs = items.id
// Show notification
showNotification('.main-notification-box-removing-multiple', 0, self.multiEditItems().length)
// Remove
callAPI({
mode: 'history',
name: 'delete',
del_files: 1,
value: strIDs
}).then(function(response) {
if(response.status) {
// Make sure the queue doesnt flicker and then fade-out
// Make sure no flickering (if there are more items left) and then remove
self.isLoading(self.totalItems() > 1)
self.parent.refresh();
// Empty it
self.multiEditItems.removeAll();
// Hide notification
hideNotification()
}
})
}
// Send the API call
callAPI({
mode: 'history',
name: 'mark_as_completed',
value: strIDs
}).then(function(response) {
// Force refresh to update the UI
self.parent.refresh(true);
});
}
// Mark all selected as completed
self.doMultiMarkCompleted = function() {
// Anything selected?
if(self.multiEditItems().length < 1) return;
// Mark them
self.markAsCompleted(self.multiEditItems());
}
// Focus on the confirm button
$('#modal-delete-history-job').on("shown.bs.modal", function() {
$('#modal-delete-history-job .btn[type="submit"]').focus()
})
// On change of page we need to check all those that were in the list!
self.historyItems.subscribe(function() {
// We need to wait until the unit is actually finished rendering
@@ -587,11 +491,6 @@ function HistoryModel(parent, data) {
return displayDateTime(self.completed(), parent.parent.dateFormat(), 'X')
});
// Format time added
self.timeAdded = ko.pureComputed(function() {
return displayDateTime(self.historyStatus.time_added(), parent.parent.dateFormat(), 'X')
});
// Subscribe to retryEvent so we can load the password
self.canRetry.subscribe(function() {
self.updateAllHistory = true;
@@ -607,11 +506,6 @@ function HistoryModel(parent, data) {
$('#modal-retry-job').modal("show")
};
// Mark as completed button
self.markAsCompleted = function() {
parent.markAsCompleted(self);
};
// Update information only on click
self.updateAllHistoryInfo = function(data, event) {
// Show
@@ -648,4 +542,36 @@ function HistoryModel(parent, data) {
return false;
})
}
}
// Delete button
self.deleteSlot = function(item, event) {
// Confirm?
if(!self.parent.parent.confirmDeleteHistory() || confirm(glitterTranslate.deleteMsg + ":\n" + item.historyStatus.name() + "\n\n" + glitterTranslate.removeDow1)) {
// Are we still processing and it can be stopped?
if(item.processingDownload() === 2) {
callAPI({
mode: 'cancel_pp',
value: self.id
})
// All we can do is wait
} else {
// Delete the item
callAPI({
mode: 'history',
name: 'delete',
del_files: 1,
value: self.id
}).then(function(response) {
if(response.status) {
// Make sure no flickering (if there are more items left) and then remove
self.parent.isLoading(self.parent.totalItems() > 1)
self.parent.historyItems.remove(self);
self.parent.multiEditItems.remove(function(inList) { return inList.id === self.id; })
self.parent.parent.refresh();
}
});
}
}
};
}
@@ -10,11 +10,10 @@ 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' });
self.displayTabbed = ko.observable(false).extend({ persist: 'displayTabbed' });
self.displayTabbed = ko.observable().extend({ persist: 'displayTabbed' });
self.displayCompact = ko.observable(false).extend({ persist: 'displayCompact' });
self.displayFullWidth = ko.observable(false).extend({ persist: 'displayFullWidth' });
self.confirmDeleteQueue = ko.observable(true).extend({ persist: 'confirmDeleteQueue' });
@@ -121,7 +120,8 @@ function ViewModel() {
// Dynamic history length check
self.hasHistory = ko.pureComputed(function() {
return (self.history.historyItems().length > 0 || self.history.searchTerm() || self.history.isLoading())
// We also 'have history' if we can't find any results of the search or there are no failed ones
return (self.history.historyItems().length > 0 || self.history.searchTerm() || self.history.showFailed() || self.history.isLoading())
})
self.hasWarnings = ko.pureComputed(function() {
@@ -338,7 +338,7 @@ function ViewModel() {
limit: parseInt(self.queue.paginationLimit())
}
if (self.queue.searchTerm()) {
parseSearchQuery(api_call, self.queue.searchTerm(), ["cat", "category", "priority", "status"])
parseSearchQuery(api_call, self.queue.searchTerm(), ["cat", "category", "priority"])
}
var queueApi = callAPI(api_call)
.done(self.updateQueue)
@@ -364,11 +364,10 @@ function ViewModel() {
failed_only: self.history.showFailed() * 1,
start: self.history.pagination.currentStart(),
limit: parseInt(self.history.paginationLimit()),
archive: self.history.showArchive() * 1,
last_history_update: self.history.lastUpdate
}
if (self.history.searchTerm()) {
parseSearchQuery(history_call, self.history.searchTerm(), ["cat", "category", "status"])
parseSearchQuery(history_call, self.history.searchTerm(), ["cat", "category"])
}
// History
@@ -398,6 +397,7 @@ function ViewModel() {
if (keyword === "priority" && api_request["priority"]) {
for (const prio_name in self.queue.priorityName) {
api_request["priority"] = api_request["priority"].replace(prio_name, self.queue.priorityName[prio_name])
}
}
}
@@ -432,16 +432,18 @@ function ViewModel() {
return;
}
// Show modal
$('#modal-custom-pause').modal('show')
}
$('#modal_custom_pause').modal('show')
// Focus on the input field
$('#modal_custom_pause').on('shown.bs.modal', function() {
$('#customPauseInput').focus()
})
$('#modal-custom-pause').on('shown.bs.modal', function() {
// Focus on the input field when opening the modal
$('#customPauseInput').focus()
}).on('hide.bs.modal', function() {
// Reset on modal close
self.pauseCustom('');
})
$('#modal_custom_pause').on('hide.bs.modal', function() {
self.pauseCustom('');
})
}
// Update on changes
self.pauseCustom.subscribe(function(newValue) {
@@ -454,7 +456,7 @@ function ViewModel() {
// At least 3 charaters
if (newValue.length < 3) {
$('#customPauseOutput').text('').data('time', 0)
$('#modal-custom-pause .btn-default').addClass('disabled')
$('#modal_custom_pause .btn-default').addClass('disabled')
return;
}
@@ -479,11 +481,11 @@ function ViewModel() {
var pauseDuration = Math.round((pauseParsed - Date.parse('now')) / 1000 / 60);
$('#customPauseOutput').html('<span class="glyphicon glyphicon-pause"></span> ' + glitterTranslate.pauseFor + ' ' + pauseDuration + ' ' + glitterTranslate.minutes)
$('#customPauseOutput').data('time', pauseDuration)
$('#modal-custom-pause .btn-default').removeClass('disabled')
$('#modal_custom_pause .btn-default').removeClass('disabled')
} else if (newValue) {
// No..
$('#customPauseOutput').text(glitterTranslate.pausePromptFail)
$('#modal-custom-pause .btn-default').addClass('disabled')
$('#modal_custom_pause .btn-default').addClass('disabled')
}
})
@@ -502,7 +504,7 @@ function ViewModel() {
// Refresh and close the modal
self.refresh()
self.downloadsPaused(true);
$('#modal-custom-pause').modal('hide')
$('#modal_custom_pause').modal('hide')
});
}
}
@@ -654,12 +656,15 @@ function ViewModel() {
name: $(form.nzbURL).val(),
nzbname: $('#nzbname').val(),
password: $('#password').val(),
cat: $('#modal-add-nzb select[name="Category"]').val(),
priority: $('#modal-add-nzb select[name="Priority"]').val(),
pp: $('#modal-add-nzb select[name="Processing"]').val(),
script: $('#modal-add-nzb select[name="Post-processing"]').val(),
priority: $('#modal-add-nzb select[name="Priority"]').val(),
pp: $('#modal-add-nzb select[name="Processing"]').val()
}
// Optional, otherwise they get mis-labeled if left empty
if ($('#modal-add-nzb select[name="Category"]').val() !== '*') theCall.cat = $('#modal-add-nzb select[name="Category"]').val()
if ($('#modal-add-nzb select[name="Processing"]').val()) theCall.pp = $('#modal-add-nzb select[name="Category"]').val()
// Add
callAPI(theCall).then(function(r) {
// Hide and reset/refresh
@@ -698,12 +703,14 @@ function ViewModel() {
data.append("mode", "addfile");
data.append("nzbname", $('#nzbname').val());
data.append("password", $('#password').val());
data.append("cat", $('#modal-add-nzb select[name="Category"]').val())
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("priority", $('#modal-add-nzb select[name="Priority"]').val())
data.append("apikey", apiKey);
// Optional, otherwise they get mis-labeled if left empty
if ($('#modal-add-nzb select[name="Category"]').val() !== '*') data.append("cat", $('#modal-add-nzb select[name="Category"]').val());
if ($('#modal-add-nzb select[name="Processing"]').val()) data.append("pp", $('#modal-add-nzb select[name="Processing"]').val());
// Add this one
$.ajax({
url: "./api",
@@ -727,9 +734,6 @@ function ViewModel() {
$('#nzbname').val('')
$('.btn-file em').html(glitterTranslate.chooseFile + '&hellip;')
}
}).fail(function(xhr, status, error) {
// Update the uploading notification text to show error
showNotification('.main-notification-box-uploading-failed', 0, error)
});
}
@@ -899,7 +903,7 @@ function ViewModel() {
// Orphaned folder deletion of all
self.removeAllOrphaned = function() {
if (confirm(glitterTranslate.clearOrphanWarning)) {
if (!self.confirmDeleteHistory() || confirm(glitterTranslate.clearWarn)) {
// Show notification
showNotification('.main-notification-box-removing-multiple', 0, self.statusInfo.folders().length)
// Delete them all
@@ -916,7 +920,7 @@ function ViewModel() {
// Orphaned folder adding of all
self.addAllOrphaned = function() {
if (confirm(glitterTranslate.confirm)) {
if (!self.confirmDeleteHistory() || confirm(glitterTranslate.clearWarn)) {
// Show notification
showNotification('.main-notification-box-sendback')
// Delete them all
@@ -1109,6 +1113,7 @@ function ViewModel() {
})
}
// Already set if we are using a proxy
if (response.config.misc.socks5_proxy_url) self.statusInfo.active_socks5_proxy(true)
@@ -1165,9 +1170,6 @@ 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
@@ -1236,41 +1238,41 @@ function ViewModel() {
});
$(document).bind('keydown', 'shift+left', function(e) {
if($("body").hasClass("container-tabbed")) {
$('#history-tab.active > ul.pagination li.active').prev().children('a').click();
$('#queue-tab.active > ul.pagination li.active').prev().children('a').click();
$('#history-tab.active > ul.pagination li.active').prev().click();
$('#queue-tab.active > ul.pagination li.active').prev().click();
} else {
$('#history-tab > ul.pagination li.active').prev().children('a').click();
$('#queue-tab > ul.pagination li.active').prev().children('a').click();
$('#history-tab > ul.pagination li.active').prev().click();
$('#queue-tab > ul.pagination li.active').prev().click();
}
e.preventDefault();
});
$(document).bind('keydown', 'shift+right', function(e) {
if($("body").hasClass("container-tabbed")) {
$('#history-tab.active > ul.pagination li.active').next().children('a').click();
$('#queue-tab.active > ul.pagination li.active').next().children('a').click();
$('#history-tab.active > ul.pagination li.active').next().click();
$('#queue-tab.active > ul.pagination li.active').next().click();
} else {
$('#history-tab > ul.pagination li.active').next().children('a').click();
$('#queue-tab > ul.pagination li.active').next().children('a').click();
$('#history-tab > ul.pagination li.active').next().click();
$('#queue-tab > ul.pagination li.active').next().click();
}
e.preventDefault();
});
$(document).bind('keydown', 'shift+up', function(e) {
if($("body").hasClass("container-tabbed")) {
$('#history-tab.active > ul.pagination li').first().children('a').click();
$('#queue-tab.active > ul.pagination li').first().children('a').click();
$('#history-tab.active > ul.pagination li').first().click();
$('#queue-tab.active > ul.pagination li').first().click();
} else {
$('#history-tab > ul.pagination li').first().children('a').click();
$('#queue-tab > ul.pagination li').first().children('a').click();
$('#history-tab > ul.pagination li').first().click();
$('#queue-tab > ul.pagination li').first().click();
}
e.preventDefault();
});
$(document).bind('keydown', 'shift+down', function(e) {
if($("body").hasClass("container-tabbed")) {
$('#history-tab.active > ul.pagination li').last().children('a').click();
$('#queue-tab.active > ul.pagination li').last().children('a').click();
$('#history-tab.active > ul.pagination li').last().click();
$('#queue-tab.active > ul.pagination li').last().click();
} else {
$('#history-tab > ul.pagination li').last().children('a').click();
$('#queue-tab > ul.pagination li').last().children('a').click();
$('#history-tab > ul.pagination li').last().click();
$('#queue-tab > ul.pagination li').last().click();
}
e.preventDefault();
});
@@ -32,7 +32,6 @@ function QueueListModel(parent) {
// External var's
self.queueItems = ko.observableArray([]);
self.totalItems = ko.observable(0);
self.deleteItems = ko.observableArray([]);
self.isMultiEditing = ko.observable(false).extend({ persist: 'queueIsMultiEditing' });
self.isLoading = ko.observable(false).extend({ rateLimit: 100 });
self.multiEditItems = ko.observableArray([]);
@@ -148,27 +147,6 @@ function QueueListModel(parent) {
}
self.triggerRemoveDownload = function(items) {
// Show and fill modal
self.deleteItems.removeAll()
// Single or multiple items?
if(items.length) {
ko.utils.arrayPushAll(self.deleteItems, items)
} else {
self.deleteItems.push(items)
}
// Show modal or delete right away
if(self.parent.confirmDeleteQueue()) {
// Open modal if desired
$('#modal-delete-queue-job').modal("show")
} else {
// Otherwise just submit right away
$('#modal-delete-queue-job form').submit()
}
}
// Save pagination state
self.paginationLimit.subscribe(function(newValue) {
// Save in config if global
@@ -204,14 +182,14 @@ function QueueListModel(parent) {
// Clear searchterm
self.clearSearchTerm = function(data, event) {
// Was it escape key or click?
if(event.type === 'click' || (event.keyCode && event.keyCode === 27)) {
if(event.type === 'mousedown' || (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 === 'click' && self.searchTerm() === '') {
if(event.type === 'mousedown' && self.searchTerm() === '') {
$(event.target).parents('.search-box').find('input[type="text"]').focus()
return false;
return;
}
// Need to return true to allow typing
return true;
@@ -228,10 +206,6 @@ function QueueListModel(parent) {
sort = 'remaining';
dir = 'asc';
break;
case 'sortRemainingBytesAsc':
sort = 'remaining_bytes';
dir = 'asc';
break;
case 'sortAgeAsc':
sort = 'avg_age';
dir = 'desc';
@@ -427,103 +401,42 @@ function QueueListModel(parent) {
}
// Handle mousedown to capture state before change
self.handleMultiEditStatusMouseDown = function(item, event) {
var clickedValue = $(event.currentTarget).find("input").val();
// If this radio was already selected (same value as previous), clear it
if ($('.multioperations-selector input[name="multiedit-status"]:checked').val() === clickedValue) {
// Clear all radio buttons in this group after the click finished
// Hacky, but it works
setTimeout(function () {
$('.multioperations-selector input[name="multiedit-status"]').prop('checked', false);
}, 200)
}
return true;
}
// Remove downloads from queue
self.removeDownloads = function(form) {
// Hide modal and show notification
$('#modal-delete-queue-job').modal("hide")
showNotification('.main-notification-box-removing')
var strIDs = '';
$.each(self.deleteItems(), function(index) {
strIDs = strIDs + this.id + ',';
})
callAPI({
mode: 'queue',
name: 'delete',
del_files: 1,
value: strIDs
}).then(function(response) {
self.queueItems.removeAll(self.deleteItems());
self.multiEditItems.removeAll(self.deleteItems())
self.parent.refresh();
hideNotification()
});
};
// Delete all selected
self.doMultiDelete = function() {
// Anything selected?
if(self.multiEditItems().length < 1) return;
// Trigger modal
self.triggerRemoveDownload(self.multiEditItems())
}
// Need confirm
if(!self.parent.confirmDeleteQueue() || confirm(glitterTranslate.removeDown)) {
// List all the ID's
var strIDs = '';
$.each(self.multiEditItems(), function(index) {
strIDs = strIDs + this.id + ',';
})
// Move all selected to top
self.doMultiMoveToTop = function() {
// Anything selected?
if(self.multiEditItems().length < 1) return;
// Show notification
showNotification('.main-notification-box-removing-multiple', 0, self.multiEditItems().length)
// Move each item to the top, starting from the last one in the sorted list
var arrayList = self.multiEditItems()
var movePromises = [];
for(var i = arrayList.length - 1; i >= 0; i--) {
movePromises.push(callAPI({
mode: "switch",
value: arrayList[i].id,
value2: 0
}));
// Remove
callAPI({
mode: 'queue',
name: 'delete',
del_files: 1,
value: strIDs
}).then(function(response) {
if(response.status) {
// Make sure the queue doesnt flicker and then fade-out
self.isLoading(true)
self.parent.refresh()
// Empty it
self.multiEditItems.removeAll();
// Hide notification
hideNotification()
}
})
}
// Wait for all moves to complete then refresh
Promise.all(movePromises).then(function() {
self.parent.refresh();
});
}
// Move all selected to bottom
self.doMultiMoveToBottom = function() {
// Anything selected?
if(self.multiEditItems().length < 1) return;
// Move each item to the bottom, starting from the first one in the sorted list
var arrayList = self.multiEditItems()
var movePromises = [];
for(var i = 0; i < arrayList.length; i++) {
movePromises.push(callAPI({
mode: "switch",
value: arrayList[i].id,
value2: self.totalItems() - 1
}));
}
// Wait for all moves to complete then refresh
Promise.all(movePromises).then(function() {
self.parent.refresh();
});
}
// Focus on the confirm button
$('#modal-delete-queue-job').on("shown.bs.modal", function() {
$('#modal-delete-queue-job .btn[type="submit"]').focus()
})
// On change of page we need to check all those that were in the list!
self.queueItems.subscribe(function() {
// We need to wait until the unit is actually finished rendering
@@ -553,7 +466,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' || !parseFloat(data.mb)) // mb===0 catches paused URL-grab jobs (status 'Paused'), needed to block show-files
self.isGrabbing = ko.observable(data.status === 'Grabbing' || data.avg_age === '-')
self.isFetchingBlocks = data.status === 'Fetching' || data.priority === 'Repair' // No need to update
self.totalMB = ko.observable(parseFloat(data.mb));
self.remainingMB = ko.observable(parseFloat(data.mbleft))
@@ -673,7 +586,7 @@ function QueueModel(parent, data) {
self.password(data.password);
self.index(data.index);
self.status(data.status)
self.isGrabbing(data.status === 'Grabbing' || !parseFloat(data.mb))
self.isGrabbing(data.status === 'Grabbing' || data.avg_age === '-')
self.totalMB(parseFloat(data.mb));
self.remainingMB(parseFloat(data.mbleft));
self.missingMB(parseFloat(data.mbmissing))
@@ -714,37 +627,9 @@ function QueueModel(parent, data) {
self.editingName(true)
self.nameForEdit(self.name())
// Select the input
const $input = $(event.target).parents('.name').find('input');
$input.select();
// Add Tab/Shift+Tab navigation
$input.off('keydown.tabnav').on('keydown.tabnav', function (e) {
if (e.key === 'Tab') {
e.preventDefault();
// Find all rename inputs that are currently visible
const inputs = $('.queue-table input[type="text"]');
const currentIndex = inputs.index(this);
let nextIndex = e.shiftKey ? currentIndex - 1 : currentIndex + 1;
// Wrap around
if (nextIndex >= inputs.length) nextIndex = 0;
if (nextIndex < 0) nextIndex = inputs.length - 1;
// Simulate clicking Rename on the next row
const $nextRow = inputs.eq(nextIndex).closest('tr');
$nextRow.find('.hover-button[title="Rename"]').click();
// Delay focusing to wait for new input to appear
setTimeout(() => {
const $nextInput = $('.queue-table input[type="text"]').eq(nextIndex);
$nextInput.focus().select();
}, 50);
}
});
};
// Select
$(event.target).parents('.name').find('input').select()
}
// Catch the submit action
self.editingNameSubmit = function() {
@@ -786,39 +671,39 @@ function QueueModel(parent, data) {
}
// Change of settings
self.changeCat = function() {
self.changeCat = function(item, event) {
callAPI({
mode: 'change_cat',
value: self.id,
value2: self.category()
value: item.id,
value2: item.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() {
self.changeScript = function(item) {
callAPI({
mode: 'change_script',
value: self.id,
value2: self.script()
value: item.id,
value2: item.script()
})
}
self.changeProcessing = function() {
self.changeProcessing = function(item) {
callAPI({
mode: 'change_opts',
value: self.id,
value2: self.unpackopts()
value: item.id,
value2: item.unpackopts()
})
}
self.changePriority = function() {
self.changePriority = function(item, event) {
// Not if we are fetching extra blocks for repair!
if(self.isFetchingBlocks) return
if(item.isFetchingBlocks) return
callAPI({
mode: 'queue',
name: 'priority',
value: self.id,
value2: self.priority()
value: item.id,
value2: item.priority()
}).then(function() {
// Hide all tooltips before we refresh
$('.queue-item-settings li').filter('[data-tooltip="true"]').tooltip('hide')
@@ -826,5 +711,29 @@ function QueueModel(parent, data) {
})
}
// Remove 1 download from queue
self.removeDownload = function(item, event) {
// Confirm and remove
if(!self.parent.parent.confirmDeleteQueue() || confirm(glitterTranslate.deleteMsg + ":\n" + item.name() + "\n\n" + glitterTranslate.removeDow1)) {
var itemToDelete = this;
// Show notification
showNotification('.main-notification-box-removing')
callAPI({
mode: 'queue',
name: 'delete',
del_files: 1,
value: item.id
}).then(function(response) {
// Make sure no flickering (if there are more items left) and then remove
self.parent.isLoading(self.parent.totalItems() > 1)
parent.queueItems.remove(itemToDelete);
parent.multiEditItems.remove(function(inList) { return inList.id === itemToDelete.id; })
self.parent.parent.refresh();
// Hide notifcation
hideNotification()
});
}
};
}
@@ -1 +1 @@
@import url('Night.css?lastmajorupdate=510') screen and (prefers-color-scheme: dark);
@import url('Night.css') screen and (prefers-color-scheme: dark);
@@ -1,48 +1,13 @@
/*
* Night theme — overrides :root color tokens defined in glitter.css.
* Only structural/behavioural rules that can't be expressed as color tokens remain below.
*/
:root {
--clr-page-bg: black;
--clr-surface: #727272;
--clr-surface-alt: #444444;
--clr-surface-hover: #666666;
--clr-border: #808080;
--clr-border-subtle: #252525;
--clr-text: #EBEBEB;
--clr-text-muted: #D6D6D6;
--clr-error: #F95151;
--clr-input-bg: #555555;
--clr-input-border: #252525;
--clr-dropdown-bg: #727272;
--clr-modal-bg: #727272;
--clr-table-bg: #555555;
--clr-table-row-alt: #444444;
--clr-progress-track: #DADADA;
--clr-toggle-inactive: #666666;
body,
.modal-backdrop {
background-color: black;
color: #EBEBEB
}
/* Links and glyphicons inside links */
a, a:active,
a:hover,
a:focus,
.btn:not(.btn-danger):active,
.hover-button.glyphicon-play,
.hover-button.glyphicon-stop,
legend,
#modal-options legend label,
#modal-options legend a,
#modal-item-files .item-files-table .progress small,
.text-muted{
color: var(--clr-text);
}
/* Navbar needs structural dark-mode overrides */
nav {
background-color: #444444 !important;
border: 0 !important;
border-radius: var(--radius-lg);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
border-bottom: 1px solid black !important;
}
nav .btn {
@@ -50,37 +15,78 @@ nav .btn {
border-color: #5D5D5D !important;
}
nav .btn:active {
/* Restore bootstrap default */
color: #333 !important;
}
@media (max-width: 767px) {
nav {
background-color: black !important;
}
}
/* Collapse dropdown loses border in dark mode */
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);
}
.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;
}
/* Feedback slider needs explicit dark bg (not a card, not an input) */
#feedback-slider {
background-color: var(--clr-input-bg);
color: var(--clr-text);
}
/* Elements that must be white regardless of --clr-text */
.main-restarting,
.main-filedrop {
color: white !important;
}
/* Tab controls — dark-mode specific colours */
.max-speed-input-clear,
.max-speed-input-clear:hover,
.nav-tabs>li>a:hover,
.fileControls a {
color: black;
}
@@ -92,61 +98,137 @@ nav .btn:active {
.nav-tabs>li>a:focus {
background-color: #ddd;
color: #555 !important;
box-shadow: 0px -1px 1px 1px rgba(0, 0, 0, 0.15);
box-shadow: 0px -1px 1px 1px rgba(0,0,0,0.15);
}
/* Opacity */
.hover-button,
.fileControls a:hover {
opacity: 0.7;
}
.form-control[disabled] {
opacity: 0.65;
}
/* Focus ring uses white glow in dark mode */
.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;
}
/* Processing animation bars */
.processing-download > div {
background-color: var(--clr-text);
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;
}
/* 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 {
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 / divider borders (dark-specific value, not matching any token) */
legend,
hr {
border-color: #C6C6C6 !important;
}
/* Slider handle */
.ui-slider .ui-slider-handle {
background-color: #969696;
border-color: #969696;
border-color: #969696
}
/* Compact layout progress shadow boost */
.container-compact .progress,
.container-compact .queue .progress-bar {
box-shadow: inset 1px 0px 1px rgba(0, 0, 0, 1);
}
.modal-footer {
border-top: 1px solid rgb(131 131 131);
}
/* Help table transparent bg override */
#modal-help table td,
#modal-help table {
background-color: transparent;
}
/* File items table structural */
#modal-item-files .item-files-table td {
border-bottom: none !important;
}
@@ -155,9 +237,45 @@ hr {
border-bottom: 1px solid black;
}
/* History/queue tab switcher */
.history-queue-swicher .nav-tabs>li>a,
.history-queue-swicher .nav-tabs>li.active>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;
}
/* Placeholders - Will not work if grouped! */
::-webkit-input-placeholder {
color: #EBEBEB !important;
}
::-moz-placeholder {
color: #EBEBEB !important;
opacity: 1 !important;
}
:-ms-input-placeholder {
color: #EBEBEB !important;
}
File diff suppressed because it is too large. Load diff
@@ -7,10 +7,6 @@
padding-right: 8px;
}
.container-full-width .container {
width: 100%;
}
.main-navbar {
margin-top: 0;
padding: 0;
@@ -22,8 +18,7 @@
.main-content {
border: 0;
margin: 0;
padding: 0;
margin-bottom: 0 !important;
}
.main-menu-link .icon-bar:first-child {
@@ -75,10 +70,6 @@ h2 {
QUEUE & HISTORY
**/
.history-queue-swicher {
margin-bottom: 0;
}
.info-container {
margin-top: 0px;
margin-bottom: 10px;
@@ -100,6 +91,13 @@ 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%;
}
@@ -126,7 +124,7 @@ h2 {
}
.queue-table .progress {
margin: 0px 15px;
margin: 0px 8px;
}
.queue-table td.name .row-wrap-text,
@@ -149,9 +147,6 @@ 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 {
@@ -168,7 +163,7 @@ tr.queue-item>td:first-child>a {
}
.multioperations-selector .add-nzb-inputbox-small {
width: 115px;
width: 72px;
}
.multioperations-selector .add-nzb-inputbox-clear {
@@ -275,4 +270,4 @@ tr.queue-item>td:first-child>a {
#modal-item-files .form-group {
clear: left;
margin-bottom: 5px;
}
}
+6 -6
View File
@@ -3,14 +3,14 @@
<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=$cache_buster"/>
<link rel="stylesheet" type="text/css" href="static/style.css?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="static/style.css?v=$version"/>
<!--#if $color_scheme not in ('Light', '') #-->
<link rel="stylesheet" type="text/css" href="../staticcfg/css/${color_scheme}.css?v=$cache_buster"/>
<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=$cache_buster" />
<script type="text/javascript" src="../staticcfg/js/jquery-3.5.1.min.js?v=$cache_buster"></script>
<script type="text/javascript" src="../staticcfg/bootstrap/js/bootstrap.min.js?v=$cache_buster"></script>
<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>
</head>
<body>
<div id="logo">
+7 -17
View File
@@ -7,9 +7,7 @@
<h1>$T('wizard-quickstart')</h1>
<hr />
<script type="text/javascript">
var txtTestServer = "$T('wizard-server-text')";
var txtChecking = "$T('srv-testing')";
var txtTestRequired = "$T('wizard-test-server-required')";
<!--#include raw $webdir + "/static/javascript/checkserver.js"#-->
</script>
<h3>$T('wizard-server')</h3>
@@ -24,7 +22,7 @@
<div class="form-group">
<label for="host" class="col-sm-4 control-label">$T('srv-host')</label>
<div class="col-sm-8">
<input type="text" class="form-control" name="host" id="host" value="$host" placeholder="$T('wizard-example') news.newshosting.com" required />
<input type="text" class="form-control" name="host" id="host" value="$host" placeholder="$T('wizard-example') news.newshosting.com" />
</div>
</div>
<div class="form-group">
@@ -59,37 +57,29 @@
<div class="form-group">
<label for="port" class="col-sm-4 control-label">$T('srv-port')</label>
<div class="col-sm-8">
<input type="number" class="form-control" name="port" id="port" value="<!--#if $port then $port else '563' #-->" min="0" max="65535" required />
<input type="number" class="form-control" name="port" id="port" value="<!--#if $port then $port else '563' #-->" />
</div>
</div>
<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 '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">
<label for="pipelining_requests" class="col-sm-4 control-label">$T('srv-pipelining_requests')</label>
<div class="col-sm-8">
<input type="number" class="form-control" name="pipelining_requests" id="pipelining_requests" value="$pipelining_requests" min="1" max="20" required />
<input type="number" class="form-control" name="connections" id="connections" value="<!--#if $connections then $connections else '8'#-->" data-toggle="tooltip" data-placement="right" title="$T('wizard-server-con-explain') $T('wizard-server-con-eg')" />
</div>
</div>
<div class="form-group">
<label for="ssl_verify" class="col-sm-4 control-label">$T('opt-ssl_verify')</label>
<div class="col-sm-8">
<select name="ssl_verify" id="ssl_verify" class="form-control">
<option value="3" <!--#if $ssl_verify == 3 then 'selected="selected"' else ""#--> >$T('ssl_verify-strict')</option>
<option value="2" <!--#if $ssl_verify == 2 then 'selected="selected"' else ""#--> >$T('ssl_verify-medium')</option>
<option value="1" <!--#if $ssl_verify == 1 then 'selected="selected"' else ""#--> >$T('ssl_verify-minimal')</option>
<select name="ssl_verify" id="ssl_verify" class="form-control" <!--#if int($certificate_validation) == 0 then "disabled=\"disabled\"" else ""#-->>
<option value="2" <!--#if $ssl_verify == 2 then 'selected="selected"' else ""#--> >$T('ssl_verify-strict')</option>
<option value="1" <!--#if $ssl_verify == 1 then 'selected="selected"' else ""#--> >$T('ssl_verify-normal')</option>
<option value="0" <!--#if $ssl_verify == 0 then 'selected="selected"' else ""#--> >$T('ssl_verify-disabled')</option>
</select>
<span class="desc">$T('explain-ssl_verify').replace('-', '<br/>-')</span>
</div>
</div>
</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"><span class="glyphicon glyphicon-sort"></span> $T('wizard-button-testServer')</button>
</div>
<div class="col-sm-8">
<div id="serverResponse" class="well well-sm">$T('wizard-server-text')</div>
@@ -1,31 +1,9 @@
// Variable to track server test results
var serverTestSuccessful = false;
function resetTestResult() {
serverTestSuccessful = false;
$('#serverResponse').html(txtTestServer);
checkRequired();
}
function setTestResult(success) {
serverTestSuccessful = success;
checkRequired();
}
function checkRequired() {
// Check if form is valid using HTML5 validation and if server test passed
if ($("form").get(0).checkValidity() && serverTestSuccessful) {
if ($("#host").val() && $("#connections").val()) {
$("#next-button").removeClass('disabled')
$("#next-button").removeAttr('data-toggle')
$("#next-button").removeAttr('title')
$("#next-button").tooltip('destroy')
return true;
} else {
$("#next-button").addClass('disabled')
$("#next-button").attr('data-toggle', 'tooltip')
$("#next-button").attr('data-placement', 'left')
$("#next-button").attr('title', txtTestRequired)
$("#next-button").tooltip()
return false;
}
}
@@ -34,13 +12,8 @@ $(document).ready(function() {
// Add tooltips
$('[data-toggle="tooltip"]').tooltip()
// On server test button click
// On form-submit
$("#serverTest").click(function() {
// Check HTML5 form validation before testing server
if (!$("form").get(0).reportValidity()) {
return false;
}
$('#serverResponse').html(txtChecking);
$.getJSON(
"../api?mode=config&name=test_server&output=json",
@@ -48,10 +21,8 @@ $(document).ready(function() {
function(result) {
if (result.value.result) {
r = '<span class="success"><span class="glyphicon glyphicon-ok"></span> ' + result.value.message + '</span>';
setTestResult(true);
} else {
r = '<span class="failed"><span class="glyphicon glyphicon-minus-sign"></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);
@@ -60,9 +31,26 @@ $(document).ready(function() {
return false;
});
// Reset test result when any form field changes
$("#host, #username, #password, #port, #connections, #ssl_verify").bind('input change', function() {
resetTestResult();
$("#port, #connections").bind('keyup blur', function() {
if (this.value > 0) {
$(this).removeClass("incorrect");
$(this).addClass("correct");
} else {
$(this).removeClass("correct");
$(this).addClass("incorrect");
}
checkRequired()
});
$("#host, #username, #password").bind('keyup blur', function() {
if (this.value) {
$(this).removeClass("incorrect");
$(this).addClass("correct");
} else {
$(this).removeClass("correct");
$(this).addClass("incorrect");
}
checkRequired();
});
$('#ssl').click(function() {
@@ -77,14 +65,13 @@ $(document).ready(function() {
$('#port').val('119')
}
}
resetTestResult();
})
checkRequired()
$('form').submit(function(event) {
// Check if server test passed (HTML5 validation is automatic)
if(!serverTestSuccessful) {
// Double check
if(!checkRequired()) {
event.preventDefault();
}
})
+48 -67
View File
@@ -1,36 +1,16 @@
: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 */
/* Color palette — light mode defaults; overridden by Night.css */
--clr-page-bg: #E4E4E4;
--clr-surface: #FFFFFF;
--clr-surface-alt: #F5F5F5;
--clr-surface-hover: #E4E4E4;
--clr-border: rgba(0, 0, 0, 0.2);
--clr-text: #000000;
--clr-text-muted: #777777;
--clr-link: #555555;
--clr-toggle-active: #f9ca33;
--clr-toggle-inactive: #aaaaaa;
}
body {
background-color: var(--clr-page-bg);
color: var(--clr-text);
background-color: #E4E4E4;
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;
@@ -53,7 +33,7 @@ body {
float: left;
}
.language:hover {
background-color: var(--clr-surface-hover);
background-color: #E4E4E4;
}
.float {
float: left;
@@ -71,7 +51,7 @@ body {
padding: 12px;
margin-top: 1px;
margin-bottom: 15px;
background-color: var(--clr-surface-alt);
background-color: #f5f5f5;
}
.quoteBlock a,
a[target="_blank"] {
@@ -100,39 +80,6 @@ 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;
}
@@ -141,25 +88,49 @@ input[type="checkbox"]:focus-visible {
float: right;
margin: 0;
}
.desc {
font-size: 12px;
font-style: italic;
}
.align-right {
text-align: right;
}
.align-center {
text-align: center;
}
.unselected,
.selected {
display: inline-block;
}
.unselected {
padding: 6px 10px 6px 10px;
border: 1px solid #636363;
margin-left: 8px;
margin-right: 8px;
color: #636363;
}
.selected {
padding: 6px 10px 6px 10px;
color: white;
background-color: #636363;
border: 1px solid #636363;
margin-left: 8px;
margin-right: 8px;
}
.bigger {
font-size: 14px;
}
.bigger input {
font-size: 16px;
}
.required-star {
color: red;
}
.full-width {
width: 100%;
}
.correct {
border: 2px solid #00cc22;
}
.incorrect {
border: 2px solid red;
}
.hidden {
display: none !important;
}
@@ -175,12 +146,20 @@ input[type="checkbox"]:focus-visible {
.input-group-bw {
width: 150px;
}
.disabled-text {
text-decoration: line-through;
color: #ccc;
}
#serverResponse {
padding: 6px 10px;
}
#host-tip {
margin-bottom: 5px;
}
.error-text {
display: inline;
color: red;
}
#bandwidth {
display: inline-block;
}
@@ -189,22 +168,24 @@ input[type="checkbox"]:focus-visible {
Bootstrap overwrites
***/
* {
border-radius: 0 !important;
}
a,
a:hover,
a:active,
a:visited,
#serverResponse {
color: var(--clr-link);
color: #555;
}
.btn {
box-shadow: 1px 1px 1px rgba(0, 0, 0, .1) !important;
min-width: 150px;
box-shadow: 1px 0px 1px rgba(0,0,0,.1);
}
.btn .glyphicon,
a .glyphicon {
top: 2px;
}
small {
color: var(--clr-text-muted);
color: #777;
}
+69 -243
View File
@@ -1,246 +1,72 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Copyright 2022-2026 by The SABnzbd-Team (sabnzbd.org) -->
<!-- Copyright 2022-2023 The SABnzbd-Team (sabnzbd.org) -->
<component type="desktop-application">
<id>org.sabnzbd.sabnzbd</id>
<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>
</branding>
<description>
<p>
SABnzbd is a free and Open Source web-based binary newsreader,
with support for the popular nzb file format. It greatly simplifies
the process of downloading from Usenet, thanks to a friendly
web-based user interface and advanced built-in post-processing
options including the ability to automatically verify, repair,
extract and clean up downloaded posts. It runs anywhere, comes in
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>
<keyword>nzb</keyword>
<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>
<category>FileTransfer</category>
</categories>
<url type="homepage">https://sabnzbd.org</url>
<url type="bugtracker">https://github.com/sabnzbd/sabnzbd/issues</url>
<url type="vcs-browser">https://github.com/sabnzbd/sabnzbd</url>
<url type="contribute">https://github.com/sabnzbd/sabnzbd</url>
<url type="translate">https://sabnzbd.org/wiki/translate</url>
<url type="donation">https://sabnzbd.org/donate</url>
<url type="help">https://sabnzbd.org/wiki/</url>
<url type="faq">https://sabnzbd.org/wiki/faq</url>
<url type="contact">https://sabnzbd.org/live-chat.html</url>
<releases>
<release version="5.1.3" date="2026-08-25" type="stable">
<url type="details">https://github.com/sabnzbd/sabnzbd/releases/tag/5.1.2</url>
</release>
<release version="5.1.2" date="2026-08-25" type="stable">
<url type="details">https://github.com/sabnzbd/sabnzbd/releases/tag/5.1.2</url>
</release>
<release version="5.1.1" date="2026-08-18" type="stable">
<url type="details">https://github.com/sabnzbd/sabnzbd/releases/tag/5.1.1</url>
</release>
<release version="5.1.0" date="2026-08-10" 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>
<release version="4.5.5" date="2025-10-24" type="stable">
<url type="details">https://github.com/sabnzbd/sabnzbd/releases/tag/4.5.5</url>
</release>
<release version="4.5.4" date="2025-10-22" type="stable">
<url type="details">https://github.com/sabnzbd/sabnzbd/releases/tag/4.5.4</url>
</release>
<release version="4.5.3" date="2025-08-25" type="stable">
<url type="details">https://github.com/sabnzbd/sabnzbd/releases/tag/4.5.3</url>
</release>
<release version="4.5.2" date="2025-07-09" type="stable">
<url type="details">https://github.com/sabnzbd/sabnzbd/releases/tag/4.5.2</url>
</release>
<release version="4.5.1" date="2025-04-11" type="stable">
<url type="details">https://github.com/sabnzbd/sabnzbd/releases/tag/4.5.1</url>
</release>
<release version="4.5.0" date="2025-04-01" type="stable">
<url type="details">https://github.com/sabnzbd/sabnzbd/releases/tag/4.5.0</url>
</release>
<release version="4.4.1" date="2024-12-23" type="stable">
<url type="details">https://github.com/sabnzbd/sabnzbd/releases/tag/4.4.1</url>
</release>
<release version="4.4.0" date="2024-12-09" type="stable">
<url type="details">https://github.com/sabnzbd/sabnzbd/releases/tag/4.4.0</url>
</release>
<release version="4.3.3" date="2024-08-01" type="stable">
<url type="details">https://github.com/sabnzbd/sabnzbd/releases/tag/4.3.3</url>
</release>
<release version="4.3.2" date="2024-05-30" type="stable">
<url type="details">https://github.com/sabnzbd/sabnzbd/releases/tag/4.3.2</url>
</release>
<release version="4.3.1" date="2024-05-03" type="stable">
<url type="details">https://github.com/sabnzbd/sabnzbd/releases/tag/4.3.1</url>
</release>
</releases>
<launchable type="desktop-id">sabnzbd.desktop</launchable>
<provides>
<mediatype>application/x-nzb</mediatype>
<mediatype>application/x-compressed-nzb</mediatype>
</provides>
<supports>
<control>pointing</control>
<control>keyboard</control>
<control>touch</control>
</supports>
<recommends>
<display_length compare="ge">640</display_length>
<internet>always</internet>
</recommends>
<project_license>GPL-2.0-or-later</project_license>
<developer id="org.sabnzbd">
<name>The SABnzbd-Team</name>
</developer>
<screenshots>
<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"/>
<id>org.sabnzbd.sabnzbd</id>
<metadata_license>MIT</metadata_license>
<name>SABnzbd</name>
<summary>Free and easy binary newsreader</summary>
<description>
<p>
SABnzbd is a free and Open Source web-based binary newsreader,
with support for the popular nzb file format. It greatly simplifies
the process of downloading from Usenet, thanks to a friendly
web-based user interface and advanced built-in post-processing
options including the ability to automatically verify, repair,
extract and clean up downloaded posts. It runs anywhere, comes in
over a dozen languages, and integrates with a host of tools, apps
and services that help automate the download process.
</p>
</description>
<categories>
<category>Network</category>
<category>FileTransfer</category>
</categories>
<url type="homepage">https://sabnzbd.org</url>
<url type="bugtracker">https://github.com/sabnzbd/sabnzbd/issues</url>
<url type="vcs-browser">https://github.com/sabnzbd/sabnzbd</url>
<url type="translate">https://sabnzbd.org/wiki/translate</url>
<url type="donation">https://sabnzbd.org/donate</url>
<url type="help">https://sabnzbd.org/wiki/</url>
<url type="faq">https://sabnzbd.org/wiki/faq</url>
<url type="contact">https://sabnzbd.org/live-chat.html</url>
<releases>
<release version="4.2.0" date="2023-11-26" type="stable"/>
<release version="4.1.0" date="2023-09-26" type="stable"/>
<release version="4.0.3" date="2023-06-16" type="stable"/>
<release version="4.0.2" date="2023-06-09" type="stable"/>
<release version="4.0.1" date="2023-05-01" type="stable"/>
<release version="4.0.0" date="2023-04-28" type="stable"/>
<release version="3.7.2" date="2023-02-05" type="stable"/>
</releases>
<launchable type="desktop-id">sabnzbd.desktop</launchable>
<provides>
<mediatype>application/x-nzb</mediatype>
<mediatype>application/x-compressed-nzb</mediatype>
</provides>
<supports>
<control>pointing</control>
<control>keyboard</control>
<control>touch</control>
</supports>
<recommends>
<display_length compare="ge">small</display_length>
<internet>always</internet>
</recommends>
<project_license>GPL-2.0-or-later</project_license>
<developer_name>The SABnzbd-Team</developer_name>
<screenshots>
<screenshot type="default">
<image>https://sabnzbd.org/images/landing/screenshots/interface.png</image>
<caption>Web interface</caption>
</screenshot>
<screenshot>
<image>https://sabnzbd.org/images/landing/screenshots/night-mode.png</image>
<caption>Night mode</caption>
</screenshot>
<screenshot>
<image>https://sabnzbd.org/images/landing/screenshots/config.png</image>
<caption>Easy configuration</caption>
</screenshot>
</screenshots>
<content_rating type="oars-1.1"/>
</component>
-6
View File
@@ -1,17 +1,11 @@
[Desktop Entry]
Name=SABnzbd
GenericName=Binary newsreader
GenericName[fr]=Lecteur de newsgroups binaires
GenericName[tr]=İkili haber grupları okuyucusu
Comment=Download from Usenet
Comment[fr]=Télécharger depuis Usenet
Comment[tr]=Usenet ağından dosya indir
Exec=/opt/sabnzbd/SABnzbd.py --browser 1 %F
Icon=sabnzbd
Terminal=false
Type=Application
Categories=Network;FileTransfer;
Keywords=usenet;binaries;download;nzb;nntp;newsreader;
Keywords[fr]=usenet;binaires;télécharger;nzb;nntp;newsreader;
Keywords[tr]=usenet;ikililer;indir;nzb;nntp;okuyucu;
MimeType=application/x-nzb;application/x-compressed-nzb;
-5
View File
@@ -22,11 +22,6 @@ ExecStart=/opt/sabnzbd/SABnzbd.py --disable-file-log --logging 1 --browser 0
User=%I
Type=simple
Restart=on-failure
ProtectSystem=full
DeviceAllow=/dev/null rw
DeviceAllow=/dev/urandom r
DevicePolicy=strict
NoNewPrivileges=yes
[Install]
WantedBy=multi-user.target
BIN
View File
Binary file not shown.
-144
View File
@@ -1,144 +0,0 @@
7-Zip for Linux and macOS
~~~~~~~~~~~~~~~~~~~~~~~~~
License for use and distribution
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
7-Zip Copyright (C) 1999-2024 Igor Pavlov.
The licenses for 7zz and 7zzs files are:
- The "GNU LGPL" as main license for most of the code
- The "GNU LGPL" with "unRAR license restriction" for some code
- The "BSD 3-clause License" for some code
- The "BSD 2-clause License" for some code
Redistributions in binary form must reproduce related license information from this file.
Note:
You can use 7-Zip on any computer, including a computer in a commercial
organization. You don't need to register or pay for 7-Zip.
GNU LGPL information
--------------------
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library 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
Lesser General Public License for more details.
You can receive a copy of the GNU Lesser General Public License from
http://www.gnu.org/
BSD 3-clause License in 7-Zip code
----------------------------------
The "BSD 3-clause License" is used for the following code in 7z.dll
1) LZFSE data decompression.
That code was derived from the code in the "LZFSE compression library" developed by Apple Inc,
that also uses the "BSD 3-clause License".
2) ZSTD data decompression.
that code was developed using original zstd decoder code as reference code.
The original zstd decoder code was developed by Facebook Inc,
that also uses the "BSD 3-clause License".
Copyright (c) 2015-2016, Apple Inc. All rights reserved.
Copyright (c) Facebook, Inc. All rights reserved.
Copyright (c) 2023-2024 Igor Pavlov.
Text of the "BSD 3-clause License"
----------------------------------
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors may
be used to endorse or promote products derived from this software without
specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
---
BSD 2-clause License in 7-Zip code
----------------------------------
The "BSD 2-clause License" is used for the XXH64 code in 7-Zip.
XXH64 code in 7-Zip was derived from the original XXH64 code developed by Yann Collet.
Copyright (c) 2012-2021 Yann Collet.
Copyright (c) 2023-2024 Igor Pavlov.
Text of the "BSD 2-clause License"
----------------------------------
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
---
unRAR license restriction
-------------------------
The decompression engine for RAR archives was developed using source
code of unRAR program.
All copyrights to original unRAR code are owned by Alexander Roshal.
The license for original unRAR code has the following restriction:
The unRAR sources cannot be used to re-create the RAR compression algorithm,
which is proprietary. Distribution of modified unRAR sources in separate form
or as a part of other software is permitted, provided that it is clearly
stated in the documentation and source comments that the code may
not be used to develop a RAR (WinRAR) compatible archiver.
--
BIN
View File
Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.
Executable
BIN
View File
Binary file not shown.
+88
View File
@@ -0,0 +1,88 @@
7-Zip
~~~~~
License for use and distribution
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
7-Zip Copyright (C) 1999-2021 Igor Pavlov.
The licenses for 7zz file are:
- The "GNU LGPL" as main license for most of the code
- The "GNU LGPL" with "unRAR license restriction" for some code
- The "BSD 3-clause License" for some code
Redistributions in binary form must reproduce related license information from this file.
Note:
You can use 7-Zip on any computer, including a computer in a commercial
organization. You don't need to register or pay for 7-Zip.
GNU LGPL information
--------------------
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library 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
Lesser General Public License for more details.
You can receive a copy of the GNU Lesser General Public License from
http://www.gnu.org/
BSD 3-clause License
--------------------
The "BSD 3-clause License" is used for the code in 7z.dll that implements LZFSE data decompression.
That code was derived from the code in the "LZFSE compression library" developed by Apple Inc,
that also uses the "BSD 3-clause License":
----
Copyright (c) 2015-2016, Apple Inc. All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder(s) nor the names of any contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
----
unRAR license restriction
-------------------------
The decompression engine for RAR archives was developed using source
code of unRAR program.
All copyrights to original unRAR code are owned by Alexander Roshal.
The license for original unRAR code has the following restriction:
The unRAR sources cannot be used to re-create the RAR compression algorithm,
which is proprietary. Distribution of modified unRAR sources in separate form
or as a part of other software is permitted, provided that it is clearly
stated in the documentation and source comments that the code may
not be used to develop a RAR (WinRAR) compatible archiver.
--
Igor Pavlov
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
File renamed without changes.
BIN
View File
Binary file not shown.
+5 -5
View File
@@ -1,10 +1,10 @@
#
# SABnzbd Translation Template file EMAIL
# Copyright 2007-2026 by The SABnzbd-Team (sabnzbd.org)
# Copyright 2007-2023 The SABnzbd-Team
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-5.1.0\n"
"Project-Id-Version: SABnzbd-4.2.0Alpha2\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: team@sabnzbd.org\n"
"Language-Team: SABnzbd <team@sabnzbd.org>\n"
@@ -16,7 +16,7 @@ msgid ""
"##\n"
"## Default Email template for SABnzbd\n"
"## This a Cheetah template\n"
"## Documentation: https://sabnzbd.org/wiki/extra/email-templates\n"
"## Documentation: http://sabnzbd.wikidot.com/email-templates\n"
"##\n"
"## Newlines and whitespace are significant!\n"
"##\n"
@@ -61,7 +61,7 @@ msgid ""
"##\n"
"## RSS Email template for SABnzbd\n"
"## This a Cheetah template\n"
"## Documentation: https://sabnzbd.org/wiki/extra/email-templates\n"
"## Documentation: http://sabnzbd.wikidot.com/email-templates\n"
"##\n"
"## Newlines and whitespace are significant!\n"
"##\n"
@@ -90,7 +90,7 @@ msgid ""
"##\n"
"## Bad URL Fetch Email template for SABnzbd\n"
"## This a Cheetah template\n"
"## Documentation: https://sabnzbd.org/wiki/extra/email-templates\n"
"## Documentation: http://sabnzbd.wikidot.com/email-templates\n"
"##\n"
"## Newlines and whitespace are significant!\n"
"##\n"
+5 -97
View File
@@ -1,14 +1,10 @@
# SABnzbd Translation Template file EMAIL
# Copyright 2007-2026 by The SABnzbd-Team (sabnzbd.org)
#
# Translators:
# Iamanro, 2026
# Copyright 2007-2023 The SABnzbd-Team
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-5.1.0\n"
"Project-Id-Version: SABnzbd-4.2.0Alpha2\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"
@@ -21,7 +17,7 @@ msgid ""
"##\n"
"## Default Email template for SABnzbd\n"
"## This a Cheetah template\n"
"## Documentation: https://sabnzbd.org/wiki/extra/email-templates\n"
"## Documentation: http://sabnzbd.wikidot.com/email-templates\n"
"##\n"
"## Newlines and whitespace are significant!\n"
"##\n"
@@ -60,54 +56,13 @@ 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 ""
"##\n"
"## RSS Email template for SABnzbd\n"
"## This a Cheetah template\n"
"## Documentation: https://sabnzbd.org/wiki/extra/email-templates\n"
"## Documentation: http://sabnzbd.wikidot.com/email-templates\n"
"##\n"
"## Newlines and whitespace are significant!\n"
"##\n"
@@ -130,38 +85,13 @@ 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 ""
"##\n"
"## Bad URL Fetch Email template for SABnzbd\n"
"## This a Cheetah template\n"
"## Documentation: https://sabnzbd.org/wiki/extra/email-templates\n"
"## Documentation: http://sabnzbd.wikidot.com/email-templates\n"
"##\n"
"## Newlines and whitespace are significant!\n"
"##\n"
@@ -181,25 +111,3 @@ 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"
+97 -5
View File
@@ -1,10 +1,14 @@
# SABnzbd Translation Template file EMAIL
# Copyright 2007-2026 by The SABnzbd-Team (sabnzbd.org)
# Copyright 2007-2023 The SABnzbd-Team
#
# Translators:
# Safihre <safihre@sabnzbd.org>, 2020
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-5.1.0\n"
"Project-Id-Version: SABnzbd-4.2.0Alpha2\n"
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2020\n"
"Language-Team: Danish (https://app.transifex.com/sabnzbd/teams/111101/da/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -17,7 +21,7 @@ msgid ""
"##\n"
"## Default Email template for SABnzbd\n"
"## This a Cheetah template\n"
"## Documentation: https://sabnzbd.org/wiki/extra/email-templates\n"
"## Documentation: http://sabnzbd.wikidot.com/email-templates\n"
"##\n"
"## Newlines and whitespace are significant!\n"
"##\n"
@@ -56,13 +60,54 @@ msgid ""
"Sorry!\n"
"<!--#end if#-->\n"
msgstr ""
"##\n"
"## Standard E-mail-skabelon til SABnzbd\n"
"## Dette er en Cheetah-skabelon\n"
"## Dokumentation: http://sabnzbd.wikidot.com/email-templates\n"
"##\n"
"## Linjeskift og blanktegn har betydning!\n"
"##\n"
"## Dette er e-mail-headerne \n"
"To: $to\n"
"From: $from\n"
"Date: $date\n"
"Subject: SABnzbd har <!--#if $status then \"hentet\" else \"fejlet\" #--> job $name\n"
"X-priority: 5\n"
"X-MS-priority: 5\n"
"## Herefter kommer kroppen, den tomme linje skal være der!\n"
"\n"
"Hej,\n"
"<!--#if $status #-->\n"
"SABnzbd har hentet \"$name\" <!--#if $msgid==\"\" then \"\" else \"(newzbin #\" + $msgid + \")\"#-->\n"
"<!--#else#-->\n"
"SABnzbd kunne ikke hente \"$name\" <!--#if $msgid==\"\" then \"\" else \"(newzbin #\" + $msgid + \")\"#-->\n"
"<!--#end if#-->\n"
"Færdig kl. $end_time\n"
"Hentet $size\n"
"\n"
"Resultat af job:\n"
"<!--#for $stage in $stages #-->\n"
"Etape $stage <!--#slurp#-->\n"
"<!--#for $result in $stages[$stage]#-->\n"
" $result <!--#slurp#-->\n"
"<!--#end for#-->\n"
"<!--#end for#-->\n"
"<!--#if $script!=\"\" #-->\n"
"Output fra brugerscriptet \"$script\" (Afslutningskode = $script_ret):\n"
"$script_output\n"
"<!--#end if#-->\n"
"<!--#if $status #-->\n"
"Hav det godt!\n"
"<!--#else#-->\n"
"Beklager!\n"
"<!--#end if#-->\n"
#: email/rss.tmpl:1
msgid ""
"##\n"
"## RSS Email template for SABnzbd\n"
"## This a Cheetah template\n"
"## Documentation: https://sabnzbd.org/wiki/extra/email-templates\n"
"## Documentation: http://sabnzbd.wikidot.com/email-templates\n"
"##\n"
"## Newlines and whitespace are significant!\n"
"##\n"
@@ -85,13 +130,38 @@ msgid ""
"\n"
"Bye\n"
msgstr ""
"##\n"
"## RSS E-mail-skabelon til SABnzbd\n"
"## Dette er en Cheetah-skabelon\n"
"## Dokumentation: http://sabnzbd.wikidot.com/email-templates\n"
"##\n"
"## Linjeskift og blanktegn har betydning!\n"
"##\n"
"## Dette er e-mai-headere\n"
"To: $to\n"
"From: $from\n"
"Date: $date\n"
"Subject: SABnzbd har tilføjet $antal jobs til køen\n"
"X-priority: 5\n"
"X-MS-priority: 5\n"
"## Herefter kommer kroppen, den tomme linje skal være der!\n"
"\n"
"Hej,\n"
"\n"
"SABnzbd har tilføjet $antal job(s) til køen.\n"
"De er fra RSS-feedet \"$feed\".\n"
"<!--#for $job in $jobs#-->\n"
" $job <!--#slurp#-->\n"
"<!--#end for#-->\n"
"\n"
"Farvel\n"
#: email/badfetch.tmpl:1
msgid ""
"##\n"
"## Bad URL Fetch Email template for SABnzbd\n"
"## This a Cheetah template\n"
"## Documentation: https://sabnzbd.org/wiki/extra/email-templates\n"
"## Documentation: http://sabnzbd.wikidot.com/email-templates\n"
"##\n"
"## Newlines and whitespace are significant!\n"
"##\n"
@@ -111,3 +181,25 @@ msgid ""
"\n"
"Bye\n"
msgstr ""
"##\n"
"## Dårlig URL-hentning af E-mail-skabelon til SABnzbd\n"
"## Dette er en Cheetah-skabelon\n"
"## Dokumentation: http://sabnzbd.wikidot.com/email-templates\n"
"##\n"
"## Linjeskift og blanktegn har betydning!\n"
"##\n"
"## Dette er e-mail-headere\n"
"To: $to\n"
"From: $from\n"
"Date: $date\n"
"Subject: SABnzbd kunne ikke hente en NZB\n"
"X-priority: 5\n"
"X-MS-priority: 5\n"
"## Herefter kommer kroppen, den tomme linje skal være der!\n"
"\n"
"Hej,\n"
"\n"
"SABnzbd kunne ikke hente NZB fra $url.\n"
"Fejlmeddelelsen er: $msg\n"
"\n"
"Farvel\n"
+7 -7
View File
@@ -1,14 +1,14 @@
# SABnzbd Translation Template file EMAIL
# Copyright 2007-2026 by The SABnzbd-Team (sabnzbd.org)
# Copyright 2007-2023 The SABnzbd-Team
#
# Translators:
# Safihre <safihre@sabnzbd.org>, 2025
# Safihre <safihre@sabnzbd.org>, 2020
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-5.1.0\n"
"Project-Id-Version: SABnzbd-4.2.0Alpha2\n"
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2025\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2020\n"
"Language-Team: German (https://app.transifex.com/sabnzbd/teams/111101/de/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -21,7 +21,7 @@ msgid ""
"##\n"
"## Default Email template for SABnzbd\n"
"## This a Cheetah template\n"
"## Documentation: https://sabnzbd.org/wiki/extra/email-templates\n"
"## Documentation: http://sabnzbd.wikidot.com/email-templates\n"
"##\n"
"## Newlines and whitespace are significant!\n"
"##\n"
@@ -109,7 +109,7 @@ msgid ""
"##\n"
"## RSS Email template for SABnzbd\n"
"## This a Cheetah template\n"
"## Documentation: https://sabnzbd.org/wiki/extra/email-templates\n"
"## Documentation: http://sabnzbd.wikidot.com/email-templates\n"
"##\n"
"## Newlines and whitespace are significant!\n"
"##\n"
@@ -161,7 +161,7 @@ msgid ""
"##\n"
"## Bad URL Fetch Email template for SABnzbd\n"
"## This a Cheetah template\n"
"## Documentation: https://sabnzbd.org/wiki/extra/email-templates\n"
"## Documentation: http://sabnzbd.wikidot.com/email-templates\n"
"##\n"
"## Newlines and whitespace are significant!\n"
"##\n"
+97 -5
View File
@@ -1,10 +1,14 @@
# SABnzbd Translation Template file EMAIL
# Copyright 2007-2026 by The SABnzbd-Team (sabnzbd.org)
# Copyright 2007-2023 The SABnzbd-Team
#
# Translators:
# Safihre <safihre@sabnzbd.org>, 2020
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-5.1.0\n"
"Project-Id-Version: SABnzbd-4.2.0Alpha2\n"
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2020\n"
"Language-Team: Spanish (https://app.transifex.com/sabnzbd/teams/111101/es/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -17,7 +21,7 @@ msgid ""
"##\n"
"## Default Email template for SABnzbd\n"
"## This a Cheetah template\n"
"## Documentation: https://sabnzbd.org/wiki/extra/email-templates\n"
"## Documentation: http://sabnzbd.wikidot.com/email-templates\n"
"##\n"
"## Newlines and whitespace are significant!\n"
"##\n"
@@ -56,13 +60,54 @@ msgid ""
"Sorry!\n"
"<!--#end if#-->\n"
msgstr ""
"##\n"
"## Plantilla de correo predeterminada para SABnzbd\n"
"## This a Cheetah template\n"
"## Documentación: http://sabnzbd.wikidot.com/email-templates\n"
"##\n"
"## !Los saltos de línea y espacios en blanco son significativos¡\n"
"##\n"
"## Cabeceras de correo electrónico\n"
"To: $to\n"
"From: $from\n"
"Date: $date\n"
"Subject: SABnzbd <!--#if $status then \"he bajado\" else \"fallo en bajar\" #--> job $name\n"
"X-priority: 5\n"
"X-MS-priority: 5\n"
"## !Después de esto viene el cuerpo del mensaje, la línea en blanco es necesaria!\n"
"\n"
"Hola,\n"
"<!--#if $status #-->\n"
"SABnzbd he bajado \"$name\" <!--#if $msgid==\"\" then \"\" else \"(newzbin #\" + $msgid + \")\"#-->\n"
"<!--#else#-->\n"
"SABnzbd fallo en bajar \"$name\" <!--#if $msgid==\"\" then \"\" else \"(newzbin #\" + $msgid + \")\"#-->\n"
"<!--#end if#-->\n"
"Terminado a las $end_time\n"
"$size bajado\n"
"\n"
"Resultado de la transferencia:\n"
"<!--#for $stage in $stages #-->\n"
"Etapa $stage <!--#slurp#-->\n"
"<!--#for $result in $stages[$stage]#-->\n"
" $result <!--#slurp#-->\n"
"<!--#end for#-->\n"
"<!--#end for#-->\n"
"<!--#if $script!=\"\" #-->\n"
"Producción desde el script de usuario \"$script\" (Exit code = $script_ret):\n"
"$script_output\n"
"<!--#end if#-->\n"
"<!--#if $status #-->\n"
"Que lo disfrutes!\n"
"<!--#else#-->\n"
"Perdon!\n"
"<!--#end if#-->\n"
#: email/rss.tmpl:1
msgid ""
"##\n"
"## RSS Email template for SABnzbd\n"
"## This a Cheetah template\n"
"## Documentation: https://sabnzbd.org/wiki/extra/email-templates\n"
"## Documentation: http://sabnzbd.wikidot.com/email-templates\n"
"##\n"
"## Newlines and whitespace are significant!\n"
"##\n"
@@ -85,13 +130,38 @@ msgid ""
"\n"
"Bye\n"
msgstr ""
"##\n"
"## Plantilla de correo RSS para SABnzbd\n"
"## This a Cheetah template\n"
"## Documentation: http://sabnzbd.wikidot.com/email-templates\n"
"##\n"
"## !Los saltos de línea y espacios en blanco son significativos¡\n"
"##\n"
"## Cabeceras de correo electrónico\n"
"To: $to\n"
"From: $from\n"
"Date: $date\n"
"Subject: SABnzbd he añadido $amount transferencia(s) a la cola\n"
"X-priority: 5\n"
"X-MS-priority: 5\n"
"## !Después de esto viene el cuerpo del mensaje, la línea en blanco es necesaria!\n"
"\n"
"Hola,\n"
"\n"
"SABnzbd he añadido $amount transferencia(s) a la cola.\n"
"Originaron desde el RSS \"$feed\".\n"
"<!--#for $job in $jobs#-->\n"
"$job <!--#slurp#-->\n"
"<!--#end for#-->\n"
"\n"
"Adios\n"
#: email/badfetch.tmpl:1
msgid ""
"##\n"
"## Bad URL Fetch Email template for SABnzbd\n"
"## This a Cheetah template\n"
"## Documentation: https://sabnzbd.org/wiki/extra/email-templates\n"
"## Documentation: http://sabnzbd.wikidot.com/email-templates\n"
"##\n"
"## Newlines and whitespace are significant!\n"
"##\n"
@@ -111,3 +181,25 @@ msgid ""
"\n"
"Bye\n"
msgstr ""
"##\n"
"## Plantilla de correo para URLs incorrectas de SABnzbd\n"
"## Esta es una plantilla Cheetah\n"
"## Documentación: http://sabnzbd.wikidot.com/email-templates\n"
"##\n"
"## Líneas nuevas y espacios en blanco IMPORTAN!\n"
"##\n"
"## Estas son las cabeceras del email\n"
"To: $to\n"
"From: $from\n"
"Date: $date\n"
"Subject: SABnzbd ha encontrado un error al recuperar un NZB\n"
"X-priority: 5\n"
"X-MS-priority: 5\n"
"## After this comes the body, the empty line is required!\n"
"\n"
"Hola,\n"
"\n"
"SABnzbd ha encontrado un error al descargar un NZB desde $url.\n"
"El error ha sido: $msg\n"
"\n"
"Un saludo\n"
+95 -5
View File
@@ -1,10 +1,14 @@
# SABnzbd Translation Template file EMAIL
# Copyright 2007-2026 by The SABnzbd-Team (sabnzbd.org)
# Copyright 2007-2023 The SABnzbd-Team
#
# Translators:
# Safihre <safihre@sabnzbd.org>, 2020
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-5.1.0\n"
"Project-Id-Version: SABnzbd-4.2.0Alpha2\n"
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2020\n"
"Language-Team: Finnish (https://app.transifex.com/sabnzbd/teams/111101/fi/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -17,7 +21,7 @@ msgid ""
"##\n"
"## Default Email template for SABnzbd\n"
"## This a Cheetah template\n"
"## Documentation: https://sabnzbd.org/wiki/extra/email-templates\n"
"## Documentation: http://sabnzbd.wikidot.com/email-templates\n"
"##\n"
"## Newlines and whitespace are significant!\n"
"##\n"
@@ -56,13 +60,54 @@ msgid ""
"Sorry!\n"
"<!--#end if#-->\n"
msgstr ""
"##\n"
"## Oletus sähköpostipohja SABnzbd:lle\n"
"## Tämä on Cheetah pohja\n"
"## Dokumentaatio: http://sabnzbd.wikidot.com/email-templates\n"
"##\n"
"## Rivinvaihdot ja välilyönnit ovat merkitseviä!\n"
"##\n"
"## Nämä ovat otsaketiedot. Rivien ensimmäisiä sanoja ei saa vaihtaa!\n"
"To: $to\n"
"From: $from\n"
"Date: $date\n"
"Subject: SABnzbd on <!--#if $status then \"valmistunut\" else \"epäonnistunut\" #--> työssä $name\n"
"X-priority: 5\n"
"X-MS-priority: 5\n"
"## Tämän jälkeen tulee viestin runko, ensimmäinen rivinvaihto on pakollinen!\n"
"\n"
"Hei,\n"
"<!--#if $status #-->\n"
"SABnzbd on ladannut \"$name\" <!--#if $msgid==\"\" then \"\" else \"(newzbin #\" + $msgid + \")\"#-->\n"
"<!--#else#-->\n"
"SABnzbd on epäonnistunut \"$name\" <!--#if $msgid==\"\" then \"\" else \"(newzbin #\" + $msgid + \")\"#--> latauksessa\n"
"<!--#end if#-->\n"
"Valmistui $end_time\n"
"Ladattu $size\n"
"\n"
"Työn lopputulos:\n"
"<!--#for $stage in $stages #-->\n"
"Tila $stage <!--#slurp#-->\n"
"<!--#for $result in $stages[$stage]#-->\n"
" $result <!--#slurp#-->\n"
"<!--#end for#-->\n"
"<!--#end for#-->\n"
"<!--#if $script!=\"\" #-->\n"
"Käyttäjän skriptin tuloste \"$script\" (Exit code = $script_ret):\n"
"$script_output\n"
"<!--#end if#-->\n"
"<!--#if $status #-->\n"
"Nauti!\n"
"<!--#else#-->\n"
"Pahoittelut!\n"
"<!--#end if#-->\n"
#: email/rss.tmpl:1
msgid ""
"##\n"
"## RSS Email template for SABnzbd\n"
"## This a Cheetah template\n"
"## Documentation: https://sabnzbd.org/wiki/extra/email-templates\n"
"## Documentation: http://sabnzbd.wikidot.com/email-templates\n"
"##\n"
"## Newlines and whitespace are significant!\n"
"##\n"
@@ -85,13 +130,38 @@ msgid ""
"\n"
"Bye\n"
msgstr ""
"##\n"
"## RSS sähköpostipohja SABnzbd:lle\n"
"## Tämä on Cheetah pohja\n"
"## Dokumentaatio: http://sabnzbd.wikidot.com/email-templates\n"
"##\n"
"## Rivinvaihdot ja välilyönnit ovat merkitseviä!\n"
"##\n"
"## Nämä ovat otsaketiedot. Rivien ensimmäisiä sanoja ei saa vaihtaa!\n"
"To: $to\n"
"From: $from\n"
"Date: $date\n"
"Subject: SABnzbd on lisännyt $amount työtä jonoon\n"
"X-priority: 5\n"
"X-MS-priority: 5\n"
"## Tämän jälkeen tulee viestin runko, ensimmäinen rivinvaihto on pakollinen!\n"
"\n"
"Hei,\n"
"\n"
"SABnzbd on lisännyt $amount työtä jonoon.\n"
"Ne ovat RSS syötteestä \"$feed\".\n"
"<!--#for $job in $jobs#-->\n"
" $job <!--#slurp#-->\n"
"<!--#end for#-->\n"
"\n"
"Heippa\n"
#: email/badfetch.tmpl:1
msgid ""
"##\n"
"## Bad URL Fetch Email template for SABnzbd\n"
"## This a Cheetah template\n"
"## Documentation: https://sabnzbd.org/wiki/extra/email-templates\n"
"## Documentation: http://sabnzbd.wikidot.com/email-templates\n"
"##\n"
"## Newlines and whitespace are significant!\n"
"##\n"
@@ -111,3 +181,23 @@ msgid ""
"\n"
"Bye\n"
msgstr ""
"##\n"
"## Virheellisen URL-noudon sähköpostin pohja SABnzbd ohjelmalle\n"
"## Tämä on Cheetah pohja\n"
"## Dokumentaatio: http://sabnzbd.wikidot.com/email-templates\n"
"##\n"
"## Rivinvaihdot ja välilyönnit ovat merkitseviä!\n"
"##\n"
"## Tässä on sähköpostin otsikkotiedot\n"
"To: $to\n"
"From: $from\n"
"Date: $date\n"
"Subject: SABnzbd ei voinut hakea NZB tiedostoa\n"
"X-priority: 5\n"
"X-MS-priority: 5\n"
"## Tämän jälkeen tulee viestin sisältö, tyhjä rivi on pakollinen!\n"
"\n"
"Hei,\n"
"\n"
"SABnzbd ei voinut hakea NZB tiedostoa osoitteesta $url.\n"
"Virheviesti: $msg\n"
+55 -54
View File
@@ -1,14 +1,14 @@
# SABnzbd Translation Template file EMAIL
# Copyright 2007-2026 by The SABnzbd-Team (sabnzbd.org)
# Copyright 2007-2023 The SABnzbd-Team
#
# Translators:
# Fred L <88com88@gmail.com>, 2025
# Safihre <safihre@sabnzbd.org>, 2020
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-5.1.0\n"
"Project-Id-Version: SABnzbd-4.2.0Alpha2\n"
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
"Last-Translator: Fred L <88com88@gmail.com>, 2025\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2020\n"
"Language-Team: French (https://app.transifex.com/sabnzbd/teams/111101/fr/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -21,7 +21,7 @@ msgid ""
"##\n"
"## Default Email template for SABnzbd\n"
"## This a Cheetah template\n"
"## Documentation: https://sabnzbd.org/wiki/extra/email-templates\n"
"## Documentation: http://sabnzbd.wikidot.com/email-templates\n"
"##\n"
"## Newlines and whitespace are significant!\n"
"##\n"
@@ -60,44 +60,45 @@ msgid ""
"Sorry!\n"
"<!--#end if#-->\n"
msgstr ""
"#encoding UTF-8\n"
"##\n"
"## Modèle d'e-mail par défaut pour SABnzbd\n"
"## Ceci est un modèle Cheetah\n"
"## Documentation : https://sabnzbd.org/wiki/extra/email-templates\n"
"## Template Email pour SABnzbd\n"
"## Ceci est un template Cheetah\n"
"## Documentation: http://sabnzbd.wikidot.com/email-templates\n"
"##\n"
"## Les retours à la ligne et les espaces sont importants !\n"
"##\n"
"## Voici les en-têtes de l'email\n"
"À : $to\n"
"De : $from\n"
"Date : $date\n"
"Objet : SABnzbd a <!--#if $status then \"terminé\" else \"n'a pu terminer\" #--> la tâche $name\n"
"X-priorité: 5\n"
"X-MS-priorité: 5\n"
"## Après cela vient le corps du message, la ligne vide est nécessaire !\n"
"## Entêtes de l'email\n"
"To: $to\n"
"From: $from\n"
"Date: $date\n"
"Subject: SABnzbd <!--#if $status#-->Succès<!--#else#-->Echec<!--#end if#--> du téléchargement $name\n"
"X-priority: 5\n"
"X-MS-priority: 5\n"
"## Après cela vient le contenu, la ligne vide est nécessaire! \n"
"\n"
"Bonjour,\n"
"<!--#if $status #-->\n"
"SABnzbd a téléchargé \"$name\" <!--#if $msgid==\"\" then \"\" else \"(newzbin #\" + $msgid + \")\"#-->\n"
"SABnzbd a téléchargé avec succès \"$name\" <!--#if $msgid==\"\" then \"\" else \"(newzbin #\" + $msgid + \")\"#-->\n"
"<!--#else#-->\n"
"SABnzbd n'a pu télécharger \"$name\" <!--#if $msgid==\"\" then \"\" else \"(newzbin #\" + $msgid + \")\"#-->\n"
"SABnzbd a téléchargé sans succès \"$name\" <!--#if $msgid==\"\" then \"\" else \"(newzbin #\" + $msgid + \")\"#-->\n"
"<!--#end if#-->\n"
"Terminé à $end_time\n"
"$size téléchargés\n"
"Téléchargé $size\n"
"\n"
"Résultats de la tâche :\n"
"Résultat du téléchargement :\n"
"<!--#for $stage in $stages #-->\n"
"Étape $stage <!--#slurp#-->\n"
"Etape $stage <!--#slurp#-->\n"
"<!--#for $result in $stages[$stage]#-->\n"
" $result <!--#slurp#-->\n"
"<!--#end for#-->\n"
"<!--#end for#-->\n"
"<!--#if $script!=\"\" #-->\n"
"Résultats du script \"$script\" (Code de sortie = $script_ret):\n"
"Sortie du script utilisateur \"$script\" (Code Retour = $script_ret):\n"
"$script_output\n"
"<!--#end if#-->\n"
"<!--#if $status #-->\n"
"Amusez-vous bien !\n"
"A bientôt !\n"
"<!--#else#-->\n"
"Désolé !\n"
"<!--#end if#-->\n"
@@ -107,7 +108,7 @@ msgid ""
"##\n"
"## RSS Email template for SABnzbd\n"
"## This a Cheetah template\n"
"## Documentation: https://sabnzbd.org/wiki/extra/email-templates\n"
"## Documentation: http://sabnzbd.wikidot.com/email-templates\n"
"##\n"
"## Newlines and whitespace are significant!\n"
"##\n"
@@ -131,37 +132,37 @@ msgid ""
"Bye\n"
msgstr ""
"##\n"
"## Modèle d'e-mail RSS pour SABnzbd\n"
"## Ceci est un modèle Cheetah\n"
"## Documentation : https://sabnzbd.org/wiki/extra/email-templates\n"
"## Template Email pour SABnzbd\n"
"## Ceci est un template Cheetah\n"
"## Documentation: http://sabnzbd.wikidot.com/email-templates\n"
"##\n"
"## Les retours à la ligne et espaces sont importants !\n"
"## Les retours à la ligne et les espaces sont importants !\n"
"##\n"
"## Voici les en-têtes de l'email\n"
"À : $to\n"
"De : $from\n"
"Date : $date\n"
"Objet : SABnzbd a ajouté $amount tâches dans la file d'attente\n"
"X-priorité: 5\n"
"X-MS-priorité: 5\n"
"## Après cela vient le corps du message, la ligne vide est nécessaire !\n"
"## Entêtes de l'email\n"
"To: $to\n"
"From: $from\n"
"Date: $date\n"
"Subject: SABnzbd a ajouté $amount fichier(s) à la file d'attente\n"
"X-priority: 5\n"
"X-MS-priority: 5\n"
"## Après cela vient le contenu, la ligne vide est nécessaire!\n"
"\n"
"Bonjour,\n"
"\n"
"SABnzbd a ajouté $amount tâche(s) à la file d'attente.\n"
"Elles viennent du flux RSS \"$feed\".\n"
"SABnzbd a ajouté $amount fichier(s) à la file d'attente.\n"
"Ils proviennent du Flux RSS \"$feed\".\n"
"<!--#for $job in $jobs#-->\n"
" $job <!--#slurp#-->\n"
"<!--#end for#-->\n"
"\n"
"Bye\n"
"Au Revoir\n"
#: email/badfetch.tmpl:1
msgid ""
"##\n"
"## Bad URL Fetch Email template for SABnzbd\n"
"## This a Cheetah template\n"
"## Documentation: https://sabnzbd.org/wiki/extra/email-templates\n"
"## Documentation: http://sabnzbd.wikidot.com/email-templates\n"
"##\n"
"## Newlines and whitespace are significant!\n"
"##\n"
@@ -182,24 +183,24 @@ msgid ""
"Bye\n"
msgstr ""
"##\n"
"## Modèle d'email d'URL incorrecte pour SABnzbd\n"
"## Ceci est un modèle Cheetah\n"
"## Documentation : https://sabnzbd.org/wiki/extra/email-templates\n"
"## Bad URL Fetch Email template for SABnzbd\n"
"## This a Cheetah template\n"
"## Documentation: http://sabnzbd.wikidot.com/email-templates\n"
"##\n"
"## Les retours à la ligne et les espaces sont importants !\n"
"## Newlines and whitespace are significant!\n"
"##\n"
"## Voici les en-têtes de l'email\n"
"À : $to\n"
"De : $from\n"
"Date : $date\n"
"Objet : SABnzbd n'a pas réussi à récupérer un NZB\n"
"X-priorité: 5\n"
"X-MS-priorité: 5\n"
"## These are the email headers\n"
"To: $to\n"
"From: $from\n"
"Date: $date\n"
"Subject: SABnzbd failed to fetch an NZB\n"
"X-priority: 5\n"
"X-MS-priority: 5\n"
"## After this comes the body, the empty line is required!\n"
"\n"
"Bonjour,\n"
"Hi,\n"
"\n"
"SABnzbd n'a pas réussi à récupérer le NZB depuis $url.\n"
"Le message d'erreur était : $msg\n"
"SABnzbd has failed to retrieve the NZB from $url.\n"
"The error message was: $msg\n"
"\n"
"Bye\n"
Loaded 100 of 344 files, more files were not shown because too many files have changed in this diff. Show more