diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
index e914485d..e9c8e05d 100644
--- a/.github/workflows/codeql.yml
+++ b/.github/workflows/codeql.yml
@@ -24,15 +24,15 @@ jobs:
steps:
- name: Checkout Code
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
- name: Initialize CodeQL
- uses: github/codeql-action/init@v2
+ uses: github/codeql-action/init@v3
with:
config-file: ./.github/codeql-config.yml
languages: ${{ matrix.language }}
- name: Perform CodeQL Analysis
- uses: github/codeql-action/analyze@v2
+ uses: github/codeql-action/analyze@v3
with:
category: "/language:${{matrix.language}}"
diff --git a/.github/workflows/issues-stale.yml b/.github/workflows/issues-stale.yml
index 0643cb0a..b805e266 100644
--- a/.github/workflows/issues-stale.yml
+++ b/.github/workflows/issues-stale.yml
@@ -10,7 +10,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Stale
- uses: actions/stale@v7
+ uses: actions/stale@v9
with:
stale-issue-message: >
This issue is stale because it has been open for 30 days with no activity.
@@ -30,7 +30,7 @@ jobs:
days-before-close: 5
- name: Invalid Template
- uses: actions/stale@v7
+ uses: actions/stale@v9
with:
stale-issue-message: >
Invalid issues template.
diff --git a/.github/workflows/issues.yml b/.github/workflows/issues.yml
index 34ceb357..a60987f5 100644
--- a/.github/workflows/issues.yml
+++ b/.github/workflows/issues.yml
@@ -10,6 +10,6 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Label Issues
- uses: dessant/label-actions@v3
+ uses: dessant/label-actions@v4
with:
github-token: ${{ github.token }}
diff --git a/.github/workflows/publish-docker.yml b/.github/workflows/publish-docker.yml
index 6480575f..62c3f86c 100644
--- a/.github/workflows/publish-docker.yml
+++ b/.github/workflows/publish-docker.yml
@@ -13,7 +13,7 @@ jobs:
if: ${{ !contains(github.event.head_commit.message, '[skip ci]') }}
steps:
- name: Checkout Code
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
- name: Prepare
id: prepare
@@ -33,21 +33,20 @@ jobs:
echo "branch=${GITHUB_REF#refs/heads/}" >> $GITHUB_OUTPUT
fi
echo "commit=${GITHUB_SHA}" >> $GITHUB_OUTPUT
- echo "build_date=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" >> $GITHUB_OUTPUT
echo "docker_platforms=linux/amd64,linux/arm64/v8,linux/arm/v7,linux/arm/v6" >> $GITHUB_OUTPUT
echo "docker_image=${{ secrets.DOCKER_REPO }}/tautulli" >> $GITHUB_OUTPUT
- name: Set Up QEMU
- uses: docker/setup-qemu-action@v2
+ uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
- uses: docker/setup-buildx-action@v2
+ uses: docker/setup-buildx-action@v3
id: buildx
with:
version: latest
- name: Cache Docker Layers
- uses: actions/cache@v3
+ uses: actions/cache@v4
with:
path: /tmp/.buildx-cache
key: ${{ runner.os }}-buildx-${{ github.sha }}
@@ -55,22 +54,28 @@ jobs:
${{ runner.os }}-buildx-
- name: Login to DockerHub
- uses: docker/login-action@v2
+ uses: docker/login-action@v3
if: success()
with:
username: ${{ secrets.DOCKER_USERNAME }}
- password: ${{ secrets.DOCKER_PASSWORD }}
+ password: ${{ secrets.DOCKER_TOKEN }}
- name: Login to GitHub Container Registry
- uses: docker/login-action@v2
+ uses: docker/login-action@v3
if: success()
with:
registry: ghcr.io
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.GHCR_TOKEN }}
+ - name: Extract Docker Metadata
+ id: metadata
+ uses: docker/metadata-action@v5
+ with:
+ images: ${{ steps.prepare.outputs.docker_image }}
+
- name: Docker Build and Push
- uses: docker/build-push-action@v4
+ uses: docker/build-push-action@v6
if: success()
with:
context: .
@@ -81,10 +86,10 @@ jobs:
TAG=${{ steps.prepare.outputs.tag }}
BRANCH=${{ steps.prepare.outputs.branch }}
COMMIT=${{ steps.prepare.outputs.commit }}
- BUILD_DATE=${{ steps.prepare.outputs.build_date }}
tags: |
${{ steps.prepare.outputs.docker_image }}:${{ steps.prepare.outputs.tag }}
ghcr.io/${{ steps.prepare.outputs.docker_image }}:${{ steps.prepare.outputs.tag }}
+ labels: ${{ steps.metadata.outputs.labels }}
cache-from: type=local,src=/tmp/.buildx-cache
cache-to: type=local,dest=/tmp/.buildx-cache
@@ -94,23 +99,10 @@ jobs:
if: always() && !contains(github.event.head_commit.message, '[skip ci]')
runs-on: ubuntu-latest
steps:
- - name: Get Build Job Status
- uses: technote-space/workflow-conclusion-action@v3.0
-
- - name: Combine Job Status
- id: status
- run: |
- failures=(neutral, skipped, timed_out, action_required)
- if [[ ${array[@]} =~ $WORKFLOW_CONCLUSION ]]; then
- echo "status=failure" >> $GITHUB_OUTPUT
- else
- echo "status=$WORKFLOW_CONCLUSION" >> $GITHUB_OUTPUT
- fi
-
- name: Post Status to Discord
uses: sarisia/actions-status-discord@v1
with:
webhook: ${{ secrets.DISCORD_WEBHOOK }}
- status: ${{ steps.status.outputs.status }}
+ status: ${{ needs.build-docker.result == 'success' && 'success' || contains(needs.*.result, 'failure') && 'failure' || 'cancelled' }}
title: ${{ github.workflow }}
nofail: true
diff --git a/.github/workflows/publish-installers.yml b/.github/workflows/publish-installers.yml
index 49d53233..b4a66960 100644
--- a/.github/workflows/publish-installers.yml
+++ b/.github/workflows/publish-installers.yml
@@ -6,10 +6,13 @@ on:
branches: [master, beta, nightly]
tags: [v*]
+env:
+ PYTHON_VERSION: '3.11'
+
jobs:
build-installer:
name: Build ${{ matrix.os_upper }} Installer
- runs-on: ${{ matrix.os }}-latest
+ runs-on: ${{ matrix.os }}-${{ matrix.os_version }}
if: ${{ !contains(github.event.head_commit.message, '[skip ci]') }}
strategy:
fail-fast: false
@@ -17,14 +20,18 @@ jobs:
include:
- os: 'windows'
os_upper: 'Windows'
+ os_version: 'latest'
+ arch: 'x64'
ext: 'exe'
- os: 'macos'
os_upper: 'MacOS'
+ os_version: '14'
+ arch: 'universal'
ext: 'pkg'
steps:
- name: Checkout Code
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
- name: Set Release Version
id: get_version
@@ -52,29 +59,29 @@ jobs:
echo $GITHUB_SHA > version.txt
- name: Set Up Python
- uses: actions/setup-python@v4
+ uses: actions/setup-python@v5
with:
- python-version: '3.9'
+ python-version: ${{ env.PYTHON_VERSION }}
cache: pip
cache-dependency-path: '**/requirements*.txt'
- name: Install Dependencies
run: |
python -m pip install --upgrade pip
- pip install -r package/requirements-package.txt
+ pip install -r package/requirements-package.txt --no-binary cffi
- name: Build Package
run: |
pyinstaller -y ./package/Tautulli-${{ matrix.os }}.spec
- name: Create Windows Installer
- uses: joncloud/makensis-action@v3.7
+ uses: joncloud/makensis-action@v4.1
if: matrix.os == 'windows'
with:
script-file: ./package/Tautulli.nsi
arguments: >
/DVERSION=${{ steps.get_version.outputs.VERSION_NSIS }}
- /DINSTALLER_NAME=..\Tautulli-windows-${{ steps.get_version.outputs.RELEASE_VERSION }}-x64.exe
+ /DINSTALLER_NAME=..\Tautulli-${{ matrix.os }}-${{ steps.get_version.outputs.RELEASE_VERSION }}-${{ matrix.arch }}.${{ matrix.ext }}
additional-plugin-paths: package/nsis-plugins
- name: Create MacOS Installer
@@ -85,13 +92,31 @@ jobs:
--version ${{ steps.get_version.outputs.VERSION }} \
--component ./dist/Tautulli.app \
--scripts ./package/macos-scripts \
- Tautulli-macos-${{ steps.get_version.outputs.RELEASE_VERSION }}-x64.pkg
+ Tautulli-${{ matrix.os }}-${{ steps.get_version.outputs.RELEASE_VERSION }}-${{ matrix.arch }}.${{ matrix.ext }}
- name: Upload Installer
- uses: actions/upload-artifact@v3
+ uses: actions/upload-artifact@v4
with:
name: Tautulli-${{ matrix.os }}-installer
- path: Tautulli-${{ matrix.os }}-${{ steps.get_version.outputs.RELEASE_VERSION }}-x64.${{ matrix.ext }}
+ path: Tautulli-${{ matrix.os }}-${{ steps.get_version.outputs.RELEASE_VERSION }}-${{ matrix.arch }}.${{ matrix.ext }}
+
+ virus-total:
+ name: VirusTotal Scan
+ needs: build-installer
+ if: needs.build-installer.result == 'success' && !contains(github.event.head_commit.message, '[skip ci]')
+ runs-on: ubuntu-latest
+ steps:
+ - name: Download Installers
+ if: needs.build-installer.result == 'success'
+ uses: actions/download-artifact@v4
+
+ - name: Upload to VirusTotal
+ uses: crazy-max/ghaction-virustotal@v4
+ with:
+ vt_api_key: ${{ secrets.VT_API_KEY }}
+ files: |
+ Tautulli-windows-installer/Tautulli-windows-*-x64.exe
+ Tautulli-macos-installer/Tautulli-macos-*-universal.pkg
release:
name: Release Installers
@@ -99,11 +124,8 @@ jobs:
if: always() && startsWith(github.ref, 'refs/tags/') && !contains(github.event.head_commit.message, '[skip ci]')
runs-on: ubuntu-latest
steps:
- - name: Get Build Job Status
- uses: technote-space/workflow-conclusion-action@v3.0
-
- name: Checkout Code
- uses: actions/checkout@v3.2.0
+ uses: actions/checkout@v4
- name: Set Release Version
id: get_version
@@ -111,8 +133,8 @@ jobs:
echo "RELEASE_VERSION=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT
- name: Download Installers
- if: env.WORKFLOW_CONCLUSION == 'success'
- uses: actions/download-artifact@v3
+ if: needs.build-installer.result == 'success'
+ uses: actions/download-artifact@v4
- name: Get Changelog
id: get_changelog
@@ -125,41 +147,21 @@ jobs:
echo "$EOF" >> $GITHUB_OUTPUT
- name: Create Release
- uses: actions/create-release@v1
+ uses: softprops/action-gh-release@v2
id: create_release
env:
GITHUB_TOKEN: ${{ secrets.GHACTIONS_TOKEN }}
with:
tag_name: ${{ steps.get_version.outputs.RELEASE_VERSION }}
- release_name: Tautulli ${{ steps.get_version.outputs.RELEASE_VERSION }}
+ name: Tautulli ${{ steps.get_version.outputs.RELEASE_VERSION }}
body: |
## Changelog
##${{ steps.get_changelog.outputs.CHANGELOG }}
- draft: false
prerelease: ${{ endsWith(steps.get_version.outputs.RELEASE_VERSION, '-beta') }}
-
- - name: Upload Windows Installer
- uses: actions/upload-release-asset@v1
- if: env.WORKFLOW_CONCLUSION == 'success'
- env:
- GITHUB_TOKEN: ${{ secrets.GHACTIONS_TOKEN }}
- with:
- upload_url: ${{ steps.create_release.outputs.upload_url }}
- asset_path: Tautulli-windows-installer/Tautulli-windows-${{ steps.get_version.outputs.RELEASE_VERSION }}-x64.exe
- asset_name: Tautulli-windows-${{ steps.get_version.outputs.RELEASE_VERSION }}-x64.exe
- asset_content_type: application/vnd.microsoft.portable-executable
-
- - name: Upload MacOS Installer
- uses: actions/upload-release-asset@v1
- if: env.WORKFLOW_CONCLUSION == 'success'
- env:
- GITHUB_TOKEN: ${{ secrets.GHACTIONS_TOKEN }}
- with:
- upload_url: ${{ steps.create_release.outputs.upload_url }}
- asset_path: Tautulli-macos-installer/Tautulli-macos-${{ steps.get_version.outputs.RELEASE_VERSION }}-x64.pkg
- asset_name: Tautulli-macos-${{ steps.get_version.outputs.RELEASE_VERSION }}-x64.pkg
- asset_content_type: application/vnd.apple.installer+xml
+ files: |
+ Tautulli-windows-installer/Tautulli-windows-${{ steps.get_version.outputs.RELEASE_VERSION }}-x64.exe
+ Tautulli-macos-installer/Tautulli-macos-${{ steps.get_version.outputs.RELEASE_VERSION }}-universal.pkg
discord:
name: Discord Notification
@@ -167,23 +169,10 @@ jobs:
if: always() && !contains(github.event.head_commit.message, '[skip ci]')
runs-on: ubuntu-latest
steps:
- - name: Get Build Job Status
- uses: technote-space/workflow-conclusion-action@v3.0
-
- - name: Combine Job Status
- id: status
- run: |
- failures=(neutral, skipped, timed_out, action_required)
- if [[ ${array[@]} =~ $WORKFLOW_CONCLUSION ]]; then
- echo "status=failure" >> $GITHUB_OUTPUT
- else
- echo "status=$WORKFLOW_CONCLUSION" >> $GITHUB_OUTPUT
- fi
-
- name: Post Status to Discord
uses: sarisia/actions-status-discord@v1
with:
webhook: ${{ secrets.DISCORD_WEBHOOK }}
- status: ${{ steps.status.outputs.status }}
+ status: ${{ needs.build-installer.result == 'success' && 'success' || contains(needs.*.result, 'failure') && 'failure' || 'cancelled' }}
title: ${{ github.workflow }}
nofail: true
diff --git a/.github/workflows/publish-snap.yml b/.github/workflows/publish-snap.yml
index 9df4d2fd..b3898a38 100644
--- a/.github/workflows/publish-snap.yml
+++ b/.github/workflows/publish-snap.yml
@@ -20,7 +20,7 @@ jobs:
- armhf
steps:
- name: Checkout Code
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
- name: Prepare
id: prepare
@@ -35,22 +35,22 @@ jobs:
fi
- name: Set Up QEMU
- uses: docker/setup-qemu-action@v2
+ uses: docker/setup-qemu-action@v3
- name: Build Snap Package
- uses: diddlesnaps/snapcraft-multiarch-action@v1
+ uses: diddlesnaps/snapcraft-multiarch-action@master
id: build
with:
architecture: ${{ matrix.architecture }}
- name: Upload Snap Package
- uses: actions/upload-artifact@v3
+ uses: actions/upload-artifact@v4
with:
name: Tautulli-snap-package-${{ matrix.architecture }}
path: ${{ steps.build.outputs.snap }}
- name: Review Snap Package
- uses: diddlesnaps/snapcraft-review-tools-action@v1
+ uses: diddlesnaps/snapcraft-review-tools-action@master
with:
snap: ${{ steps.build.outputs.snap }}
@@ -69,23 +69,10 @@ jobs:
if: always() && !contains(github.event.head_commit.message, '[skip ci]')
runs-on: ubuntu-latest
steps:
- - name: Get Build Job Status
- uses: technote-space/workflow-conclusion-action@v3.0
-
- - name: Combine Job Status
- id: status
- run: |
- failures=(neutral, skipped, timed_out, action_required)
- if [[ ${array[@]} =~ $WORKFLOW_CONCLUSION ]]; then
- echo "status=failure" >> $GITHUB_OUTPUT
- else
- echo "status=$WORKFLOW_CONCLUSION" >> $GITHUB_OUTPUT
- fi
-
- name: Post Status to Discord
uses: sarisia/actions-status-discord@v1
with:
webhook: ${{ secrets.DISCORD_WEBHOOK }}
- status: ${{ steps.status.outputs.status }}
+ status: ${{ needs.build-snap.result == 'success' && 'success' || contains(needs.*.result, 'failure') && 'failure' || 'cancelled' }}
title: ${{ github.workflow }}
nofail: true
diff --git a/.github/workflows/pull-requests.yml b/.github/workflows/pull-requests.yml
index 58cb4ee4..ac550fe2 100644
--- a/.github/workflows/pull-requests.yml
+++ b/.github/workflows/pull-requests.yml
@@ -10,7 +10,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout Code
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
- name: Comment on Pull Request
uses: mshick/add-pr-comment@v2
@@ -18,7 +18,6 @@ jobs:
with:
message: Pull requests must be made to the `nightly` branch. Thanks.
repo-token: ${{ secrets.GITHUB_TOKEN }}
- repo-token-user-login: 'github-actions[bot]'
- name: Fail Workflow
if: github.base_ref != 'nightly'
diff --git a/.github/workflows/submit-winget.yml b/.github/workflows/submit-winget.yml
index aa1c4dec..efa6cee7 100644
--- a/.github/workflows/submit-winget.yml
+++ b/.github/workflows/submit-winget.yml
@@ -11,6 +11,11 @@ jobs:
runs-on: windows-latest
if: ${{ !github.event.release.prerelease }}
steps:
+ - name: Sync Winget Fork
+ run: gh repo sync ${{ secrets.WINGET_USERNAME }}/winget-pkgs -b master
+ env:
+ GH_TOKEN: ${{ secrets.WINGET_TOKEN }}
+
- name: Submit package to Windows Package Manager Community Repository
run: |
$wingetPackage = "Tautulli.Tautulli"
@@ -23,3 +28,17 @@ jobs:
# getting latest wingetcreate file
iwr https://aka.ms/wingetcreate/latest -OutFile wingetcreate.exe
.\wingetcreate.exe update $wingetPackage -s -v $version -u $installerUrl -t $gitToken
+
+ virus-total:
+ name: VirusTotal Scan
+ runs-on: ubuntu-latest
+ steps:
+ - name: Upload to VirusTotal
+ uses: crazy-max/ghaction-virustotal@v4
+ with:
+ vt_api_key: ${{ secrets.VT_API_KEY }}
+ github_token: ${{ secrets.GHACTIONS_TOKEN }}
+ update_release_body: true
+ files: |
+ .exe$
+ .pkg$
diff --git a/.gitignore b/.gitignore
index 68d5abaf..1e54132b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -53,6 +53,9 @@ Thumbs.db
#Ignore files generated by PyCharm
*.idea/*
+#Ignore files generated by VSCode
+*.vscode/*
+
#Ignore files generated by vi
*.swp
diff --git a/CHANGELOG.md b/CHANGELOG.md
index c8463420..b349b355 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,261 @@
# Changelog
+## v2.15.3 (2025-08-03)
+
+* Exporter:
+ * New: Added hearingImpaired for subtitles and visualImpaired for audio attributes to exporter fields.
+* Graphs:
+ * Fix: Remove duplicate "Total" entry in graph tooltips. (Thanks @zdimension) (#2534)
+* UI:
+ * Fix: Failing to retrieve collections / playlists with over 1000 items.
+ * Fix: Scrollbar not showing on macosx and webkit browsers. (#2221)
+ * Fix: Incorrect rounding of minutes in global stats play duration.
+ * Fix: Disable browser autocomplete for notification agent and newsletter agent configurations. (#2557)
+* API:
+ * New: Added ability to return svg files using pms_image_proxy API command.
+* Other:
+ * New: Added ability to set config values using environment variables. (Thanks @komuw) (#2309, #2543)
+
+
+## v2.15.2 (2025-04-12)
+
+* Activity:
+ * New: Added link to library by clicking media type icon.
+ * New: Added stream count to tab title on homepage. (#2517)
+* History:
+ * Fix: Check stream watched status before stream stopped status. (#2506)
+* Notifications:
+ * Fix: ntfy notifications failing to send if provider link is blank.
+ * Fix: Check Pushover notification attachment is under 5MB limit. (#2396)
+ * Fix: Track URLs redirecting to the correct media page. (#2513)
+ * New: Added audio profile notification parameters.
+ * New: Added PATCH method for Webhook notifications.
+* Graphs:
+ * New: Added Total line to daily streams graph. (Thanks @zdimension) (#2497)
+* UI:
+ * Fix: Do not redirect API requests to the login page. (#2490)
+ * Change: Swap source and stream columns in stream info modal.
+* Other:
+ * Fix: Various typos. (Thanks @luzpaz) (#2520)
+ * Fix: CherryPy CORS response header not being set correctly. (#2279)
+
+
+## v2.15.1 (2025-01-11)
+
+* Activity:
+ * Fix: Detection of HDR transcodes. (Thanks @cdecker08) (#2412, #2466)
+* Newsletters:
+ * Fix: Disable basic authentication for /newsletter and /image endpoints. (#2472)
+* Exporter:
+ * New: Added logos to season and episode exports.
+* Other:
+ * Fix: Docker container https health check.
+
+
+## v2.15.0 (2024-11-24)
+
+* Notes:
+ * Support for Python 3.8 has been dropped. The minimum Python version is now 3.9.
+* Notifications:
+ * New: Allow Telegram blockquote and tg-emoji HTML tags. (Thanks @MythodeaLoL) (#2427)
+ * New: Added Plex slug and Plex Watch URL notification parameters. (#2420)
+ * Change: Update OneSignal API calls to use the new API endpoint for Tautulli Remote App notifications.
+* Newsletters:
+ * Fix: Dumping custom dates in raw newsletter json.
+* History:
+ * Fix: Unable to fix match for artists. (#2429)
+* Exporter:
+ * New: Added movie and episode hasVoiceActivity attribute to exporter fields.
+ * New: Added subtitle canAutoSync attribute to exporter fields.
+ * New: Added logos to the exporter fields.
+* UI:
+ * New: Add friendly name to the top bar of config modals. (Thanks @peagravel) (#2432)
+* API:
+ * New: Added plex slugs to metadata in the get_metadata API command.
+* Other:
+ * Fix: Tautulli failing to start with Python 3.13. (#2426)
+
+
+## v2.14.6 (2024-10-12)
+
+* Newsletters:
+ * Fix: Allow formatting newsletter date parameters.
+ * Change: Support apscheduler compatible cron expressions.
+* UI:
+ * Fix: Round runtime before converting to human duration.
+ * Fix: Make recently added/watched rows touch scrollable.
+* Other:
+ * Fix: Auto-updater not running.
+
+
+## v2.14.5 (2024-09-20)
+
+* Activity:
+ * Fix: Display of 2k resolution on activity card.
+* Notifications:
+ * Fix: ntfy notifications with special characters failing to send.
+* Other:
+ * Fix: Memory leak with database closing. (#2404)
+
+
+## v2.14.4 (2024-08-10)
+
+* Notifications:
+ * Fix: Update Slack notification info card.
+ * New: Added ntfy notification agent. (Thanks @nwithan8) (#2356, #2000)
+* UI:
+ * Fix: macOS platform capitalization.
+* Other:
+ * Fix: Remove deprecated getdefaultlocale. (Thanks @teodorstelian) (#2364, #2345)
+
+
+## v2.14.3 (2024-06-19)
+
+* Graphs:
+ * Fix: History table not loading when clicking on the graphs in some instances.
+* UI:
+ * Fix: Scheduled tasks table not loading when certain tasks are disabled.
+ * Removed: Unnecessary Remote Server checkbox from the settings page.
+* Other:
+ * Fix: Webserver not restarting after the setup wizard.
+ * Fix: Workaround webserver crashing in some instances.
+
+
+## v2.14.2 (2024-05-18)
+
+* History:
+ * Fix: Live TV activity not logging to history.
+ * Fix: Incorrect grouping of live TV history.
+* Notifications:
+ * Fix: Pushover configuration settings refreshing after entering a token.
+ * Fix: Plex remote access down notifications not triggering.
+ * Fix: Deleting all images from Cloudinary only deleting 1000 images.
+ * New: Added platform version and product version notification parameters. (#2244)
+ * New: Added LAN streams and WAN streams notification parameters. (#2276)
+ * New: Added Dolby Vision notification parameters. (#2240)
+ * New: Added live TV channel notification parameters.
+ * Change: Improved Tautulli Remote App notification encryption method.
+ * Note: Requires Tautulli Remote App version 3.2.4.
+* Exporter:
+ * New: Added slug attribute to exporter fields.
+ * New: Added track genres to exporter fields.
+ * New: Added playlist source URI to exporter fields.
+ * New: Added artProvider and thumbProvider to exporter fields.
+* UI:
+ * Fix: Mask deleted usernames in the logs.
+ * Fix: Live TV watch stats not showing on the media info page.
+ * Fix: Users without access to Plex server not showing as inactive.
+ * Removed: Deprecated synced item pages.
+ * Removed: Anonymous redirect settings. Links now use browser no-referrer policy instead.
+* API:
+ * New: Added Dolby Vision info to the get_metadata API command.
+ * New: Added before and after parameters to the get_home_stats API command. (#2231)
+* Packages:
+ * New: Universal binary for macOS for Apple silicon.
+ * New: Bump Snap package to core22.
+* Other:
+ * Change: Login cookie expires changed to max-age.
+ * Change: Improved key generation for login password. It is recommended to reenter your HTTP Password in the settings after upgrading.
+ * Removed: Python 2 compatibility code. (#2098, #2226) (Thanks @zdimension)
+
+
+## v2.13.4 (2023-12-07)
+
+* UI:
+ * Fix: Tautulli configuration settings page not loading when system language is None.
+ * Fix: Login cookie expiring too quickly.
+
+
+## v2.13.3 (2023-12-03)
+
+* Notifications:
+ * New: Added duration_time notification parameter.
+ * New: Added file_size_bytes notification parameter.
+ * New: Added time formats notification text modifiers.
+ * New: Added support for thetvdb_url for movies.
+* UI:
+ * Fix: Activity card overflowing due to screen scaling. (#2033)
+ * Fix: Stream duration on activity card not being updated on track changes in some cases. (#2206)
+
+
+## v2.13.2 (2023-10-26)
+
+* History:
+ * New: Added quarter values icons for history watch status. (#2179, #2156) (Thanks @herby2212)
+* Graphs:
+ * New: Added concurrent streams per day graph. (#2046) (Thanks @herby2212)
+* Exporter:
+ * New: Added metadata directory to exporter fields.
+ * Removed: Banner exporter fields for tv shows.
+* UI:
+ * New: Added last triggered time to notification agents and newsletter agent lists.
+* Other:
+ * New: Added X-Plex-Language header override to config file.
+
+
+## v2.13.1 (2023-08-25)
+
+* Notes:
+ * Support for Python 3.7 has been dropped. The minimum Python version is now 3.8.
+* Other:
+ * Fix: Tautulli failing to start on some systems.
+
+
+## v2.13.0 (2023-08-25)
+
+* Notes:
+ * Support for Python 3.7 has been dropped. The minimum Python version is now 3.8.
+* Notifications:
+ * Fix: Improved watched notification trigger description. (#2104)
+ * New: Added notification image option for iOS Tautulli Remote app.
+* Exporter:
+ * New: Added track chapter export fields.
+ * New: Added on-demand subtitle export fields.
+
+
+## v2.12.5 (2023-07-13)
+
+* Activity:
+ * New: Added d3d11va to list of hardware decoders.
+* History:
+ * Fix: Incorrect grouping of play history.
+ * New: Added button in settings to regroup play history.
+* Notifications:
+ * Fix: Incorrect concurrent streams notifications by IP addresss for IPv6 addresses (#2096) (Thanks @pooley182)
+* UI:
+ * Fix: Occasional UI crashing on Python 3.11.
+ * New: Added multiselect user filters to History and Graphs pages. (#2090) (Thanks @zdimension)
+* API:
+ * New: Added regroup_history API command.
+ * Change: Updated graph API commands to accept a comma separated list of user IDs.
+
+
+## v2.12.4 (2023-05-23)
+
+* History:
+ * Fix: Set view offset equal to duration if a stream is stopped within the last 10 sec.
+* Other:
+ * Fix: Database import may fail for some older databases.
+ * Fix: Double-quoted strings for newer versions of SQLite. (#2015, #2057)
+* API:
+ * Change: Return the ID for async API calls (export_metadata, notify, notify_newsletter).
+
+
+## v2.12.3 (2023-04-14)
+
+* Activity:
+ * Fix: Incorrect subtitle decision shown when subtitles are transcoded.
+* History:
+ * Fix: Incorrect order when sorting by the duration column in the history tables.
+* Notifications:
+ * Fix: Logging error when running scripts that use PlexAPI.
+* UI:
+ * Fix: Calculate file sizes setting causing the media info table to fail to load.
+ * Fix: Incorrect artwork and thumbnail shown for Live TV on the Most Active Libraries statistics card.
+* API:
+ * Change: Renamed duration to play_duration in the get_history API response. (Note: duration kept for backwards compatibility.)
+
+
## v2.12.2 (2023-03-16)
* Other:
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 19644c56..46a644e5 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -9,7 +9,7 @@ All pull requests should be based on the `nightly` branch, to minimize cross mer
### Python Code
#### Compatibility
-The code should work with Python 3.7+. Note that Tautulli runs on many different platforms.
+The code should work with Python 3.8+. Note that Tautulli runs on many different platforms.
Re-use existing code. Do not hesitate to add logging in your code. You can the logger module `plexpy.logger.*` for this. Web requests are invoked via `plexpy.request.*` and derived ones. Use these methods to automatically add proper and meaningful error handling.
diff --git a/Dockerfile b/Dockerfile
index 7a52841f..8d8c324b 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -25,4 +25,4 @@ CMD [ "python", "Tautulli.py", "--datadir", "/config" ]
ENTRYPOINT [ "./start.sh" ]
EXPOSE 8181
-HEALTHCHECK --start-period=90s CMD curl -ILfSs http://localhost:8181/status > /dev/null || curl -ILfkSs https://localhost:8181/status > /dev/null || exit 1
+HEALTHCHECK --start-period=90s CMD curl -ILfks https://localhost:8181/status > /dev/null || curl -ILfs http://localhost:8181/status > /dev/null || exit 1
diff --git a/README.md b/README.md
index d38f69e1..37829290 100644
--- a/README.md
+++ b/README.md
@@ -36,7 +36,7 @@ and [PlexWatchWeb](https://github.com/ecleese/plexWatchWeb).
[![Docker Stars][badge-docker-stars]][DockerHub]
[![Downloads][badge-downloads]][Releases Latest]
-[badge-python]: https://img.shields.io/badge/python->=3.7-blue?style=flat-square
+[badge-python]: https://img.shields.io/badge/python->=3.9-blue?style=flat-square
[badge-docker-pulls]: https://img.shields.io/docker/pulls/tautulli/tautulli?style=flat-square
[badge-docker-stars]: https://img.shields.io/docker/stars/tautulli/tautulli?style=flat-square
[badge-downloads]: https://img.shields.io/github/downloads/Tautulli/Tautulli/total?style=flat-square
@@ -129,7 +129,7 @@ This is free software under the GPL v3 open source license. Feel free to do with
but any modification must be open sourced. A copy of the license is included.
This software includes Highsoft software libraries which you may freely distribute for
-non-commercial use. Commerical users must licence this software, for more information visit
+non-commercial use. Commercial users must licence this software, for more information visit
https://shop.highsoft.com/faq/non-commercial#non-commercial-redistribution.
diff --git a/Tautulli.py b/Tautulli.py
index eebfa55a..b3cf4736 100755
--- a/Tautulli.py
+++ b/Tautulli.py
@@ -23,18 +23,18 @@ import sys
# Ensure lib added to path, before any other imports
sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), 'lib'))
-from future.builtins import str
-import appdirs
import argparse
import datetime
import locale
+import platformdirs
import pytz
import signal
import shutil
import time
import threading
import tzlocal
+import ctypes
import plexpy
from plexpy import common, config, database, helpers, logger, webstart
@@ -70,8 +70,26 @@ def main():
plexpy.SYS_ENCODING = None
try:
- locale.setlocale(locale.LC_ALL, "")
- plexpy.SYS_LANGUAGE, plexpy.SYS_ENCODING = locale.getdefaultlocale()
+
+ # Attempt to get the system's locale settings
+ language_code, encoding = locale.getlocale()
+
+ # Special handling for Windows platform
+ if sys.platform == 'win32':
+ # Get the user's current language settings on Windows
+ windll = ctypes.windll.kernel32
+ lang_id = windll.GetUserDefaultLCID()
+
+ # Map Windows language ID to locale identifier
+ language_code = locale.windows_locale.get(lang_id, '')
+
+ # Get the preferred encoding
+ encoding = locale.getpreferredencoding()
+
+ # Assign values to application-specific variable
+ plexpy.SYS_LANGUAGE = language_code
+ plexpy.SYS_ENCODING = encoding
+
except (locale.Error, IOError):
pass
@@ -111,7 +129,7 @@ def main():
if args.quiet:
plexpy.QUIET = True
- # Do an intial setup of the logger.
+ # Do an initial setup of the logger.
# Require verbose for pre-initilization to see critical errors
logger.initLogger(console=not plexpy.QUIET, log_dir=False, verbose=True)
@@ -186,7 +204,7 @@ def main():
if args.datadir:
plexpy.DATA_DIR = args.datadir
elif plexpy.FROZEN:
- plexpy.DATA_DIR = appdirs.user_data_dir("Tautulli", False)
+ plexpy.DATA_DIR = platformdirs.user_data_dir("Tautulli", False)
else:
plexpy.DATA_DIR = plexpy.PROG_DIR
diff --git a/data/interfaces/default/base.html b/data/interfaces/default/base.html
index 19edb94b..d6c9f859 100644
--- a/data/interfaces/default/base.html
+++ b/data/interfaces/default/base.html
@@ -13,6 +13,7 @@
+
@@ -123,11 +124,6 @@
% else:
Graphs
% endif
- % if title == "Synced Items":
- Synced Items
- % else:
- Synced Items
- % endif
% if title == "Settings":
% else:
@@ -238,7 +234,7 @@ ${next.modalIncludes()}
Patreon
Stripe
PayPal
- Crypto
+ Crypto
@@ -287,7 +283,16 @@ ${next.modalIncludes()}
- Click the button below to continue to Coinbase.
+ Select a cryptocurrency.
+
+
+
+
+
+
+
+
+ Or click the button below to continue to Coinbase.
Donate with Crypto
@@ -335,6 +340,7 @@ ${next.modalIncludes()}
+
@@ -287,6 +301,10 @@
return obj;
}, {});
+ if (!("Total" in chart_visibility)) {
+ chart_visibility["Total"] = false;
+ }
+
return data_series.map(function(s) {
var obj = Object.assign({}, s);
obj.visible = (chart_visibility[s.name] !== false);
@@ -312,7 +330,9 @@
'Live TV': '#19A0D7',
'Direct Play': '#E5A00D',
'Direct Stream': '#FFFFFF',
- 'Transcode': '#F06464'
+ 'Transcode': '#F06464',
+ 'Max. Concurrent Streams': '#96C83C',
+ 'Total': '#96C83C'
};
var series_colors = [];
$.each(data_series, function(index, series) {
@@ -327,6 +347,7 @@
+
@@ -356,6 +377,10 @@
break
}
+ if (window.location.hash === '#concurrent-graph') {
+ current_tab = '#tabs-stream';
+ }
+
$('#yaxis-' + yaxis).prop('checked', true);
$('#yaxis-' + yaxis).closest('label').addClass('active');
$('#graph-days').val(current_day_range);
@@ -373,14 +398,35 @@
type: 'get',
dataType: "json",
success: function (data) {
- var select = $('#graph-user');
+ let select = $('#graph-user');
+ let by_id = {};
data.sort(function(a, b) {
return a.friendly_name.localeCompare(b.friendly_name);
});
data.forEach(function(item) {
select.append('');
+ by_id[item.user_id] = item.friendly_name;
});
+ select.selectpicker({
+ countSelectedText: function(sel, total) {
+ if (sel === 0 || sel === total) {
+ return 'All users';
+ } else if (sel > 1) {
+ return sel + ' users';
+ } else {
+ return select.val().map(function(id) {
+ return by_id[id];
+ }).join(', ');
+ }
+ },
+ style: 'btn-dark',
+ actionsBox: true,
+ selectedTextFormat: 'count',
+ noneSelectedText: 'All users'
+ });
+ select.selectpicker('render');
+ select.selectpicker('selectAll');
}
});
@@ -519,6 +565,33 @@
}
});
+ $.ajax({
+ url: "get_concurrent_streams_by_stream_type",
+ type: 'get',
+ data: { time_range: time_range, user_id: selected_user_id },
+ dataType: "json",
+ success: function(data) {
+ var dateArray = [];
+ $.each(data.categories, function (i, day) {
+ dateArray.push(moment(day, 'YYYY-MM-DD').valueOf());
+ // Highlight the weekend
+ if ((moment(day, 'YYYY-MM-DD').format('ddd') == 'Sat') ||
+ (moment(day, 'YYYY-MM-DD').format('ddd') == 'Sun')) {
+ hc_plays_by_day_options.xAxis.plotBands.push({
+ from: i-0.5,
+ to: i+0.5,
+ color: 'rgba(80,80,80,0.3)'
+ });
+ }
+ });
+ hc_concurrent_streams_by_stream_type_options.yAxis.min = 0;
+ hc_concurrent_streams_by_stream_type_options.xAxis.categories = dateArray;
+ hc_concurrent_streams_by_stream_type_options.series = getGraphVisibility(hc_concurrent_streams_by_stream_type_options.chart.renderTo, data.series);
+ hc_concurrent_streams_by_stream_type_options.colors = getGraphColors(data.series);
+ var hc_plays_by_stream_type = new Highcharts.Chart(hc_concurrent_streams_by_stream_type_options);
+ }
+ });
+
$.ajax({
url: "get_plays_by_source_resolution",
type: 'get',
@@ -575,7 +648,7 @@
}
});
- $('#nav-tabs-2').tab('show');
+ $('#nav-tabs-stream').tab('show');
}
function loadGraphsTab3(time_range, yaxis) {
@@ -602,11 +675,6 @@
$('#nav-tabs-total').tab('show');
}
- // Set initial state
- if (current_tab === '#tabs-plays') { loadGraphsTab1(current_day_range, yaxis); }
- if (current_tab === '#tabs-stream') { loadGraphsTab2(current_day_range, yaxis); }
- if (current_tab === '#tabs-total') { loadGraphsTab3(current_month_range, yaxis); }
-
// Tab1 opened
$('#nav-tabs-plays').on('shown.bs.tab', function (e) {
e.preventDefault();
@@ -652,9 +720,20 @@
$('.months').text(current_month_range);
});
+ let graph_user_last_id = undefined;
+
// User changed
$('#graph-user').on('change', function() {
- selected_user_id = $(this).val() || null;
+ let val = $(this).val();
+ if (val.length === 0 || val.length === $(this).children().length) {
+ selected_user_id = null; // if all users are selected, just send an empty list
+ } else {
+ selected_user_id = val.join(",");
+ }
+ if (selected_user_id === graph_user_last_id) {
+ return;
+ }
+ graph_user_last_id = selected_user_id;
if (current_tab === '#tabs-plays') { loadGraphsTab1(current_day_range, yaxis); }
if (current_tab === '#tabs-stream') { loadGraphsTab2(current_day_range, yaxis); }
if (current_tab === '#tabs-total') { loadGraphsTab3(current_month_range, yaxis); }
@@ -681,6 +760,7 @@
if (this.points.length > 1) {
var total = 0;
$.each(this.points, function(i, point) {
+ if (point.series.name === 'Total') return;
s += '
'+point.series.name+': '+point.y;
total += point.y;
});
@@ -707,6 +787,7 @@
if (this.points.length > 1) {
var total = 0;
$.each(this.points, function(i, point) {
+ if (point.series.name === 'Total') return;
s += '
'+point.series.name+': '+moment.duration(point.y, 'hours').format('D [days] H [hrs] m [mins]');
total += point.y;
});
@@ -727,6 +808,7 @@
hc_plays_by_day_options.xAxis.plotBands = [];
hc_plays_by_stream_type_options.xAxis.plotBands = [];
+ hc_concurrent_streams_by_stream_type_options.xAxis.plotBands = [];
hc_plays_by_day_options.yAxis.labels.formatter = yaxis_format;
hc_plays_by_dayofweek_options.yAxis.labels.formatter = yaxis_format;
diff --git a/data/interfaces/default/history.html b/data/interfaces/default/history.html
index cf15c87f..8ab8b19e 100644
--- a/data/interfaces/default/history.html
+++ b/data/interfaces/default/history.html
@@ -1,6 +1,7 @@
<%inherit file="base.html"/>
<%def name="headIncludes()">
+
@@ -31,9 +32,7 @@
% if _session['user_group'] == 'admin':
@@ -84,7 +83,7 @@
Started |
Paused |
Stopped |
-
Duration |
+
Duration |
|
@@ -121,6 +120,7 @@
%def>
<%def name="javascriptIncludes()">
+
@@ -134,17 +134,40 @@
type: 'GET',
dataType: 'json',
success: function (data) {
- var select = $('#history-user');
+ let select = $('#history-user');
+ let by_id = {};
data.sort(function (a, b) {
return a.friendly_name.localeCompare(b.friendly_name);
});
data.forEach(function (item) {
select.append('
');
+ by_id[item.user_id] = item.friendly_name;
});
+ select.selectpicker({
+ countSelectedText: function(sel, total) {
+ if (sel === 0 || sel === total) {
+ return 'All users';
+ } else if (sel > 1) {
+ return sel + ' users';
+ } else {
+ return select.val().map(function(id) {
+ return by_id[id];
+ }).join(', ');
+ }
+ },
+ style: 'btn-dark',
+ actionsBox: true,
+ selectedTextFormat: 'count',
+ noneSelectedText: 'All users'
+ });
+ select.selectpicker('render');
+ select.selectpicker('selectAll');
}
});
+ let history_user_last_id = undefined;
+
function loadHistoryTable(media_type, transcode_decision, selected_user_id) {
history_table_options.ajax = {
url: 'get_history',
@@ -187,7 +210,16 @@
});
$('#history-user').on('change', function () {
- selected_user_id = $(this).val() || null;
+ let val = $(this).val();
+ if (val.length === 0 || val.length === $(this).children().length) {
+ selected_user_id = null; // if all users are selected, just send an empty list
+ } else {
+ selected_user_id = val.join(",");
+ }
+ if (selected_user_id === history_user_last_id) {
+ return;
+ }
+ history_user_last_id = selected_user_id;
history_table.draw();
});
}
diff --git a/data/interfaces/default/history_table_modal.html b/data/interfaces/default/history_table_modal.html
index 8ed42bf5..b4066627 100644
--- a/data/interfaces/default/history_table_modal.html
+++ b/data/interfaces/default/history_table_modal.html
@@ -32,7 +32,7 @@
Started |
Paused |
Stopped |
-
Duration |
+
Duration |
|
diff --git a/data/interfaces/default/home_stats.html b/data/interfaces/default/home_stats.html
index df2b7b69..3e1e118c 100644
--- a/data/interfaces/default/home_stats.html
+++ b/data/interfaces/default/home_stats.html
@@ -77,7 +77,8 @@ DOCUMENTATION :: END
<% fallback = 'art-live' if row0['live'] else 'art' %>
% elif stat_id == 'top_libraries':
-
+ <% fallback = 'art-live' if row0['live'] else row0['library_art'] %>
+
% elif stat_id == 'top_users':
% elif stat_id == 'top_platforms':
@@ -109,8 +110,8 @@ DOCUMENTATION :: END
% elif stat_id == 'top_libraries':
- % if row0['thumb'].startswith('http'):
-
+ % if row0['library_thumb'].startswith('http'):
+
% else:
% endif
@@ -147,7 +148,8 @@ DOCUMENTATION :: END
data-rating_key="${row.get('rating_key')}" data-grandparent_rating_key="${row.get('grandparent_rating_key')}" data-guid="${row.get('guid')}" data-title="${row.get('title')}"
data-art="${row.get('art')}" data-thumb="${row.get('thumb')}" data-platform="${row.get('platform_name')}" data-library-type="${row.get('section_type')}"
data-user_id="${row.get('user_id')}" data-user="${row.get('user')}" data-friendly_name="${row.get('friendly_name')}" data-user_thumb="${row.get('user_thumb')}"
- data-last_watch="${row.get('last_watch')}" data-started="${row.get('started')}" data-live="${row.get('live')}" data-library_art="${row.get('library_art', '')}">
+ data-last_watch="${row.get('last_watch')}" data-started="${row.get('started')}" data-live="${row.get('live')}"
+ data-library_art="${row.get('library_art', '')}" data-library_thumb="${row.get('library_thumb', '')}">
${loop.index + 1}
% if stat_id in ('top_movies', 'popular_movies', 'top_tv', 'popular_tv', 'top_music', 'popular_music', 'last_watched'):
@@ -175,7 +177,9 @@ DOCUMENTATION :: END
% elif stat_id == 'top_platforms':
${row['platform']}
% elif stat_id == 'most_concurrent':
- ${row['title']}
+
+ ${row['title']}
+
% endif
diff --git a/data/interfaces/default/index.html b/data/interfaces/default/index.html
index 88d973da..ca95be42 100644
--- a/data/interfaces/default/index.html
+++ b/data/interfaces/default/index.html
@@ -92,10 +92,10 @@
Recently Added
@@ -212,28 +212,6 @@
-<% from plexpy.helpers import anon_url %>
-
-
-
-
-
-
Tautulli is still running using Python 2 and cannot be updated past v2.6.3.
-
Python 3 is required to continue receiving updates.
-
- Please see the wiki
- for instructions on how to upgrade to Python 3.
-
-
-
-
-
-
% endif
@@ -320,6 +298,8 @@
$('#currentActivityHeader-bandwidth-tooltip').tooltip({ container: 'body', placement: 'right', delay: 50 });
+ var title = document.title;
+
function getCurrentActivity() {
activity_ready = false;
@@ -390,6 +370,8 @@
$('#currentActivityHeader').show();
+ document.title = stream_count + ' stream' + (stream_count > 1 ? 's' : '') + ' | ' + title;
+
sessions.forEach(function (session) {
var s = (typeof Proxy === "function") ? new Proxy(session, defaultHandler) : session;
var key = s.session_key;
@@ -584,6 +566,7 @@
// Update the stream progress times
$('#stream-eta-' + key).html(moment().add(parseInt(s.duration) - parseInt(s.view_offset), 'milliseconds').format(time_format));
+ $('#stream-duration-' + key).html(millisecondsToMinutes(parseInt(s.stream_duration), false));
var stream_view_offset = $('#stream-view-offset-' + key);
stream_view_offset.data('state', s.state);
if (stream_view_offset.data('last_view_offset') !== s.view_offset) {
@@ -621,6 +604,8 @@
} else {
$('#currentActivityHeader').hide();
$('#currentActivity').html('
Nothing is currently being played.
');
+
+ document.title = title;
}
activity_ready = true;
@@ -798,6 +783,7 @@
var guid = $(elem).data('guid');
var live = $(elem).data('live');
var library_art = $(elem).data('library_art');
+ var library_thumb = $(elem).data('library_thumb');
var [height, fallback_poster, fallback_art] = [450, 'poster', 'art'];
if ($.inArray(stat_id, ['top_music', 'popular_music']) > -1) {
[height, fallback_poster, fallback_art] = [300, 'cover', 'art'];
@@ -809,11 +795,11 @@
if (stat_id === 'most_concurrent') {
return
} else if (stat_id === 'top_libraries') {
- $('#stats-background-' + stat_id).css('background-image', 'url(' + page('pms_image_proxy', art || library_art, null, 500, 280, 40, '282828', 3, library_art || fallback_art) + ')');
+ $('#stats-background-' + stat_id).css('background-image', 'url(' + page('pms_image_proxy', art || library_art, null, 500, 280, 40, '282828', 3, fallback_art) + ')');
$('#stats-thumb-' + stat_id).removeClass(function (index, className) {
return (className.match (/(^|\s)svg-icon library-\S+/g) || []).join(' ')});
- if (thumb.startsWith('http')) {
- $('#stats-thumb-' + stat_id).css('background-image', 'url(' + page('pms_image_proxy', thumb, null, 300, 300, null, null, null, 'cover') + ')');
+ if (library_thumb.startsWith('http')) {
+ $('#stats-thumb-' + stat_id).css('background-image', 'url(' + page('pms_image_proxy', library_thumb, null, 100, 100, null, null, null, 'cover') + ')');
} else {
$('#stats-thumb-' + stat_id).css('background-image', '')
.addClass('svg-icon library-' + library_type);
@@ -956,10 +942,14 @@
count: recently_added_count,
media_type: recently_added_type
},
+ beforeSend: function () {
+ $(".dashboard-recent-media-row").animate({ scrollLeft: 0 }, 1000);
+ },
complete: function (xhr, status) {
$("#recentlyAdded").html(xhr.responseText);
$('#ajaxMsg').fadeOut();
- highlightAddedScrollerButton();
+ highlightScrollerButton("#recently-added");
+ paginateScroller("#recently-added", ".paginate-added");
}
});
}
@@ -975,57 +965,11 @@
recentlyAdded(recently_added_count, recently_added_type);
}
- function highlightAddedScrollerButton() {
- var scroller = $("#recently-added-row-scroller");
- var numElems = scroller.find("li:visible").length;
- scroller.width(numElems * 175);
- if (scroller.width() > $("body").find(".container-fluid").width()) {
- $("#recently-added-page-right").removeClass("disabled");
- } else {
- $("#recently-added-page-right").addClass("disabled");
- }
- }
-
- $(window).resize(function () {
- highlightAddedScrollerButton();
- });
-
- function resetScroller() {
- leftTotal = 0;
- $("#recently-added-row-scroller").animate({ left: leftTotal }, 1000);
- $("#recently-added-page-left").addClass("disabled").blur();
- }
-
- var leftTotal = 0;
- $(".paginate").click(function (e) {
- e.preventDefault();
- var scroller = $("#recently-added-row-scroller");
- var containerWidth = $("body").find(".container-fluid").width();
- var scrollAmount = $(this).data("id") * parseInt((containerWidth - 15) / 175) * 175;
- var leftMax = Math.min(-parseInt(scroller.width()) + Math.abs(scrollAmount), 0);
-
- leftTotal = Math.max(Math.min(leftTotal + scrollAmount, 0), leftMax);
- scroller.animate({ left: leftTotal }, 250);
-
- if (leftTotal === 0) {
- $("#recently-added-page-left").addClass("disabled").blur();
- } else {
- $("#recently-added-page-left").removeClass("disabled");
- }
-
- if (leftTotal === leftMax) {
- $("#recently-added-page-right").addClass("disabled").blur();
- } else {
- $("#recently-added-page-right").removeClass("disabled");
- }
- });
-
$('#recently-added-toggles').on('change', function () {
$('#recently-added-toggles > label').removeClass('active');
selected_filter = $('input[name=recently-added-toggle]:checked', '#recently-added-toggles');
$(selected_filter).closest('label').addClass('active');
recently_added_type = $(selected_filter).val();
- resetScroller();
setLocalStorage('home_stats_recently_added_type', recently_added_type);
recentlyAdded(recently_added_count, recently_added_type);
});
@@ -1033,7 +977,6 @@
$('#recently-added-count').change(function () {
forceMinMax($(this));
recently_added_count = $(this).val();
- resetScroller();
setLocalStorage('home_stats_recently_added_count', recently_added_count);
recentlyAdded(recently_added_count, recently_added_type);
});
@@ -1065,16 +1008,4 @@
});
% endif
-% if _session['user_group'] == 'admin':
-
-% endif
%def>
diff --git a/data/interfaces/default/info.html b/data/interfaces/default/info.html
index 43e57694..ead534a1 100644
--- a/data/interfaces/default/info.html
+++ b/data/interfaces/default/info.html
@@ -408,8 +408,8 @@ DOCUMENTATION :: END
% endif
- % if media_info['channel_identifier']:
- Channel ${media_info['channel_call_sign']} ${media_info['channel_identifier']}
+ % if media_info['channel_vcn']:
+ Channel ${media_info['channel_title'] or (media_info['channel_vcn'] + ' ' + media_info['channel_call_sign'])}
% endif
@@ -692,7 +692,7 @@ DOCUMENTATION :: END
Started |
Paused |
Stopped |
-
Duration |
+
Duration |
|
@@ -878,7 +878,7 @@ DOCUMENTATION :: END
transcode_decision: transcode_decision,
user_id: "${history_user_id}",
% if data['live']:
- guid: "${data['guid']}
+ guid: "${data['guid']}"
% elif data['media_type'] in ('show', 'artist'):
grandparent_rating_key: "${data['rating_key']}"
% elif data['media_type'] in ('season', 'album'):
@@ -947,8 +947,12 @@ DOCUMENTATION :: END
url: 'item_watch_time_stats',
async: true,
data: {
+ % if data['live']:
+ guid: "${data['guid']}"
+ % else:
rating_key: "${data['rating_key']}",
- media_type: "${data['media_type']}"
+ media_type: "${data['media_type']}"
+ % endif
},
complete: function(xhr, status) {
$("#watch-time-stats").html(xhr.responseText);
@@ -959,8 +963,12 @@ DOCUMENTATION :: END
url: 'item_user_stats',
async: true,
data: {
+ % if data['live']:
+ guid: "${data['guid']}"
+ % else:
rating_key: "${data['rating_key']}",
- media_type: "${data['media_type']}"
+ media_type: "${data['media_type']}"
+ % endif
},
complete: function(xhr, status) {
$("#user-stats").html(xhr.responseText);
diff --git a/data/interfaces/default/js/bootstrap-select.min.js b/data/interfaces/default/js/bootstrap-select.min.js
new file mode 100644
index 00000000..92e3a32e
--- /dev/null
+++ b/data/interfaces/default/js/bootstrap-select.min.js
@@ -0,0 +1,9 @@
+/*!
+ * Bootstrap-select v1.13.14 (https://developer.snapappointments.com/bootstrap-select)
+ *
+ * Copyright 2012-2020 SnapAppointments, LLC
+ * Licensed under MIT (https://github.com/snapappointments/bootstrap-select/blob/master/LICENSE)
+ */
+
+!function(e,t){void 0===e&&void 0!==window&&(e=window),"function"==typeof define&&define.amd?define(["jquery"],function(e){return t(e)}):"object"==typeof module&&module.exports?module.exports=t(require("jquery")):t(e.jQuery)}(this,function(e){!function(z){"use strict";var d=["sanitize","whiteList","sanitizeFn"],r=["background","cite","href","itemtype","longdesc","poster","src","xlink:href"],e={"*":["class","dir","id","lang","role","tabindex","style",/^aria-[\w-]*$/i],a:["target","href","title","rel"],area:[],b:[],br:[],col:[],code:[],div:[],em:[],hr:[],h1:[],h2:[],h3:[],h4:[],h5:[],h6:[],i:[],img:["src","alt","title","width","height"],li:[],ol:[],p:[],pre:[],s:[],small:[],span:[],sub:[],sup:[],strong:[],u:[],ul:[]},l=/^(?:(?:https?|mailto|ftp|tel|file):|[^&:/?#]*(?:[/?#]|$))/gi,a=/^data:(?:image\/(?:bmp|gif|jpeg|jpg|png|tiff|webp)|video\/(?:mpeg|mp4|ogg|webm)|audio\/(?:mp3|oga|ogg|opus));base64,[a-z0-9+/]+=*$/i;function v(e,t){var i=e.nodeName.toLowerCase();if(-1!==z.inArray(i,t))return-1===z.inArray(i,r)||Boolean(e.nodeValue.match(l)||e.nodeValue.match(a));for(var s=z(t).filter(function(e,t){return t instanceof RegExp}),n=0,o=s.length;n
]+>/g,"")),s&&(a=w(a)),a=a.toUpperCase(),o="contains"===i?0<=a.indexOf(t):a.startsWith(t)))break}return o}function L(e){return parseInt(e,10)||0}z.fn.triggerNative=function(e){var t,i=this[0];i.dispatchEvent?(u?t=new Event(e,{bubbles:!0}):(t=document.createEvent("Event")).initEvent(e,!0,!1),i.dispatchEvent(t)):i.fireEvent?((t=document.createEventObject()).eventType=e,i.fireEvent("on"+e,t)):this.trigger(e)};var f={"\xc0":"A","\xc1":"A","\xc2":"A","\xc3":"A","\xc4":"A","\xc5":"A","\xe0":"a","\xe1":"a","\xe2":"a","\xe3":"a","\xe4":"a","\xe5":"a","\xc7":"C","\xe7":"c","\xd0":"D","\xf0":"d","\xc8":"E","\xc9":"E","\xca":"E","\xcb":"E","\xe8":"e","\xe9":"e","\xea":"e","\xeb":"e","\xcc":"I","\xcd":"I","\xce":"I","\xcf":"I","\xec":"i","\xed":"i","\xee":"i","\xef":"i","\xd1":"N","\xf1":"n","\xd2":"O","\xd3":"O","\xd4":"O","\xd5":"O","\xd6":"O","\xd8":"O","\xf2":"o","\xf3":"o","\xf4":"o","\xf5":"o","\xf6":"o","\xf8":"o","\xd9":"U","\xda":"U","\xdb":"U","\xdc":"U","\xf9":"u","\xfa":"u","\xfb":"u","\xfc":"u","\xdd":"Y","\xfd":"y","\xff":"y","\xc6":"Ae","\xe6":"ae","\xde":"Th","\xfe":"th","\xdf":"ss","\u0100":"A","\u0102":"A","\u0104":"A","\u0101":"a","\u0103":"a","\u0105":"a","\u0106":"C","\u0108":"C","\u010a":"C","\u010c":"C","\u0107":"c","\u0109":"c","\u010b":"c","\u010d":"c","\u010e":"D","\u0110":"D","\u010f":"d","\u0111":"d","\u0112":"E","\u0114":"E","\u0116":"E","\u0118":"E","\u011a":"E","\u0113":"e","\u0115":"e","\u0117":"e","\u0119":"e","\u011b":"e","\u011c":"G","\u011e":"G","\u0120":"G","\u0122":"G","\u011d":"g","\u011f":"g","\u0121":"g","\u0123":"g","\u0124":"H","\u0126":"H","\u0125":"h","\u0127":"h","\u0128":"I","\u012a":"I","\u012c":"I","\u012e":"I","\u0130":"I","\u0129":"i","\u012b":"i","\u012d":"i","\u012f":"i","\u0131":"i","\u0134":"J","\u0135":"j","\u0136":"K","\u0137":"k","\u0138":"k","\u0139":"L","\u013b":"L","\u013d":"L","\u013f":"L","\u0141":"L","\u013a":"l","\u013c":"l","\u013e":"l","\u0140":"l","\u0142":"l","\u0143":"N","\u0145":"N","\u0147":"N","\u014a":"N","\u0144":"n","\u0146":"n","\u0148":"n","\u014b":"n","\u014c":"O","\u014e":"O","\u0150":"O","\u014d":"o","\u014f":"o","\u0151":"o","\u0154":"R","\u0156":"R","\u0158":"R","\u0155":"r","\u0157":"r","\u0159":"r","\u015a":"S","\u015c":"S","\u015e":"S","\u0160":"S","\u015b":"s","\u015d":"s","\u015f":"s","\u0161":"s","\u0162":"T","\u0164":"T","\u0166":"T","\u0163":"t","\u0165":"t","\u0167":"t","\u0168":"U","\u016a":"U","\u016c":"U","\u016e":"U","\u0170":"U","\u0172":"U","\u0169":"u","\u016b":"u","\u016d":"u","\u016f":"u","\u0171":"u","\u0173":"u","\u0174":"W","\u0175":"w","\u0176":"Y","\u0177":"y","\u0178":"Y","\u0179":"Z","\u017b":"Z","\u017d":"Z","\u017a":"z","\u017c":"z","\u017e":"z","\u0132":"IJ","\u0133":"ij","\u0152":"Oe","\u0153":"oe","\u0149":"'n","\u017f":"s"},m=/[\xc0-\xd6\xd8-\xf6\xf8-\xff\u0100-\u017f]/g,g=RegExp("[\\u0300-\\u036f\\ufe20-\\ufe2f\\u20d0-\\u20ff\\u1ab0-\\u1aff\\u1dc0-\\u1dff]","g");function b(e){return f[e]}function w(e){return(e=e.toString())&&e.replace(m,b).replace(g,"")}var I,x,y,$,S=(I={"&":"&","<":"<",">":">",'"':""","'":"'","`":"`"},x="(?:"+Object.keys(I).join("|")+")",y=RegExp(x),$=RegExp(x,"g"),function(e){return e=null==e?"":""+e,y.test(e)?e.replace($,E):e});function E(e){return I[e]}var C={32:" ",48:"0",49:"1",50:"2",51:"3",52:"4",53:"5",54:"6",55:"7",56:"8",57:"9",59:";",65:"A",66:"B",67:"C",68:"D",69:"E",70:"F",71:"G",72:"H",73:"I",74:"J",75:"K",76:"L",77:"M",78:"N",79:"O",80:"P",81:"Q",82:"R",83:"S",84:"T",85:"U",86:"V",87:"W",88:"X",89:"Y",90:"Z",96:"0",97:"1",98:"2",99:"3",100:"4",101:"5",102:"6",103:"7",104:"8",105:"9"},N=27,D=13,H=32,W=9,B=38,M=40,R={success:!1,major:"3"};try{R.full=(z.fn.dropdown.Constructor.VERSION||"").split(" ")[0].split("."),R.major=R.full[0],R.success=!0}catch(e){}var U=0,j=".bs.select",V={DISABLED:"disabled",DIVIDER:"divider",SHOW:"open",DROPUP:"dropup",MENU:"dropdown-menu",MENURIGHT:"dropdown-menu-right",MENULEFT:"dropdown-menu-left",BUTTONCLASS:"btn-default",POPOVERHEADER:"popover-title",ICONBASE:"glyphicon",TICKICON:"glyphicon-ok"},F={MENU:"."+V.MENU},_={span:document.createElement("span"),i:document.createElement("i"),subtext:document.createElement("small"),a:document.createElement("a"),li:document.createElement("li"),whitespace:document.createTextNode("\xa0"),fragment:document.createDocumentFragment()};_.a.setAttribute("role","option"),"4"===R.major&&(_.a.className="dropdown-item"),_.subtext.className="text-muted",_.text=_.span.cloneNode(!1),_.text.className="text",_.checkMark=_.span.cloneNode(!1);var G=new RegExp(B+"|"+M),q=new RegExp("^"+W+"$|"+N),K={li:function(e,t,i){var s=_.li.cloneNode(!1);return e&&(1===e.nodeType||11===e.nodeType?s.appendChild(e):s.innerHTML=e),void 0!==t&&""!==t&&(s.className=t),null!=i&&s.classList.add("optgroup-"+i),s},a:function(e,t,i){var s=_.a.cloneNode(!0);return e&&(11===e.nodeType?s.appendChild(e):s.insertAdjacentHTML("beforeend",e)),void 0!==t&&""!==t&&s.classList.add.apply(s.classList,t.split(" ")),i&&s.setAttribute("style",i),s},text:function(e,t){var i,s,n=_.text.cloneNode(!1);if(e.content)n.innerHTML=e.content;else{if(n.textContent=e.text,e.icon){var o=_.whitespace.cloneNode(!1);(s=(!0===t?_.i:_.span).cloneNode(!1)).className=this.options.iconBase+" "+e.icon,_.fragment.appendChild(s),_.fragment.appendChild(o)}e.subtext&&((i=_.subtext.cloneNode(!1)).textContent=e.subtext,n.appendChild(i))}if(!0===t)for(;0'},maxOptions:!1,mobile:!1,selectOnTab:!1,dropdownAlignRight:!1,windowPadding:0,virtualScroll:600,display:!1,sanitize:!0,sanitizeFn:null,whiteList:e},Y.prototype={constructor:Y,init:function(){var i=this,e=this.$element.attr("id");U++,this.selectId="bs-select-"+U,this.$element[0].classList.add("bs-select-hidden"),this.multiple=this.$element.prop("multiple"),this.autofocus=this.$element.prop("autofocus"),this.$element[0].classList.contains("show-tick")&&(this.options.showTick=!0),this.$newElement=this.createDropdown(),this.buildData(),this.$element.after(this.$newElement).prependTo(this.$newElement),this.$button=this.$newElement.children("button"),this.$menu=this.$newElement.children(F.MENU),this.$menuInner=this.$menu.children(".inner"),this.$searchbox=this.$menu.find("input"),this.$element[0].classList.remove("bs-select-hidden"),!0===this.options.dropdownAlignRight&&this.$menu[0].classList.add(V.MENURIGHT),void 0!==e&&this.$button.attr("data-id",e),this.checkDisabled(),this.clickListener(),this.options.liveSearch?(this.liveSearchListener(),this.focusedParent=this.$searchbox[0]):this.focusedParent=this.$menuInner[0],this.setStyle(),this.render(),this.setWidth(),this.options.container?this.selectPosition():this.$element.on("hide"+j,function(){if(i.isVirtual()){var e=i.$menuInner[0],t=e.firstChild.cloneNode(!1);e.replaceChild(t,e.firstChild),e.scrollTop=0}}),this.$menu.data("this",this),this.$newElement.data("this",this),this.options.mobile&&this.mobile(),this.$newElement.on({"hide.bs.dropdown":function(e){i.$element.trigger("hide"+j,e)},"hidden.bs.dropdown":function(e){i.$element.trigger("hidden"+j,e)},"show.bs.dropdown":function(e){i.$element.trigger("show"+j,e)},"shown.bs.dropdown":function(e){i.$element.trigger("shown"+j,e)}}),i.$element[0].hasAttribute("required")&&this.$element.on("invalid"+j,function(){i.$button[0].classList.add("bs-invalid"),i.$element.on("shown"+j+".invalid",function(){i.$element.val(i.$element.val()).off("shown"+j+".invalid")}).on("rendered"+j,function(){this.validity.valid&&i.$button[0].classList.remove("bs-invalid"),i.$element.off("rendered"+j)}),i.$button.on("blur"+j,function(){i.$element.trigger("focus").trigger("blur"),i.$button.off("blur"+j)})}),setTimeout(function(){i.buildList(),i.$element.trigger("loaded"+j)})},createDropdown:function(){var e=this.multiple||this.options.showTick?" show-tick":"",t=this.multiple?' aria-multiselectable="true"':"",i="",s=this.autofocus?" autofocus":"";R.major<4&&this.$element.parent().hasClass("input-group")&&(i=" input-group-btn");var n,o="",r="",l="",a="";return this.options.header&&(o='"),this.options.liveSearch&&(r=''),this.multiple&&this.options.actionsBox&&(l=' "),this.multiple&&this.options.doneButton&&(a='"),n='",z(n)},setPositionData:function(){this.selectpicker.view.canHighlight=[];for(var e=this.selectpicker.view.size=0;e=this.options.virtualScroll||!0===this.options.virtualScroll},createView:function(A,e,t){var L,N,D=this,i=0,H=[];if(this.selectpicker.isSearching=A,this.selectpicker.current=A?this.selectpicker.search:this.selectpicker.main,this.setPositionData(),e)if(t)i=this.$menuInner[0].scrollTop;else if(!D.multiple){var s=D.$element[0],n=(s.options[s.selectedIndex]||{}).liIndex;if("number"==typeof n&&!1!==D.options.size){var o=D.selectpicker.main.data[n],r=o&&o.position;r&&(i=r-(D.sizeInfo.menuInnerHeight+D.sizeInfo.liHeight)/2)}}function l(e,t){var i,s,n,o,r,l,a,c,d=D.selectpicker.current.elements.length,h=[],p=!0,u=D.isVirtual();D.selectpicker.view.scrollTop=e,i=Math.ceil(D.sizeInfo.menuInnerHeight/D.sizeInfo.liHeight*1.5),s=Math.round(d/i)||1;for(var f=0;fd-1?0:D.selectpicker.current.data[d-1].position-D.selectpicker.current.data[D.selectpicker.view.position1-1].position,b.firstChild.style.marginTop=v+"px",b.firstChild.style.marginBottom=g+"px"):(b.firstChild.style.marginTop=0,b.firstChild.style.marginBottom=0),b.firstChild.appendChild(w),!0===u&&D.sizeInfo.hasScrollBar){var C=b.firstChild.offsetWidth;if(t&&CD.sizeInfo.selectWidth)b.firstChild.style.minWidth=D.sizeInfo.menuInnerInnerWidth+"px";else if(C>D.sizeInfo.menuInnerInnerWidth){D.$menu[0].style.minWidth=0;var O=b.firstChild.offsetWidth;O>D.sizeInfo.menuInnerInnerWidth&&(D.sizeInfo.menuInnerInnerWidth=O,b.firstChild.style.minWidth=D.sizeInfo.menuInnerInnerWidth+"px"),D.$menu[0].style.minWidth=""}}}if(D.prevActiveIndex=D.activeIndex,D.options.liveSearch){if(A&&t){var z,T=0;D.selectpicker.view.canHighlight[T]||(T=1+D.selectpicker.view.canHighlight.slice(1).indexOf(!0)),z=D.selectpicker.view.visibleElements[T],D.defocusItem(D.selectpicker.view.currentActive),D.activeIndex=(D.selectpicker.current.data[T]||{}).index,D.focusItem(z)}}else D.$menuInner.trigger("focus")}l(i,!0),this.$menuInner.off("scroll.createView").on("scroll.createView",function(e,t){D.noScroll||l(this.scrollTop,t),D.noScroll=!1}),z(window).off("resize"+j+"."+this.selectId+".createView").on("resize"+j+"."+this.selectId+".createView",function(){D.$newElement.hasClass(V.SHOW)&&l(D.$menuInner[0].scrollTop)})},focusItem:function(e,t,i){if(e){t=t||this.selectpicker.main.data[this.activeIndex];var s=e.firstChild;s&&(s.setAttribute("aria-setsize",this.selectpicker.view.size),s.setAttribute("aria-posinset",t.posinset),!0!==i&&(this.focusedParent.setAttribute("aria-activedescendant",s.id),e.classList.add("active"),s.classList.add("active")))}},defocusItem:function(e){e&&(e.classList.remove("active"),e.firstChild&&e.firstChild.classList.remove("active"))},setPlaceholder:function(){var e=!1;if(this.options.title&&!this.multiple){this.selectpicker.view.titleOption||(this.selectpicker.view.titleOption=document.createElement("option")),e=!0;var t=this.$element[0],i=!1,s=!this.selectpicker.view.titleOption.parentNode;if(s)this.selectpicker.view.titleOption.className="bs-title-option",this.selectpicker.view.titleOption.value="",i=void 0===z(t.options[t.selectedIndex]).attr("selected")&&void 0===this.$element.data("selected");!s&&0===this.selectpicker.view.titleOption.index||t.insertBefore(this.selectpicker.view.titleOption,t.firstChild),i&&(t.selectedIndex=0)}return e},buildData:function(){var p=':not([hidden]):not([data-hidden="true"])',u=[],f=0,e=this.setPlaceholder()?1:0;this.options.hideDisabled&&(p+=":not(:disabled)");var t=this.$element[0].querySelectorAll("select > *"+p);function m(e){var t=u[u.length-1];t&&"divider"===t.type&&(t.optID||e.optID)||((e=e||{}).type="divider",u.push(e))}function v(e,t){if((t=t||{}).divider="true"===e.getAttribute("data-divider"),t.divider)m({optID:t.optID});else{var i=u.length,s=e.style.cssText,n=s?S(s):"",o=(e.className||"")+(t.optgroupClass||"");t.optID&&(o="opt "+o),t.optionClass=o.trim(),t.inlineStyle=n,t.text=e.textContent,t.content=e.getAttribute("data-content"),t.tokens=e.getAttribute("data-tokens"),t.subtext=e.getAttribute("data-subtext"),t.icon=e.getAttribute("data-icon"),e.liIndex=i,t.display=t.content||t.text,t.type="option",t.index=i,t.option=e,t.selected=!!e.selected,t.disabled=t.disabled||!!e.disabled,u.push(t)}}function i(e,t){var i=t[e],s=t[e-1],n=t[e+1],o=i.querySelectorAll("option"+p);if(o.length){var r,l,a={display:S(i.label),subtext:i.getAttribute("data-subtext"),icon:i.getAttribute("data-icon"),type:"optgroup-label",optgroupClass:" "+(i.className||"")};f++,s&&m({optID:f}),a.optID=f,u.push(a);for(var c=0,d=o.length;c li")},render:function(){var e,t=this,i=this.$element[0],s=this.setPlaceholder()&&0===i.selectedIndex,n=O(i,this.options.hideDisabled),o=n.length,r=this.$button[0],l=r.querySelector(".filter-option-inner-inner"),a=document.createTextNode(this.options.multipleSeparator),c=_.fragment.cloneNode(!1),d=!1;if(r.classList.toggle("bs-placeholder",t.multiple?!o:!T(i,n)),this.tabIndex(),"static"===this.options.selectedTextFormat)c=K.text.call(this,{text:this.options.title},!0);else if(!1===(this.multiple&&-1!==this.options.selectedTextFormat.indexOf("count")&&1")).length&&o>e[1]||1===e.length&&2<=o))){if(!s){for(var h=0;h option"+m+", optgroup"+m+" option"+m).length,g="function"==typeof this.options.countSelectedText?this.options.countSelectedText(o,v):this.options.countSelectedText;c=K.text.call(this,{text:g.replace("{0}",o.toString()).replace("{1}",v.toString())},!0)}if(null==this.options.title&&(this.options.title=this.$element.attr("title")),c.childNodes.length||(c=K.text.call(this,{text:void 0!==this.options.title?this.options.title:this.options.noneSelectedText},!0)),r.title=c.textContent.replace(/<[^>]*>?/g,"").trim(),this.options.sanitize&&d&&P([c],t.options.whiteList,t.options.sanitizeFn),l.innerHTML="",l.appendChild(c),R.major<4&&this.$newElement[0].classList.contains("bs3-has-addon")){var b=r.querySelector(".filter-expand"),w=l.cloneNode(!0);w.className="filter-expand",b?r.replaceChild(w,b):r.appendChild(w)}this.$element.trigger("rendered"+j)},setStyle:function(e,t){var i,s=this.$button[0],n=this.$newElement[0],o=this.options.style.trim();this.$element.attr("class")&&this.$newElement.addClass(this.$element.attr("class").replace(/selectpicker|mobile-device|bs-select-hidden|validate\[.*\]/gi,"")),R.major<4&&(n.classList.add("bs3"),n.parentNode.classList.contains("input-group")&&(n.previousElementSibling||n.nextElementSibling)&&(n.previousElementSibling||n.nextElementSibling).classList.contains("input-group-addon")&&n.classList.add("bs3-has-addon")),i=e?e.trim():o,"add"==t?i&&s.classList.add.apply(s.classList,i.split(" ")):"remove"==t?i&&s.classList.remove.apply(s.classList,i.split(" ")):(o&&s.classList.remove.apply(s.classList,o.split(" ")),i&&s.classList.add.apply(s.classList,i.split(" ")))},liHeight:function(e){if(e||!1!==this.options.size&&!Object.keys(this.sizeInfo).length){var t=document.createElement("div"),i=document.createElement("div"),s=document.createElement("div"),n=document.createElement("ul"),o=document.createElement("li"),r=document.createElement("li"),l=document.createElement("li"),a=document.createElement("a"),c=document.createElement("span"),d=this.options.header&&0this.sizeInfo.menuExtras.vert&&l+this.sizeInfo.menuExtras.vert+50>this.sizeInfo.selectOffsetBot,!0===this.selectpicker.isSearching&&(a=this.selectpicker.dropup),this.$newElement.toggleClass(V.DROPUP,a),this.selectpicker.dropup=a),"auto"===this.options.size)n=3this.options.size){for(var b=0;bthis.sizeInfo.menuInnerHeight&&(this.sizeInfo.hasScrollBar=!0,this.sizeInfo.totalMenuWidth=this.sizeInfo.menuWidth+this.sizeInfo.scrollBarWidth),"auto"===this.options.dropdownAlignRight&&this.$menu.toggleClass(V.MENURIGHT,this.sizeInfo.selectOffsetLeft>this.sizeInfo.selectOffsetRight&&this.sizeInfo.selectOffsetRightthis.options.size&&i.off("resize"+j+"."+this.selectId+".setMenuSize scroll"+j+"."+this.selectId+".setMenuSize")}this.createView(!1,!0,e)},setWidth:function(){var i=this;"auto"===this.options.width?requestAnimationFrame(function(){i.$menu.css("min-width","0"),i.$element.on("loaded"+j,function(){i.liHeight(),i.setMenuSize();var e=i.$newElement.clone().appendTo("body"),t=e.css("width","auto").children("button").outerWidth();e.remove(),i.sizeInfo.selectWidth=Math.max(i.sizeInfo.totalMenuWidth,t),i.$newElement.css("width",i.sizeInfo.selectWidth+"px")})}):"fit"===this.options.width?(this.$menu.css("min-width",""),this.$newElement.css("width","").addClass("fit-width")):this.options.width?(this.$menu.css("min-width",""),this.$newElement.css("width",this.options.width)):(this.$menu.css("min-width",""),this.$newElement.css("width","")),this.$newElement.hasClass("fit-width")&&"fit"!==this.options.width&&this.$newElement[0].classList.remove("fit-width")},selectPosition:function(){this.$bsContainer=z('');function e(e){var t={},i=r.options.display||!!z.fn.dropdown.Constructor.Default&&z.fn.dropdown.Constructor.Default.display;r.$bsContainer.addClass(e.attr("class").replace(/form-control|fit-width/gi,"")).toggleClass(V.DROPUP,e.hasClass(V.DROPUP)),s=e.offset(),l.is("body")?n={top:0,left:0}:((n=l.offset()).top+=parseInt(l.css("borderTopWidth"))-l.scrollTop(),n.left+=parseInt(l.css("borderLeftWidth"))-l.scrollLeft()),o=e.hasClass(V.DROPUP)?0:e[0].offsetHeight,(R.major<4||"static"===i)&&(t.top=s.top-n.top+o,t.left=s.left-n.left),t.width=e[0].offsetWidth,r.$bsContainer.css(t)}var s,n,o,r=this,l=z(this.options.container);this.$button.on("click.bs.dropdown.data-api",function(){r.isDisabled()||(e(r.$newElement),r.$bsContainer.appendTo(r.options.container).toggleClass(V.SHOW,!r.$button.hasClass(V.SHOW)).append(r.$menu))}),z(window).off("resize"+j+"."+this.selectId+" scroll"+j+"."+this.selectId).on("resize"+j+"."+this.selectId+" scroll"+j+"."+this.selectId,function(){r.$newElement.hasClass(V.SHOW)&&e(r.$newElement)}),this.$element.on("hide"+j,function(){r.$menu.data("height",r.$menu.height()),r.$bsContainer.detach()})},setOptionStatus:function(e){var t=this;if(t.noScroll=!1,t.selectpicker.view.visibleElements&&t.selectpicker.view.visibleElements.length)for(var i=0;i');y[2]&&($=$.replace("{var}",y[2][1
"+$+" ")),d=!1,C.$element.trigger("maxReached"+j)),g&&w&&(E.append(z("
"+S+"
")),d=!1,C.$element.trigger("maxReachedGrp"+j)),setTimeout(function(){C.setSelected(r,!1)},10),E[0].classList.add("fadeOut"),setTimeout(function(){E.remove()},1050)}}}else c&&(c.selected=!1),h.selected=!0,C.setSelected(r,!0);!C.multiple||C.multiple&&1===C.options.maxOptions?C.$button.trigger("focus"):C.options.liveSearch&&C.$searchbox.trigger("focus"),d&&(!C.multiple&&a===s.selectedIndex||(A=[h.index,p.prop("selected"),l],C.$element.triggerNative("change")))}}),this.$menu.on("click","li."+V.DISABLED+" a, ."+V.POPOVERHEADER+", ."+V.POPOVERHEADER+" :not(.close)",function(e){e.currentTarget==this&&(e.preventDefault(),e.stopPropagation(),C.options.liveSearch&&!z(e.target).hasClass("close")?C.$searchbox.trigger("focus"):C.$button.trigger("focus"))}),this.$menuInner.on("click",".divider, .dropdown-header",function(e){e.preventDefault(),e.stopPropagation(),C.options.liveSearch?C.$searchbox.trigger("focus"):C.$button.trigger("focus")}),this.$menu.on("click","."+V.POPOVERHEADER+" .close",function(){C.$button.trigger("click")}),this.$searchbox.on("click",function(e){e.stopPropagation()}),this.$menu.on("click",".actions-btn",function(e){C.options.liveSearch?C.$searchbox.trigger("focus"):C.$button.trigger("focus"),e.preventDefault(),e.stopPropagation(),z(this).hasClass("bs-select-all")?C.selectAll():C.deselectAll()}),this.$element.on("change"+j,function(){C.render(),C.$element.trigger("changed"+j,A),A=null}).on("focus"+j,function(){C.options.mobile||C.$button.trigger("focus")})},liveSearchListener:function(){var u=this,f=document.createElement("li");this.$button.on("click.bs.dropdown.data-api",function(){u.$searchbox.val()&&u.$searchbox.val("")}),this.$searchbox.on("click.bs.dropdown.data-api focus.bs.dropdown.data-api touchend.bs.dropdown.data-api",function(e){e.stopPropagation()}),this.$searchbox.on("input propertychange",function(){var e=u.$searchbox.val();if(u.selectpicker.search.elements=[],u.selectpicker.search.data=[],e){var t=[],i=e.toUpperCase(),s={},n=[],o=u._searchStyle(),r=u.options.liveSearchNormalize;r&&(i=w(i));for(var l=0;l
=a.selectpicker.view.canHighlight.length&&(t=0),a.selectpicker.view.canHighlight[t+f]||(t=t+1+a.selectpicker.view.canHighlight.slice(t+f+1).indexOf(!0))),e.preventDefault();var m=f+t;e.which===B?0===f&&t===c.length-1?(a.$menuInner[0].scrollTop=a.$menuInner[0].scrollHeight,m=a.selectpicker.current.elements.length-1):d=(o=(n=a.selectpicker.current.data[m]).position-n.height)u+a.sizeInfo.menuInnerHeight),s=a.selectpicker.main.elements[v],a.activeIndex=b[x],a.focusItem(s),s&&s.firstChild.focus(),d&&(a.$menuInner[0].scrollTop=o),r.trigger("focus")}}i&&(e.which===H&&!a.selectpicker.keydown.keyHistory||e.which===D||e.which===W&&a.options.selectOnTab)&&(e.which!==H&&e.preventDefault(),a.options.liveSearch&&e.which===H||(a.$menuInner.find(".active a").trigger("click",!0),r.trigger("focus"),a.options.liveSearch||(e.preventDefault(),z(document).data("spaceSelect",!0))))}},mobile:function(){this.$element[0].classList.add("mobile-device")},refresh:function(){var e=z.extend({},this.options,this.$element.data());this.options=e,this.checkDisabled(),this.setStyle(),this.render(),this.buildData(),this.buildList(),this.setWidth(),this.setSize(!0),this.$element.trigger("refreshed"+j)},hide:function(){this.$newElement.hide()},show:function(){this.$newElement.show()},remove:function(){this.$newElement.remove(),this.$element.remove()},destroy:function(){this.$newElement.before(this.$element).remove(),this.$bsContainer?this.$bsContainer.remove():this.$menu.remove(),this.$element.off(j).removeData("selectpicker").removeClass("bs-select-hidden selectpicker"),z(window).off(j+"."+this.selectId)}};var J=z.fn.selectpicker;z.fn.selectpicker=Z,z.fn.selectpicker.Constructor=Y,z.fn.selectpicker.noConflict=function(){return z.fn.selectpicker=J,this};var Q=z.fn.dropdown.Constructor._dataApiKeydownHandler||z.fn.dropdown.Constructor.prototype.keydown;z(document).off("keydown.bs.dropdown.data-api").on("keydown.bs.dropdown.data-api",':not(.bootstrap-select) > [data-toggle="dropdown"]',Q).on("keydown.bs.dropdown.data-api",":not(.bootstrap-select) > .dropdown-menu",Q).on("keydown"+j,'.bootstrap-select [data-toggle="dropdown"], .bootstrap-select [role="listbox"], .bootstrap-select .bs-searchbox input',Y.prototype.keydown).on("focusin.modal",'.bootstrap-select [data-toggle="dropdown"], .bootstrap-select [role="listbox"], .bootstrap-select .bs-searchbox input',function(e){e.stopPropagation()}),z(window).on("load"+j+".data-api",function(){z(".selectpicker").each(function(){var e=z(this);Z.call(e,e.data())})})}(e)});
+//# sourceMappingURL=bootstrap-select.min.js.map
\ No newline at end of file
diff --git a/data/interfaces/default/js/graphs/concurrent_streams_by_stream_type.js b/data/interfaces/default/js/graphs/concurrent_streams_by_stream_type.js
new file mode 100644
index 00000000..623e4735
--- /dev/null
+++ b/data/interfaces/default/js/graphs/concurrent_streams_by_stream_type.js
@@ -0,0 +1,76 @@
+var formatter_function = function() {
+ if (moment(this.x, 'X').isValid() && (this.x > 946684800)) {
+ var s = ''+ moment(this.x).format('ddd MMM D') +'';
+ } else {
+ var s = ''+ this.x +'';
+ }
+ $.each(this.points, function(i, point) {
+ s += '
'+point.series.name+': '+point.y;
+ });
+ return s;
+};
+
+var hc_concurrent_streams_by_stream_type_options = {
+ chart: {
+ type: 'line',
+ backgroundColor: 'rgba(0,0,0,0)',
+ renderTo: 'graph_concurrent_streams_by_stream_type'
+ },
+ title: {
+ text: ''
+ },
+ legend: {
+ enabled: true,
+ itemStyle: {
+ font: '9pt "Open Sans", sans-serif',
+ color: '#A0A0A0'
+ },
+ itemHoverStyle: {
+ color: '#FFF'
+ },
+ itemHiddenStyle: {
+ color: '#444'
+ }
+ },
+ credits: {
+ enabled: false
+ },
+ plotOptions: {
+ series: {
+ events: {
+ legendItemClick: function() {
+ setGraphVisibility(this.chart.renderTo.id, this.chart.series, this.name);
+ }
+ }
+ }
+ },
+ xAxis: {
+ type: 'datetime',
+ labels: {
+ formatter: function() {
+ return moment(this.value).format("MMM D");
+ },
+ style: {
+ color: '#aaa'
+ }
+ },
+ categories: [{}],
+ plotBands: []
+ },
+ yAxis: {
+ title: {
+ text: null
+ },
+ labels: {
+ style: {
+ color: '#aaa'
+ }
+ }
+ },
+ tooltip: {
+ shared: true,
+ crosshairs: true,
+ formatter: formatter_function
+ },
+ series: [{}]
+};
\ No newline at end of file
diff --git a/data/interfaces/default/js/jquery.scrollbar.min.js b/data/interfaces/default/js/jquery.scrollbar.min.js
index 5a86f9ad..cf9d8935 100644
--- a/data/interfaces/default/js/jquery.scrollbar.min.js
+++ b/data/interfaces/default/js/jquery.scrollbar.min.js
@@ -13,4 +13,4 @@
* @url https://github.com/gromo/jquery.scrollbar/
*
*/
-!function(a,b){"function"==typeof define&&define.amd?define(["jquery"],b):b("undefined"!=typeof exports?require("jquery"):a.jQuery)}(this,function(a){"use strict";function h(b){if(c.webkit&&!b)return{height:0,width:0};if(!c.data.outer){var d={border:"none","box-sizing":"content-box",height:"200px",margin:"0",padding:"0",width:"200px"};c.data.inner=a("").css(a.extend({},d)),c.data.outer=a("
").css(a.extend({left:"-1000px",overflow:"scroll",position:"absolute",top:"-1000px"},d)).append(c.data.inner).appendTo("body")}return c.data.outer.scrollLeft(1e3).scrollTop(1e3),{height:Math.ceil(c.data.outer.offset().top-c.data.inner.offset().top||0),width:Math.ceil(c.data.outer.offset().left-c.data.inner.offset().left||0)}}function i(){var a=h(!0);return!(a.height||a.width)}function j(a){var b=a.originalEvent;return(!b.axis||b.axis!==b.HORIZONTAL_AXIS)&&!b.wheelDeltaX}var b=!1,c={data:{index:0,name:"scrollbar"},firefox:/firefox/i.test(navigator.userAgent),macosx:/mac/i.test(navigator.platform),msedge:/edge\/\d+/i.test(navigator.userAgent),msie:/(msie|trident)/i.test(navigator.userAgent),mobile:/android|webos|iphone|ipad|ipod|blackberry/i.test(navigator.userAgent),overlay:null,scroll:null,scrolls:[],webkit:/webkit/i.test(navigator.userAgent)&&!/edge\/\d+/i.test(navigator.userAgent)};c.scrolls.add=function(a){this.remove(a).push(a)},c.scrolls.remove=function(b){for(;a.inArray(b,this)>=0;)this.splice(a.inArray(b,this),1);return this};var d={autoScrollSize:!0,autoUpdate:!0,debug:!1,disableBodyScroll:!1,duration:200,ignoreMobile:!1,ignoreOverlay:!1,isRtl:!1,scrollStep:30,showArrows:!1,stepScrolling:!0,scrollx:null,scrolly:null,onDestroy:null,onFallback:null,onInit:null,onScroll:null,onUpdate:null},e=function(b){c.scroll||(c.overlay=i(),c.scroll=h(),g(),a(window).resize(function(){var a=!1;if(c.scroll&&(c.scroll.height||c.scroll.width)){var b=h();b.height===c.scroll.height&&b.width===c.scroll.width||(c.scroll=b,a=!0)}g(a)})),this.container=b,this.namespace=".scrollbar_"+c.data.index++,this.options=a.extend({},d,window.jQueryScrollbarOptions||{}),this.scrollTo=null,this.scrollx={},this.scrolly={},b.data(c.data.name,this),c.scrolls.add(this)};e.prototype={destroy:function(){if(this.wrapper){this.container.removeData(c.data.name),c.scrolls.remove(this);var b=this.container.scrollLeft(),d=this.container.scrollTop();this.container.insertBefore(this.wrapper).css({height:"",margin:"","max-height":""}).removeClass("scroll-content scroll-scrollx_visible scroll-scrolly_visible").off(this.namespace).scrollLeft(b).scrollTop(d),this.scrollx.scroll.removeClass("scroll-scrollx_visible").find("div").addBack().off(this.namespace),this.scrolly.scroll.removeClass("scroll-scrolly_visible").find("div").addBack().off(this.namespace),this.wrapper.remove(),a(document).add("body").off(this.namespace),a.isFunction(this.options.onDestroy)&&this.options.onDestroy.apply(this,[this.container])}},init:function(b){var d=this,e=this.container,f=this.containerWrapper||e,g=this.namespace,h=a.extend(this.options,b||{}),i={x:this.scrollx,y:this.scrolly},k=this.wrapper,l={},m={scrollLeft:e.scrollLeft(),scrollTop:e.scrollTop()};if(c.mobile&&h.ignoreMobile||c.overlay&&h.ignoreOverlay||c.macosx&&!c.webkit)return a.isFunction(h.onFallback)&&h.onFallback.apply(this,[e]),!1;if(k)l={height:"auto","margin-bottom":c.scroll.height*-1+"px","max-height":""},l[h.isRtl?"margin-left":"margin-right"]=c.scroll.width*-1+"px",f.css(l);else{if(this.wrapper=k=a("
").addClass("scroll-wrapper").addClass(e.attr("class")).css("position","absolute"===e.css("position")?"absolute":"relative").insertBefore(e).append(e),h.isRtl&&k.addClass("scroll--rtl"),e.is("textarea")&&(this.containerWrapper=f=a("
").insertBefore(e).append(e),k.addClass("scroll-textarea")),l={height:"auto","margin-bottom":c.scroll.height*-1+"px","max-height":""},l[h.isRtl?"margin-left":"margin-right"]=c.scroll.width*-1+"px",f.addClass("scroll-content").css(l),e.on("scroll"+g,function(b){var f=e.scrollLeft(),g=e.scrollTop();if(h.isRtl)switch(!0){case c.firefox:f=Math.abs(f);case c.msedge||c.msie:f=e[0].scrollWidth-e[0].clientWidth-f}a.isFunction(h.onScroll)&&h.onScroll.call(d,{maxScroll:i.y.maxScrollOffset,scroll:g,size:i.y.size,visible:i.y.visible},{maxScroll:i.x.maxScrollOffset,scroll:f,size:i.x.size,visible:i.x.visible}),i.x.isVisible&&i.x.scroll.bar.css("left",f*i.x.kx+"px"),i.y.isVisible&&i.y.scroll.bar.css("top",g*i.y.kx+"px")}),k.on("scroll"+g,function(){k.scrollTop(0).scrollLeft(0)}),h.disableBodyScroll){var n=function(a){j(a)?i.y.isVisible&&i.y.mousewheel(a):i.x.isVisible&&i.x.mousewheel(a)};k.on("MozMousePixelScroll"+g,n),k.on("mousewheel"+g,n),c.mobile&&k.on("touchstart"+g,function(b){var c=b.originalEvent.touches&&b.originalEvent.touches[0]||b,d={pageX:c.pageX,pageY:c.pageY},f={left:e.scrollLeft(),top:e.scrollTop()};a(document).on("touchmove"+g,function(a){var b=a.originalEvent.targetTouches&&a.originalEvent.targetTouches[0]||a;e.scrollLeft(f.left+d.pageX-b.pageX),e.scrollTop(f.top+d.pageY-b.pageY),a.preventDefault()}),a(document).on("touchend"+g,function(){a(document).off(g)})})}a.isFunction(h.onInit)&&h.onInit.apply(this,[e])}a.each(i,function(b,f){var k=null,l=1,m="x"===b?"scrollLeft":"scrollTop",n=h.scrollStep,o=function(){var a=e[m]();e[m](a+n),1==l&&a+n>=p&&(a=e[m]()),l==-1&&a+n<=p&&(a=e[m]()),e[m]()==a&&k&&k()},p=0;f.scroll||(f.scroll=d._getScroll(h["scroll"+b]).addClass("scroll-"+b),h.showArrows&&f.scroll.addClass("scroll-element_arrows_visible"),f.mousewheel=function(a){if(!f.isVisible||"x"===b&&j(a))return!0;if("y"===b&&!j(a))return i.x.mousewheel(a),!0;var c=a.originalEvent.wheelDelta*-1||a.originalEvent.detail,g=f.size-f.visible-f.offset;return c||("x"===b&&a.originalEvent.deltaX?c=40*a.originalEvent.deltaX:"y"===b&&a.originalEvent.deltaY&&(c=40*a.originalEvent.deltaY)),(c>0&&p
0)&&(p+=c,p<0&&(p=0),p>g&&(p=g),d.scrollTo=d.scrollTo||{},d.scrollTo[m]=p,setTimeout(function(){d.scrollTo&&(e.stop().animate(d.scrollTo,240,"linear",function(){p=e[m]()}),d.scrollTo=null)},1)),a.preventDefault(),!1},f.scroll.on("MozMousePixelScroll"+g,f.mousewheel).on("mousewheel"+g,f.mousewheel).on("mouseenter"+g,function(){p=e[m]()}),f.scroll.find(".scroll-arrow, .scroll-element_track").on("mousedown"+g,function(g){if(1!=g.which)return!0;l=1;var i={eventOffset:g["x"===b?"pageX":"pageY"],maxScrollValue:f.size-f.visible-f.offset,scrollbarOffset:f.scroll.bar.offset()["x"===b?"left":"top"],scrollbarSize:f.scroll.bar["x"===b?"outerWidth":"outerHeight"]()},j=0,q=0;if(a(this).hasClass("scroll-arrow")){if(l=a(this).hasClass("scroll-arrow_more")?1:-1,n=h.scrollStep*l,p=l>0?i.maxScrollValue:0,h.isRtl)switch(!0){case c.firefox:p=l>0?0:i.maxScrollValue*-1;break;case c.msie||c.msedge:}}else l=i.eventOffset>i.scrollbarOffset+i.scrollbarSize?1:i.eventOffset','','','','",""].join(""),simple:['
"].join("")};return c[b]&&(b=c[b]),b||(b=c.simple),b="string"==typeof b?a(b).appendTo(this.wrapper):a(b),a.extend(b,{bar:b.find(".scroll-bar"),size:b.find(".scroll-element_size"),track:b.find(".scroll-element_track")}),b},_handleMouseDown:function(b,c){var d=this.namespace;return a(document).on("blur"+d,function(){a(document).add("body").off(d),b&&b()}),a(document).on("dragstart"+d,function(a){return a.preventDefault(),!1}),a(document).on("mouseup"+d,function(){a(document).add("body").off(d),b&&b()}),a("body").on("selectstart"+d,function(a){return a.preventDefault(),!1}),c&&c.preventDefault(),!1},_updateScroll:function(b,d){var e=this.container,f=this.containerWrapper||e,g="scroll-scroll"+b+"_visible",h="x"===b?this.scrolly:this.scrollx,i=parseInt(this.container.css("x"===b?"left":"top"),10)||0,j=this.wrapper,k=d.size,l=d.visible+i;d.isVisible=k-l>1,d.isVisible?(d.scroll.addClass(g),h.scroll.addClass(g),f.addClass(g)):(d.scroll.removeClass(g),h.scroll.removeClass(g),f.removeClass(g)),"y"===b&&(e.is("textarea")||k
10?(window.console&&console.log("Scroll updates exceed 10"),g=function(){}):(clearTimeout(a),a=setTimeout(g,300))}}();window.angular&&!function(a){a.module("jQueryScrollbar",[]).provider("jQueryScrollbar",function(){var b=d;return{setOptions:function(c){a.extend(b,c)},$get:function(){return{options:a.copy(b)}}}}).directive("jqueryScrollbar",["jQueryScrollbar","$parse",function(a,b){return{restrict:"AC",link:function(c,d,e){var f=b(e.jqueryScrollbar),g=f(c);d.scrollbar(g||a.options).on("$destroy",function(){d.scrollbar("destroy")})}}}])}(window.angular)});
\ No newline at end of file
+!function(a,b){"function"==typeof define&&define.amd?define(["jquery"],b):b("undefined"!=typeof exports?require("jquery"):a.jQuery)}(this,function(a){"use strict";function h(b){if(c.webkit&&!b)return{height:0,width:0};if(!c.data.outer){var d={border:"none","box-sizing":"content-box",height:"200px",margin:"0",padding:"0",width:"200px"};c.data.inner=a("").css(a.extend({},d)),c.data.outer=a("
").css(a.extend({left:"-1000px",overflow:"scroll",position:"absolute",top:"-1000px"},d)).append(c.data.inner).appendTo("body")}return c.data.outer.scrollLeft(1e3).scrollTop(1e3),{height:Math.ceil(c.data.outer.offset().top-c.data.inner.offset().top||0),width:Math.ceil(c.data.outer.offset().left-c.data.inner.offset().left||0)}}function i(){var a=h(!0);return!(a.height||a.width)}function j(a){var b=a.originalEvent;return(!b.axis||b.axis!==b.HORIZONTAL_AXIS)&&!b.wheelDeltaX}var b=!1,c={data:{index:0,name:"scrollbar"},firefox:/firefox/i.test(navigator.userAgent),macosx:/mac/i.test(navigator.platform),msedge:/edge\/\d+/i.test(navigator.userAgent),msie:/(msie|trident)/i.test(navigator.userAgent),mobile:/android|webos|iphone|ipad|ipod|blackberry/i.test(navigator.userAgent),overlay:null,scroll:null,scrolls:[],webkit:/webkit/i.test(navigator.userAgent)&&!/edge\/\d+/i.test(navigator.userAgent)};c.scrolls.add=function(a){this.remove(a).push(a)},c.scrolls.remove=function(b){for(;a.inArray(b,this)>=0;)this.splice(a.inArray(b,this),1);return this};var d={autoScrollSize:!0,autoUpdate:!0,debug:!1,disableBodyScroll:!1,duration:200,ignoreMobile:!1,ignoreOverlay:!1,isRtl:!1,scrollStep:30,showArrows:!1,stepScrolling:!0,scrollx:null,scrolly:null,onDestroy:null,onFallback:null,onInit:null,onScroll:null,onUpdate:null},e=function(b){c.scroll||(c.overlay=i(),c.scroll=h(),g(),a(window).resize(function(){var a=!1;if(c.scroll&&(c.scroll.height||c.scroll.width)){var b=h();b.height===c.scroll.height&&b.width===c.scroll.width||(c.scroll=b,a=!0)}g(a)})),this.container=b,this.namespace=".scrollbar_"+c.data.index++,this.options=a.extend({},d,window.jQueryScrollbarOptions||{}),this.scrollTo=null,this.scrollx={},this.scrolly={},b.data(c.data.name,this),c.scrolls.add(this)};e.prototype={destroy:function(){if(this.wrapper){this.container.removeData(c.data.name),c.scrolls.remove(this);var b=this.container.scrollLeft(),d=this.container.scrollTop();this.container.insertBefore(this.wrapper).css({height:"",margin:"","max-height":""}).removeClass("scroll-content scroll-scrollx_visible scroll-scrolly_visible").off(this.namespace).scrollLeft(b).scrollTop(d),this.scrollx.scroll.removeClass("scroll-scrollx_visible").find("div").addBack().off(this.namespace),this.scrolly.scroll.removeClass("scroll-scrolly_visible").find("div").addBack().off(this.namespace),this.wrapper.remove(),a(document).add("body").off(this.namespace),a.isFunction(this.options.onDestroy)&&this.options.onDestroy.apply(this,[this.container])}},init:function(b){var d=this,e=this.container,f=this.containerWrapper||e,g=this.namespace,h=a.extend(this.options,b||{}),i={x:this.scrollx,y:this.scrolly},k=this.wrapper,l={},m={scrollLeft:e.scrollLeft(),scrollTop:e.scrollTop()};if(c.mobile&&h.ignoreMobile||c.overlay&&h.ignoreOverlay)return a.isFunction(h.onFallback)&&h.onFallback.apply(this,[e]),!1;if(k)l={height:"auto","margin-bottom":c.scroll.height*-1+"px","max-height":""},l[h.isRtl?"margin-left":"margin-right"]=c.scroll.width*-1+"px",f.css(l);else{if(this.wrapper=k=a("
").addClass("scroll-wrapper").addClass(e.attr("class")).css("position","absolute"===e.css("position")?"absolute":"relative").insertBefore(e).append(e),h.isRtl&&k.addClass("scroll--rtl"),e.is("textarea")&&(this.containerWrapper=f=a("
").insertBefore(e).append(e),k.addClass("scroll-textarea")),l={height:"auto","margin-bottom":c.scroll.height*-1+"px","max-height":""},l[h.isRtl?"margin-left":"margin-right"]=c.scroll.width*-1+"px",f.addClass("scroll-content").css(l),e.on("scroll"+g,function(b){var f=e.scrollLeft(),g=e.scrollTop();if(h.isRtl)switch(!0){case c.firefox:f=Math.abs(f);case c.msedge||c.msie:f=e[0].scrollWidth-e[0].clientWidth-f}a.isFunction(h.onScroll)&&h.onScroll.call(d,{maxScroll:i.y.maxScrollOffset,scroll:g,size:i.y.size,visible:i.y.visible},{maxScroll:i.x.maxScrollOffset,scroll:f,size:i.x.size,visible:i.x.visible}),i.x.isVisible&&i.x.scroll.bar.css("left",f*i.x.kx+"px"),i.y.isVisible&&i.y.scroll.bar.css("top",g*i.y.kx+"px")}),k.on("scroll"+g,function(){k.scrollTop(0).scrollLeft(0)}),h.disableBodyScroll){var n=function(a){j(a)?i.y.isVisible&&i.y.mousewheel(a):i.x.isVisible&&i.x.mousewheel(a)};k.on("MozMousePixelScroll"+g,n),k.on("mousewheel"+g,n),c.mobile&&k.on("touchstart"+g,function(b){var c=b.originalEvent.touches&&b.originalEvent.touches[0]||b,d={pageX:c.pageX,pageY:c.pageY},f={left:e.scrollLeft(),top:e.scrollTop()};a(document).on("touchmove"+g,function(a){var b=a.originalEvent.targetTouches&&a.originalEvent.targetTouches[0]||a;e.scrollLeft(f.left+d.pageX-b.pageX),e.scrollTop(f.top+d.pageY-b.pageY),a.preventDefault()}),a(document).on("touchend"+g,function(){a(document).off(g)})})}a.isFunction(h.onInit)&&h.onInit.apply(this,[e])}a.each(i,function(b,f){var k=null,l=1,m="x"===b?"scrollLeft":"scrollTop",n=h.scrollStep,o=function(){var a=e[m]();e[m](a+n),1==l&&a+n>=p&&(a=e[m]()),l==-1&&a+n<=p&&(a=e[m]()),e[m]()==a&&k&&k()},p=0;f.scroll||(f.scroll=d._getScroll(h["scroll"+b]).addClass("scroll-"+b),h.showArrows&&f.scroll.addClass("scroll-element_arrows_visible"),f.mousewheel=function(a){if(!f.isVisible||"x"===b&&j(a))return!0;if("y"===b&&!j(a))return i.x.mousewheel(a),!0;var c=a.originalEvent.wheelDelta*-1||a.originalEvent.detail,g=f.size-f.visible-f.offset;return c||("x"===b&&a.originalEvent.deltaX?c=40*a.originalEvent.deltaX:"y"===b&&a.originalEvent.deltaY&&(c=40*a.originalEvent.deltaY)),(c>0&&p
0)&&(p+=c,p<0&&(p=0),p>g&&(p=g),d.scrollTo=d.scrollTo||{},d.scrollTo[m]=p,setTimeout(function(){d.scrollTo&&(e.stop().animate(d.scrollTo,240,"linear",function(){p=e[m]()}),d.scrollTo=null)},1)),a.preventDefault(),!1},f.scroll.on("MozMousePixelScroll"+g,f.mousewheel).on("mousewheel"+g,f.mousewheel).on("mouseenter"+g,function(){p=e[m]()}),f.scroll.find(".scroll-arrow, .scroll-element_track").on("mousedown"+g,function(g){if(1!=g.which)return!0;l=1;var i={eventOffset:g["x"===b?"pageX":"pageY"],maxScrollValue:f.size-f.visible-f.offset,scrollbarOffset:f.scroll.bar.offset()["x"===b?"left":"top"],scrollbarSize:f.scroll.bar["x"===b?"outerWidth":"outerHeight"]()},j=0,q=0;if(a(this).hasClass("scroll-arrow")){if(l=a(this).hasClass("scroll-arrow_more")?1:-1,n=h.scrollStep*l,p=l>0?i.maxScrollValue:0,h.isRtl)switch(!0){case c.firefox:p=l>0?0:i.maxScrollValue*-1;break;case c.msie||c.msedge:}}else l=i.eventOffset>i.scrollbarOffset+i.scrollbarSize?1:i.eventOffset','','','','",""].join(""),simple:['
"].join("")};return c[b]&&(b=c[b]),b||(b=c.simple),b="string"==typeof b?a(b).appendTo(this.wrapper):a(b),a.extend(b,{bar:b.find(".scroll-bar"),size:b.find(".scroll-element_size"),track:b.find(".scroll-element_track")}),b},_handleMouseDown:function(b,c){var d=this.namespace;return a(document).on("blur"+d,function(){a(document).add("body").off(d),b&&b()}),a(document).on("dragstart"+d,function(a){return a.preventDefault(),!1}),a(document).on("mouseup"+d,function(){a(document).add("body").off(d),b&&b()}),a("body").on("selectstart"+d,function(a){return a.preventDefault(),!1}),c&&c.preventDefault(),!1},_updateScroll:function(b,d){var e=this.container,f=this.containerWrapper||e,g="scroll-scroll"+b+"_visible",h="x"===b?this.scrolly:this.scrollx,i=parseInt(this.container.css("x"===b?"left":"top"),10)||0,j=this.wrapper,k=d.size,l=d.visible+i;d.isVisible=k-l>1,d.isVisible?(d.scroll.addClass(g),h.scroll.addClass(g),f.addClass(g)):(d.scroll.removeClass(g),h.scroll.removeClass(g),f.removeClass(g)),"y"===b&&(e.is("textarea")||k
10?(window.console&&console.log("Scroll updates exceed 10"),g=function(){}):(clearTimeout(a),a=setTimeout(g,300))}}();window.angular&&!function(a){a.module("jQueryScrollbar",[]).provider("jQueryScrollbar",function(){var b=d;return{setOptions:function(c){a.extend(b,c)},$get:function(){return{options:a.copy(b)}}}}).directive("jqueryScrollbar",["jQueryScrollbar","$parse",function(a,b){return{restrict:"AC",link:function(c,d,e){var f=b(e.jqueryScrollbar),g=f(c);d.scrollbar(g||a.options).on("$destroy",function(){d.scrollbar("destroy")})}}}])}(window.angular)});
\ No newline at end of file
diff --git a/data/interfaces/default/js/script.js b/data/interfaces/default/js/script.js
index 2df5daeb..9f2a92eb 100644
--- a/data/interfaces/default/js/script.js
+++ b/data/interfaces/default/js/script.js
@@ -288,23 +288,10 @@ function isPrivateIP(ip_address) {
}
function humanTime(seconds) {
- var d = Math.floor(moment.duration(seconds, 'seconds').asDays());
- var h = Math.floor(moment.duration((seconds % 86400), 'seconds').asHours());
- var m = Math.round(moment.duration(((seconds % 86400) % 3600), 'seconds').asMinutes());
-
- var text = '';
- if (d > 0) {
- text = '' + d + '
day' + ((d > 1) ? 's' : '') + '
'
- + '' + h + '
hr' + ((h > 1) ? 's' : '') + '
'
- + '' + m + '
min' + ((m > 1) ? 's' : '') + '
';
- } else if (h > 0) {
- text = '' + h + '
hr' + ((h > 1) ? 's' : '') + '
'
- + '' + m + '
min' + ((m > 1) ? 's' : '') + '
';
- } else {
- text = '' + m + '
min' + ((m > 1) ? 's' : '') + '
';
+ if (seconds > 0) {
+ return humanDuration(seconds * 1000).replaceAll(/(\d+) (\w+)/g, '$1
$2
')
}
-
- return text
+ return "0
mins
";
}
String.prototype.toProperCase = function () {
@@ -360,7 +347,8 @@ function humanDuration(ms, sig='dhm', units='ms', return_seconds=300000) {
sig = 'dhms'
}
- ms = ms * factors[units];
+ r = factors[sig.slice(-1)];
+ ms = Math.round(ms * factors[units] / r) * r;
h = ms % factors['d'];
d = Math.trunc(ms / factors['d']);
@@ -929,3 +917,50 @@ $('.modal').on('hide.bs.modal', function (e) {
$.fn.hasScrollBar = function() {
return this.get(0).scrollHeight > this.get(0).clientHeight;
}
+
+function paginateScroller(scrollerId, buttonClass) {
+ $(buttonClass).click(function (e) {
+ e.preventDefault();
+ var scroller = $(scrollerId + "-row-scroller");
+ var scrollerParent = scroller.parent();
+ var containerWidth = scrollerParent.width();
+ var scrollCurrent = scrollerParent.scrollLeft();
+ var scrollAmount = $(this).data("id") * parseInt(containerWidth / 175) * 175;
+ var scrollMax = scroller.width() - Math.abs(scrollAmount);
+ var scrollTotal = Math.min(parseInt(scrollCurrent / 175) * 175 + scrollAmount, scrollMax);
+ scrollerParent.animate({ scrollLeft: scrollTotal }, 250);
+ });
+}
+
+function highlightScrollerButton(scrollerId) {
+ var scroller = $(scrollerId + "-row-scroller");
+ var scrollerParent = scroller.parent();
+ var buttonLeft = $(scrollerId + "-page-left");
+ var buttonRight = $(scrollerId + "-page-right");
+
+ var numElems = scroller.find("li").length;
+ scroller.width(numElems * 175);
+ $(buttonLeft).addClass("disabled").blur();
+ if (scroller.width() > scrollerParent.width()) {
+ $(buttonRight).removeClass("disabled");
+ } else {
+ $(buttonRight).addClass("disabled");
+ }
+
+ scrollerParent.scroll(function () {
+ var scrollCurrent = $(this).scrollLeft();
+ var scrollMax = scroller.width() - $(this).width();
+
+ if (scrollCurrent == 0) {
+ $(buttonLeft).addClass("disabled").blur();
+ } else {
+ $(buttonLeft).removeClass("disabled");
+ }
+
+ if (scrollCurrent >= scrollMax) {
+ $(buttonRight).addClass("disabled").blur();
+ } else {
+ $(buttonRight).removeClass("disabled");
+ }
+ });
+}
diff --git a/data/interfaces/default/js/tables/export_table.js b/data/interfaces/default/js/tables/export_table.js
index 44fe4e13..c0f6cf2f 100644
--- a/data/interfaces/default/js/tables/export_table.js
+++ b/data/interfaces/default/js/tables/export_table.js
@@ -100,7 +100,7 @@ export_table_options = {
"createdCell": function (td, cellData, rowData, row, col) {
if (cellData !== '') {
var images = '';
- if (rowData['thumb_level'] || rowData['art_level']) {
+ if (rowData['thumb_level'] || rowData['art_level'] || rowData['logo_level']) {
images = ' + images';
}
$(td).html(cellData + images);
@@ -161,14 +161,14 @@ export_table_options = {
if (cellData === 1 && rowData['exists']) {
var tooltip_title = '';
var icon = '';
- if (rowData['thumb_level'] || rowData['art_level'] || rowData['individual_files']) {
- tooltip_title = 'Zip Archive';
+ if (rowData['thumb_level'] || rowData['art_level'] || rowData['logo_level'] || rowData['individual_files']) {
+ tooltip_title = 'ZIP Archive';
icon = 'fa-file-archive';
} else {
tooltip_title = rowData['file_format'].toUpperCase() + ' File';
icon = 'fa-file-download';
}
- var icon = (rowData['thumb_level'] || rowData['art_level'] || rowData['individual_files']) ? 'fa-file-archive' : 'fa-file-download';
+ var icon = (rowData['thumb_level'] || rowData['art_level'] || rowData['logo_level'] || rowData['individual_files']) ? 'fa-file-archive' : 'fa-file-download';
$(td).html('');
} else if (cellData === 0) {
var percent = Math.min(getPercent(rowData['exported_items'], rowData['total_items']), 99)
diff --git a/data/interfaces/default/js/tables/history_table.js b/data/interfaces/default/js/tables/history_table.js
index deefa067..7f9d578f 100644
--- a/data/interfaces/default/js/tables/history_table.js
+++ b/data/interfaces/default/js/tables/history_table.js
@@ -247,7 +247,7 @@ history_table_options = {
},
{
"targets": [11],
- "data": "duration",
+ "data": "play_duration",
"render": function (data, type, full) {
if (data !== null) {
return Math.round(moment.duration(data, 'seconds').as('minutes')) + ' mins';
@@ -263,13 +263,17 @@ history_table_options = {
"targets": [12],
"data": "watched_status",
"createdCell": function (td, cellData, rowData, row, col) {
+ var circleValue = "";
if (cellData == 1) {
- $(td).html('');
+ circleValue = " circle-full";
+ } else if (cellData == 0.75) {
+ circleValue = " circle-three-quarter";
} else if (cellData == 0.5) {
- $(td).html('');
- } else {
- $(td).html('');
+ circleValue = " circle-half";
+ } else if (cellData == 0.25) {
+ circleValue = " circle-quarter";
}
+ $(td).html('');
},
"searchable": false,
"orderable": false,
@@ -529,7 +533,7 @@ function childTableFormat(rowData) {
'Started | ' +
'Paused | ' +
'Stopped | ' +
- 'Duration | ' +
+ 'Duration | ' +
' | ' +
'' +
'' +
diff --git a/data/interfaces/default/library.html b/data/interfaces/default/library.html
index 59de6497..ba61153d 100644
--- a/data/interfaces/default/library.html
+++ b/data/interfaces/default/library.html
@@ -149,10 +149,10 @@ DOCUMENTATION :: END