diff --git a/.github/ISSUE_TEMPLATE/BUG-REPORT.yml b/.github/ISSUE_TEMPLATE/BUG-REPORT.yml new file mode 100644 index 00000000..34f617df --- /dev/null +++ b/.github/ISSUE_TEMPLATE/BUG-REPORT.yml @@ -0,0 +1,103 @@ +name: Bug Report +description: Please do not use bug reports for support issues. +labels: ['status:awaiting-triage', 'type:bug'] +body: + - type: markdown + attributes: + value: | + **THIS IS NOT THE PLACE TO ASK FOR SUPPORT!** Please use [Discord](https://tautulli.com/discord) for support issues. + - type: textarea + id: description + attributes: + label: Describe the Bug + description: A clear and concise description of the bug. + validations: + required: true + - type: textarea + id: steps + attributes: + label: Steps to Reproduce + description: List each action required in order to reproduce the issue. + placeholder: | + 1. Go to '...' + 2. Click on '...' + 3. Scroll down to '...' + 4. See error + - type: textarea + id: expected + attributes: + label: Expected Behavior + description: A clear and concise description of what you expected to happen. + - type: textarea + id: screenshots + attributes: + label: Screenshots + description: Provide screenshots to help explain your problem. + - type: textarea + id: relevant + attributes: + label: Relevant Settings + description: Include all settings/configuration that are relevant to your issue. For example, Plex Media Server, newsletter, or notification settings. + placeholder: | + - eg. Plex Media Server IP address/port/checkboxes/proxy/etc. + - eg. Notification agent configuration/triggers/conditions/text/delay/grouping/etc. + - eg. Newsletter agent configuration/checkboxes/template/etc. + - Other settings + - type: input + id: version + attributes: + label: Tautulli Version + description: Check Tautulli Settings > Help & Info page. + placeholder: eg. v2.7.5 + validations: + required: true + - type: input + id: branch + attributes: + label: Git Branch + description: Check Tautulli Settings > Help & Info page. + placeholder: eg. master + validations: + required: true + - type: input + id: hash + attributes: + label: Git Commit Hash + description: Check Tautulli Settings > Help & Info page. + placeholder: eg. 2cc5bf812fe05e0666aeaeb37ed550c59816fb4c + validations: + required: true + - type: input + id: platform + attributes: + label: Platform and Version + description: Check Tautulli Settings > Help & Info page. + placeholder: eg. Windows 10 + validations: + required: true + - type: input + id: python + attributes: + label: Python Version + description: Check Tautulli Settings > Help & Info page. + placeholder: eg. 3.8.10 + validations: + required: true + - type: input + id: browser + attributes: + label: Browser and Version + placeholder: eg. Chrome 88 + validations: + required: true + - type: input + id: logs + attributes: + label: Link to Logs + description: Include a link to your **FULL** logs (not just a few lines) on [Gist](http://gist.github.com). + validations: + required: true + - type: markdown + attributes: + value: | + Make sure to close your issue when it's solved! If you found the solution yourself please comment so that others benefit from it. diff --git a/.github/ISSUE_TEMPLATE/FEATURE-REQUEST.yml b/.github/ISSUE_TEMPLATE/FEATURE-REQUEST.yml new file mode 100644 index 00000000..70e2ab33 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/FEATURE-REQUEST.yml @@ -0,0 +1,31 @@ +name: Feature Request +description: Suggest a new feature for Tautulli. +labels: ['status:awaiting-triage', 'type:enhancement'] +body: + - type: markdown + attributes: + value: | + Thanks for taking the time to help improve Tautulli! + - type: textarea + id: problem + attributes: + label: Is your feature request related to a problem? + description: If so, please provide clear and concise description of the problem. + placeholder: eg. I'm always frustrated when '...' + - type: textarea + id: feature + attributes: + label: What is your feature request? + description: A clear and concise description of the feature. + validations: + required: true + - type: textarea + id: workaround + attributes: + label: Are there any workarounds? + description: A clear and concise description of any alternative solutions or features you've considered. + - type: textarea + id: additional + attributes: + label: Additional Context + description: Add any other context or screenshots about the feature request here. diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index 877e934c..00000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,49 +0,0 @@ ---- -name: Bug Report -about: Please do not use bug reports for support issues. -title: '' -labels: 'status:awaiting-triage, type:bug' -assignees: '' - ---- - - - -**Describe the Bug** -A clear and concise description of what the bug is. - -**Steps to Reproduce** -1. Go to '...' -2. Click on '...' -3. Scroll down to '...' -4. See error - -**Expected Behavior** -A clear and concise description of what you expected to happen. - -**Screenshots** -Provide screenshots to help explain your problem. - -**Relevant Settings** -- eg. Plex Media Server IP address/port/checkboxes/proxy/etc. -- eg. Notification agent configuration/triggers/conditions/text/delay/grouping/etc. -- eg. Newsletter agent configuration/checkboxes/template/etc. -- Other settings - -**Tautulli and System Info (see Tautulli settings page)** -- Version: [eg. v2.6.6] -- Git Branch: [eg. master] -- Git Commit Hash: [eg. 2cc5bf812fe05e0666aeaeb37ed550c59816fb4c] -- Platform and Version: [eg. Windows 10] -- Python Version: [e.g. 3.8.8] -- Browser and Version: [e.g. Chrome 88] - -**Link to logs (required)** -Include a link to your **FULL** logs (not just a few lines) on [Gist](http://gist.github.com). - - diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md deleted file mode 100644 index 747aefd6..00000000 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -name: Feature Request -about: Suggest a new feature for Tautulli. -title: '' -labels: 'status:awaiting-triage, type:enhancement' -assignees: '' - ---- - -**Is your feature request related to a problem? Please describe.** -A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] - -**Describe the solution you'd like** -A clear and concise description of what you want to happen. - -**Describe alternatives you've considered** -A clear and concise description of any alternative solutions or features you've considered. - -**Additional context** -Add any other context or screenshots about the feature request here. diff --git a/.github/codeql-config.yml b/.github/codeql-config.yml new file mode 100644 index 00000000..574f9b33 --- /dev/null +++ b/.github/codeql-config.yml @@ -0,0 +1,4 @@ +name: CodeQL Config + +paths-ignore: + - lib diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..1028cc5a --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,15 @@ +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "daily" + target-branch: "nightly" + open-pull-requests-limit: 20 + + - package-ecosystem: "pip" + directory: "/" + schedule: + interval: "daily" + target-branch: "nightly" + open-pull-requests-limit: 20 diff --git a/.github/label-actions.yml b/.github/label-actions.yml index 8c308a0c..ae236e9a 100644 --- a/.github/label-actions.yml +++ b/.github/label-actions.yml @@ -1,8 +1,14 @@ # Configuration for Label Actions - https://github.com/dessant/label-actions -fixed:next-release: +added: comment: > - This issue has been fixed and will be available in the next release of Tautulli. + This feature has been added and will be available in the next release of Tautulli. + This issue will be automatically closed once the update is available. + +fixed: + comment: > + This bug has been fixed and will be available in the next release of Tautulli. + This issue will be automatically closed once the update is available. invalid:duplicate: comment: > @@ -30,6 +36,13 @@ invalid:support: unlock: true invalid:template-incomplete: - comment: > - :wave: @{issue-author}, please edit your issue to complete the template with - all the required info. Thanks. + issues: + comment: > + :wave: @{issue-author}, please edit your issue to complete the template with + all the required info. Your issue will be automatically closed in 5 days if + the template is not completed. Thanks. + prs: + comment: > + :wave: @{issue-author}, please edit your PR to complete the template with + all the required info. Your PR will be automatically closed in 5 days if + the template is not completed. Thanks. diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index ea871c55..63ab24be 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,10 +1,16 @@ ## Description -Please include a summary of the change and which issue is fixed. +Please include a summary of the changes. -Fixes #(issue) +### Screenshot -## Type of change +Include screenshots if the changes are UI-related. + +### Issues Fixed or Closed + +- Fixes #(issue) + +## Type of Change Please delete options that are not relevant. @@ -12,7 +18,7 @@ Please delete options that are not relevant. - [ ] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) -## Checklist: +## Checklist - [ ] My code follows the style guidelines of this project - [ ] I have performed a self-review of my own code diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 00000000..e9c8e05d --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,38 @@ +name: CodeQL + +on: + push: + branches: [nightly] + pull_request: + branches: [nightly] + schedule: + - cron: '05 10 * * 1' + +jobs: + codeql-analysis: + name: CodeQL Analysis + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: ['javascript', 'python'] + + steps: + - name: Checkout Code + uses: actions/checkout@v4 + + - name: Initialize CodeQL + 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@v3 + with: + category: "/language:${{matrix.language}}" diff --git a/.github/workflows/issues-stale.yml b/.github/workflows/issues-stale.yml new file mode 100644 index 00000000..b805e266 --- /dev/null +++ b/.github/workflows/issues-stale.yml @@ -0,0 +1,48 @@ +name: Stale Issues / PRs + +on: + schedule: + - cron: '00 19 * * *' + +jobs: + stale: + name: Check Issues / PRs + runs-on: ubuntu-latest + steps: + - name: Stale + uses: actions/stale@v9 + with: + stale-issue-message: > + This issue is stale because it has been open for 30 days with no activity. + Remove the stale label or comment, otherwise this will be closed in 5 days. + close-issue-message: > + This issue was closed because it has been stalled for 5 days with no activity. + stale-issue-label: 'stale' + exempt-issue-labels: 'added,fixed,type:enhancement,status:awaiting-triage,status:in-progress' + stale-pr-message: > + This PR is stale because it has been open for 30 days with no activity. + Remove the stale label or comment, otherwise this will be closed in 5 days. + close-pr-message: > + This PR was closed because it has been stalled for 5 days with no activity. + stale-pr-label: 'stale' + exempt-pr-labels: 'status:in-progress,status:in-review,dependencies' + days-before-stale: 30 + days-before-close: 5 + + - name: Invalid Template + uses: actions/stale@v9 + with: + stale-issue-message: > + Invalid issues template. + close-issue-message: > + This issue was closed because the the template was not completed after 5 days. + stale-issue-label: 'invalid:template-incomplete' + stale-pr-message: > + Invalid PR template. + close-pr-message: > + This PR was closed because the the template was not completed after 5 days. + stale-pr-label: 'invalid:template-incomplete' + exempt-pr-labels: 'status:in-progress,status:in-review,dependencies' + only-labels: 'invalid:template-incomplete' + days-before-stale: 0 + days-before-close: 5 diff --git a/.github/workflows/issues.yml b/.github/workflows/issues.yml index 6505a7d6..a60987f5 100644 --- a/.github/workflows/issues.yml +++ b/.github/workflows/issues.yml @@ -9,7 +9,7 @@ jobs: name: Label Issues runs-on: ubuntu-latest steps: - - uses: dessant/label-actions@v2 + - name: Label Issues + uses: dessant/label-actions@v4 with: github-token: ${{ github.token }} - process-only: issues diff --git a/.github/workflows/publish-docker.yml b/.github/workflows/publish-docker.yml index 876c46b9..62c3f86c 100644 --- a/.github/workflows/publish-docker.yml +++ b/.github/workflows/publish-docker.yml @@ -1,6 +1,7 @@ name: Publish Docker on: + workflow_dispatch: ~ push: branches: [master, beta, nightly] tags: [v*] @@ -12,41 +13,40 @@ jobs: if: ${{ !contains(github.event.head_commit.message, '[skip ci]') }} steps: - name: Checkout Code - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Prepare id: prepare run: | if [[ $GITHUB_REF == refs/tags/* ]]; then - echo ::set-output name=tag::${GITHUB_REF#refs/tags/} + echo "tag=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT elif [[ $GITHUB_REF == refs/heads/master ]]; then - echo ::set-output name=tag::latest + echo "tag=latest" >> $GITHUB_OUTPUT else - echo ::set-output name=tag::${GITHUB_REF#refs/heads/} + echo "tag=${GITHUB_REF#refs/heads/}" >> $GITHUB_OUTPUT fi if [[ $GITHUB_REF == refs/tags/*-beta ]]; then - echo ::set-output name=branch::beta + echo "branch=beta" >> $GITHUB_OUTPUT elif [[ $GITHUB_REF == refs/tags/* ]]; then - echo ::set-output name=branch::master + echo "branch=master" >> $GITHUB_OUTPUT else - echo ::set-output name=branch::${GITHUB_REF#refs/heads/} + echo "branch=${GITHUB_REF#refs/heads/}" >> $GITHUB_OUTPUT fi - echo ::set-output name=commit::${GITHUB_SHA} - echo ::set-output name=build_date::$(date -u +'%Y-%m-%dT%H:%M:%SZ') - echo ::set-output name=docker_platforms::linux/amd64,linux/arm64/v8,linux/arm/v7,linux/arm/v6 - echo ::set-output name=docker_image::${{ secrets.DOCKER_REPO }}/tautulli + echo "commit=${GITHUB_SHA}" >> $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@v1 + uses: docker/setup-qemu-action@v3 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v1 + uses: docker/setup-buildx-action@v3 id: buildx with: version: latest - name: Cache Docker Layers - uses: actions/cache@v2 + uses: actions/cache@v4 with: path: /tmp/.buildx-cache key: ${{ runner.os }}-buildx-${{ github.sha }} @@ -54,22 +54,28 @@ jobs: ${{ runner.os }}-buildx- - name: Login to DockerHub - uses: docker/login-action@v1 + 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@v1 + 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@v2 + uses: docker/build-push-action@v6 if: success() with: context: . @@ -80,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 @@ -93,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@v1 - - - name: Combine Job Status - id: status - run: | - failures=(neutral, skipped, timed_out, action_required) - if [[ ${array[@]} =~ $WORKFLOW_CONCLUSION ]]; then - echo ::set-output name=status::failure - else - echo ::set-output name=status::$WORKFLOW_CONCLUSION - 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 90c845e2..b4a66960 100644 --- a/.github/workflows/publish-installers.yml +++ b/.github/workflows/publish-installers.yml @@ -1,14 +1,18 @@ name: Publish Installers on: + workflow_dispatch: ~ push: 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 @@ -16,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@v2 + uses: actions/checkout@v4 - name: Set Release Version id: get_version @@ -32,14 +40,14 @@ jobs: if [[ $GITHUB_REF == refs/tags/* ]]; then echo "VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_ENV VERSION_NSIS=${GITHUB_REF#refs/tags/v}.1 - echo ::set-output name=VERSION_NSIS::${VERSION_NSIS/%-beta.1/.0} - echo ::set-output name=VERSION::${GITHUB_REF#refs/tags/v} - echo ::set-output name=RELEASE_VERSION::${GITHUB_REF#refs/tags/} + echo "VERSION_NSIS=${VERSION_NSIS/%-beta.1/.0}" >> $GITHUB_OUTPUT + echo "VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_OUTPUT + echo "RELEASE_VERSION=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT else echo "VERSION=0.0.0" >> $GITHUB_ENV - echo ::set-output name=VERSION_NSIS::0.0.0.0 - echo ::set-output name=VERSION::0.0.0 - echo ::set-output name=RELEASE_VERSION::${GITHUB_SHA::7} + echo "VERSION_NSIS=0.0.0.0" >> $GITHUB_OUTPUT + echo "VERSION=0.0.0" >> $GITHUB_OUTPUT + echo "RELEASE_VERSION=${GITHUB_SHA::7}" >> $GITHUB_OUTPUT fi if [[ $GITHUB_REF == refs/tags/*-beta ]]; then echo "beta" > branch.txt @@ -51,34 +59,29 @@ jobs: echo $GITHUB_SHA > version.txt - name: Set Up Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v5 with: - python-version: 3.8 - - - name: Cache Dependencies - uses: actions/cache@v2 - with: - path: ~\AppData\Local\pip\Cache - key: ${{ runner.os }}-pip-${{ hashFiles(format('package/requirements-{0}.txt', matrix.os)) }} - restore-keys: ${{ runner.os }}-pip- + 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-${{ matrix.os }}.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.4 + 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 @@ -89,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@v2 + 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 @@ -103,63 +124,44 @@ 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@v1 - - name: Checkout Code - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Set Release Version id: get_version run: | - echo ::set-output name=RELEASE_VERSION::${GITHUB_REF#refs/tags/} + echo "RELEASE_VERSION=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT - name: Download Installers - if: env.WORKFLOW_CONCLUSION == 'success' - uses: actions/download-artifact@v2 + if: needs.build-installer.result == 'success' + uses: actions/download-artifact@v4 - name: Get Changelog id: get_changelog run: | - echo ::set-output name=CHANGELOG::"$( sed -n '/^## /{p; :loop n; p; /^## /q; b loop}' CHANGELOG.md \ - | sed '$d' | sed '$d' | sed '$d' | sed ':a;N;$!ba;s/\n/%0A/g' )" + CHANGELOG="$( sed -n '/^## /{p; :loop n; p; /^## /q; b loop}' CHANGELOG.md \ + | sed '$d' | sed '$d' | sed '$d' )" + EOF=$(dd if=/dev/urandom bs=15 count=1 status=none | base64) + echo "CHANGELOG<<$EOF" >> $GITHUB_OUTPUT + echo "$CHANGELOG" >> $GITHUB_OUTPUT + 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.GITHUB_TOKEN }} + 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.GITHUB_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.GITHUB_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@v1 - - - name: Combine Job Status - id: status - run: | - failures=(neutral, skipped, timed_out, action_required) - if [[ ${array[@]} =~ $WORKFLOW_CONCLUSION ]]; then - echo ::set-output name=status::failure - else - echo ::set-output name=status::$WORKFLOW_CONCLUSION - 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 ba43e153..b3898a38 100644 --- a/.github/workflows/publish-snap.yml +++ b/.github/workflows/publish-snap.yml @@ -1,6 +1,7 @@ name: Publish Snap on: + workflow_dispatch: ~ push: branches: [master, beta, nightly] tags: [v*] @@ -14,55 +15,51 @@ jobs: fail-fast: false matrix: architecture: - - i386 - amd64 - arm64 - armhf - - ppc64el - #- s390x # broken at the moment steps: - name: Checkout Code - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Prepare id: prepare run: | git fetch --prune --unshallow --tags if [[ $GITHUB_REF == refs/tags/*-beta || $GITHUB_REF == refs/heads/beta ]]; then - echo ::set-output name=RELEASE::beta + echo "RELEASE=beta" >> $GITHUB_OUTPUT elif [[ $GITHUB_REF == refs/tags/* || $GITHUB_REF == refs/heads/master ]]; then - echo ::set-output name=RELEASE::stable + echo "RELEASE=stable" >> $GITHUB_OUTPUT else - echo ::set-output name=RELEASE::edge + echo "RELEASE=edge" >> $GITHUB_OUTPUT fi - name: Set Up QEMU - uses: docker/setup-qemu-action@v1 - with: - image: tonistiigi/binfmt@sha256:df15403e06a03c2f461c1f7938b171fda34a5849eb63a70e2a2109ed5a778bde + 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@v2 + 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 }} - name: Publish Snap Package uses: snapcore/action-publish@v1 if: startsWith(github.ref, 'refs/tags/') || github.ref == 'refs/heads/nightly' + env: + SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.SNAP_LOGIN }} with: - store_login: ${{ secrets.SNAP_LOGIN }} snap: ${{ steps.build.outputs.snap }} release: ${{ steps.prepare.outputs.RELEASE }} @@ -72,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@v1 - - - name: Combine Job Status - id: status - run: | - failures=(neutral, skipped, timed_out, action_required) - if [[ ${array[@]} =~ $WORKFLOW_CONCLUSION ]]; then - echo ::set-output name=status::failure - else - echo ::set-output name=status::$WORKFLOW_CONCLUSION - 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 91b155bc..ac550fe2 100644 --- a/.github/workflows/pull-requests.yml +++ b/.github/workflows/pull-requests.yml @@ -10,15 +10,14 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout Code - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Comment on Pull Request - uses: mshick/add-pr-comment@v1 + uses: mshick/add-pr-comment@v2 if: github.base_ref != 'nightly' 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 new file mode 100644 index 00000000..efa6cee7 --- /dev/null +++ b/.github/workflows/submit-winget.yml @@ -0,0 +1,44 @@ +name: Submit winget + +on: + workflow_dispatch: ~ + release: + types: [published] + +jobs: + winget: + name: Submit Winget Package + 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" + $gitToken = "${{ secrets.WINGET_TOKEN }}" + + $github = Invoke-RestMethod -uri "https://api.github.com/repos/Tautulli/Tautulli/releases/latest" + $installerUrl = $github | Select -ExpandProperty assets -First 1 | Where-Object -Property name -match "Tautulli-windows-.*-x64.exe" | Select -ExpandProperty browser_download_url + $version = "$($github.tag_name.Trim('v')).1" + + # 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 25777166..1e54132b 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,13 @@ __pycache__ *.ini release.lock version.lock +lib/*.dist-info +lib/*.egg-info +lib/*.pth +lib/*/*.pyd +lib/_distutils_hack +lib/pkg_resources +lib/setuptools logs/* backups/* cache/* @@ -23,6 +30,7 @@ newsletters/* *.mmdb version.txt branch.txt +.TEST # HTTPS Cert/Key # ################## @@ -45,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 a053d982..b349b355 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,673 @@ # 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: + * Fix: Tautulli not starting on FreeBSD jails. + + +## v2.12.1 (2023-03-14) + +* Activity: + * Fix: Stop checking for deprecated sync items sessions. + * Change: Do not show audio language on activity cards for music. +* Other: + * Fix: Tautulli not starting on macOS. + + +## v2.12.0 (2023-03-13) + +* Notifications: + * New: Added support for Telegram group topics. (#1980) + * New: Added anidb_id and anidb_url notification parameters. (#1973) + * New: Added notification triggers for Intro Marker, Commercial Marker, and Credits Marker. + * New: Added various intro, commercial, and credits marker notification parameters. + * New: Allow setting a custom Pushover notification sound. (#2005) + * Change: Notification images are now uploaded directly to Discord without the need for a 3rd party image hosting service. + * Change: Automatically strip whitespace from notification condition values. + * Change: Trigger watched notifications based on the video watched completion behaviour setting. +* Exporter: + * Fix: Unable to run exporter when using the Snap package. (#2007) + * New: Added credits marker, and audio/subtitle settings to export fields. +* UI: + * Fix: Incorrect styling and missing content for collection media info pages. + * New: Added edition details field on movie media info pages. (#1957) (Thanks @herby2212) + * New: Added setting to change the video watched completion behaviour. + * New: Added watch time and user statistics to collection and playlist media info pages. (#1982, #2012) (Thanks @herby2212) + * New: Added history table to collection and playlist media info pages. + * New: Dynamically change watched status in the UI based on video watched completion behaviour setting. + * New: Added hidden setting to override server name. + * Change: Move track artist to a details field instead of in the title on track media info pages. +* API: + * New: Added section_id and user_id parameters to get_home_stats API command. (#1944) + * New: Added marker info to get_metadata API command results. + * New: Added media_type parameter to get_item_watch_time_stats and get_item_user_stats API commands. (#1982) (Thanks @herby2212) + * New: Added last_refreshed timestamp to get_library_media_info API command response. +* Other: + * Change: Migrate analytics to Google Analytics 4. + + +## v2.11.1 (2022-12-22) + +* Activity: + * Fix: Use source language instead of stream language on activity cards. +* Notifications: + * Fix: Blank start time notification parameters causing recently added notifications to fail. (#1940) +* Other: + * Fix: Tautulli failing to start when using python 3.7. + * Fix: Snap install failing to start. (#1941) + * Fix: Update check crashing when git is missing. (#1943) (Thanks @Minituff) + + +## v2.11.0 (2022-12-22) + +* Activity: + * New: Added audio and subtitle language to activity cards. (#1831, #1900) (Thanks @fscorrupt) +* History: + * New: Log subtitle language and subtitle forced to database. (#1826) +* Notifications: + * Fix: Validating condition operators would fail with a blank parameter. + * New: Added start time and stop time notification parameters. (#1931) + * New: Added session_key to LunaSea notification payload. (#1929) (Thanks @JagandeepBrar) +* Newsletters: + * Fix: Allow CSS to support light and dark themes. +* Exporter: + * New: Added editionTitle to movie exporter fields. + * Change: m3u8 export changed to .m3u file extension. File is still encoded using UTF-8. +* UI: + * Fix: Link watch statistics to media page using metadata from history. (#1882) + * New: Show subtitle language and subtitle forced flag in stream data modal. +* Other: + * Fix: Mask more user and metadata fields for guest access. (#1913) + * Change: Disable TLS 1.0 and 1.1 for the webserver. Minimum TLS version is 1.2. (#1870) + * Change: Use system language for requests to Plex Media Server. + + +## v2.10.5 (2022-11-07) + +* Notifications: + * New: Added edition_title notification parameter. (#1838) + * Change: Track notifications link to MusicBrainz track instead of album. +* Newsletters: + * New: Added months time frame for newsletters. (#1876) +* UI: + * Fix: Broken link on library statistic cards. (#1852) + * Fix: Check for IPv6 host when generating QR code for app registration. + * Fix: Missing padding on condition operator dropdown on small screens. +* Other: + * Fix: Launching browser when webserver is bound to IPv6. + * New: Tautulli can be installed via the Windows Package Manager (winget). + * Change: Separate stdout and stderr console logging. (#1874) +* API: + * Fix: API not returning 400 response code. + * New: Added edition_title to get_metadata API response. + * New: Added collections to get_children_metadata API response. + * New: Added user_thumb to get_history API response. + * New: Validate custom notification conditions before saving notification agents. (#1846) + * Change: Fallback to parent_thumb for seasons in get_metadata API response. + + +## v2.10.4 (2022-09-05) + +* Activity: + * New: Added tooltip for quality profile on activity cards. +* Notifications: + * New: Added "does not begin with" and "does not end with" condition operators. +* UI: + * Fix: Album count showing 0 on library statistics. + * Fix: Library statistics not showing up for libraries without any history. + + +## v2.10.3 (2022-08-09) + +* Notifications: + * New: Added JSON support for MQTT notifications. (#1763) + * New: Added show year notification parameter. +* Exporter: + * New: Added guids to artist, album, and track metadata export fields. + * New: Added languageTag to stream media info export fields. +* UI: + * Fix: Long channel identifier overflowing activity card. (#1802) + * Change: Use the last played item's artwork for library statistics cards. +* Other: + * Fix: Username log filter causing database to lock up. (#1705) + * Change: Username log filter only applies to usernames longer than 3 characters. (#1806) +* API: + * New: Added parent_year and grandparent_year to get_metadata_details API command. + * New: Added last played metadata to top_libraries and top_users in get_home_stats API command. + * New: Allow fallback to another PMS image in pms_image_proxy API command. + + +## v2.10.2 (2022-07-03) + +* Activity: + * Fix: Incorrect audio stream info shown on the activity card when playing a secondary audio track. +* UI: + * Fix: Usernames not showing on the home statistics cards. + * Fix: Do not save a user's friendly name if it is the same as the username. + * Change: Update library icons to the latest Plex style. + + +## v2.10.1 (2022-06-01) + +* Notifications: + * New: Added support for MusicBrainz (mbid://) guids in notification parameters without MusicBrainz lookup enabled. Requires Plex Media Server 1.27.0 or newer with refreshed Plex Music agent metadata. +* Mobile App: + * Fix: OneSignal validation failing when registering a device. +* API: + * New: Added grandparent_guids and parent_guids to get_metadata API command. + * Change: Updated continent in get_geoip_lookup API command. + * Change: Removed server_token from from get_users API command. + * Change: shared_libraries changed to a list instead of a string for get_users API command. + + +## v2.10.0 (2022-05-23) + +* Activity: + * Fix: Detection of Dolby Vision missing for PMS 1.26.1. +* Notifications: + * Fix: Parsing of filename notification parameter incorrect for Windows PMS. +* Exporter: + * New: Added additional theme and label export fields. +* UI: + * Fix: Slow loading of collections and playlists tables. + * Change: Update default user thumbnail image to match Plex Web. +* API: + * Change: Values for get_users_table and get_libraries_table return an integer instead of "Checked". + + +## v2.9.7 (2022-04-11) + +* UI: + * Fix: Managed user missing the username in the Users table. + + +## v2.9.6 (2022-04-10) + +* Activity: + * New: Improved display of dynamic range on the activity cards. (Thanks @herby2212) +* Notifications: + * Change: Make include summary option apply to all media types for Discord and Slack notifications. +* UI: + * Fix: Validating Plex login in the setup wizard. (#1697) + * New: Added hidden username, email, and full name columns to users table. +* Other: + * Fix: Apply pms_timeout setting to websocket connection. + * Fix: Importing of Plex username instead of the full name. (#1710) + + +## v2.9.5 (2022-03-26) + +* Note: + * Updated Snap packages are currently unavailable due to an upstream issue. +* Activity: + * Change: Improve calculation for transcode progress bar percentage on the activity cards. +* History: + * Fix: Live TV history filter not working. (#1691) +* Newsletter: + * Fix: Newsletter not showing different album types. (#1559) +* UI: + * Fix: Display season summary on the media info page if available with a fallback to show summary. (#1657) + * Change: Colour active filter buttons to improve contrast. (#1663) +* API: + * New: Added transcode offset keys to get_activity command. +* Other: + * Fix: Reschedule backup task after changing backup interval. (#1662) + * Fix: Dynamic anonymous redirect setting not being enabled by default after the setup wizard. + * Fix: Usernames with special characters not being filtered in the logs. + + +## v2.9.4 (2022-02-12) + +* UI: + * Fix: Setup wizard appearing when restarting after saving settings. +* Other: + * Fix: Stop Tautulli from starting multiple instances on Windows after a clean reinstall. Check the startup items in Windows Task Manager if it is still occurring. + + +## v2.9.3 (2022-02-09) + +* UI: + * Fix: Setup wizard looping. +* Other: + * Fix: Logger username masking preventing Tautulli from starting on new installs. + + +## v2.9.2 (2022-02-08) + +* Notification: + * New: Added support for additional Telegram HTML tags. + * Removed: Revert Telegram defaulting to MarkdownV2 and only support HTML. (#1635) +* Other: + * Fix: The Local user being masked in the logs. + + +## v2.9.1 (2022-02-07) + +* Other: + * Fix: Incorrect changelog version number and date. + + +## v2.9.0 (2022-02-07) + +* Notification: + * New: Added track disc number notification parameter. + * Change: Default Telegram messages to MarkdownV2 when HTML is disabled. (#1635) +* Exporter: + * Fix: Images not being included in export zip file download. +* UI: + * Fix: Favicon missing from the newsletter authentication page. + * Fix: IPv6 details not being shown in IP address modal. (#1629) + * Fix: PWA not respecting device rotation settings. (#1633) + * New: Added intermediary login page to the Plex XML shortcuts. + * New: Added setting to mask usernames in logs (enabled by default). + * New: Added location, secure connection, and Plex Relay details to IP address modal. + * Change: Remove Plex token from the settings page. + * Change: Increase verifying server timeout to 30 seconds. +* API: + * New: Added get_tautulli_info API command. + * New: Added location, secure, and relayed to get_history API response. + * Change: Null pms_token and jwt_token in the response of the get_settings API command. (#1616) +* Other: + * Fix: Better validation of config when saving settings. + * Fix: Correct section_id and prevent rating_key collisions when updating metadata. (#1640) + * Change: Proxy Plex token check and Plex downloads json through the Tautulli server. + * Change: Remove tokens from downloaded database and config files. + * Change: Do not import pms_token or jwt_secret when importing a config file. + + +## v2.8.1 (2022-01-04) + +* API: + * New: Added grouping and query_days parameters to the get_item_watch_time_stats API command. + * New: Added grouping parameter to the get_item_user_stats API command. + * New: Added total_time to the get_library_user_stats, get_user_player_stats, and get_item_user_stats API command responses. + * Removed: media_type parameter no longer required for the get_item_watch_time_stats, and get_item_user_stats API commands. The media type is determined automatically. +* Other: + * Fix: Clean .pyc files automatically after updating. + * New: Allow Snap package to access /media and /mnt locations. Refer to the FAQ for instructions on how to enable access. + + +## v2.8.0 (2021-12-15) + +* History: + * Fix: Live TV history filter not working correctly when combined with other filters. + * Fix: Direct Stream history filter not remembering the state when reloading the page. + * Fix: History table not loading when no filters are selected. + * New: Added watch time and user stats to media info pages. (Thanks @herby2212) (#1417, #1471) +* Notifications: + * New: Added Microsoft Teams notification agent. (#1514) + * New: Added Gotify notification agent. (#1584) + * New: Add warning message that passwords are not copied when duplicating a notification or newsletter agent. (#1540) +* Newsletters: + * Fix: Different album types not shown on newsletter. (#1559) +* Exporter: + * New: Added album formats, subformats, and sonic analysis export fields. +* UI: + * Fix: Docker config volume message overlapping modal windows. (#1567) + * Fix: Different album types not shown on artist media info page. + * New: Added show more/less toggle for summaries on media info pages. (#1546) + * Change: Do not save datatable page or search states when reloading the page. (#1532) + * Change: Improve the Plex log reader. +* API: + * New: Added before and after parameters to the get_history API command. +* Other: + * Fix: Updated Python dependencies. (#1499) + * Fix: Some websocket connections not respecting the verify SSL setting. (Thanks @nmaggioni) (#1541) + * New: Support for Python 3.10. (#1522) + * New: Added dynamic anonymous redirect service setting. (#1526) + + +## v2.7.7 (2021-10-14) + +* Notifications: + * Fix: Colons and exclamation marks being replaced outside of expressions. + * New: Added LunaSea notification agent. Note: Requires a future LunaSea app update to function. +* Newsletters: + * Fix: Star rating not showing on newsletter with the new Plex metadata agents. (#1511) +* UI: + * Fix: Sorting of mobile devices table with uppercase and lowercase device names. + * Fix: Various dropdown menus with centered text to left-aligned text. +* Other: + * Fix: Plex.tv account token not changing when fetching a new token. + * New: Added check and warning message for missing Docker container volume mount. + + +## v2.7.6 (2021-08-31) + +* Notifications: + * Fix: Unable to parse colons (:) and exclamation marks (!) in notification text eval strings. +* Exporter: + * Fix: Unable to export playlists and collections from a library. (#1484) + * New: Added new episode export fields. +* Mobile App: + * Fix: Unable to scan QR code with dark mode enabled. + * New: Tautulli Remote App is out of beta for iOS and can be downloaded in the App Store. +* Other: + * New: Update PlexAPI to 4.6.3. + * New: Added popup alert message for Windows and macOS when Tautulli fails to start. + + +## v2.7.5 (2021-07-15) + +* History: + * Fix: Guest users were unable to view history. + * Fix: Most Active Library statistics was counting deleted libraries. +* Newsletters: + * Fix: Incorrect padding on the newsletter configuration modal. +* Mobile App: + * New: Tautulli Remote App is out of beta on Android. The iOS app is available for beta testing on TestFlight. +* API: + * New: Added mobile device platform and version to device registration. +* Other: + * Fix: Unable to remove authentication. + * Change: Improve API key and device token security on Python 3. + * Remove: Basic Authentication setting. + + +## v2.7.4 (2021-06-19) + +* Activity: + * Fix: Incorrect quality profile shown on the activity card. +* Notifications: + * New: Added ability to evaluate Python expressions in notification parameters. + * New: Added tilde (~) to represent blank notification condition values. +* Exporter: + * Fix: Blank fields missing from csv and json exports. + * New: Added some new exporter fields. +* Graphs: + * Fix: Play counts and durations not matching the homepage statistics. +* UI: + * New: Show search bar in the collapsed menu on the mobile layout. (#1446) + * Change: Do not show seconds for total played duration on Users and Libraries tables. +* Other: + * Fix: Check the Tautulli data folder is writable on startup. (#1441) + * New: Update PlexAPI to 4.6.1. + * Change: Always hash HTTP password in config file. + + +## v2.7.3 (2021-05-22) + +* Activity: + * Fix: Incorrect quality profile being shown when transcoding to a higher bitrate. +* Notifications: + * New: Added notification parameters for duration values in seconds. (#1434) + * New: Added setting to allow repeat Tautulli update notifications. By default the Tautulli update notification will only notify once. + * New: Added setting to allow repeat Plex Media Server update notifications. By default the Plex Media Server update notification will only notify once. + * New: Added setting to configure the Tautulli update check interval. +* UI: + * Fix: Prevent accidentally closing modals when dragging the mouse outside the window. + * Fix: Recently added queue modal not loading. (#1429) + * New: Show collections tab in music libraries. (#1421) + * New: Added method to logout of active Tautulli sessions from the login logs. +* API: + * Fix: Update edit_user and edit_library doc strings for required parameters. (#1432) +* Other: + * New: Added advanced hidden setting for CherryPy thread pool size. (Thanks @psaab #1425) + + +## v2.7.2 (2021-04-24) + +* UI: + * New: Show smart collections in the library collections tab. + + ## v2.7.1 (2021-04-22) * Notifications: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a415fb54..46a644e5 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -9,12 +9,12 @@ All pull requests should be based on the `nightly` branch, to minimize cross mer ### Python Code #### Compatibility -The code should work with Python 3.6+. 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. #### Code conventions -Although Tautulli did not adapt a code convention in the past, we try to follow the [PEP8](http://legacy.python.org/dev/peps/pep-0008/) conventions for future code. A short summary to remind you (copied from http://wiki.ros.org/PyStyleGuide): +Although Tautulli did not adopt a code convention in the past, we try to follow [PEP8](http://legacy.python.org/dev/peps/pep-0008/) conventions for future code. A short summary to remind you (copied from http://wiki.ros.org/PyStyleGuide): * 4 space indentation * 80 characters per line diff --git a/Dockerfile b/Dockerfile index f82e7551..8d8c324b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM tautulli/tautulli-baseimage:python3 +FROM ghcr.io/tautulli/tautulli-baseimage:python3 LABEL maintainer="Tautulli" @@ -9,18 +9,20 @@ ENV TAUTULLI_DOCKER=True ENV TZ=UTC WORKDIR /app - +COPY . /app RUN \ groupadd -g 1000 tautulli && \ useradd -u 1000 -g 1000 tautulli && \ echo ${BRANCH} > /app/branch.txt && \ echo ${COMMIT} > /app/version.txt -COPY . /app +RUN \ + mkdir /config && \ + touch /config/DOCKER +VOLUME /config CMD [ "python", "Tautulli.py", "--datadir", "/config" ] ENTRYPOINT [ "./start.sh" ] -VOLUME /config 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 93301401..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.6-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 @@ -57,24 +57,24 @@ Read the [Installation Guides][Installation] for instructions on how to install [badge-release-nightly-last-commit]: https://img.shields.io/github/last-commit/Tautulli/Tautulli/nightly?style=flat-square&color=blue [badge-release-nightly-commits]: https://img.shields.io/github/commits-since/Tautulli/Tautulli/latest/nightly?style=flat-square&color=blue [badge-docker-master]: https://img.shields.io/badge/docker-latest-blue?style=flat-square -[badge-docker-master-ci]: https://img.shields.io/github/workflow/status/Tautulli/Tautulli/Publish%20Docker/master?style=flat-square +[badge-docker-master-ci]: https://img.shields.io/github/actions/workflow/status/Tautulli/Tautulli/.github/workflows/publish-docker.yml?style=flat-square&branch=master [badge-docker-beta]: https://img.shields.io/badge/docker-beta-blue?style=flat-square -[badge-docker-beta-ci]: https://img.shields.io/github/workflow/status/Tautulli/Tautulli/Publish%20Docker/beta?style=flat-square +[badge-docker-beta-ci]: https://img.shields.io/github/actions/workflow/status/Tautulli/Tautulli/.github/workflows/publish-docker.yml?style=flat-square&branch=beta [badge-docker-nightly]: https://img.shields.io/badge/docker-nightly-blue?style=flat-square -[badge-docker-nightly-ci]: https://img.shields.io/github/workflow/status/Tautulli/Tautulli/Publish%20Docker/nightly?style=flat-square +[badge-docker-nightly-ci]: https://img.shields.io/github/actions/workflow/status/Tautulli/Tautulli/.github/workflows/publish-docker.yml?style=flat-square&branch=nightly [badge-snap-master]: https://img.shields.io/badge/snap-stable-blue?style=flat-square -[badge-snap-master-ci]: https://img.shields.io/github/workflow/status/Tautulli/Tautulli/Publish%20Snap/master?style=flat-square +[badge-snap-master-ci]: https://img.shields.io/github/actions/workflow/status/Tautulli/Tautulli/.github/workflows/publish-snap.yml?style=flat-square&branch=master [badge-snap-beta]: https://img.shields.io/badge/snap-beta-blue?style=flat-square -[badge-snap-beta-ci]: https://img.shields.io/github/workflow/status/Tautulli/Tautulli/Publish%20Snap/beta?style=flat-square +[badge-snap-beta-ci]: https://img.shields.io/github/actions/workflow/status/Tautulli/Tautulli/.github/workflows/publish-snap.yml?style=flat-square&branch=beta [badge-snap-nightly]: https://img.shields.io/badge/snap-edge-blue?style=flat-square -[badge-snap-nightly-ci]: https://img.shields.io/github/workflow/status/Tautulli/Tautulli/Publish%20Snap/nightly?style=flat-square +[badge-snap-nightly-ci]: https://img.shields.io/github/actions/workflow/status/Tautulli/Tautulli/.github/workflows/publish-snap.yml?style=flat-square&branch=nightly [badge-installer-master-win]: https://img.shields.io/github/v/release/Tautulli/Tautulli?label=windows&style=flat-square [badge-installer-master-macos]: https://img.shields.io/github/v/release/Tautulli/Tautulli?label=macos&style=flat-square -[badge-installer-master-ci]: https://img.shields.io/github/workflow/status/Tautulli/Tautulli/Publish%20Installers/master?style=flat-square +[badge-installer-master-ci]: https://img.shields.io/github/actions/workflow/status/Tautulli/Tautulli/.github/workflows/publish-installers.yml?style=flat-square&branch=master [badge-installer-beta-win]: https://img.shields.io/github/v/release/Tautulli/Tautulli?label=windows&include_prereleases&style=flat-square [badge-installer-beta-macos]: https://img.shields.io/github/v/release/Tautulli/Tautulli?label=macos&include_prereleases&style=flat-square -[badge-installer-beta-ci]: https://img.shields.io/github/workflow/status/Tautulli/Tautulli/Publish%20Installers/beta?style=flat-square -[badge-installer-nightly-ci]: https://img.shields.io/github/workflow/status/Tautulli/Tautulli/Publish%20Installers/nightly?style=flat-square +[badge-installer-beta-ci]: https://img.shields.io/github/actions/workflow/status/Tautulli/Tautulli/.github/workflows/publish-installers.yml?style=flat-square&branch=beta +[badge-installer-nightly-ci]: https://img.shields.io/github/actions/workflow/status/Tautulli/Tautulli/.github/workflows/publish-installers.yml?style=flat-square&branch=nightly ## Support @@ -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 069f771b..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) @@ -125,6 +143,7 @@ def main(): if helpers.bool_true(os.getenv('TAUTULLI_DOCKER', False)): plexpy.DOCKER = True + plexpy.DOCKER_MOUNT = not os.path.isfile('/config/DOCKER') if helpers.bool_true(os.getenv('TAUTULLI_SNAP', False)): plexpy.SNAP = True @@ -185,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 @@ -212,9 +231,18 @@ def main(): 'Could not create data directory: ' + plexpy.DATA_DIR + '. Exiting....') # Make sure the DATA_DIR is writeable - if not os.access(plexpy.DATA_DIR, os.W_OK): + test_file = os.path.join(plexpy.DATA_DIR, '.TEST') + try: + with open(test_file, 'w'): + pass + except IOError: raise SystemExit( 'Cannot write to the data directory: ' + plexpy.DATA_DIR + '. Exiting...') + finally: + try: + os.remove(test_file) + except OSError: + pass # Put the database in the DATA_DIR plexpy.DB_FILE = os.path.join(plexpy.DATA_DIR, database.FILENAME) @@ -236,23 +264,13 @@ def main(): # Start the background threads plexpy.start() - # Force the http port if neccessary + # Force the http port if necessary if args.port: plexpy.HTTP_PORT = args.port logger.info('Using forced web server port: %i', plexpy.HTTP_PORT) else: plexpy.HTTP_PORT = int(plexpy.CONFIG.HTTP_PORT) - # Check if pyOpenSSL is installed. It is required for certificate generation - # and for CherryPy. - if plexpy.CONFIG.ENABLE_HTTPS: - try: - import OpenSSL - except ImportError: - logger.warn("The pyOpenSSL module is missing. Install this " - "module to enable HTTPS. HTTPS will be disabled.") - plexpy.CONFIG.ENABLE_HTTPS = False - # Try to start the server. Will exit here is address is already in use. webstart.start() diff --git a/contrib/clean_pyc.bat b/contrib/clean_pyc.bat new file mode 100755 index 00000000..e4436dea --- /dev/null +++ b/contrib/clean_pyc.bat @@ -0,0 +1,11 @@ +@echo off +:: Display information +echo This script will remove *.pyc files. These files are generated by Python, but they can cause conflicts after an upgrade. It's safe to remove them, because they will be regenerated. +echo Press enter to continue, or CTRL + C to quit. +pause + +cd ..\ +:: Remove *.pyc files +del /S *.pyc +:: Remove __pycache__ folders +for /d /r . %%d in (__pycache__) do @if exist "%%d" rd /s /q "%%d" \ No newline at end of file diff --git a/contrib/clean_pyc.sh b/contrib/clean_pyc.sh index 9283fb59..ea555159 100755 --- a/contrib/clean_pyc.sh +++ b/contrib/clean_pyc.sh @@ -5,5 +5,7 @@ echo "This script will remove *.pyc files. These files are generated by Python, echo "Press enter to continue, or CTRL + C to quit." read -# Remove the *.pyc -find "`dirname $0`/.." -type f -name "*.pyc" -exec rm -rf {} \; \ No newline at end of file +# Remove *.pyc files +find "`dirname $0`/.." -type f -name "*.pyc" -exec rm -rf {} \; +# Remove __pycache__ folders +find "`dirname $0`/.." -type d -name "__pycache__" -exec rm -rf {} \; \ No newline at end of file diff --git a/data/interfaces/default/app_import.html b/data/interfaces/default/app_import.html index a96009a7..74af4313 100644 --- a/data/interfaces/default/app_import.html +++ b/data/interfaces/default/app_import.html @@ -32,7 +32,7 @@ -
Upload the ${app} database file you wish to import.
+Upload the ${app} database file you wish to import (max file size is 1GB).