Compare commits

..

36 Commits

Author SHA1 Message Date
JonnyWong16
a29bc7f4f9 v2.0.22-beta 2018-03-09 17:58:40 -08:00
JonnyWong16
288f4c5f7f Fix expanding selectize box 2018-03-09 15:50:53 -08:00
JonnyWong16
a6bf78ed56 Check is schedulers running before shutdown 2018-03-08 18:32:47 -08:00
JonnyWong16
8dbb05931e Fix library refresh when missing library 2018-03-08 18:23:12 -08:00
JonnyWong16
ac8a712ff0 Fix refreshing activity after losing connection 2018-03-06 20:01:11 -08:00
JonnyWong16
39406c25c3 Add retry and expire for Pushover priority 2 2018-03-06 09:57:06 -08:00
JonnyWong16
48d7c2c54c Fix photo library count and media info table 2018-03-05 09:49:48 -08:00
JonnyWong16
0217188274 Fix update check 2018-03-04 22:49:32 -08:00
JonnyWong16
fd762e71de Fix cherrypy sending wrong Content-Type header for svg 2018-03-04 22:32:26 -08:00
JonnyWong16
4d5c3b6df0 v2.0.21-beta 2018-03-04 14:51:27 -08:00
JonnyWong16
7df54e4d1b Replace Flattr with Patreon 2018-03-04 14:25:38 -08:00
JonnyWong16
5d085de9d3 Rename logger name 2018-03-04 12:24:25 -08:00
JonnyWong16
a8a4299086 Add execute permission to PlexPy.py 2018-03-04 12:17:09 -08:00
JonnyWong16
86f0e8425c Add execute permission to Tautulli.py 2018-03-04 12:15:05 -08:00
JonnyWong16
d2e879be4a Add PlexPy.py file to run Tautulli.py 2018-03-04 12:01:31 -08:00
JonnyWong16
544114fffe Rename css files to tautulli 2018-03-04 11:40:38 -08:00
JonnyWong16
3b3e207b11 Rename log files to tautulli 2018-03-04 11:38:31 -08:00
JonnyWong16
84aad638ac Rename database backup to tautulli 2018-03-04 11:28:35 -08:00
JonnyWong16
2bb691966e Rename default notifier settings to tautulli 2018-03-04 11:18:04 -08:00
JonnyWong16
8f5e788270 Rename plexpy.db to tautulli.db 2018-03-04 11:17:35 -08:00
JonnyWong16
7c43ea2f46 Rename PlexPy.py to Tautulli.py 2018-03-04 11:17:11 -08:00
JonnyWong16
8146e1e3cf Capitalize Tautulli folder in init scripts 2018-03-04 10:28:28 -08:00
JonnyWong16
51b1ff6d4a Rename variables in Ubuntu script 2018-03-04 10:17:16 -08:00
JonnyWong16
403e8dfbea Update all init scripts to Tautulli 2018-03-04 09:44:02 -08:00
JonnyWong16
9d08717c83 Fix missing country in whois lookup causing error 2018-03-02 15:39:05 -08:00
JonnyWong16
66167d5960 Remove word "allowed" 2018-03-02 10:24:28 -08:00
JonnyWong16
624863d826 Hide number input spinners on Firefox 2018-03-02 08:48:31 -08:00
JonnyWong16
d4b3810fbc Reduce number input width 2018-03-01 19:34:52 -08:00
JonnyWong16
6056e1d3b9 Hide arrows on number inputes 2018-03-01 13:08:43 -08:00
JonnyWong16
1a293d525f Update database session on state change 2018-02-28 13:34:21 -08:00
JonnyWong16
b87eb68bdd Identify if a stream is using Plex Relay 2018-02-27 20:03:31 -08:00
JonnyWong16
8620546d07 Move import from a082109 2018-02-27 15:17:35 -08:00
JonnyWong16
a082109045 Don't ping for activity if websocket is not connected 2018-02-27 15:02:17 -08:00
JonnyWong16
559a9b393e Catch failure to send analytics event 2018-02-24 15:08:58 -08:00
JonnyWong16
ae41b22e59 Forgot one version number in 754fd24 2018-02-24 14:51:19 -08:00
JonnyWong16
754fd24421 Refactor some code 2018-02-24 10:09:02 -08:00
52 changed files with 763 additions and 643 deletions

1
API.md
View File

@@ -401,6 +401,7 @@ Returns:
"quality_profile": "Original", "quality_profile": "Original",
"rating": "7.8", "rating": "7.8",
"rating_key": "153037", "rating_key": "153037",
"relay": 0,
"section_id": "2", "section_id": "2",
"session_id": "helf15l3rxgw01xxe0jf3l3d", "session_id": "helf15l3rxgw01xxe0jf3l3d",
"session_key": "27", "session_key": "27",

View File

@@ -1,5 +1,32 @@
# Changelog # Changelog
## v2.0.22-beta (2018-03-09)
* Notifications:
* Fix: Pushover notifications failing with priority 2 is set.
* Fix: Expanding selectize box for some notification agent settings.
* Other:
* Fix: Update check failing when an update is available.
* Fix: Item count incorrect for photo libraries.
## v2.0.21-beta (2018-03-04)
* Monitoring:
* New: Identify if a stream is using Plex Relay.
* Change: Don't ping the Plex server if the websocket is disconnected.
* Notifications:
* Fix: Pause/resume state not being sent correctly in some instances.
* Other:
* New: Add Patreon donation method.
* Fix: Catch failure to send analytics.
* Fix: IP address connection lookup error when the country is missing.
* Change: Updated all init scripts to Tautulli.
* Change: Move database to tautulli.db.
* Change: Move logs to tautulli.log.
* Change: Move startup file to Tautulli.py.
## v2.0.20-beta (2018-02-24) ## v2.0.20-beta (2018-02-24)
* Notifications: * Notifications:

235
PlexPy.py
View File

@@ -21,239 +21,8 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with Tautulli. If not, see <http://www.gnu.org/licenses/>. # along with Tautulli. If not, see <http://www.gnu.org/licenses/>.
import os from Tautulli import main
import sys
# Ensure lib added to path, before any other imports # Call main() from Tautulli.py
sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'lib/'))
import argparse
import locale
import signal
import time
import plexpy
from plexpy import config, database, logger, webstart
# Register signals, such as CTRL + C
signal.signal(signal.SIGINT, plexpy.sig_handler)
signal.signal(signal.SIGTERM, plexpy.sig_handler)
def main():
"""
Tautulli application entry point. Parses arguments, setups encoding and
initializes the application.
"""
# Fixed paths to Tautulli
if hasattr(sys, 'frozen'):
plexpy.FULL_PATH = os.path.abspath(sys.executable)
else:
plexpy.FULL_PATH = os.path.abspath(__file__)
plexpy.PROG_DIR = os.path.dirname(plexpy.FULL_PATH)
plexpy.ARGS = sys.argv[1:]
# From sickbeard
plexpy.SYS_PLATFORM = sys.platform
plexpy.SYS_ENCODING = None
try:
locale.setlocale(locale.LC_ALL, "")
plexpy.SYS_LANGUAGE, plexpy.SYS_ENCODING = locale.getdefaultlocale()
except (locale.Error, IOError):
pass
# for OSes that are poorly configured I'll just force UTF-8
if not plexpy.SYS_ENCODING or plexpy.SYS_ENCODING in ('ANSI_X3.4-1968', 'US-ASCII', 'ASCII'):
plexpy.SYS_ENCODING = 'UTF-8'
# Set up and gather command line arguments
parser = argparse.ArgumentParser(
description='A Python based monitoring and tracking tool for Plex Media Server.')
parser.add_argument(
'-v', '--verbose', action='store_true', help='Increase console logging verbosity')
parser.add_argument(
'-q', '--quiet', action='store_true', help='Turn off console logging')
parser.add_argument(
'-d', '--daemon', action='store_true', help='Run as a daemon')
parser.add_argument(
'-p', '--port', type=int, help='Force Tautulli to run on a specified port')
parser.add_argument(
'--dev', action='store_true', help='Start Tautulli in the development environment')
parser.add_argument(
'--datadir', help='Specify a directory where to store your data files')
parser.add_argument(
'--config', help='Specify a config file to use')
parser.add_argument(
'--nolaunch', action='store_true', help='Prevent browser from launching on startup')
parser.add_argument(
'--pidfile', help='Create a pid file (only relevant when running as a daemon)')
parser.add_argument(
'--nofork', action='store_true', help='Start Tautulli as a service, do not fork when restarting')
args = parser.parse_args()
if args.verbose:
plexpy.VERBOSE = True
if args.quiet:
plexpy.QUIET = True
# Do an intial setup of the logger.
logger.initLogger(console=not plexpy.QUIET, log_dir=False,
verbose=plexpy.VERBOSE)
if args.dev:
plexpy.DEV = True
logger.debug(u"Tautulli is running in the dev environment.")
if args.daemon:
if sys.platform == 'win32':
sys.stderr.write(
"Daemonizing not supported under Windows, starting normally\n")
else:
plexpy.DAEMON = True
plexpy.QUIET = True
if args.nofork:
plexpy.NOFORK = True
logger.info("Tautulli is running as a service, it will not fork when restarted.")
if args.pidfile:
plexpy.PIDFILE = str(args.pidfile)
# If the pidfile already exists, plexpy may still be running, so
# exit
if os.path.exists(plexpy.PIDFILE):
try:
with open(plexpy.PIDFILE, 'r') as fp:
pid = int(fp.read())
os.kill(pid, 0)
except IOError as e:
raise SystemExit("Unable to read PID file: %s", e)
except OSError:
logger.warn("PID file '%s' already exists, but PID %d is " \
"not running. Ignoring PID file." %
(plexpy.PIDFILE, pid))
else:
# The pidfile exists and points to a live PID. plexpy may
# still be running, so exit.
raise SystemExit("PID file '%s' already exists. Exiting." %
plexpy.PIDFILE)
# The pidfile is only useful in daemon mode, make sure we can write the
# file properly
if plexpy.DAEMON:
plexpy.CREATEPID = True
try:
with open(plexpy.PIDFILE, 'w') as fp:
fp.write("pid\n")
except IOError as e:
raise SystemExit("Unable to write PID file: %s", e)
else:
logger.warn("Not running in daemon mode. PID file creation " \
"disabled.")
# Determine which data directory and config file to use
if args.datadir:
plexpy.DATA_DIR = args.datadir
else:
plexpy.DATA_DIR = plexpy.PROG_DIR
if args.config:
config_file = args.config
else:
config_file = os.path.join(plexpy.DATA_DIR, config.FILENAME)
# Try to create the DATA_DIR if it doesn't exist
if not os.path.exists(plexpy.DATA_DIR):
try:
os.makedirs(plexpy.DATA_DIR)
except OSError:
raise SystemExit(
'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):
raise SystemExit(
'Cannot write to the data directory: ' + plexpy.DATA_DIR + '. Exiting...')
# Put the database in the DATA_DIR
plexpy.DB_FILE = os.path.join(plexpy.DATA_DIR, database.FILENAME)
if plexpy.DAEMON:
plexpy.daemonize()
# Read config and start logging
plexpy.initialize(config_file)
# Start the background threads
plexpy.start()
# Force the http port if neccessary
if args.port:
http_port = args.port
logger.info('Using forced web server port: %i', http_port)
else:
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.
web_config = {
'http_port': http_port,
'http_host': plexpy.CONFIG.HTTP_HOST,
'http_root': plexpy.CONFIG.HTTP_ROOT,
'http_environment': plexpy.CONFIG.HTTP_ENVIRONMENT,
'http_proxy': plexpy.CONFIG.HTTP_PROXY,
'enable_https': plexpy.CONFIG.ENABLE_HTTPS,
'https_cert': plexpy.CONFIG.HTTPS_CERT,
'https_cert_chain': plexpy.CONFIG.HTTPS_CERT_CHAIN,
'https_key': plexpy.CONFIG.HTTPS_KEY,
'http_username': plexpy.CONFIG.HTTP_USERNAME,
'http_password': plexpy.CONFIG.HTTP_PASSWORD,
'http_basic_auth': plexpy.CONFIG.HTTP_BASIC_AUTH
}
webstart.initialize(web_config)
# Open webbrowser
if plexpy.CONFIG.LAUNCH_BROWSER and not args.nolaunch and not plexpy.DEV:
plexpy.launch_browser(plexpy.CONFIG.HTTP_HOST, http_port,
plexpy.CONFIG.HTTP_ROOT)
# Wait endlessy for a signal to happen
while True:
if not plexpy.SIGNAL:
try:
time.sleep(1)
except KeyboardInterrupt:
plexpy.SIGNAL = 'shutdown'
else:
logger.info('Received signal: %s', plexpy.SIGNAL)
if plexpy.SIGNAL == 'shutdown':
plexpy.shutdown()
elif plexpy.SIGNAL == 'restart':
plexpy.shutdown(restart=True)
elif plexpy.SIGNAL == 'checkout':
plexpy.shutdown(restart=True, checkout=True)
else:
plexpy.shutdown(restart=True, update=True)
plexpy.SIGNAL = None
# Call main()
if __name__ == "__main__": if __name__ == "__main__":
main() main()

263
Tautulli.py Executable file
View File

@@ -0,0 +1,263 @@
#!/bin/sh
''''which python >/dev/null 2>&1 && exec python "$0" "$@" # '''
''''which python2 >/dev/null 2>&1 && exec python2 "$0" "$@" # '''
''''which python2.7 >/dev/null 2>&1 && exec python2.7 "$0" "$@" # '''
''''exec echo "Error: Python not found!" # '''
# -*- coding: utf-8 -*-
# This file is part of Tautulli.
#
# Tautulli is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Tautulli is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Tautulli. If not, see <http://www.gnu.org/licenses/>.
import os
import sys
# Ensure lib added to path, before any other imports
sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'lib/'))
import argparse
import locale
import signal
import time
import plexpy
from plexpy import config, database, logger, webstart
# Register signals, such as CTRL + C
signal.signal(signal.SIGINT, plexpy.sig_handler)
signal.signal(signal.SIGTERM, plexpy.sig_handler)
def main():
"""
Tautulli application entry point. Parses arguments, setups encoding and
initializes the application.
"""
# Fixed paths to Tautulli
if hasattr(sys, 'frozen'):
plexpy.FULL_PATH = os.path.abspath(sys.executable)
else:
plexpy.FULL_PATH = os.path.abspath(__file__)
plexpy.PROG_DIR = os.path.dirname(plexpy.FULL_PATH)
plexpy.ARGS = sys.argv[1:]
# From sickbeard
plexpy.SYS_PLATFORM = sys.platform
plexpy.SYS_ENCODING = None
try:
locale.setlocale(locale.LC_ALL, "")
plexpy.SYS_LANGUAGE, plexpy.SYS_ENCODING = locale.getdefaultlocale()
except (locale.Error, IOError):
pass
# for OSes that are poorly configured I'll just force UTF-8
if not plexpy.SYS_ENCODING or plexpy.SYS_ENCODING in ('ANSI_X3.4-1968', 'US-ASCII', 'ASCII'):
plexpy.SYS_ENCODING = 'UTF-8'
# Set up and gather command line arguments
parser = argparse.ArgumentParser(
description='A Python based monitoring and tracking tool for Plex Media Server.')
parser.add_argument(
'-v', '--verbose', action='store_true', help='Increase console logging verbosity')
parser.add_argument(
'-q', '--quiet', action='store_true', help='Turn off console logging')
parser.add_argument(
'-d', '--daemon', action='store_true', help='Run as a daemon')
parser.add_argument(
'-p', '--port', type=int, help='Force Tautulli to run on a specified port')
parser.add_argument(
'--dev', action='store_true', help='Start Tautulli in the development environment')
parser.add_argument(
'--datadir', help='Specify a directory where to store your data files')
parser.add_argument(
'--config', help='Specify a config file to use')
parser.add_argument(
'--nolaunch', action='store_true', help='Prevent browser from launching on startup')
parser.add_argument(
'--pidfile', help='Create a pid file (only relevant when running as a daemon)')
parser.add_argument(
'--nofork', action='store_true', help='Start Tautulli as a service, do not fork when restarting')
args = parser.parse_args()
if args.verbose:
plexpy.VERBOSE = True
if args.quiet:
plexpy.QUIET = True
# Do an intial setup of the logger.
logger.initLogger(console=not plexpy.QUIET, log_dir=False,
verbose=plexpy.VERBOSE)
if args.dev:
plexpy.DEV = True
logger.debug(u"Tautulli is running in the dev environment.")
if args.daemon:
if sys.platform == 'win32':
sys.stderr.write(
"Daemonizing not supported under Windows, starting normally\n")
else:
plexpy.DAEMON = True
plexpy.QUIET = True
if args.nofork:
plexpy.NOFORK = True
logger.info("Tautulli is running as a service, it will not fork when restarted.")
if args.pidfile:
plexpy.PIDFILE = str(args.pidfile)
# If the pidfile already exists, plexpy may still be running, so
# exit
if os.path.exists(plexpy.PIDFILE):
try:
with open(plexpy.PIDFILE, 'r') as fp:
pid = int(fp.read())
os.kill(pid, 0)
except IOError as e:
raise SystemExit("Unable to read PID file: %s", e)
except OSError:
logger.warn("PID file '%s' already exists, but PID %d is " \
"not running. Ignoring PID file." %
(plexpy.PIDFILE, pid))
else:
# The pidfile exists and points to a live PID. plexpy may
# still be running, so exit.
raise SystemExit("PID file '%s' already exists. Exiting." %
plexpy.PIDFILE)
# The pidfile is only useful in daemon mode, make sure we can write the
# file properly
if plexpy.DAEMON:
plexpy.CREATEPID = True
try:
with open(plexpy.PIDFILE, 'w') as fp:
fp.write("pid\n")
except IOError as e:
raise SystemExit("Unable to write PID file: %s", e)
else:
logger.warn("Not running in daemon mode. PID file creation " \
"disabled.")
# Determine which data directory and config file to use
if args.datadir:
plexpy.DATA_DIR = args.datadir
else:
plexpy.DATA_DIR = plexpy.PROG_DIR
if args.config:
config_file = args.config
else:
config_file = os.path.join(plexpy.DATA_DIR, config.FILENAME)
# Try to create the DATA_DIR if it doesn't exist
if not os.path.exists(plexpy.DATA_DIR):
try:
os.makedirs(plexpy.DATA_DIR)
except OSError:
raise SystemExit(
'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):
raise SystemExit(
'Cannot write to the data directory: ' + plexpy.DATA_DIR + '. Exiting...')
# Put the database in the DATA_DIR
plexpy.DB_FILE = os.path.join(plexpy.DATA_DIR, database.FILENAME)
# Move 'plexpy.db' to 'tautulli.db'
if os.path.isfile(os.path.join(plexpy.DATA_DIR, 'plexpy.db')):
os.rename(os.path.join(plexpy.DATA_DIR, 'plexpy.db'), plexpy.DB_FILE)
if plexpy.DAEMON:
plexpy.daemonize()
# Read config and start logging
plexpy.initialize(config_file)
# Start the background threads
plexpy.start()
# Force the http port if neccessary
if args.port:
http_port = args.port
logger.info('Using forced web server port: %i', http_port)
else:
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.
web_config = {
'http_port': http_port,
'http_host': plexpy.CONFIG.HTTP_HOST,
'http_root': plexpy.CONFIG.HTTP_ROOT,
'http_environment': plexpy.CONFIG.HTTP_ENVIRONMENT,
'http_proxy': plexpy.CONFIG.HTTP_PROXY,
'enable_https': plexpy.CONFIG.ENABLE_HTTPS,
'https_cert': plexpy.CONFIG.HTTPS_CERT,
'https_cert_chain': plexpy.CONFIG.HTTPS_CERT_CHAIN,
'https_key': plexpy.CONFIG.HTTPS_KEY,
'http_username': plexpy.CONFIG.HTTP_USERNAME,
'http_password': plexpy.CONFIG.HTTP_PASSWORD,
'http_basic_auth': plexpy.CONFIG.HTTP_BASIC_AUTH
}
webstart.initialize(web_config)
# Open webbrowser
if plexpy.CONFIG.LAUNCH_BROWSER and not args.nolaunch and not plexpy.DEV:
plexpy.launch_browser(plexpy.CONFIG.HTTP_HOST, http_port,
plexpy.CONFIG.HTTP_ROOT)
# Wait endlessy for a signal to happen
while True:
if not plexpy.SIGNAL:
try:
time.sleep(1)
except KeyboardInterrupt:
plexpy.SIGNAL = 'shutdown'
else:
logger.info('Received signal: %s', plexpy.SIGNAL)
if plexpy.SIGNAL == 'shutdown':
plexpy.shutdown()
elif plexpy.SIGNAL == 'restart':
plexpy.shutdown(restart=True)
elif plexpy.SIGNAL == 'checkout':
plexpy.shutdown(restart=True, checkout=True)
else:
plexpy.shutdown(restart=True, update=True)
plexpy.SIGNAL = None
# Call main()
if __name__ == "__main__":
main()

View File

@@ -15,7 +15,7 @@
<meta name="author" content=""> <meta name="author" content="">
<link href="${http_root}css/bootstrap3/bootstrap.css" rel="stylesheet"> <link href="${http_root}css/bootstrap3/bootstrap.css" rel="stylesheet">
<link href="${http_root}css/pnotify.custom.min.css" rel="stylesheet" /> <link href="${http_root}css/pnotify.custom.min.css" rel="stylesheet" />
<link href="${http_root}css/plexpy.css${cache_param}" rel="stylesheet"> <link href="${http_root}css/tautulli.css${cache_param}" rel="stylesheet">
<link href="${http_root}css/opensans.min.css" rel="stylesheet"> <link href="${http_root}css/opensans.min.css" rel="stylesheet">
<link href="${http_root}css/font-awesome.min.css" rel="stylesheet"> <link href="${http_root}css/font-awesome.min.css" rel="stylesheet">
${next.headIncludes()} ${next.headIncludes()}
@@ -47,7 +47,7 @@
You are running an unknown version of Tautulli.<br /> You are running an unknown version of Tautulli.<br />
<a href="update">Update</a> or <a href="#" id="updateDismiss">Dismiss</a> <a href="update">Update</a> or <a href="#" id="updateDismiss">Dismiss</a>
</div> </div>
% elif plexpy.CONFIG.CHECK_GITHUB and plexpy.COMMITS_BEHIND > 0 and plexpy.common.BRANCH in ('master', 'beta') and plexpy.common.VERSION_NUMBER != plexpy.LATEST_RELEASE: % elif plexpy.CONFIG.CHECK_GITHUB and plexpy.COMMITS_BEHIND > 0 and plexpy.common.BRANCH in ('master', 'beta') and plexpy.common.RELEASE != plexpy.LATEST_RELEASE:
<div id="updatebar" style="display: none;"> <div id="updatebar" style="display: none;">
A <a href="${anon_url('https://github.com/%s/%s/releases/tag/%s' % (plexpy.CONFIG.GIT_USER, plexpy.CONFIG.GIT_REPO, plexpy.LATEST_RELEASE))}" target="_blank"> A <a href="${anon_url('https://github.com/%s/%s/releases/tag/%s' % (plexpy.CONFIG.GIT_USER, plexpy.CONFIG.GIT_REPO, plexpy.LATEST_RELEASE))}" target="_blank">
new release (${plexpy.LATEST_RELEASE})</a> of Tautulli is available!<br /> new release (${plexpy.LATEST_RELEASE})</a> of Tautulli is available!<br />
@@ -227,15 +227,23 @@ ${next.modalIncludes()}
</div> </div>
</div> </div>
<ul id="donation_type" class="nav nav-pills" role="tablist" style="display: flex; justify-content: center; margin: 10px 0;"> <ul id="donation_type" class="nav nav-pills" role="tablist" style="display: flex; justify-content: center; margin: 10px 0;">
<li class="active"><a href="#paypal-donation" role="tab" data-toggle="tab">PayPal</a></li> <li class="active"><a href="#patreon-donation" role="tab" data-toggle="tab">Patreon</a></li>
<li><a href="#flattr-donation" role="tab" data-toggle="tab">Flattr</a></li> <li><a href="#paypal-donation" role="tab" data-toggle="tab">PayPal</a></li>
<li><a href="#crypto-donation" role="tab" data-toggle="tab" class="crypto-donation" data-coin="bitcoin" data-name="Bitcoin" data-address="3FdfJAyNWU15Sf11U9FTgPHuP1hPz32eEN">Bitcoin</a></li> <li><a href="#crypto-donation" role="tab" data-toggle="tab" class="crypto-donation" data-coin="bitcoin" data-name="Bitcoin" data-address="3FdfJAyNWU15Sf11U9FTgPHuP1hPz32eEN">Bitcoin</a></li>
<li><a href="#crypto-donation" role="tab" data-toggle="tab" class="crypto-donation" data-coin="bitcoincash" data-name="Bitcoin Cash" data-address="1H2atabxAQGaFAWYQEiLkXKSnK9CZZvt2n">Bitcoin Cash</a></li> <li><a href="#crypto-donation" role="tab" data-toggle="tab" class="crypto-donation" data-coin="bitcoincash" data-name="Bitcoin Cash" data-address="1H2atabxAQGaFAWYQEiLkXKSnK9CZZvt2n">Bitcoin Cash</a></li>
<li><a href="#crypto-donation" role="tab" data-toggle="tab" class="crypto-donation" data-coin="ethereum" data-name="Ethereum" data-address="0x77ae4c2b8de1a1ccfa93553db39971da58c873d3">Ethereum</a></li> <li><a href="#crypto-donation" role="tab" data-toggle="tab" class="crypto-donation" data-coin="ethereum" data-name="Ethereum" data-address="0x77ae4c2b8de1a1ccfa93553db39971da58c873d3">Ethereum</a></li>
<li><a href="#crypto-donation" role="tab" data-toggle="tab" class="crypto-donation" data-coin="litecoin" data-name="Litecoin" data-address="LWpPmUqQYHBhMV83XSCsHzPmKLhJt6r57J">Litecoin</a></li> <li><a href="#crypto-donation" role="tab" data-toggle="tab" class="crypto-donation" data-coin="litecoin" data-name="Litecoin" data-address="LWpPmUqQYHBhMV83XSCsHzPmKLhJt6r57J">Litecoin</a></li>
</ul> </ul>
<div class="tab-content"> <div class="tab-content">
<div role="tabpanel" class="tab-pane active" id="paypal-donation" style="text-align: center"> <div role="tabpanel" class="tab-pane active" id="patreon-donation" style="text-align: center">
<p>
Click the button below to continue to Patreon.
</p>
<a href="${anon_url('https://www.patreon.com/bePatron?u=10078609')}" target="_blank">
<img src="images/become_a_patron_button.png" alt="Become a Patron" height="40">
</a>
</div>
<div role="tabpanel" class="tab-pane" id="paypal-donation" style="text-align: center">
<p> <p>
Click the button below to continue to PayPal. Click the button below to continue to PayPal.
</p> </p>
@@ -243,14 +251,6 @@ ${next.modalIncludes()}
<img src="images/gold-rect-paypal-34px.png" alt="PayPal"> <img src="images/gold-rect-paypal-34px.png" alt="PayPal">
</a> </a>
</div> </div>
<div role="tabpanel" class="tab-pane" id="flattr-donation" style="text-align: center">
<p>
Click the button below to continue to Flattr.
</p>
<a href="${anon_url('https://flattr.com/submit/auto?user_id=JonnyWong16&url=https://github.com/%s/%s&title=Tautulli&language=en_GB&tags=github&category=software' % (plexpy.CONFIG.GIT_USER, plexpy.CONFIG.GIT_REPO))}" target="_blank">
<img src="images/flattr-badge-large.png" alt="Flattr">
</a>
</div>
<div role="tabpanel" class="tab-pane" id="crypto-donation"> <div role="tabpanel" class="tab-pane" id="crypto-donation">
<label>QR Code</label> <label>QR Code</label>
<pre id="crypto_qr_code" style="text-align: center"></pre> <pre id="crypto_qr_code" style="text-align: center"></pre>

View File

@@ -66,7 +66,6 @@ div.form-control .selectize-input {
color: #fff; color: #fff;
border: 0px solid #444; border: 0px solid #444;
background: #555; background: #555;
height: 32px;
padding: 6px 12px; padding: 6px 12px;
background-color: #555; background-color: #555;
border-radius: 3px; border-radius: 3px;
@@ -92,6 +91,7 @@ div.form-control .selectize-input {
border-top-left-radius: 3px; border-top-left-radius: 3px;
border-bottom-left-radius: 3px; border-bottom-left-radius: 3px;
min-height: 32px !important; min-height: 32px !important;
height: 32px !important;
} }
.input-group .selectize-control.form-control.selectize-pms-ip .selectize-input > div { .input-group .selectize-control.form-control.selectize-pms-ip .selectize-input > div {
max-width: 450px; max-width: 450px;
@@ -1419,7 +1419,7 @@ a .dashboard-activity-metadata-user-thumb:hover {
} }
.dashboard-stats-info-item .sub-count { .dashboard-stats-info-item .sub-count {
height: 100%; height: 100%;
margin-left: 10px; margin-left: 5px;
color: #f9be03; color: #f9be03;
font-size: 12px; font-size: 12px;
text-align: right; text-align: right;
@@ -1430,7 +1430,7 @@ a .dashboard-activity-metadata-user-thumb:hover {
} }
.dashboard-stats-info-item .sub-divider { .dashboard-stats-info-item .sub-divider {
height: 100%; height: 100%;
margin-left: 10px; margin-left: 5px;
color: #aaa; color: #aaa;
font-size: 12px; font-size: 12px;
text-align: left; text-align: left;
@@ -2372,21 +2372,6 @@ a .library-user-instance-box:hover {
#watched-stats-days-selection label { #watched-stats-days-selection label {
margin-bottom: 0; margin-bottom: 0;
} }
#watched-stats-days {
margin: 0;
width: 75px;
height: 34px;
}
#watched-stats-count {
margin: 0;
width: 75px;
height: 34px;
}
#recently-added-count {
margin: 0;
width: 75px;
height: 34px;
}
.home-padded-header { .home-padded-header {
margin: 25px 0; margin: 25px 0;
height: 34px; height: 34px;
@@ -3435,22 +3420,10 @@ pre::-webkit-scrollbar-thumb {
.notification-params tr:nth-child(even) td { .notification-params tr:nth-child(even) td {
background-color: rgba(255,255,255,0.010); background-color: rgba(255,255,255,0.010);
} }
#days-selection label { #days-selection label,
margin-bottom: 0;
}
#graph-days {
margin: 0;
width: 75px;
height: 34px;
}
#months-selection label { #months-selection label {
margin-bottom: 0; margin-bottom: 0;
} }
#graph-months {
margin: 0;
width: 75px;
height: 34px;
}
.card-sortable { .card-sortable {
height: 36px; height: 36px;
padding: 0 20px 0 0; padding: 0 20px 0 0;
@@ -3967,3 +3940,14 @@ a:hover .overlay-refresh-image:hover {
.stream-info tr:nth-child(even) td { .stream-info tr:nth-child(even) td {
background-color: rgba(255,255,255,0.010); background-color: rgba(255,255,255,0.010);
} }
.number-input {
margin: 0 !important;
width: 55px !important;
height: 34px !important;
-moz-appearance: textfield;
}
.number-input::-webkit-inner-spin-button,
.number-input::-webkit-outer-spin-button {
-webkit-appearance: none;
margin: 0;
}

View File

@@ -279,6 +279,9 @@ DOCUMENTATION :: END
<span id="location-${sk}">${data['location'].upper()}</span>: <span id="location-${sk}">${data['location'].upper()}</span>:
% if data['ip_address'] != 'N/A': % if data['ip_address'] != 'N/A':
<span class="ip-container"><span class="ip-address">${data['ip_address']}</span></span> <span class="ip-container"><span class="ip-address">${data['ip_address']}</span></span>
% if data['relay']:
<span data-toggle="tooltip" title="Plex Relay"><i class="fa fa-exclamation-circle"></i></span>
% else:
<a href="#" class="external_ip-modal" data-toggle="modal" data-target="#ip-info-modal" data-ip="${data['ip_address']}"> <a href="#" class="external_ip-modal" data-toggle="modal" data-target="#ip-info-modal" data-ip="${data['ip_address']}">
<span id="external_ip-${sk}" class="external-ip-tooltip" data-toggle="tooltip" title="Lookup External IP" style="display: none;"><i class="fa fa-map-marker"></i></span> <span id="external_ip-${sk}" class="external-ip-tooltip" data-toggle="tooltip" title="Lookup External IP" style="display: none;"><i class="fa fa-map-marker"></i></span>
</a> </a>
@@ -289,6 +292,7 @@ DOCUMENTATION :: END
$("#external_ip-${sk}").show(); $("#external_ip-${sk}").show();
}); });
</script> </script>
% endif
% else: % else:
N/A N/A
% endif % endif

View File

@@ -2,7 +2,7 @@
<%def name="headIncludes()"> <%def name="headIncludes()">
<link rel="stylesheet" href="${http_root}css/dataTables.bootstrap.css"> <link rel="stylesheet" href="${http_root}css/dataTables.bootstrap.css">
<link rel="stylesheet" href="${http_root}css/plexpy-dataTables.css"> <link rel="stylesheet" href="${http_root}css/tautulli-dataTables.css">
</%def> </%def>
<%def name="body()"> <%def name="body()">
@@ -39,12 +39,12 @@
</div> </div>
<div class="input-group pull-right" style="width: 1px;" id="days-selection"> <div class="input-group pull-right" style="width: 1px;" id="days-selection">
<span class="input-group-addon btn-dark inactive">Last</span> <span class="input-group-addon btn-dark inactive">Last</span>
<input type="number" class="form-control" name="graph-days" id="graph-days" value="${config['graph_days']}" min="1" data-default="7" data-toggle="tooltip" title="Min: 1 day" /> <input type="number" class="form-control number-input" name="graph-days" id="graph-days" value="${config['graph_days']}" min="1" data-default="7" data-toggle="tooltip" title="Min: 1 day" />
<span class="input-group-addon btn-dark inactive">days</span> <span class="input-group-addon btn-dark inactive">days</span>
</div> </div>
<div class="input-group pull-right" style="width: 1px;" id="months-selection"> <div class="input-group pull-right" style="width: 1px;" id="months-selection">
<span class="input-group-addon btn-dark inactive">Last</span> <span class="input-group-addon btn-dark inactive">Last</span>
<input type="number" class="form-control" name="graph-months" id="graph-months" value="${config['graph_months']}" min="1" data-default="12" data-toggle="tooltip" title="Min: 1 month" /> <input type="number" class="form-control number-input" name="graph-months" id="graph-months" value="${config['graph_months']}" min="1" data-default="12" data-toggle="tooltip" title="Min: 1 month" />
<span class="input-group-addon btn-dark inactive">months</span> <span class="input-group-addon btn-dark inactive">months</span>
</div> </div>
</div> </div>

View File

@@ -3,7 +3,7 @@
<%def name="headIncludes()"> <%def name="headIncludes()">
<link rel="stylesheet" href="${http_root}css/dataTables.bootstrap.css"> <link rel="stylesheet" href="${http_root}css/dataTables.bootstrap.css">
<link rel="stylesheet" href="${http_root}css/dataTables.colVis.css"> <link rel="stylesheet" href="${http_root}css/dataTables.colVis.css">
<link rel="stylesheet" href="${http_root}css/plexpy-dataTables.css"> <link rel="stylesheet" href="${http_root}css/tautulli-dataTables.css">
</%def> </%def>
<%def name="body()"> <%def name="body()">

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

View File

@@ -22,7 +22,16 @@
</h3> </h3>
</div> </div>
<div id="currentActivity"> <div id="currentActivity">
<% from plexpy import PLEX_SERVER_UP %>
% if PLEX_SERVER_UP:
<div class="text-muted" id="dashboard-checking-activity"><i class="fa fa-refresh fa-spin"></i> Checking for activity...</div> <div class="text-muted" id="dashboard-checking-activity"><i class="fa fa-refresh fa-spin"></i> Checking for activity...</div>
% else:
<div id="dashboard-no-activity" class="text-muted">There was an error communicating with your Plex Server.
% if _session['user_group'] == 'admin':
Check the <a href="logs">logs</a> and verify your server connection in the <a href="settings#tab_tabs-plex_media_server">settings</a>.
% endif
</div>
% endif
</div> </div>
</div> </div>
</div> </div>
@@ -51,7 +60,7 @@
</div> </div>
<div class="input-group pull-left" style="width: 1px; margin-right: 3px" id="watched-stats-days-selection"> <div class="input-group pull-left" style="width: 1px; margin-right: 3px" id="watched-stats-days-selection">
<span class="input-group-addon btn-dark inactive">Last</span> <span class="input-group-addon btn-dark inactive">Last</span>
<input type="number" class="form-control" name="watched-stats-days" id="watched-stats-days" value="${config['home_stats_length']}" min="1" data-default="30" data-toggle="tooltip" title="Min: 1 day" /> <input type="number" class="form-control number-input" name="watched-stats-days" id="watched-stats-days" value="${config['home_stats_length']}" min="1" data-default="30" data-toggle="tooltip" title="Min: 1 day" />
<span class="input-group-addon btn-dark inactive">days</span> <span class="input-group-addon btn-dark inactive">days</span>
</div> </div>
</div> </div>
@@ -114,7 +123,7 @@
</label> </label>
</div> </div>
<div class="input-group pull-left" style="width: 1px;" id="recently-added-count-selection"> <div class="input-group pull-left" style="width: 1px;" id="recently-added-count-selection">
<input type="number" class="form-control" name="recently-added-count" id="recently-added-count" value="${config['home_stats_recently_added_count']}" min="1" max="100" data-default="50" data-toggle="tooltip" title="Min: 1 item<br>Max: 100 items" /> <input type="number" class="form-control number-input" name="recently-added-count" id="recently-added-count" value="${config['home_stats_recently_added_count']}" min="1" max="100" data-default="50" data-toggle="tooltip" title="Min: 1 item<br>Max: 100 items" />
<span class="input-group-addon btn-dark inactive">items</span> <span class="input-group-addon btn-dark inactive">items</span>
</div> </div>
</div> </div>
@@ -137,13 +146,13 @@
<%def name="modalIncludes()"> <%def name="modalIncludes()">
% if _session['user_group'] == 'admin' and config['update_show_changelog']: % if _session['user_group'] == 'admin' and config['update_show_changelog']:
<% from plexpy.common import VERSION_NUMBER %> <% from plexpy.common import RELEASE %>
<div id="changelog-modal" class="modal fade wide" tabindex="-1" role="dialog" aria-labelledby="changelog-modal"> <div id="changelog-modal" class="modal fade wide" tabindex="-1" role="dialog" aria-labelledby="changelog-modal">
<div class="modal-dialog" role="document"> <div class="modal-dialog" role="document">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true"><i class="fa fa-remove"></i></button> <button type="button" class="close" data-dismiss="modal" aria-hidden="true"><i class="fa fa-remove"></i></button>
<h4 class="modal-title">Tautulli Updated to <strong>${VERSION_NUMBER}</strong></h4> <h4 class="modal-title">Tautulli Updated to <strong>${RELEASE}</strong></h4>
</div> </div>
<div class="modal-body"> <div class="modal-body">
</div> </div>
@@ -241,9 +250,10 @@
}); });
} }
}); });
}; }
</script> </script>
% if 'current_activity' in config['home_sections']: <% from plexpy import PLEX_SERVER_UP %>
% if 'current_activity' in config['home_sections'] and PLEX_SERVER_UP:
<script> <script>
var defaultHandler = { var defaultHandler = {
get: function(target, name) { get: function(target, name) {
@@ -266,6 +276,7 @@
async: true, async: true,
error: function (xhr, status, error) { error: function (xhr, status, error) {
console.log(status + ': ' + error); console.log(status + ': ' + error);
activity_ready = true;
}, },
complete: function (xhr, status) { complete: function (xhr, status) {
$('#dashboard-checking-activity').remove(); $('#dashboard-checking-activity').remove();
@@ -280,9 +291,9 @@
if (!(current_activity)) { if (!(current_activity)) {
% if _session['user_group'] == 'admin': % if _session['user_group'] == 'admin':
var msg_settings = ' Verify your server connection in the <a href="settings#tab_tabs-plex_media_server">settings</a>.'; var msg_settings = ' Check the <a href="logs">logs</a> and verify your server connection in the <a href="settings#tab_tabs-plex_media_server">settings</a>.';
% else: % else:
var msg_settings = '' var msg_settings = '';
% endif % endif
$('#currentActivityHeader').hide(); $('#currentActivityHeader').hide();
$('#currentActivity').html('<div id="dashboard-no-activity" class="text-muted">There was an error communicating with your Plex Server.' + msg_settings + '</div>'); $('#currentActivity').html('<div id="dashboard-no-activity" class="text-muted">There was an error communicating with your Plex Server.' + msg_settings + '</div>');

View File

@@ -64,7 +64,7 @@ DOCUMENTATION :: END
<%def name="headIncludes()"> <%def name="headIncludes()">
<link rel="stylesheet" href="${http_root}css/dataTables.bootstrap.css"> <link rel="stylesheet" href="${http_root}css/dataTables.bootstrap.css">
<link rel="stylesheet" href="${http_root}css/dataTables.colVis.css"> <link rel="stylesheet" href="${http_root}css/dataTables.colVis.css">
<link rel="stylesheet" href="${http_root}css/plexpy-dataTables.css"> <link rel="stylesheet" href="${http_root}css/tautulli-dataTables.css">
</%def> </%def>
<%def name="body()"> <%def name="body()">

View File

@@ -54,7 +54,7 @@ media_info_table_options = {
} else if (rowData['media_type'] === 'album') { } else if (rowData['media_type'] === 'album') {
expand_details = '<span class="expand-media-info-tooltip" data-toggle="tooltip" title="Show Tracks"><i class="fa fa-plus-circle fa-fw"></i></span>'; expand_details = '<span class="expand-media-info-tooltip" data-toggle="tooltip" title="Show Tracks"><i class="fa fa-plus-circle fa-fw"></i></span>';
$(td).html('<div><a href="#"><div style="float: left;">' + expand_details + '&nbsp;' + date + '</div></a></div>'); $(td).html('<div><a href="#"><div style="float: left;">' + expand_details + '&nbsp;' + date + '</div></a></div>');
} else if (rowData['media_type'] === 'photo' && rowData['parent_rating_key'] == '') { } else if (rowData['media_type'] === 'photo_album') {
expand_details = '<span class="expand-media-info-tooltip" data-toggle="tooltip" title="Show Photos"><i class="fa fa-plus-circle fa-fw"></i></span>'; expand_details = '<span class="expand-media-info-tooltip" data-toggle="tooltip" title="Show Photos"><i class="fa fa-plus-circle fa-fw"></i></span>';
$(td).html('<div><a href="#"><div style="float: left;">' + expand_details + '&nbsp;' + date + '</div></a></div>'); $(td).html('<div><a href="#"><div style="float: left;">' + expand_details + '&nbsp;' + date + '</div></a></div>');
} else { } else {
@@ -77,32 +77,44 @@ media_info_table_options = {
if (rowData['media_type'] === 'movie') { if (rowData['media_type'] === 'movie') {
if (rowData['year']) { parent_info = ' (' + rowData['year'] + ')'; } if (rowData['year']) { parent_info = ' (' + rowData['year'] + ')'; }
media_type = '<span class="media-type-tooltip" data-toggle="tooltip" title="Movie"><i class="fa fa-film fa-fw"></i></span>'; media_type = '<span class="media-type-tooltip" data-toggle="tooltip" title="Movie"><i class="fa fa-film fa-fw"></i></span>';
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="pms_image_proxy?img=' + rowData['thumb'] + '&width=300&height=450&fallback=poster" data-height="120" data-width="80">' + rowData['title'] + parent_info + '</span>' thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="pms_image_proxy?img=' + rowData['thumb'] + '&width=300&height=450&fallback=poster" data-height="120" data-width="80">' + rowData['title'] + parent_info + '</span>';
$(td).html('<div class="history-title"><a href="info?rating_key=' + rowData['rating_key'] + '"><div style="float: left;">' + media_type + '&nbsp;' + thumb_popover + '</div></a></div>'); $(td).html('<div class="history-title"><a href="info?rating_key=' + rowData['rating_key'] + '"><div style="float: left;">' + media_type + '&nbsp;' + thumb_popover + '</div></a></div>');
} else if (rowData['media_type'] === 'show') { } else if (rowData['media_type'] === 'show') {
media_type = '<span class="media-type-tooltip" data-toggle="tooltip" title="TV Show"><i class="fa fa-television fa-fw"></i></span>'; media_type = '<span class="media-type-tooltip" data-toggle="tooltip" title="TV Show"><i class="fa fa-television fa-fw"></i></span>';
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="pms_image_proxy?img=' + rowData['thumb'] + '&width=300&height=450&fallback=poster" data-height="120" data-width="80">' + rowData['title'] + '</span>' thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="pms_image_proxy?img=' + rowData['thumb'] + '&width=300&height=450&fallback=poster" data-height="120" data-width="80">' + rowData['title'] + '</span>';
$(td).html('<div class="history-title"><a href="info?rating_key=' + rowData['rating_key'] + '"><div style="float: left;">' + media_type + '&nbsp;' + thumb_popover + '</div></a></div>'); $(td).html('<div class="history-title"><a href="info?rating_key=' + rowData['rating_key'] + '"><div style="float: left;">' + media_type + '&nbsp;' + thumb_popover + '</div></a></div>');
} else if (rowData['media_type'] === 'season') { } else if (rowData['media_type'] === 'season') {
media_type = '<span class="media-type-tooltip" data-toggle="tooltip" title="Season"><i class="fa fa-television fa-fw"></i></span>'; media_type = '<span class="media-type-tooltip" data-toggle="tooltip" title="Season"><i class="fa fa-television fa-fw"></i></span>';
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="pms_image_proxy?img=' + rowData['thumb'] + '&width=300&height=450&fallback=poster" data-height="120" data-width="80">' + rowData['title'] + '</span>' thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="pms_image_proxy?img=' + rowData['thumb'] + '&width=300&height=450&fallback=poster" data-height="120" data-width="80">' + rowData['title'] + '</span>';
$(td).html('<div class="history-title"><a href="info?rating_key=' + rowData['rating_key'] + '"><div style="float: left; padding-left: 15px;">' + media_type + '&nbsp;' + thumb_popover + '</div></a></div>'); $(td).html('<div class="history-title"><a href="info?rating_key=' + rowData['rating_key'] + '"><div style="float: left; padding-left: 15px;">' + media_type + '&nbsp;' + thumb_popover + '</div></a></div>');
} else if (rowData['media_type'] === 'episode') { } else if (rowData['media_type'] === 'episode') {
media_type = '<span class="media-type-tooltip" data-toggle="tooltip" title="Episode"><i class="fa fa-television fa-fw"></i></span>'; media_type = '<span class="media-type-tooltip" data-toggle="tooltip" title="Episode"><i class="fa fa-television fa-fw"></i></span>';
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="pms_image_proxy?img=' + rowData['thumb'] + '&width=300&height=450&fallback=art" data-height="80" data-width="140">E' + rowData['media_index'] + ' - ' + rowData['title'] + '</span>' thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="pms_image_proxy?img=' + rowData['thumb'] + '&width=300&height=450&fallback=art" data-height="80" data-width="140">E' + rowData['media_index'] + ' - ' + rowData['title'] + '</span>';
$(td).html('<div class="history-title"><a href="info?rating_key=' + rowData['rating_key'] + '"><div style="float: left; padding-left: 30px;">' + media_type + '&nbsp;' + thumb_popover + '</div></a></div>'); $(td).html('<div class="history-title"><a href="info?rating_key=' + rowData['rating_key'] + '"><div style="float: left; padding-left: 30px;">' + media_type + '&nbsp;' + thumb_popover + '</div></a></div>');
} else if (rowData['media_type'] === 'artist') { } else if (rowData['media_type'] === 'artist') {
media_type = '<span class="media-type-tooltip" data-toggle="tooltip" title="Artist"><i class="fa fa-music fa-fw"></i></span>'; media_type = '<span class="media-type-tooltip" data-toggle="tooltip" title="Artist"><i class="fa fa-music fa-fw"></i></span>';
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="pms_image_proxy?img=' + rowData['thumb'] + '&width=300&height=300&fallback=cover" data-height="80" data-width="80">' + rowData['title'] + '</span>' thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="pms_image_proxy?img=' + rowData['thumb'] + '&width=300&height=300&fallback=cover" data-height="80" data-width="80">' + rowData['title'] + '</span>';
$(td).html('<div class="history-title"><a href="info?rating_key=' + rowData['rating_key'] + '"><div style="float: left;">' + media_type + '&nbsp;' + thumb_popover + '</div></a></div>'); $(td).html('<div class="history-title"><a href="info?rating_key=' + rowData['rating_key'] + '"><div style="float: left;">' + media_type + '&nbsp;' + thumb_popover + '</div></a></div>');
} else if (rowData['media_type'] === 'album') { } else if (rowData['media_type'] === 'album') {
media_type = '<span class="media-type-tooltip" data-toggle="tooltip" title="Album"><i class="fa fa-music fa-fw"></i></span>'; media_type = '<span class="media-type-tooltip" data-toggle="tooltip" title="Album"><i class="fa fa-music fa-fw"></i></span>';
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="pms_image_proxy?img=' + rowData['thumb'] + '&width=300&height=300&fallback=cover" data-height="80" data-width="80">' + rowData['title'] + '</span>' thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="pms_image_proxy?img=' + rowData['thumb'] + '&width=300&height=300&fallback=cover" data-height="80" data-width="80">' + rowData['title'] + '</span>';
$(td).html('<div class="history-title"><a href="info?rating_key=' + rowData['rating_key'] + '"><div style="float: left; padding-left: 15px;">' + media_type + '&nbsp;' + thumb_popover + '</div></a></div>'); $(td).html('<div class="history-title"><a href="info?rating_key=' + rowData['rating_key'] + '"><div style="float: left; padding-left: 15px;">' + media_type + '&nbsp;' + thumb_popover + '</div></a></div>');
} else if (rowData['media_type'] === 'track') { } else if (rowData['media_type'] === 'track') {
media_type = '<span class="media-type-tooltip" data-toggle="tooltip" title="Track"><i class="fa fa-music fa-fw"></i></span>'; media_type = '<span class="media-type-tooltip" data-toggle="tooltip" title="Track"><i class="fa fa-music fa-fw"></i></span>';
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="pms_image_proxy?img=' + rowData['thumb'] + '&width=300&height=300&fallback=cover" data-height="80" data-width="80">T' + rowData['media_index'] + ' - ' + rowData['title'] + '</span>' thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="pms_image_proxy?img=' + rowData['thumb'] + '&width=300&height=300&fallback=cover" data-height="80" data-width="80">T' + rowData['media_index'] + ' - ' + rowData['title'] + '</span>';
$(td).html('<div class="history-title"><a href="info?rating_key=' + rowData['rating_key'] + '"><div style="float: left; padding-left: 30px;">' + media_type + '&nbsp;' + thumb_popover + '</div></a></div>'); $(td).html('<div class="history-title"><a href="info?rating_key=' + rowData['rating_key'] + '"><div style="float: left; padding-left: 30px;">' + media_type + '&nbsp;' + thumb_popover + '</div></a></div>');
} else if (rowData['media_type'] === 'photo_album') {
media_type = '<span class="media-type-tooltip" data-toggle="tooltip" title="Photo Album"><i class="fa fa-camera fa-fw"></i></span>';
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="pms_image_proxy?img=' + rowData['thumb'] + '&width=300&height=450&fallback=poster" data-height="120" data-width="80">' + rowData['title'] + '</span>';
$(td).html('<div class="history-title"><div style="float: left; padding-left: 15px;">' + media_type + '&nbsp;' + thumb_popover + '</div></div>');
} else if (rowData['media_type'] === 'photo') {
media_type = '<span class="media-type-tooltip" data-toggle="tooltip" title="Photo"><i class="fa fa-picture-o fa-fw"></i></span>';
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="pms_image_proxy?img=' + rowData['thumb'] + '&width=300&height=450&fallback=poster" data-height="120" data-width="80">' + rowData['title'] + '</span>';
$(td).html('<div class="history-title"><div style="float: left; padding-left: 15px;">' + media_type + '&nbsp;' + thumb_popover + '</div></div>');
} else if (rowData['media_type'] === 'clip') {
media_type = '<span class="media-type-tooltip" data-toggle="tooltip" title="Video"><i class="fa fa-video-camera fa-fw"></i></span>';
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="pms_image_proxy?img=' + rowData['thumb'] + '&width=300&height=450&fallback=art" data-height="80" data-width="140">' + rowData['title'] + '</span>';
$(td).html('<div class="history-title"><div style="float: left; padding-left: 15px;">' + media_type + '&nbsp;' + thumb_popover + '</div></div>');
} else { } else {
$(td).html(cellData); $(td).html(cellData);
} }
@@ -335,7 +347,7 @@ function childTableOptionsMedia(rowData) {
case 'album': case 'album':
section_type = 'track'; section_type = 'track';
break; break;
case 'photo': case 'photo_album':
section_type = 'picture'; section_type = 'picture';
break; break;
} }

View File

@@ -3,7 +3,7 @@
<%def name="headIncludes()"> <%def name="headIncludes()">
<link rel="stylesheet" href="${http_root}css/dataTables.bootstrap.css"> <link rel="stylesheet" href="${http_root}css/dataTables.bootstrap.css">
<link rel="stylesheet" href="${http_root}css/dataTables.colVis.css"> <link rel="stylesheet" href="${http_root}css/dataTables.colVis.css">
<link rel="stylesheet" href="${http_root}css/plexpy-dataTables.css"> <link rel="stylesheet" href="${http_root}css/tautulli-dataTables.css">
</%def> </%def>
<%def name="body()"> <%def name="body()">

View File

@@ -30,7 +30,7 @@ DOCUMENTATION :: END
<%def name="headIncludes()"> <%def name="headIncludes()">
<link rel="stylesheet" href="${http_root}css/dataTables.bootstrap.css"> <link rel="stylesheet" href="${http_root}css/dataTables.bootstrap.css">
<link rel="stylesheet" href="${http_root}css/dataTables.colVis.css"> <link rel="stylesheet" href="${http_root}css/dataTables.colVis.css">
<link rel="stylesheet" href="${http_root}css/plexpy-dataTables.css"> <link rel="stylesheet" href="${http_root}css/tautulli-dataTables.css">
</%def> </%def>
<%def name="body()"> <%def name="body()">

View File

@@ -29,7 +29,7 @@ DOCUMENTATION :: END
headers = {'movie': ('Movie Libraries', ('Movies', '', '')), headers = {'movie': ('Movie Libraries', ('Movies', '', '')),
'show': ('TV Show Libraries', ('Shows', 'Seasons', 'Episodes')), 'show': ('TV Show Libraries', ('Shows', 'Seasons', 'Episodes')),
'artist': ('Music Libraries', ('Artists', 'Albums', 'Tracks')), 'artist': ('Music Libraries', ('Artists', 'Albums', 'Tracks')),
'photo': ('Photo Libraries', ('Albums', '', 'Photos'))} 'photo': ('Photo Libraries', ('Albums', 'Photos', 'Videos'))}
%> %>
% for section_type in types: % for section_type in types:
% if section_type in data: % if section_type in data:

View File

@@ -9,7 +9,7 @@
<meta name="author" content=""> <meta name="author" content="">
<link href="${http_root}css/bootstrap3/bootstrap.css" rel="stylesheet"> <link href="${http_root}css/bootstrap3/bootstrap.css" rel="stylesheet">
<link href="${http_root}css/pnotify.custom.min.css" rel="stylesheet" /> <link href="${http_root}css/pnotify.custom.min.css" rel="stylesheet" />
<link href="${http_root}css/plexpy.css${cache_param}" rel="stylesheet"> <link href="${http_root}css/tautulli.css${cache_param}" rel="stylesheet">
<link href="${http_root}css/opensans.min.css" rel="stylesheet"> <link href="${http_root}css/opensans.min.css" rel="stylesheet">
<link href="${http_root}css/font-awesome.min.css" rel="stylesheet"> <link href="${http_root}css/font-awesome.min.css" rel="stylesheet">

View File

@@ -5,7 +5,7 @@
<%def name="headIncludes()"> <%def name="headIncludes()">
<link rel="stylesheet" href="${http_root}css/dataTables.bootstrap.css"> <link rel="stylesheet" href="${http_root}css/dataTables.bootstrap.css">
<link rel="stylesheet" href="${http_root}css/plexpy-dataTables.css"> <link rel="stylesheet" href="${http_root}css/tautulli-dataTables.css">
<style> <style>
td {word-break: break-all;} td {word-break: break-all;}
</style> </style>
@@ -21,9 +21,9 @@
<span><i class="fa fa-list-alt"></i> Logs</span> <span><i class="fa fa-list-alt"></i> Logs</span>
</div> </div>
<div class="button-bar"> <div class="button-bar">
<div class="btn-group" id="plexpy-log-levels"> <div class="btn-group" id="tautulli-log-levels">
<label> <label>
<select name="plexpy-log-level-filter" id="plexpy-log-level-filter" class="btn" style="color: inherit;"> <select name="tautulli-log-level-filter" id="tautulli-log-level-filter" class="btn" style="color: inherit;">
<option value="">All log levels</option> <option value="">All log levels</option>
<option disabled>&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;</option> <option disabled>&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;</option>
<option value="DEBUG">Debug</option> <option value="DEBUG">Debug</option>
@@ -45,7 +45,7 @@
</select> </select>
</label> </label>
</div> </div>
<button class="btn btn-dark" id="download-plexpylog"><i class="fa fa-download"></i> Download logs</button> <button class="btn btn-dark" id="download-tautullilog"><i class="fa fa-download"></i> Download logs</button>
<button class="btn btn-dark" id="download-plexserverlog" style="display: none;"><i class="fa fa-download"></i> Download logs</button> <button class="btn btn-dark" id="download-plexserverlog" style="display: none;"><i class="fa fa-download"></i> Download logs</button>
<button class="btn btn-dark" id="download-plexscannerlog" style="display: none;"><i class="fa fa-download"></i> Download logs</button> <button class="btn btn-dark" id="download-plexscannerlog" style="display: none;"><i class="fa fa-download"></i> Download logs</button>
<button class="btn btn-dark" id="clear-logs"><i class="fa fa-trash-o"></i> Clear logs</button> <button class="btn btn-dark" id="clear-logs"><i class="fa fa-trash-o"></i> Clear logs</button>
@@ -56,17 +56,17 @@
<div class='table-card-back'> <div class='table-card-back'>
<div> <div>
<ul id="log_tabs" class="nav nav-pills" role="tablist"> <ul id="log_tabs" class="nav nav-pills" role="tablist">
<li role="presentation" class="active"><a id="plexpy-logs-btn" href="#tabs-plexpy_log" aria-controls="tabs-plexpy_log" role="tab" data-toggle="tab">Tautulli Logs</a></li> <li role="presentation" class="active"><a id="tautulli-logs-btn" href="#tabs-tautulli_log" aria-controls="tabs-tautulli_log" role="tab" data-toggle="tab">Tautulli Logs</a></li>
<li role="presentation"><a id="plexpy-api-logs-btn" href="#tabs-plexpy_api_log" aria-controls="tabs-plexpy_api_log" role="tab" data-toggle="tab">Tautulli API Logs</a></li> <li role="presentation"><a id="tautulli-api-logs-btn" href="#tabs-tautulli_api_log" aria-controls="tabs-tautulli_api_log" role="tab" data-toggle="tab">Tautulli API Logs</a></li>
<li role="presentation"><a id="plex-logs-btn" href="#tabs-plex_log" aria-controls="tabs-plex_log" role="tab" data-toggle="tab">Plex Media Server Logs</a></li> <li role="presentation"><a id="plex-logs-btn" href="#tabs-plex_log" aria-controls="tabs-plex_log" role="tab" data-toggle="tab">Plex Media Server Logs</a></li>
<li role="presentation"><a id="plex-scanner-logs-btn" href="#tabs-plex_scanner_log" aria-controls="tabs-plex_scanner_log" role="tab" data-toggle="tab">Plex Media Scanner Logs</a></li> <li role="presentation"><a id="plex-scanner-logs-btn" href="#tabs-plex_scanner_log" aria-controls="tabs-plex_scanner_log" role="tab" data-toggle="tab">Plex Media Scanner Logs</a></li>
<li role="presentation"><a id="plexpy-websocket-logs-btn" href="#tabs-plex_websocket_log" aria-controls="tabs-plex_websocket_log" role="tab" data-toggle="tab">Plex Websocket Logs</a></li> <li role="presentation"><a id="plex-websocket-logs-btn" href="#tabs-plex_websocket_log" aria-controls="tabs-plex_websocket_log" role="tab" data-toggle="tab">Plex Websocket Logs</a></li>
<li role="presentation"><a id="notification-logs-btn" href="#tabs-notification_log" aria-controls="tabs-notification_log" role="tab" data-toggle="tab">Notification Logs</a></li> <li role="presentation"><a id="notification-logs-btn" href="#tabs-notification_log" aria-controls="tabs-notification_log" role="tab" data-toggle="tab">Notification Logs</a></li>
<li role="presentation"><a id="login-logs-btn" href="#tabs-login_log" aria-controls="tabs-login_log" role="tab" data-toggle="tab">Login Logs</a></li> <li role="presentation"><a id="login-logs-btn" href="#tabs-login_log" aria-controls="tabs-login_log" role="tab" data-toggle="tab">Login Logs</a></li>
</ul> </ul>
<div class="tab-content"> <div class="tab-content">
<div role="tabpanel" class="tab-pane active" id="tabs-plexpy_log" data-logfile="plexpy"> <div role="tabpanel" class="tab-pane active" id="tabs-tautulli_log" data-logfile="tautulli">
<table class="display" id="plexpy_log_table" width="100%"> <table class="display" id="tautulli_log_table" width="100%">
<thead> <thead>
<tr> <tr>
<th class="min-tablet" align="left" id="timestamp">Timestamp</th> <th class="min-tablet" align="left" id="timestamp">Timestamp</th>
@@ -77,8 +77,8 @@
<tbody></tbody> <tbody></tbody>
</table> </table>
</div> </div>
<div role="tabpanel" class="tab-pane" id="tabs-plexpy_api_log" data-logfile="plexpy_api"> <div role="tabpanel" class="tab-pane" id="tabs-tautulli_api_log" data-logfile="tautulli_api">
<table class="display" id="plexpy_api_log_table" width="100%"> <table class="display" id="tautulli_api_log_table" width="100%">
<thead> <thead>
<tr> <tr>
<th class="min-tablet" align="left" id="timestamp">Timestamp</th> <th class="min-tablet" align="left" id="timestamp">Timestamp</th>
@@ -195,8 +195,8 @@
<script> <script>
$(document).ready(function() { $(document).ready(function() {
loadPlexPyLogs('plexpy', selected_log_level); loadtautullilogs('tautulli', selected_log_level);
clearSearchButton('plexpy_log_table', log_table); clearSearchButton('tautulli_log_table', log_table);
}); });
var log_levels = ['DEBUG', 'INFO', 'WARN', 'ERROR']; var log_levels = ['DEBUG', 'INFO', 'WARN', 'ERROR'];
@@ -227,7 +227,7 @@
} }
var selected_log_level = null; var selected_log_level = null;
function loadPlexPyLogs(logfile, selected_log_level) { function loadtautullilogs(logfile, selected_log_level) {
log_table_options.ajax = { log_table_options.ajax = {
url: "get_log", url: "get_log",
type: 'post', type: 'post',
@@ -238,10 +238,10 @@
log_level: selected_log_level log_level: selected_log_level
}; };
} }
} };
log_table = $('#' + logfile + '_log_table').DataTable(log_table_options); log_table = $('#' + logfile + '_log_table').DataTable(log_table_options);
$('#plexpy-log-level-filter').on('change', function () { $('#tautulli-log-level-filter').on('change', function () {
selected_log_level = $(this).val() || null; selected_log_level = $(this).val() || null;
log_table.draw(); log_table.draw();
}); });
@@ -250,7 +250,7 @@
function loadPlexLogs() { function loadPlexLogs() {
plex_log_table_options.ajax = { plex_log_table_options.ajax = {
url: "get_plex_log?log_type=server" url: "get_plex_log?log_type=server"
} };
plex_log_table_options.initComplete = bindLogLevelFilter; plex_log_table_options.initComplete = bindLogLevelFilter;
plex_log_table = $('#plex_log_table').DataTable(plex_log_table_options); plex_log_table = $('#plex_log_table').DataTable(plex_log_table_options);
} }
@@ -258,7 +258,7 @@
function loadPlexScannerLogs() { function loadPlexScannerLogs() {
plex_log_table_options.ajax = { plex_log_table_options.ajax = {
url: "get_plex_log?log_type=scanner" url: "get_plex_log?log_type=scanner"
} };
plex_log_table_options.initComplete = bindLogLevelFilter; plex_log_table_options.initComplete = bindLogLevelFilter;
plex_scanner_log_table = $('#plex_scanner_log_table').DataTable(plex_log_table_options); plex_scanner_log_table = $('#plex_scanner_log_table').DataTable(plex_log_table_options);
} }
@@ -271,7 +271,7 @@
json_data: JSON.stringify(d) json_data: JSON.stringify(d)
}; };
} }
} };
notification_log_table = $('#notification_log_table').DataTable(notification_log_table_options); notification_log_table = $('#notification_log_table').DataTable(notification_log_table_options);
} }
@@ -284,56 +284,56 @@
json_data: JSON.stringify(d) json_data: JSON.stringify(d)
}; };
} }
} };
login_log_table = $('#login_log_table').DataTable(login_log_table_options); login_log_table = $('#login_log_table').DataTable(login_log_table_options);
} }
$("#plexpy-logs-btn").click(function () { $("#tautulli-logs-btn").click(function () {
$("#plexpy-log-levels").show(); $("#tautulli-log-levels").show();
$("#plex-log-levels").hide(); $("#plex-log-levels").hide();
$("#clear-logs").show(); $("#clear-logs").show();
$("#download-plexpylog").show() $("#download-tautullilog").show();
$("#download-plexserverlog").hide() $("#download-plexserverlog").hide();
$("#download-plexscannerlog").hide() $("#download-plexscannerlog").hide();
$("#clear-notify-logs").hide(); $("#clear-notify-logs").hide();
$("#clear-login-logs").hide(); $("#clear-login-logs").hide();
loadPlexPyLogs('plexpy', selected_log_level); loadtautullilogs('tautulli', selected_log_level);
clearSearchButton('plexpy_log_table', log_table); clearSearchButton('tautulli_log_table', log_table);
}); });
$("#plexpy-api-logs-btn").click(function () { $("#tautulli-api-logs-btn").click(function () {
$("#plexpy-log-levels").show(); $("#tautulli-log-levels").show();
$("#plex-log-levels").hide(); $("#plex-log-levels").hide();
$("#clear-logs").show(); $("#clear-logs").show();
$("#download-plexpylog").show() $("#download-tautullilog").show();
$("#download-plexserverlog").hide() $("#download-plexserverlog").hide();
$("#download-plexscannerlog").hide() $("#download-plexscannerlog").hide();
$("#clear-notify-logs").hide(); $("#clear-notify-logs").hide();
$("#clear-login-logs").hide(); $("#clear-login-logs").hide();
loadPlexPyLogs('plexpy_api', selected_log_level); loadtautullilogs('tautulli_api', selected_log_level);
clearSearchButton('plexpy_api_log_table', log_table); clearSearchButton('tautulli_api_log_table', log_table);
}); });
$("#plexpy-websocket-logs-btn").click(function () { $("#plex-websocket-logs-btn").click(function () {
$("#plexpy-log-levels").show(); $("#tautulli-log-levels").show();
$("#plex-log-levels").hide(); $("#plex-log-levels").hide();
$("#clear-logs").show(); $("#clear-logs").show();
$("#download-plexpylog").show() $("#download-tautullilog").show();
$("#download-plexserverlog").hide() $("#download-plexserverlog").hide();
$("#download-plexscannerlog").hide() $("#download-plexscannerlog").hide();
$("#clear-notify-logs").hide(); $("#clear-notify-logs").hide();
$("#clear-login-logs").hide(); $("#clear-login-logs").hide();
loadPlexPyLogs('plex_websocket', selected_log_level); loadtautullilogs('plex_websocket', selected_log_level);
clearSearchButton('plex_websocket_log_table', log_table); clearSearchButton('plex_websocket_log_table', log_table);
}); });
$("#plex-logs-btn").click(function () { $("#plex-logs-btn").click(function () {
$("#plexpy-log-levels").hide(); $("#tautulli-log-levels").hide();
$("#plex-log-levels").show(); $("#plex-log-levels").show();
$("#clear-logs").hide(); $("#clear-logs").hide();
$("#download-plexpylog").hide() $("#download-tautullilog").hide();
$("#download-plexserverlog").show() $("#download-plexserverlog").show();
$("#download-plexscannerlog").hide() $("#download-plexscannerlog").hide();
$("#clear-notify-logs").hide(); $("#clear-notify-logs").hide();
$("#clear-login-logs").hide(); $("#clear-login-logs").hide();
loadPlexLogs(); loadPlexLogs();
@@ -341,12 +341,12 @@
}); });
$("#plex-scanner-logs-btn").click(function () { $("#plex-scanner-logs-btn").click(function () {
$("#plexpy-log-levels").hide(); $("#tautulli-log-levels").hide();
$("#plex-log-levels").show(); $("#plex-log-levels").show();
$("#clear-logs").hide(); $("#clear-logs").hide();
$("#download-plexpylog").hide() $("#download-tautullilog").hide();
$("#download-plexserverlog").hide() $("#download-plexserverlog").hide();
$("#download-plexscannerlog").show() $("#download-plexscannerlog").show();
$("#clear-notify-logs").hide(); $("#clear-notify-logs").hide();
$("#clear-login-logs").hide(); $("#clear-login-logs").hide();
loadPlexScannerLogs(); loadPlexScannerLogs();
@@ -354,12 +354,12 @@
}); });
$("#notification-logs-btn").click(function () { $("#notification-logs-btn").click(function () {
$("#plexpy-log-levels").hide(); $("#tautulli-log-levels").hide();
$("#plex-log-levels").hide(); $("#plex-log-levels").hide();
$("#clear-logs").hide(); $("#clear-logs").hide();
$("#download-plexpylog").hide() $("#download-tautullilog").hide();
$("#download-plexserverlog").hide() $("#download-plexserverlog").hide();
$("#download-plexscannerlog").hide() $("#download-plexscannerlog").hide();
$("#clear-notify-logs").show(); $("#clear-notify-logs").show();
$("#clear-login-logs").hide(); $("#clear-login-logs").hide();
loadNotificationLogs(); loadNotificationLogs();
@@ -367,12 +367,12 @@
}); });
$("#login-logs-btn").click(function () { $("#login-logs-btn").click(function () {
$("#plexpy-log-levels").hide(); $("#tautulli-log-levels").hide();
$("#plex-log-levels").hide(); $("#plex-log-levels").hide();
$("#clear-logs").hide(); $("#clear-logs").hide();
$("#download-plexpylog").hide() $("#download-tautullilog").hide();
$("#download-plexserverlog").hide() $("#download-plexserverlog").hide();
$("#download-plexscannerlog").hide() $("#download-plexscannerlog").hide();
$("#clear-notify-logs").hide(); $("#clear-notify-logs").hide();
$("#clear-login-logs").show(); $("#clear-login-logs").show();
loadLoginLogs(); loadLoginLogs();
@@ -384,8 +384,8 @@
}); });
$("#clear-logs").click(function () { $("#clear-logs").click(function () {
var logfile = $(".tab-pane.active").data('logfile') var logfile = $(".tab-pane.active").data('logfile');
var title = $("#log_tabs li.active a").text() var title = $("#log_tabs li.active a").text();
$("#confirm-message").text("Are you sure you want to clear the " + title + "?"); $("#confirm-message").text("Are you sure you want to clear the " + title + "?");
$('#confirm-modal').modal(); $('#confirm-modal').modal();
@@ -397,7 +397,7 @@
complete: function (xhr, status) { complete: function (xhr, status) {
result = $.parseJSON(xhr.responseText); result = $.parseJSON(xhr.responseText);
msg = result.message; msg = result.message;
if (result.result == 'success') { if (result.result === 'success') {
showMsg('<i class="fa fa-check"></i> ' + msg, false, true, 5000) showMsg('<i class="fa fa-check"></i> ' + msg, false, true, 5000)
} else { } else {
showMsg('<i class="fa fa-times"></i> ' + msg, false, true, 5000, true) showMsg('<i class="fa fa-times"></i> ' + msg, false, true, 5000, true)
@@ -408,7 +408,7 @@
}); });
}); });
$("#download-plexpylog").click(function () { $("#download-tautullilog").click(function () {
var logfile = $(".tab-pane.active").data('logfile'); var logfile = $(".tab-pane.active").data('logfile');
window.location.href = "download_log?logfile=" + logfile; window.location.href = "download_log?logfile=" + logfile;
}); });
@@ -431,7 +431,7 @@
complete: function (xhr, status) { complete: function (xhr, status) {
result = $.parseJSON(xhr.responseText); result = $.parseJSON(xhr.responseText);
msg = result.message; msg = result.message;
if (result.result == 'success') { if (result.result === 'success') {
showMsg('<i class="fa fa-check"></i> ' + msg, false, true, 5000) showMsg('<i class="fa fa-check"></i> ' + msg, false, true, 5000)
} else { } else {
showMsg('<i class="fa fa-times"></i> ' + msg, false, true, 5000, true) showMsg('<i class="fa fa-times"></i> ' + msg, false, true, 5000, true)
@@ -452,7 +452,7 @@
complete: function (xhr, status) { complete: function (xhr, status) {
result = $.parseJSON(xhr.responseText); result = $.parseJSON(xhr.responseText);
msg = result.message; msg = result.message;
if (result.result == 'success') { if (result.result === 'success') {
showMsg('<i class="fa fa-check"></i> ' + msg, false, true, 5000) showMsg('<i class="fa fa-check"></i> ' + msg, false, true, 5000)
} else { } else {
showMsg('<i class="fa fa-times"></i> ' + msg, false, true, 5000, true) showMsg('<i class="fa fa-times"></i> ' + msg, false, true, 5000, true)
@@ -473,10 +473,10 @@
{ {
clearInterval(timer); clearInterval(timer);
} }
if(refreshrate.value != 0) if(refreshrate.value !== 0)
{ {
timer = setInterval(function() { timer = setInterval(function() {
if ($("#tabs-plexpy_log").hasClass("active") || $("#tabs-plexpy_api_log").hasClass("active") || $("#tabs-plex_websocket_log").hasClass("active")) { if ($("#tabs-tautulli_log").hasClass("active") || $("#tabs-tautulli_api_log").hasClass("active") || $("#tabs-plex_websocket_log").hasClass("active")) {
log_table.ajax.reload(); log_table.ajax.reload();
} else if ($("#tabs-plex_log").hasClass("active")) { } else if ($("#tabs-plex_log").hasClass("active")) {
plex_log_table.ajax.reload(); plex_log_table.ajax.reload();

View File

@@ -606,6 +606,22 @@
}); });
}); });
% elif notifier['agent_name'] == 'pushover':
function pushoverPriority() {
if ($('#pushover_priority').val() == '2') {
$('#pushover_retry').closest('.form-group').show();
$('#pushover_expire').closest('.form-group').show();
} else {
$('#pushover_retry').closest('.form-group').hide();
$('#pushover_expire').closest('.form-group').hide();
}
}
pushoverPriority();
$('#pushover_priority').change( function () {
pushoverPriority();
});
% endif % endif
function validateLogic() { function validateLogic() {

View File

@@ -60,9 +60,9 @@
<input type="hidden" id="show_advanced_settings" name="show_advanced_settings" value="${config['show_advanced_settings']}" required> <input type="hidden" id="show_advanced_settings" name="show_advanced_settings" value="${config['show_advanced_settings']}" required>
<div class="tab-content"> <div class="tab-content">
<div role="tabpanel" class="tab-pane active" id="tabs-help_info"> <div role="tabpanel" class="tab-pane active" id="tabs-help_info">
% if common.VERSION_NUMBER: % if common.RELEASE:
<div class="padded-header"> <div class="padded-header">
<h3>Version ${common.VERSION_NUMBER} <small><a id="changelog-modal-link" href="#"><i class="fa fa-info-circle"></i> Changelog</a></small></h3> <h3>Version ${common.RELEASE} <small><a id="changelog-modal-link" href="#"><i class="fa fa-info-circle"></i> Changelog</a></small></h3>
</div> </div>
% endif % endif
<div class="padded-header"> <div class="padded-header">

View File

@@ -2,7 +2,7 @@
<%def name="headIncludes()"> <%def name="headIncludes()">
<link rel="stylesheet" href="${http_root}css/dataTables.bootstrap.css"> <link rel="stylesheet" href="${http_root}css/dataTables.bootstrap.css">
<link rel="stylesheet" href="${http_root}css/plexpy-dataTables.css"> <link rel="stylesheet" href="${http_root}css/tautulli-dataTables.css">
<link rel="stylesheet" href="${http_root}css/dataTables.colVis.css"> <link rel="stylesheet" href="${http_root}css/dataTables.colVis.css">
<style> <style>
td {word-wrap: break-word} td {word-wrap: break-word}

View File

@@ -32,7 +32,7 @@ DOCUMENTATION :: END
<%def name="headIncludes()"> <%def name="headIncludes()">
<link rel="stylesheet" href="${http_root}css/dataTables.bootstrap.css"> <link rel="stylesheet" href="${http_root}css/dataTables.bootstrap.css">
<link rel="stylesheet" href="${http_root}css/dataTables.colVis.css"> <link rel="stylesheet" href="${http_root}css/dataTables.colVis.css">
<link rel="stylesheet" href="${http_root}css/plexpy-dataTables.css"> <link rel="stylesheet" href="${http_root}css/tautulli-dataTables.css">
</%def> </%def>
<%def name="body()"> <%def name="body()">

View File

@@ -3,7 +3,7 @@
<%def name="headIncludes()"> <%def name="headIncludes()">
<link rel="stylesheet" href="${http_root}css/dataTables.bootstrap.css"> <link rel="stylesheet" href="${http_root}css/dataTables.bootstrap.css">
<link rel="stylesheet" href="${http_root}css/dataTables.colVis.css"> <link rel="stylesheet" href="${http_root}css/dataTables.colVis.css">
<link rel="stylesheet" href="${http_root}css/plexpy-dataTables.css"> <link rel="stylesheet" href="${http_root}css/tautulli-dataTables.css">
</%def> </%def>
<%def name="body()"> <%def name="body()">

View File

@@ -14,7 +14,7 @@
<meta name="author" content=""> <meta name="author" content="">
<link href="${http_root}css/bootstrap3/bootstrap.css" rel="stylesheet"> <link href="${http_root}css/bootstrap3/bootstrap.css" rel="stylesheet">
<link href="${http_root}css/bootstrap-wizard.css" rel="stylesheet"> <link href="${http_root}css/bootstrap-wizard.css" rel="stylesheet">
<link href="${http_root}css/plexpy.css${cache_param}" rel="stylesheet"> <link href="${http_root}css/tautulli.css${cache_param}" rel="stylesheet">
<link href="${http_root}css/selectize.bootstrap3.css" rel="stylesheet"> <link href="${http_root}css/selectize.bootstrap3.css" rel="stylesheet">
<link href="${http_root}css/opensans.min.css" rel="stylesheet"> <link href="${http_root}css/opensans.min.css" rel="stylesheet">
<link href="${http_root}css/font-awesome.min.css" rel="stylesheet"> <link href="${http_root}css/font-awesome.min.css" rel="stylesheet">

View File

@@ -1,54 +1,54 @@
#!/bin/sh #!/bin/sh
# #
# PROVIDE: plexpy # PROVIDE: tautulli
# REQUIRE: plexpy # REQUIRE: tautulli
# KEYWORD: shutdown # KEYWORD: shutdown
# #
# Add the following lines to /etc/rc.conf.local or /etc/rc.conf # Add the following lines to /etc/rc.conf.local or /etc/rc.conf
# to enable this service: # to enable this service:
# #
# plexpy_enable (bool): Set to NO by default. # tautulli_enable (bool): Set to NO by default.
# Set it to YES to enable it. # Set it to YES to enable it.
# plexpy_user: The user account PlexPy daemon runs as what # tautulli_user: The user account Tautulli daemon runs as what
# you want it to be. It uses 'plexpy' user by # you want it to be. It uses 'tautulli' user by
# default. Do not sets it as empty or it will run # default. Do not sets it as empty or it will run
# as root. # as root.
# plexpy_dir: Directory where PlexPy lives. # tautulli_dir: Directory where Tautulli lives.
# Default: /usr/local/plexpy # Default: /usr/local/share/Tautulli
# plexpy_chdir: Change to this directory before running PlexPy. # tautulli_chdir: Change to this directory before running Tautulli.
# Default is same as plexpy_dir. # Default is same as tautulli_dir.
# plexpy_pid: The name of the pidfile to create. # tautulli_pid: The name of the pidfile to create.
# Default is plexpy.pid in plexpy_dir. # Default is tautulli.pid in tautulli_dir.
. /etc/rc.subr . /etc/rc.subr
name="plexpy" name="tautulli"
rcvar=${name}_enable rcvar=${name}_enable
load_rc_config ${name} load_rc_config ${name}
: ${plexpy_enable:="NO"} : ${tautulli_enable:="NO"}
: ${plexpy_user:="plexpy"} : ${tautulli_user:="tautulli"}
: ${plexpy_dir:="/usr/local/plexpy"} : ${tautulli_dir:="/usr/local/share/Tautulli"}
: ${plexpy_chdir:="${plexpy_dir}"} : ${tautulli_chdir:="${tautulli_dir}"}
: ${plexpy_pid:="${plexpy_dir}/plexpy.pid"} : ${tautulli_pid:="${tautulli_dir}/tautulli.pid"}
: ${plexpy_conf:="${plexpy_dir}/config.ini"} : ${tautulli_conf:="${tautulli_dir}/config.ini"}
WGET="/usr/local/bin/wget" # You need wget for this script to safely shutdown PlexPy. WGET="/usr/local/bin/wget" # You need wget for this script to safely shutdown Tautulli.
if [ -e "${plexpy_conf}" ]; then if [ -e "${tautulli_conf}" ]; then
HOST=`grep -A64 "\[General\]" "${plexpy_conf}"|egrep "^http_host"|perl -wple 's/^http_host = (.*)$/$1/'` HOST=`grep -A64 "\[General\]" "${tautulli_conf}"|egrep "^http_host"|perl -wple 's/^http_host = (.*)$/$1/'`
PORT=`grep -A64 "\[General\]" "${plexpy_conf}"|egrep "^http_port"|perl -wple 's/^http_port = (.*)$/$1/'` PORT=`grep -A64 "\[General\]" "${tautulli_conf}"|egrep "^http_port"|perl -wple 's/^http_port = (.*)$/$1/'`
fi fi
status_cmd="${name}_status" status_cmd="${name}_status"
stop_cmd="${name}_stop" stop_cmd="${name}_stop"
command="${plexpy_dir}/PlexPy.py" command="${tautulli_dir}/Tautulli.py"
command_args="--daemon --quiet --nolaunch --port ${PORT} --pidfile ${plexpy_pid} --config ${plexpy_conf}" command_args="--daemon --quiet --nolaunch --port ${PORT} --pidfile ${tautulli_pid} --config ${tautulli_conf}"
# Check for wget and refuse to start without it. # Check for wget and refuse to start without it.
if [ ! -x "${WGET}" ]; then if [ ! -x "${WGET}" ]; then
warn "PlexPy not started: You need wget to safely shut down PlexPy." warn "Tautulli not started: You need wget to safely shut down Tautulli."
exit 1 exit 1
fi fi
@@ -58,21 +58,21 @@ if [ `id -u` != "0" ]; then
exit 1 exit 1
fi fi
verify_plexpy_pid() { verify_tautulli_pid() {
# Make sure the pid corresponds to the PlexPy process. # Make sure the pid corresponds to the Tautulli process.
pid=`cat ${plexpy_pid} 2>/dev/null` pid=`cat ${tautulli_pid} 2>/dev/null`
ps -p ${pid} | grep -q "python ${plexpy_dir}/PlexPy.py" ps -p ${pid} | grep -q "python ${tautulli_dir}/Tautulli.py"
return $? return $?
} }
# Try to stop PlexPy cleanly by calling shutdown over http. # Try to stop Tautulli cleanly by calling shutdown over http.
plexpy_stop() { tautulli_stop() {
if [ ! -e "${plexpy_conf}" ]; then if [ ! -e "${tautulli_conf}" ]; then
echo "PlexPy' settings file does not exist. Try starting PlexPy, as this should create the file." echo "Tautulli' settings file does not exist. Try starting Tautulli, as this should create the file."
exit 1 exit 1
fi fi
echo "Stopping $name" echo "Stopping $name"
verify_plexpy_pid verify_tautulli_pid
${WGET} -O - -q --user=${SBUSR} --password=${SBPWD} "http://${HOST}:${PORT}/shutdown/" >/dev/null ${WGET} -O - -q --user=${SBUSR} --password=${SBPWD} "http://${HOST}:${PORT}/shutdown/" >/dev/null
if [ -n "${pid}" ]; then if [ -n "${pid}" ]; then
@@ -81,8 +81,8 @@ plexpy_stop() {
fi fi
} }
plexpy_status() { tautulli_status() {
verify_plexpy_pid && echo "$name is running as ${pid}" || echo "$name is not running" verify_tautulli_pid && echo "$name is running as ${pid}" || echo "$name is not running"
} }
run_rc_command "$1" run_rc_command "$1"

View File

@@ -1,25 +1,25 @@
#!/bin/sh #!/bin/sh
# #
### BEGIN INIT INFO ### BEGIN INIT INFO
# Provides: PlexPy # Provides: Tautulli
# Required-Start: $all # Required-Start: $all
# Required-Stop: $all # Required-Stop: $all
# Default-Start: 2 3 4 5 # Default-Start: 2 3 4 5
# Default-Stop: 0 1 6 # Default-Stop: 0 1 6
# Short-Description: starts PlexPy # Short-Description: starts Tautulli
# Description: starts PlexPy # Description: starts Tautulli
### END INIT INFO ### END INIT INFO
# Source function library. # Source function library.
. /etc/init.d/functions . /etc/init.d/functions
## Variables ## Variables
prog=plexpy prog=tautulli
lockfile=/var/lock/subsys/$prog lockfile=/var/lock/subsys/$prog
homedir=/opt/plexpy homedir=/opt/Tautulli
datadir=/opt/plexpy datadir=/opt/Tautulli
configfile=/opt/plexpy/config.ini configfile=/opt/Tautulli/config.ini
pidfile=/var/run/plexpy.pid pidfile=/var/run/tautulli.pid
nice= nice=
# The following line must point to your Python 2.7 install # The following line must point to your Python 2.7 install
python27=/usr/src/Python-2.7.11/python python27=/usr/src/Python-2.7.11/python
@@ -30,7 +30,7 @@ options=" --daemon --config $configfile --pidfile $pidfile --datadir $datadir --
start() { start() {
# Start daemon. # Start daemon.
echo -n $"Starting $prog: " echo -n $"Starting $prog: "
daemon --pidfile=$pidfile $nice $python27 $homedir/PlexPy.py $options daemon --pidfile=$pidfile $nice $python27 $homedir/Tautulli.py $options
RETVAL=$? RETVAL=$?
echo echo
[ $RETVAL -eq 0 ] && touch $lockfile [ $RETVAL -eq 0 ] && touch $lockfile

View File

@@ -1,45 +1,45 @@
#!/bin/sh #!/bin/sh
# #
# PROVIDE: plexpy # PROVIDE: tautulli
# REQUIRE: DAEMON plexpy # REQUIRE: DAEMON tautulli
# KEYWORD: shutdown # KEYWORD: shutdown
# #
# Add the following lines to /etc/rc.conf.local or /etc/rc.conf # Add the following lines to /etc/rc.conf.local or /etc/rc.conf
# to enable this service: # to enable this service:
# #
# plexpy_enable (bool): Set to NO by default. # tautulli_enable (bool): Set to NO by default.
# Set it to YES to enable it. # Set it to YES to enable it.
# plexpy_user: The user account PlexPy daemon runs as what # tautulli_user: The user account Tautulli daemon runs as what
# you want it to be. It uses 'plexpy' user by # you want it to be. It uses 'tautulli' user by
# default. Do not sets it as empty or it will run # default. Do not sets it as empty or it will run
# as root. # as root.
# plexpy_dir: Directory where PlexPy lives. # tautulli_dir: Directory where Tautulli lives.
# Default: /usr/local/share/plexpy # Default: /usr/local/share/Tautulli
# plexpy_chdir: Change to this directory before running PlexPy. # tautulli_chdir: Change to this directory before running Tautulli.
# Default is same as plexpy_dir. # Default is same as tautulli_dir.
# plexpy_pid: The name of the pidfile to create. # tautulli_pid: The name of the pidfile to create.
# Default is plexpy.pid in plexpy_dir. # Default is tautulli.pid in tautulli_dir.
PATH="/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin" PATH="/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin"
. /etc/rc.subr . /etc/rc.subr
name="plexpy" name="tautulli"
rcvar=${name}_enable rcvar=${name}_enable
load_rc_config ${name} load_rc_config ${name}
: ${plexpy_enable:="NO"} : ${tautulli_enable:="NO"}
: ${plexpy_user:="plexpy"} : ${tautulli_user:="tautulli"}
: ${plexpy_dir:="/usr/local/share/plexpy"} : ${tautulli_dir:="/usr/local/share/Tautulli"}
: ${plexpy_chdir:="${plexpy_dir}"} : ${tautulli_chdir:="${tautulli_dir}"}
: ${plexpy_pid:="${plexpy_dir}/plexpy.pid"} : ${tautulli_pid:="${tautulli_dir}/tautulli.pid"}
: ${plexpy_flags:=""} : ${tautulli_flags:=""}
status_cmd="${name}_status" status_cmd="${name}_status"
stop_cmd="${name}_stop" stop_cmd="${name}_stop"
command="${plexpy_dir}/PlexPy.py" command="${tautulli_dir}/Tautulli.py"
command_args="--daemon --pidfile ${plexpy_pid} --quiet --nolaunch ${plexpy_flags}" command_args="--daemon --pidfile ${tautulli_pid} --quiet --nolaunch ${tautulli_flags}"
# Ensure user is root when running this script. # Ensure user is root when running this script.
if [ `id -u` != "0" ]; then if [ `id -u` != "0" ]; then
@@ -47,21 +47,21 @@ if [ `id -u` != "0" ]; then
exit 1 exit 1
fi fi
verify_plexpy_pid() { verify_tautulli_pid() {
# Make sure the pid corresponds to the PlexPy process. # Make sure the pid corresponds to the Tautulli process.
if [ -f ${plexpy_pid} ]; then if [ -f ${tautulli_pid} ]; then
pid=`cat ${plexpy_pid} 2>/dev/null` pid=`cat ${tautulli_pid} 2>/dev/null`
ps -p ${pid} | grep -q "python2 ${plexpy_dir}/PlexPy.py" ps -p ${pid} | grep -q "python2 ${tautulli_dir}/Tautulli.py"
return $? return $?
else else
return 0 return 0
fi fi
} }
# Try to stop PlexPy cleanly by sending SIGTERM # Try to stop Tautulli cleanly by sending SIGTERM
plexpy_stop() { tautulli_stop() {
echo "Stopping $name" echo "Stopping $name"
verify_plexpy_pid verify_tautulli_pid
if [ -n "${pid}" ]; then if [ -n "${pid}" ]; then
kill ${pid} kill ${pid}
wait_for_pids ${pid} wait_for_pids ${pid}
@@ -69,8 +69,8 @@ plexpy_stop() {
fi fi
} }
plexpy_status() { tautulli_status() {
verify_plexpy_pid verify_tautulli_pid
if [ -n "${pid}" ]; then if [ -n "${pid}" ]; then
echo "$name is running as ${pid}." echo "$name is running as ${pid}."
else else

View File

@@ -1,45 +1,45 @@
#!/bin/sh #!/bin/sh
# #
# PROVIDE: plexpy # PROVIDE: tautulli
# REQUIRE: DAEMON plexpy # REQUIRE: DAEMON tautulli
# KEYWORD: shutdown # KEYWORD: shutdown
# #
# Add the following lines to /etc/rc.conf.local or /etc/rc.conf # Add the following lines to /etc/rc.conf.local or /etc/rc.conf
# to enable this service: # to enable this service:
# #
# plexpy_enable (bool): Set to NO by default. # tautulli_enable (bool): Set to NO by default.
# Set it to YES to enable it. # Set it to YES to enable it.
# plexpy_user: The user account PlexPy daemon runs as what # tautulli_user: The user account Tautulli daemon runs as what
# you want it to be. It uses 'plexpy' user by # you want it to be. It uses 'tautulli' user by
# default. Do not sets it as empty or it will run # default. Do not sets it as empty or it will run
# as root. # as root.
# plexpy_dir: Directory where PlexPy lives. # tautulli_dir: Directory where Tautulli lives.
# Default: /usr/local/share/plexpy # Default: /usr/local/share/Tautulli
# plexpy_chdir: Change to this directory before running PlexPy. # tautulli_chdir: Change to this directory before running Tautulli.
# Default is same as plexpy_dir. # Default is same as tautulli_dir.
# plexpy_pid: The name of the pidfile to create. # tautulli_pid: The name of the pidfile to create.
# Default is plexpy.pid in plexpy_dir. # Default is tautulli.pid in tautulli_dir.
PATH="/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin" PATH="/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin"
. /etc/rc.subr . /etc/rc.subr
name="plexpy" name="tautulli"
rcvar=${name}_enable rcvar=${name}_enable
load_rc_config ${name} load_rc_config ${name}
: ${plexpy_enable:="NO"} : ${tautulli_enable:="NO"}
: ${plexpy_user:="plexpy"} : ${tautulli_user:="tautulli"}
: ${plexpy_dir:="/usr/local/share/plexpy"} : ${tautulli_dir:="/usr/local/share/Tautulli"}
: ${plexpy_chdir:="${plexpy_dir}"} : ${tautulli_chdir:="${tautulli_dir}"}
: ${plexpy_pid:="${plexpy_dir}/plexpy.pid"} : ${tautulli_pid:="${tautulli_dir}/tautulli.pid"}
: ${plexpy_flags:=""} : ${tautulli_flags:=""}
status_cmd="${name}_status" status_cmd="${name}_status"
stop_cmd="${name}_stop" stop_cmd="${name}_stop"
command="${plexpy_dir}/PlexPy.py" command="${tautulli_dir}/Tautulli.py"
command_args="--daemon --pidfile ${plexpy_pid} --quiet --nolaunch ${plexpy_flags}" command_args="--daemon --pidfile ${tautulli_pid} --quiet --nolaunch ${tautulli_flags}"
# Ensure user is root when running this script. # Ensure user is root when running this script.
if [ `id -u` != "0" ]; then if [ `id -u` != "0" ]; then
@@ -47,21 +47,21 @@ if [ `id -u` != "0" ]; then
exit 1 exit 1
fi fi
verify_plexpy_pid() { verify_tautulli_pid() {
# Make sure the pid corresponds to the PlexPy process. # Make sure the pid corresponds to the Tautulli process.
if [ -f ${plexpy_pid} ]; then if [ -f ${tautulli_pid} ]; then
pid=`cat ${plexpy_pid} 2>/dev/null` pid=`cat ${tautulli_pid} 2>/dev/null`
ps -p ${pid} | grep -q "python2 ${plexpy_dir}/PlexPy.py" ps -p ${pid} | grep -q "python2 ${tautulli_dir}/Tautulli.py"
return $? return $?
else else
return 0 return 0
fi fi
} }
# Try to stop PlexPy cleanly by sending SIGTERM # Try to stop Tautulli cleanly by sending SIGTERM
plexpy_stop() { tautulli_stop() {
echo "Stopping $name." echo "Stopping $name."
verify_plexpy_pid verify_tautulli_pid
if [ -n "${pid}" ]; then if [ -n "${pid}" ]; then
kill ${pid} kill ${pid}
wait_for_pids ${pid} wait_for_pids ${pid}
@@ -69,8 +69,8 @@ plexpy_stop() {
fi fi
} }
plexpy_status() { tautulli_status() {
verify_plexpy_pid verify_tautulli_pid
if [ -n "${pid}" ]; then if [ -n "${pid}" ]; then
echo "$name is running as ${pid}." echo "$name is running as ${pid}."
else else

View File

@@ -3,12 +3,12 @@
<plist version="1.0"> <plist version="1.0">
<dict> <dict>
<key>Label</key> <key>Label</key>
<string>plexpy</string> <string>tautulli</string>
<key>ProgramArguments</key> <key>ProgramArguments</key>
<array> <array>
<!-- Modify these two lines if you need to to reflect your python location and PlexPy install location --> <!-- Modify these two lines if you need to to reflect your python location and Tautulli install location -->
<string>/usr/bin/python</string> <string>/usr/bin/python</string>
<string>/Applications/PlexPy/PlexPy.py</string> <string>/Applications/Tautulli/Tautulli.py</string>
</array> </array>
<key>RunAtLoad</key> <key>RunAtLoad</key>
<true/> <true/>

View File

@@ -2,9 +2,9 @@
<!DOCTYPE service_bundle SYSTEM "/usr/share/lib/xml/dtd/service_bundle.dtd.1"> <!DOCTYPE service_bundle SYSTEM "/usr/share/lib/xml/dtd/service_bundle.dtd.1">
<!-- <!--
Created by Manifold Created by Manifold
--><service_bundle type="manifest" name="plexpy"> --><service_bundle type="manifest" name="tautulli">
<service name="application/plexpy" type="service" version="1"> <service name="application/tautulli" type="service" version="1">
<create_default_instance enabled="true"/> <create_default_instance enabled="true"/>
@@ -19,10 +19,10 @@
</dependency> </dependency>
<method_context> <method_context>
<method_credential user="plexpy" group="nogroup"/> <method_credential user="tautulli" group="nogroup"/>
</method_context> </method_context>
<exec_method type="method" name="start" exec="python /opt/plexpy/PlexPy.py --daemon --quiet --nolaunch" timeout_seconds="60"/> <exec_method type="method" name="start" exec="python /opt/Tautulli/Tautulli.py --daemon --quiet --nolaunch" timeout_seconds="60"/>
<exec_method type="method" name="stop" exec=":kill" timeout_seconds="60"/> <exec_method type="method" name="stop" exec=":kill" timeout_seconds="60"/>
@@ -37,7 +37,7 @@
<template> <template>
<common_name> <common_name>
<loctext xml:lang="C"> <loctext xml:lang="C">
PlexPy Tautulli
</loctext> </loctext>
</common_name> </common_name>
</template> </template>

View File

@@ -1,11 +1,11 @@
# PlexPy - Stats for Plex Media Server usage # Tautulli - Stats for Plex Media Server usage
# #
# Service Unit file for systemd system manager # Service Unit file for systemd system manager
# #
# INSTALLATION NOTES # INSTALLATION NOTES
# #
# 1. Rename this file as you want, ensuring that it ends in .service # 1. Rename this file as you want, ensuring that it ends in .service
# e.g. 'plexpy.service' # e.g. 'tautulli.service'
# #
# 2. Adjust configuration settings as required. More details in the # 2. Adjust configuration settings as required. More details in the
# "CONFIGURATION NOTES" section shown below. # "CONFIGURATION NOTES" section shown below.
@@ -15,39 +15,39 @@
# #
# 4. Enable boot-time autostart with the following commands: # 4. Enable boot-time autostart with the following commands:
# systemctl daemon-reload # systemctl daemon-reload
# systemctl enable plexpy.service # systemctl enable tautulli.service
# #
# 5. Start now with the following command: # 5. Start now with the following command:
# systemctl start plexpy.service # systemctl start tautulli.service
# #
# CONFIGURATION NOTES # CONFIGURATION NOTES
# #
# - The example settings in this file assume that you will run PlexPy as user: plexpy # - The example settings in this file assume that you will run Tautulli as user: tautulli
# - To create this user and give it ownership of the plexpy directory: # - To create this user and give it ownership of the tautulli directory:
# sudo adduser --system --no-create-home plexpy # sudo adduser --system --no-create-home tautulli
# sudo chown plexpy:nogroup -R /opt/plexpy # sudo chown tautulli:nogroup -R /opt/Tautulli
# #
# - Option names (e.g. ExecStart=, Type=) appear to be case-sensitive) # - Option names (e.g. ExecStart=, Type=) appear to be case-sensitive)
# #
# - Adjust ExecStart= to point to: # - Adjust ExecStart= to point to:
# 1. Your PlexPy executable, # 1. Your Tautulli executable,
# 2. Your config file (recommended is to put it somewhere in /etc) # 2. Your config file (recommended is to put it somewhere in /etc)
# 3. Your datadir (recommended is to NOT put it in your PlexPy exec dir) # 3. Your datadir (recommended is to NOT put it in your Tautulli exec dir)
# #
# - Adjust User= and Group= to the user/group you want PlexPy to run as. # - Adjust User= and Group= to the user/group you want Tautulli to run as.
# #
# - WantedBy= specifies which target (i.e. runlevel) to start PlexPy for. # - WantedBy= specifies which target (i.e. runlevel) to start Tautulli for.
# multi-user.target equates to runlevel 3 (multi-user text mode) # multi-user.target equates to runlevel 3 (multi-user text mode)
# graphical.target equates to runlevel 5 (multi-user X11 graphical mode) # graphical.target equates to runlevel 5 (multi-user X11 graphical mode)
[Unit] [Unit]
Description=PlexPy - Stats for Plex Media Server usage Description=Tautulli - Stats for Plex Media Server usage
[Service] [Service]
ExecStart=/opt/plexpy/PlexPy.py --quiet --daemon --nolaunch --config /opt/plexpy/config.ini --datadir /opt/plexpy ExecStart=/opt/Tautulli/Tautulli.py --quiet --daemon --nolaunch --config /opt/Tautulli/config.ini --datadir /opt/Tautulli
GuessMainPID=no GuessMainPID=no
Type=forking Type=forking
User=plexpy User=tautulli
Group=nogroup Group=nogroup
[Install] [Install]

View File

@@ -1,71 +1,71 @@
#!/bin/sh #!/bin/sh
# #
## Don't edit this file ## Don't edit this file
## Edit user configuation in /etc/default/plexpy to change ## Edit user configuation in /etc/default/tautulli to change
## ##
## Make sure init script is executable ## Make sure init script is executable
## sudo chmod +x /path/to/init.ubuntu ## sudo chmod +x /path/to/init.ubuntu
## ##
## Install the init script ## Install the init script
## sudo ln -s /path/to/init.ubuntu /etc/init.d/plexpy ## sudo ln -s /path/to/init.ubuntu /etc/init.d/tautulli
## ##
## Create the plexpy daemon user: ## Create the tautulli daemon user:
## sudo adduser --system --no-create-home plexpy ## sudo adduser --system --no-create-home tautulli
## ##
## Make sure /opt/plexpy is owned by the plexpy user ## Make sure /opt/Tautulli is owned by the tautulli user
## sudo chown plexpy:nogroup -R /opt/plexpy ## sudo chown tautulli:nogroup -R /opt/Tautulli
## ##
## Touch the default file to stop the warning message when starting ## Touch the default file to stop the warning message when starting
## sudo touch /etc/default/plexpy ## sudo touch /etc/default/tautulli
## ##
## To start PlexPy automatically ## To start Tautulli automatically
## sudo update-rc.d plexpy defaults ## sudo update-rc.d tautulli defaults
## ##
## To start/stop/restart PlexPy ## To start/stop/restart Tautulli
## sudo service plexpy start ## sudo service tautulli start
## sudo service plexpy stop ## sudo service tautulli stop
## sudo service plexpy restart ## sudo service tautulli restart
## ##
## HP_USER= #$RUN_AS, username to run plexpy under, the default is plexpy ## TAUTULLI_USER= #$RUN_AS, username to run Tautulli under, the default is tautulli
## HP_HOME= #$APP_PATH, the location of PlexPy.py, the default is /opt/plexpy ## TAUTULLI_HOME= #$APP_PATH, the location of Tautulli.py, the default is /opt/Tautulli
## HP_DATA= #$DATA_DIR, the location of plexpy.db, cache, logs, the default is /opt/plexpy ## TAUTULLI_DATA= #$DATA_DIR, the location of plexpy.db, cache, logs, the default is /opt/Tautulli
## HP_PIDFILE= #$PID_FILE, the location of plexpy.pid, the default is /var/run/plexpy/plexpy.pid ## TAUTULLI_PIDFILE= #$PID_FILE, the location of tautulli.pid, the default is /var/run/tautulli/tautulli.pid
## PYTHON_BIN= #$DAEMON, the location of the python binary, the default is /usr/bin/python ## PYTHON_BIN= #$DAEMON, the location of the python binary, the default is /usr/bin/python
## HP_OPTS= #$EXTRA_DAEMON_OPTS, extra cli option for plexpy, i.e. " --config=/home/plexpy/config.ini" ## TAUTULLI_OPTS= #$EXTRA_DAEMON_OPTS, extra cli option for Tautulli, i.e. " --config=/home/Tautulli/config.ini"
## SSD_OPTS= #$EXTRA_SSD_OPTS, extra start-stop-daemon option like " --group=users" ## SSD_OPTS= #$EXTRA_SSD_OPTS, extra start-stop-daemon option like " --group=users"
## HP_PORT= #$PORT_OPTS, hardcoded port for the webserver, overrides value in config.ini ## TAUTULLI_PORT= #$PORT_OPTS, hardcoded port for the webserver, overrides value in config.ini
## ##
## EXAMPLE if want to run as different user ## EXAMPLE if want to run as different user
## add HP_USER=username to /etc/default/plexpy ## add TAUTULLI_USER=username to /etc/default/tautulli
## otherwise default plexpy is used ## otherwise default tautulli is used
# #
### BEGIN INIT INFO ### BEGIN INIT INFO
# Provides: plexpy # Provides: tautulli
# Required-Start: $local_fs $network $remote_fs # Required-Start: $local_fs $network $remote_fs
# Required-Stop: $local_fs $network $remote_fs # Required-Stop: $local_fs $network $remote_fs
# Should-Start: $NetworkManager # Should-Start: $NetworkManager
# Should-Stop: $NetworkManager # Should-Stop: $NetworkManager
# Default-Start: 2 3 4 5 # Default-Start: 2 3 4 5
# Default-Stop: 0 1 6 # Default-Stop: 0 1 6
# Short-Description: starts instance of PlexPy # Short-Description: starts instance of Tautulli
# Description: starts instance of PlexPy using start-stop-daemon # Description: starts instance of Tautulli using start-stop-daemon
### END INIT INFO ### END INIT INFO
# Script name # Script name
NAME=plexpy NAME=tautulli
# App name # App name
DESC=PlexPy DESC=Tautulli
SETTINGS_LOADED=FALSE SETTINGS_LOADED=FALSE
. /lib/lsb/init-functions . /lib/lsb/init-functions
# Source PlexPy configuration # Source Tautulli configuration
if [ -f /etc/default/plexpy ]; then if [ -f /etc/default/tautulli ]; then
SETTINGS=/etc/default/plexpy SETTINGS=/etc/default/tautulli
else else
log_warning_msg "/etc/default/plexpy not found using default settings."; log_warning_msg "/etc/default/tautulli not found using default settings.";
fi fi
check_retval() { check_retval() {
@@ -84,32 +84,32 @@ load_settings() {
## The defaults ## The defaults
# Run as username # Run as username
RUN_AS=${HP_USER-plexpy} RUN_AS=${TAUTULLI_USER-tautulli}
# Path to app HP_HOME=path_to_app_PlexPy.py # Path to app TAUTULLI_HOME=path_to_app_Tautulli.py
APP_PATH=${HP_HOME-/opt/plexpy} APP_PATH=${TAUTULLI_HOME-/opt/Tautulli}
# Data directory where plexpy.db, cache and logs are stored # Data directory where plexpy.db, cache and logs are stored
DATA_DIR=${HP_DATA-/opt/plexpy} DATA_DIR=${TAUTULLI_DATA-/opt/Tautulli}
# Path to store PID file # Path to store PID file
PID_FILE=${HP_PIDFILE-/var/run/plexpy/plexpy.pid} PID_FILE=${TAUTULLI_PIDFILE-/var/run/tautulli/tautulli.pid}
# Path to python bin # Path to python bin
DAEMON=${PYTHON_BIN-/usr/bin/python} DAEMON=${PYTHON_BIN-/usr/bin/python}
# Extra daemon option like: HP_OPTS=" --config=/home/plexpy/config.ini" # Extra daemon option like: TAUTULLI_OPTS=" --config=/home/Tautulli/config.ini"
EXTRA_DAEMON_OPTS=${HP_OPTS-} EXTRA_DAEMON_OPTS=${TAUTULLI_OPTS-}
# Extra start-stop-daemon option like START_OPTS=" --group=users" # Extra start-stop-daemon option like START_OPTS=" --group=users"
EXTRA_SSD_OPTS=${SSD_OPTS-} EXTRA_SSD_OPTS=${SSD_OPTS-}
# Hardcoded port to run on, overrides config.ini settings # Hardcoded port to run on, overrides config.ini settings
[ -n "$HP_PORT" ] && { [ -n "$TAUTULLI_PORT" ] && {
PORT_OPTS=" --port=${HP_PORT} " PORT_OPTS=" --port=${TAUTULLI_PORT} "
} }
DAEMON_OPTS=" PlexPy.py --quiet --daemon --nolaunch --pidfile=${PID_FILE} --datadir=${DATA_DIR} ${PORT_OPTS}${EXTRA_DAEMON_OPTS}" DAEMON_OPTS=" Tautulli.py --quiet --daemon --nolaunch --pidfile=${PID_FILE} --datadir=${DATA_DIR} ${PORT_OPTS}${EXTRA_DAEMON_OPTS}"
SETTINGS_LOADED=TRUE SETTINGS_LOADED=TRUE
fi fi
@@ -162,7 +162,7 @@ handle_updates () {
return 0; } return 0; }
} }
start_plexpy () { start_tautulli () {
handle_pid handle_pid
handle_datadir handle_datadir
handle_updates handle_updates
@@ -175,7 +175,7 @@ start_plexpy () {
fi fi
} }
stop_plexpy () { stop_tautulli () {
if is_running; then if is_running; then
log_daemon_msg "Stopping $DESC" log_daemon_msg "Stopping $DESC"
start-stop-daemon -o --stop --pidfile $PID_FILE --retry 15 start-stop-daemon -o --stop --pidfile $PID_FILE --retry 15
@@ -187,14 +187,14 @@ stop_plexpy () {
case "$1" in case "$1" in
start) start)
start_plexpy start_tautulli
;; ;;
stop) stop)
stop_plexpy stop_tautulli
;; ;;
restart|force-reload) restart|force-reload)
stop_plexpy stop_tautulli
start_plexpy start_tautulli
;; ;;
status) status)
status_of_proc -p "$PID_FILE" "$DAEMON" "$DESC" status_of_proc -p "$PID_FILE" "$DAEMON" "$DESC"

View File

@@ -1,18 +1,18 @@
# plexpy # tautulli
# #
# This is a session/user job. Install this file into /usr/share/upstart/sessions # This is a session/user job. Install this file into /usr/share/upstart/sessions
# if plexpy is installed system wide, and into $XDG_CONFIG_HOME/upstart if # if Tautulli is installed system wide, and into $XDG_CONFIG_HOME/upstart if
# plexpy is installed per user. Change the executable path appropiately. # Tautulli is installed per user. Change the executable path appropiately.
start on desktop-start start on desktop-start
stop on desktop-end stop on desktop-end
env CONFIG=""$XDG_CONFIG_HOME"/plexpy" env CONFIG=""$XDG_CONFIG_HOME"/Tautulli"
env DATA=""$XDG_DATA_HOME"/plexpy" env DATA=""$XDG_DATA_HOME"/Tautulli"
pre-start script pre-start script
[ -d "$CONFIG" ] || mkdir -p "$CONFIG" [ -d "$CONFIG" ] || mkdir -p "$CONFIG"
[ -d "$DATA" ] || mkdir -p "$DATA" [ -d "$DATA" ] || mkdir -p "$DATA"
end script end script
exec PlexPy.py --nolaunch --config "$CONFIG"/config.ini --datadir "$DATA" exec Tautulli.py --nolaunch --config "$CONFIG"/config.ini --datadir "$DATA"

View File

@@ -240,7 +240,7 @@ def initialize(config_file):
# Get the previous release from the file # Get the previous release from the file
release_file = os.path.join(DATA_DIR, "release.lock") release_file = os.path.join(DATA_DIR, "release.lock")
PREV_RELEASE = common.VERSION_NUMBER PREV_RELEASE = common.RELEASE
if os.path.isfile(release_file): if os.path.isfile(release_file):
try: try:
with open(release_file, "r") as fp: with open(release_file, "r") as fp:
@@ -252,7 +252,7 @@ def initialize(config_file):
PREV_RELEASE = 'v1.4.25' PREV_RELEASE = 'v1.4.25'
# Check if the release was updated # Check if the release was updated
if common.VERSION_NUMBER != PREV_RELEASE: if common.RELEASE != PREV_RELEASE:
CONFIG.UPDATE_SHOW_CHANGELOG = 1 CONFIG.UPDATE_SHOW_CHANGELOG = 1
CONFIG.write() CONFIG.write()
_UPDATE = True _UPDATE = True
@@ -260,7 +260,7 @@ def initialize(config_file):
# Write current release version to file for update checking # Write current release version to file for update checking
try: try:
with open(release_file, "w") as fp: with open(release_file, "w") as fp:
fp.write(common.VERSION_NUMBER) fp.write(common.RELEASE)
except IOError as e: except IOError as e:
logger.error(u"Unable to write current release to file '%s': %s" % logger.error(u"Unable to write current release to file '%s': %s" %
(release_file, e)) (release_file, e))
@@ -1621,7 +1621,10 @@ def upgrade():
def shutdown(restart=False, update=False, checkout=False): def shutdown(restart=False, update=False, checkout=False):
cherrypy.engine.exit() cherrypy.engine.exit()
if SCHED.running:
SCHED.shutdown(wait=False) SCHED.shutdown(wait=False)
if activity_handler.ACTIVITY_SCHED.running:
activity_handler.ACTIVITY_SCHED.shutdown(wait=False) activity_handler.ACTIVITY_SCHED.shutdown(wait=False)
# Stop the notification threads # Stop the notification threads
@@ -1693,7 +1696,7 @@ def initialize_tracker():
data = { data = {
'dataSource': 'server', 'dataSource': 'server',
'appName': 'Tautulli', 'appName': 'Tautulli',
'appVersion': common.VERSION_NUMBER, 'appVersion': common.RELEASE,
'appId': plexpy.INSTALL_TYPE, 'appId': plexpy.INSTALL_TYPE,
'appInstallerId': plexpy.CONFIG.GIT_BRANCH, 'appInstallerId': plexpy.CONFIG.GIT_BRANCH,
'dimension1': '{} {}'.format(common.PLATFORM, common.PLATFORM_VERSION), # App Platform 'dimension1': '{} {}'.format(common.PLATFORM, common.PLATFORM_VERSION), # App Platform
@@ -1702,7 +1705,8 @@ def initialize_tracker():
'noninteractive': True 'noninteractive': True
} }
tracker = Tracker.create('UA-111522699-2', client_id=CONFIG.PMS_UUID, hash_client_id=True) tracker = Tracker.create('UA-111522699-2', client_id=CONFIG.PMS_UUID, hash_client_id=True,
user_agent=common.USER_AGENT)
tracker.set(data) tracker.set(data)
return tracker return tracker
@@ -1721,4 +1725,7 @@ def analytics_event(category, action, label=None, value=None, **kwargs):
data.update(kwargs) data.update(kwargs)
if TRACKER: if TRACKER:
try:
TRACKER.send('event', data) TRACKER.send('event', data)
except Exception as e:
logger.warn(u"Failed to send analytics event for category '%s', action '%s': %s" % (category, action, e))

View File

@@ -74,9 +74,22 @@ class ActivityHandler(object):
return None return None
def update_db_session(self, session=None): def update_db_session(self, session=None):
if session is None:
session = self.get_live_session()
if session:
# Update our session temp table values # Update our session temp table values
monitor_proc = activity_processor.ActivityProcessor() ap = activity_processor.ActivityProcessor()
monitor_proc.write_session(session=session, notify=False) ap.write_session(session=session, notify=False)
self.set_session_state()
def set_session_state(self):
ap = activity_processor.ActivityProcessor()
ap.set_session_state(session_key=self.get_session_key(),
state=self.timeline['state'],
view_offset=self.timeline['viewOffset'],
stopped=int(time.time()))
def on_start(self): def on_start(self):
if self.is_valid_session(): if self.is_valid_session():
@@ -114,10 +127,7 @@ class ActivityHandler(object):
# Update the session state and viewOffset # Update the session state and viewOffset
# Set force_stop to true to disable the state set # Set force_stop to true to disable the state set
if not force_stop: if not force_stop:
ap.set_session_state(session_key=self.get_session_key(), self.set_session_state()
state=self.timeline['state'],
view_offset=self.timeline['viewOffset'],
stopped=int(time.time()))
# Retrieve the session data from our temp table # Retrieve the session data from our temp table
db_session = ap.get_session_by_key(session_key=self.get_session_key()) db_session = ap.get_session_by_key(session_key=self.get_session_key())
@@ -150,10 +160,7 @@ class ActivityHandler(object):
ap.set_session_last_paused(session_key=self.get_session_key(), timestamp=int(time.time())) ap.set_session_last_paused(session_key=self.get_session_key(), timestamp=int(time.time()))
# Update the session state and viewOffset # Update the session state and viewOffset
ap.set_session_state(session_key=self.get_session_key(), self.update_db_session()
state=self.timeline['state'],
view_offset=self.timeline['viewOffset'],
stopped=int(time.time()))
# Retrieve the session data from our temp table # Retrieve the session data from our temp table
db_session = ap.get_session_by_key(session_key=self.get_session_key()) db_session = ap.get_session_by_key(session_key=self.get_session_key())
@@ -170,10 +177,7 @@ class ActivityHandler(object):
ap.set_session_last_paused(session_key=self.get_session_key(), timestamp=None) ap.set_session_last_paused(session_key=self.get_session_key(), timestamp=None)
# Update the session state and viewOffset # Update the session state and viewOffset
ap.set_session_state(session_key=self.get_session_key(), self.update_db_session()
state=self.timeline['state'],
view_offset=self.timeline['viewOffset'],
stopped=int(time.time()))
# Retrieve the session data from our temp table # Retrieve the session data from our temp table
db_session = ap.get_session_by_key(session_key=self.get_session_key()) db_session = ap.get_session_by_key(session_key=self.get_session_key())
@@ -198,10 +202,7 @@ class ActivityHandler(object):
buffer_last_triggered = ap.get_session_buffer_trigger_time(self.get_session_key()) buffer_last_triggered = ap.get_session_buffer_trigger_time(self.get_session_key())
# Update the session state and viewOffset # Update the session state and viewOffset
ap.set_session_state(session_key=self.get_session_key(), self.update_db_session()
state=self.timeline['state'],
view_offset=self.timeline['viewOffset'],
stopped=int(time.time()))
time_since_last_trigger = 0 time_since_last_trigger = 0
if buffer_last_triggered: if buffer_last_triggered:
@@ -243,9 +244,7 @@ class ActivityHandler(object):
# Update the session in our temp session table # Update the session in our temp session table
# if the last set temporary stopped time exceeds 15 seconds # if the last set temporary stopped time exceeds 15 seconds
if int(time.time()) - db_session['stopped'] > 60: if int(time.time()) - db_session['stopped'] > 60:
session = self.get_live_session() self.update_db_session()
if session:
self.update_db_session(session=session)
# Start our state checks # Start our state checks
if this_state != last_state: if this_state != last_state:

View File

@@ -167,8 +167,8 @@ class API2:
""" """
logfile = os.path.join(plexpy.CONFIG.LOG_DIR, logger.FILENAME) logfile = os.path.join(plexpy.CONFIG.LOG_DIR, logger.FILENAME)
templog = [] templog = []
start = int(kwargs.get('start', 0)) start = int(start)
end = int(kwargs.get('end', 0)) end = int(end)
if regex: if regex:
logger.api_debug(u'Tautulli APIv2 :: Filtering log using regex %s' % regex) logger.api_debug(u'Tautulli APIv2 :: Filtering log using regex %s' % regex)

View File

@@ -19,13 +19,12 @@ from collections import OrderedDict
import version import version
# Identify Our Application # Identify Our Application
USER_AGENT = 'Tautulli/-' + version.PLEXPY_BRANCH + ' v' + version.PLEXPY_RELEASE_VERSION + ' (' + platform.system() + \
' ' + platform.release() + ')'
PLATFORM = platform.system() PLATFORM = platform.system()
PLATFORM_VERSION = platform.release() PLATFORM_VERSION = platform.release()
BRANCH = version.PLEXPY_BRANCH BRANCH = version.PLEXPY_BRANCH
VERSION_NUMBER = version.PLEXPY_RELEASE_VERSION RELEASE = version.PLEXPY_RELEASE_VERSION
USER_AGENT = 'Tautulli/{} ({} {})'.format(RELEASE, PLATFORM, PLATFORM_VERSION)
DEFAULT_USER_THUMB = "interfaces/default/images/gravatar-default-80x80.png" DEFAULT_USER_THUMB = "interfaces/default/images/gravatar-default-80x80.png"
DEFAULT_POSTER_THUMB = "interfaces/default/images/poster.png" DEFAULT_POSTER_THUMB = "interfaces/default/images/poster.png"

View File

@@ -23,7 +23,7 @@ import time
import plexpy import plexpy
import logger import logger
FILENAME = "plexpy.db" FILENAME = "tautulli.db"
db_lock = threading.Lock() db_lock = threading.Lock()
@@ -63,9 +63,9 @@ def make_backup(cleanup=False, scheduler=False):
""" Makes a backup of db, removes all but the last 5 backups """ """ Makes a backup of db, removes all but the last 5 backups """
if scheduler: if scheduler:
backup_file = 'plexpy.backup-%s.sched.db' % arrow.now().format('YYYYMMDDHHmmss') backup_file = 'tautulli.backup-%s.sched.db' % arrow.now().format('YYYYMMDDHHmmss')
else: else:
backup_file = 'plexpy.backup-%s.db' % arrow.now().format('YYYYMMDDHHmmss') backup_file = 'tautulli.backup-%s.db' % arrow.now().format('YYYYMMDDHHmmss')
backup_folder = plexpy.CONFIG.BACKUP_DIR backup_folder = plexpy.CONFIG.BACKUP_DIR
backup_file_fp = os.path.join(backup_folder, backup_file) backup_file_fp = os.path.join(backup_folder, backup_file)

View File

@@ -646,7 +646,7 @@ def whois_lookup(ip_address):
countries = ipwhois.utils.get_countries() countries = ipwhois.utils.get_countries()
nets = whois['nets'] nets = whois['nets']
for net in nets: for net in nets:
net['country'] = countries[net['country']] net['country'] = countries.get(net['country'])
if net['postal_code']: if net['postal_code']:
net['postal_code'] = net['postal_code'].replace('-', ' ') net['postal_code'] = net['postal_code'].replace('-', ' ')
except ValueError as e: except ValueError as e:

View File

@@ -41,7 +41,7 @@ class HTTPHandler(object):
self.headers = {'X-Plex-Device-Name': 'Tautulli', self.headers = {'X-Plex-Device-Name': 'Tautulli',
'X-Plex-Product': 'Tautulli', 'X-Plex-Product': 'Tautulli',
'X-Plex-Version': plexpy.common.VERSION_NUMBER, 'X-Plex-Version': plexpy.common.RELEASE,
'X-Plex-Platform': plexpy.common.PLATFORM, 'X-Plex-Platform': plexpy.common.PLATFORM,
'X-Plex-Platform-Version': plexpy.common.PLATFORM_VERSION, 'X-Plex-Platform-Version': plexpy.common.PLATFORM_VERSION,
'X-Plex-Client-Identifier': plexpy.CONFIG.PMS_UUID, 'X-Plex-Client-Identifier': plexpy.CONFIG.PMS_UUID,

View File

@@ -744,7 +744,7 @@ class Libraries(object):
logger.warn(u"Tautulli Libraries :: Unable to retrieve library %s from database. Requesting library list refresh." logger.warn(u"Tautulli Libraries :: Unable to retrieve library %s from database. Requesting library list refresh."
% section_id) % section_id)
# Let's first refresh the libraries list to make sure the library isn't newly added and not in the db yet # Let's first refresh the libraries list to make sure the library isn't newly added and not in the db yet
pmsconnect.refresh_libraries() refresh_libraries()
library_details = get_library_details(section_id=section_id) library_details = get_library_details(section_id=section_id)

View File

@@ -30,8 +30,8 @@ import plexpy
import helpers import helpers
# These settings are for file logging only # These settings are for file logging only
FILENAME = "plexpy.log" FILENAME = "tautulli.log"
FILENAME_API = "plexpy_api.log" FILENAME_API = "tautulli_api.log"
FILENAME_PLEX_WEBSOCKET = "plex_websocket.log" FILENAME_PLEX_WEBSOCKET = "plex_websocket.log"
MAX_SIZE = 5000000 # 5 MB MAX_SIZE = 5000000 # 5 MB
MAX_FILES = 5 MAX_FILES = 5
@@ -39,9 +39,9 @@ MAX_FILES = 5
_BLACKLIST_WORDS = set() _BLACKLIST_WORDS = set()
# Tautulli logger # Tautulli logger
logger = logging.getLogger("plexpy") logger = logging.getLogger("tautulli")
# Tautulli API logger # Tautulli API logger
logger_api = logging.getLogger("plexpy_api") logger_api = logging.getLogger("tautulli_api")
# Tautulli websocket logger # Tautulli websocket logger
logger_plex_websocket = logging.getLogger("plex_websocket") logger_plex_websocket = logging.getLogger("plex_websocket")
@@ -178,9 +178,9 @@ def initMultiprocessing():
def initLogger(console=False, log_dir=False, verbose=False): def initLogger(console=False, log_dir=False, verbose=False):
""" """
Setup logging for Tautulli. It uses the logger instance with the name Setup logging for Tautulli. It uses the logger instance with the name
'plexpy'. Three log handlers are added: 'tautulli'. Three log handlers are added:
* RotatingFileHandler: for the file plexpy.log * RotatingFileHandler: for the file tautulli.log
* LogListHandler: for Web UI * LogListHandler: for Web UI
* StreamHandler: for console (if console) * StreamHandler: for console (if console)

View File

@@ -677,7 +677,7 @@ def build_media_notify_params(notify_action=None, session=None, timeline=None, m
available_params = { available_params = {
# Global paramaters # Global paramaters
'tautulli_version': common.VERSION_NUMBER, 'tautulli_version': common.RELEASE,
'tautulli_remote': plexpy.CONFIG.GIT_REMOTE, 'tautulli_remote': plexpy.CONFIG.GIT_REMOTE,
'tautulli_branch': plexpy.CONFIG.GIT_BRANCH, 'tautulli_branch': plexpy.CONFIG.GIT_BRANCH,
'tautulli_commit': plexpy.CURRENT_VERSION, 'tautulli_commit': plexpy.CURRENT_VERSION,
@@ -877,7 +877,7 @@ def build_server_notify_params(notify_action=None, **kwargs):
available_params = { available_params = {
# Global paramaters # Global paramaters
'tautulli_version': common.VERSION_NUMBER, 'tautulli_version': common.RELEASE,
'tautulli_remote': plexpy.CONFIG.GIT_REMOTE, 'tautulli_remote': plexpy.CONFIG.GIT_REMOTE,
'tautulli_branch': plexpy.CONFIG.GIT_BRANCH, 'tautulli_branch': plexpy.CONFIG.GIT_BRANCH,
'tautulli_commit': plexpy.CURRENT_VERSION, 'tautulli_commit': plexpy.CURRENT_VERSION,

View File

@@ -1932,7 +1932,7 @@ class IFTTT(Notifier):
""" """
NAME = 'IFTTT' NAME = 'IFTTT'
_DEFAULT_CONFIG = {'key': '', _DEFAULT_CONFIG = {'key': '',
'event': 'plexpy' 'event': 'tautulli'
} }
def agent_notify(self, subject='', body='', action='', **kwargs): def agent_notify(self, subject='', body='', action='', **kwargs):
@@ -2135,7 +2135,7 @@ class MQTT(Notifier):
'protocol': 'MQTTv311', 'protocol': 'MQTTv311',
'username': '', 'username': '',
'password': '', 'password': '',
'clientid': 'plexpy', 'clientid': 'tautulli',
'topic': '', 'topic': '',
'qos': 1, 'qos': 1,
'retain': 0, 'retain': 0,
@@ -2320,7 +2320,7 @@ class OSX(Notifier):
self.objc.classAddMethod(cls, SEL, new_IMP) self.objc.classAddMethod(cls, SEL, new_IMP)
def _swizzled_bundleIdentifier(self, original, swizzled): def _swizzled_bundleIdentifier(self, original, swizzled):
return 'ade.plexpy.osxnotify' return 'ade.tautulli.osxnotify'
def agent_notify(self, subject='', body='', action='', **kwargs): def agent_notify(self, subject='', body='', action='', **kwargs):
@@ -2691,8 +2691,10 @@ class PUSHOVER(Notifier):
_DEFAULT_CONFIG = {'api_token': '', _DEFAULT_CONFIG = {'api_token': '',
'key': '', 'key': '',
'html_support': 1, 'html_support': 1,
'priority': 0,
'sound': '', 'sound': '',
'priority': 0,
'retry': 30,
'expire': 3600,
'incl_url': 1, 'incl_url': 1,
'incl_subject': 1, 'incl_subject': 1,
'incl_poster': 0, 'incl_poster': 0,
@@ -2713,6 +2715,10 @@ class PUSHOVER(Notifier):
if self.config['incl_subject']: if self.config['incl_subject']:
data['title'] = subject.encode("utf-8") data['title'] = subject.encode("utf-8")
if self.config['priority'] == 2:
data['retry'] = max(30, self.config['retry'])
data['expire'] = max(30, self.config['expire'])
headers = {'Content-type': 'application/x-www-form-urlencoded'} headers = {'Content-type': 'application/x-www-form-urlencoded'}
files = {} files = {}
@@ -2789,6 +2795,13 @@ class PUSHOVER(Notifier):
'description': 'Your Pushover user or group key.', 'description': 'Your Pushover user or group key.',
'input_type': 'text' 'input_type': 'text'
}, },
{'label': 'Sound',
'value': self.config['sound'],
'name': 'pushover_sound',
'description': 'Set the notification sound. Leave blank for the default sound.',
'input_type': 'select',
'select_options': self.get_sounds()
},
{'label': 'Priority', {'label': 'Priority',
'value': self.config['priority'], 'value': self.config['priority'],
'name': 'pushover_priority', 'name': 'pushover_priority',
@@ -2796,12 +2809,19 @@ class PUSHOVER(Notifier):
'input_type': 'select', 'input_type': 'select',
'select_options': {-2: -2, -1: -1, 0: 0, 1: 1, 2: 2} 'select_options': {-2: -2, -1: -1, 0: 0, 1: 1, 2: 2}
}, },
{'label': 'Sound', {'label': 'Retry Interval',
'value': self.config['sound'], 'value': self.config['retry'],
'name': 'pushover_sound', 'name': 'pushover_retry',
'description': 'Set the notification sound. Leave blank for the default sound.', 'description': 'Set the interval in seconds to keep retrying the notification.<br>'
'input_type': 'select', 'Note: For priority 2 only. Minimum 30 seconds.',
'select_options': self.get_sounds() 'input_type': 'number'
},
{'label': 'Expire Duration',
'value': self.config['expire'],
'name': 'pushover_expire',
'description': 'Set the duration in seconds when the notification will stop retrying.<br>'
'Note: For priority 2 only. Minimum 30 seconds.',
'input_type': 'number'
}, },
{'label': 'Enable HTML Support', {'label': 'Enable HTML Support',
'value': self.config['html_support'], 'value': self.config['html_support'],

View File

@@ -1404,6 +1404,10 @@ class PmsConnect(object):
'location': 'wan' if player_details['local'] == '0' else 'lan' 'location': 'wan' if player_details['local'] == '0' else 'lan'
} }
# Check if using Plex Relay
session_details['relay'] = int(session_details['location'] != 'lan'
and player_details['ip_address_public'] == '127.0.0.1')
# Get the transcode details # Get the transcode details
if session.getElementsByTagName('TranscodeSession'): if session.getElementsByTagName('TranscodeSession'):
transcode_info = session.getElementsByTagName('TranscodeSession')[0] transcode_info = session.getElementsByTagName('TranscodeSession')[0]
@@ -2134,10 +2138,12 @@ class PmsConnect(object):
sort_type = '&type=10' sort_type = '&type=10'
elif section_type == 'photo': elif section_type == 'photo':
sort_type = '' sort_type = ''
elif section_type == 'photoAlbum': elif section_type == 'photo_album':
sort_type = '&type=14' sort_type = '&type=14'
elif section_type == 'picture': elif section_type == 'picture':
sort_type = '&type=13' sort_type = '&type=13&clusterZoomLevel=1'
elif section_type == 'clip':
sort_type = '&type=12&clusterZoomLevel=1'
else: else:
sort_type = '' sort_type = ''
@@ -2280,12 +2286,12 @@ class PmsConnect(object):
library_stats.update(child_stats) library_stats.update(child_stats)
if section_type == 'photo': if section_type == 'photo':
parent_list = self.get_library_children_details(section_id=section_id, section_type='photoAlbum', count='1') parent_list = self.get_library_children_details(section_id=section_id, section_type='picture', count='1')
if parent_list: if parent_list:
parent_stats = {'parent_count': parent_list['library_count']} parent_stats = {'parent_count': parent_list['library_count']}
library_stats.update(parent_stats) library_stats.update(parent_stats)
child_list = self.get_library_children_details(section_id=section_id, section_type='picture', count='1') child_list = self.get_library_children_details(section_id=section_id, section_type='clip', count='1')
if child_list: if child_list:
child_stats = {'child_count': child_list['library_count']} child_stats = {'child_count': child_list['library_count']}
library_stats.update(child_stats) library_stats.update(child_stats)

View File

@@ -79,7 +79,7 @@ def get_session_library_filters_type(filters, media_type=None):
filters = filters.get('filter_tv', ()) filters = filters.get('filter_tv', ())
elif media_type == 'artist' or media_type == 'album' or media_type == 'track': elif media_type == 'artist' or media_type == 'album' or media_type == 'track':
filters = filters.get('filter_music', ()) filters = filters.get('filter_music', ())
elif media_type == 'photo' or media_type == 'photoAlbum' or media_type == 'picture': elif media_type == 'photo' or media_type == 'photo_album' or media_type == 'picture' or media_type == 'clip':
filters = filters.get('filter_photos', ()) filters = filters.get('filter_photos', ())
else: else:
filters = filters.get('filter_all', ()) filters = filters.get('filter_all', ())

View File

@@ -1,2 +1,2 @@
PLEXPY_BRANCH = "beta" PLEXPY_BRANCH = "beta"
PLEXPY_RELEASE_VERSION = "v2.0.20-beta" PLEXPY_RELEASE_VERSION = "v2.0.22-beta"

View File

@@ -20,9 +20,9 @@ import subprocess
import tarfile import tarfile
import plexpy import plexpy
import common
import logger import logger
import request import request
import version
def runGit(args): def runGit(args):
@@ -65,7 +65,7 @@ def runGit(args):
def getVersion(): def getVersion():
if version.PLEXPY_BRANCH.startswith('win32build'): if common.BRANCH.startswith('win32build'):
plexpy.INSTALL_TYPE = 'win' plexpy.INSTALL_TYPE = 'win'
# Don't have a way to update exe yet, but don't want to set VERSION to None # Don't have a way to update exe yet, but don't want to set VERSION to None
@@ -120,15 +120,15 @@ def getVersion():
version_file = os.path.join(plexpy.PROG_DIR, 'version.txt') version_file = os.path.join(plexpy.PROG_DIR, 'version.txt')
if not os.path.isfile(version_file): if not os.path.isfile(version_file):
return None, 'origin', 'master' return None, 'origin', common.BRANCH
with open(version_file, 'r') as f: with open(version_file, 'r') as f:
current_version = f.read().strip(' \n\r') current_version = f.read().strip(' \n\r')
if current_version: if current_version:
return current_version, plexpy.CONFIG.GIT_REMOTE, plexpy.CONFIG.GIT_BRANCH return current_version, 'origin', common.BRANCH
else: else:
return None, 'origin', 'master' return None, 'origin', common.BRANCH
def checkGithub(auto_update=False): def checkGithub(auto_update=False):

View File

@@ -63,7 +63,7 @@ def serve_template(templatename, **kwargs):
http_root = plexpy.HTTP_ROOT http_root = plexpy.HTTP_ROOT
server_name = plexpy.CONFIG.PMS_NAME server_name = plexpy.CONFIG.PMS_NAME
cache_param = '?' + (plexpy.CURRENT_VERSION or common.VERSION_NUMBER) cache_param = '?' + (plexpy.CURRENT_VERSION or common.RELEASE)
_session = get_session_info() _session = get_session_info()
@@ -2293,7 +2293,7 @@ class WebInterface(object):
filtered = [] filtered = []
fa = filt.append fa = filt.append
if logfile == "plexpy_api": if logfile == "tautulli_api":
filename = logger.FILENAME_API filename = logger.FILENAME_API
elif logfile == "plex_websocket": elif logfile == "plex_websocket":
filename = logger.FILENAME_PLEX_WEBSOCKET filename = logger.FILENAME_PLEX_WEBSOCKET
@@ -2496,7 +2496,7 @@ class WebInterface(object):
@cherrypy.tools.json_out() @cherrypy.tools.json_out()
@requireAuth(member_of("admin")) @requireAuth(member_of("admin"))
def delete_logs(self, logfile='', **kwargs): def delete_logs(self, logfile='', **kwargs):
if logfile == "plexpy_api": if logfile == "tautulli_api":
filename = logger.FILENAME_API filename = logger.FILENAME_API
elif logfile == "plex_websocket": elif logfile == "plex_websocket":
filename = logger.FILENAME_PLEX_WEBSOCKET filename = logger.FILENAME_PLEX_WEBSOCKET
@@ -2538,7 +2538,7 @@ class WebInterface(object):
@cherrypy.expose @cherrypy.expose
@requireAuth(member_of("admin")) @requireAuth(member_of("admin"))
def logFile(self, logfile='', **kwargs): def logFile(self, logfile='', **kwargs):
if logfile == "plexpy_api": if logfile == "tautulli_api":
filename = logger.FILENAME_API filename = logger.FILENAME_API
elif logfile == "plex_websocket": elif logfile == "plex_websocket":
filename = logger.FILENAME_PLEX_WEBSOCKET filename = logger.FILENAME_PLEX_WEBSOCKET
@@ -3581,11 +3581,11 @@ class WebInterface(object):
} }
elif plexpy.COMMITS_BEHIND > 0 and plexpy.common.BRANCH in ('master', 'beta') and \ elif plexpy.COMMITS_BEHIND > 0 and plexpy.common.BRANCH in ('master', 'beta') and \
plexpy.common.VERSION_NUMBER != plexpy.LATEST_RELEASE: plexpy.common.RELEASE != plexpy.LATEST_RELEASE:
return {'result': 'success', return {'result': 'success',
'update': True, 'update': True,
'release': True, 'release': True,
'message': 'A new release (%) of Tautulli is available.' % plexpy.LATEST_RELEASE, 'message': 'A new release (%s) of Tautulli is available.' % plexpy.LATEST_RELEASE,
'latest_release': plexpy.LATEST_RELEASE, 'latest_release': plexpy.LATEST_RELEASE,
'release_url': helpers.anon_url( 'release_url': helpers.anon_url(
'https://github.com/%s/%s/releases/tag/%s' 'https://github.com/%s/%s/releases/tag/%s'
@@ -3668,7 +3668,7 @@ class WebInterface(object):
latest_only = (latest_only == 'true') latest_only = (latest_only == 'true')
since_prev_release = (since_prev_release == 'true') since_prev_release = (since_prev_release == 'true')
if since_prev_release and plexpy.PREV_RELEASE == common.VERSION_NUMBER: if since_prev_release and plexpy.PREV_RELEASE == common.RELEASE:
latest_only = True latest_only = True
since_prev_release = False since_prev_release = False
@@ -3912,7 +3912,7 @@ class WebInterface(object):
@addtoapi() @addtoapi()
def download_log(self, logfile='', **kwargs): def download_log(self, logfile='', **kwargs):
""" Download the Tautulli log file. """ """ Download the Tautulli log file. """
if logfile == "plexpy_api": if logfile == "tautulli_api":
filename = logger.FILENAME_API filename = logger.FILENAME_API
log = logger.logger_api log = logger.logger_api
elif logfile == "plex_websocket": elif logfile == "plex_websocket":
@@ -4706,6 +4706,7 @@ class WebInterface(object):
"quality_profile": "Original", "quality_profile": "Original",
"rating": "7.8", "rating": "7.8",
"rating_key": "153037", "rating_key": "153037",
"relay": 0,
"section_id": "2", "section_id": "2",
"session_id": "helf15l3rxgw01xxe0jf3l3d", "session_id": "helf15l3rxgw01xxe0jf3l3d",
"session_key": "27", "session_key": "27",

View File

@@ -72,7 +72,7 @@ def initialize(options):
if plexpy.CONFIG.HTTP_PLEX_ADMIN: if plexpy.CONFIG.HTTP_PLEX_ADMIN:
login_allowed.append("Plex admin") login_allowed.append("Plex admin")
logger.info(u"Tautulli WebStart :: Web server authentication is enabled: %s allowed", ' and '.join(login_allowed)) logger.info(u"Tautulli WebStart :: Web server authentication is enabled: %s.", ' and '.join(login_allowed))
if options['http_basic_auth']: if options['http_basic_auth']:
auth_enabled = False auth_enabled = False
@@ -122,6 +122,7 @@ def initialize(options):
'/images': { '/images': {
'tools.staticdir.on': True, 'tools.staticdir.on': True,
'tools.staticdir.dir': "interfaces/default/images", 'tools.staticdir.dir': "interfaces/default/images",
'tools.staticdir.content_types': {'svg': 'image/svg+xml'},
'tools.caching.on': True, 'tools.caching.on': True,
'tools.caching.force': True, 'tools.caching.force': True,
'tools.caching.delay': 0, 'tools.caching.delay': 0,