Browse Source

Merge pull request #1235 from causefx/v2-develop

V2 develop
causefx 6 years ago
parent
commit
e526dc715d

+ 13 - 2
api/config/default.php

@@ -230,6 +230,17 @@ return array(
 	'debugErrors' => false,
 	'healthChecksURL' => 'https://healthchecks.io/api/v1/checks/',
 	'gaTrackingID' => '',
-	'loginAttempts' => '3',
-	'loginLockout' => '60000'
+	'loginAttempts' => '5',
+	'loginLockout' => '60000',
+	'ombiDefaultFilterAvailable' => true,
+	'ombiDefaultFilterUnavailable' => true,
+	'ombiDefaultFilterApproved' => true,
+	'ombiDefaultFilterUnapproved' => true,
+	'ombiDefaultFilterDenied' => true,
+	'selfSignedCert' => '',
+	'homepagePlexRecentlyAddedMethod' => 'legacy',
+	'authProxyEnabled' => false,
+	'authProxyHeaderName' => '',
+	'authProxyWhitelist' => '',
+	'ignoreTFALocal' => false
 );

+ 6 - 0
api/functions.php

@@ -36,6 +36,12 @@ if (isset($GLOBALS['dbLocation'])) {
 			$GLOBALS['commit'] = $GLOBALS['quickCommit'];
 		}
 	}
+	// Oauth?
+	if($GLOBALS['authProxyEnabled'] && $GLOBALS['authProxyHeaderName'] !== '' && $GLOBALS['authProxyWhitelist'] !== ''){
+		if(isset(getallheaders()[$GLOBALS['authProxyHeaderName']])){
+			coookieSeconds('set', 'organizrOAuth', 'true', 20000, false);
+		}
+	}
 	//Upgrade Check
 	upgradeCheck();
 }

+ 35 - 10
api/functions/api-functions.php

@@ -89,6 +89,23 @@ function login($array)
 			'database' => $GLOBALS['dbLocation'] . $GLOBALS['dbName'],
 		]);
 		$authSuccess = false;
+		$authProxy = false;
+		if($GLOBALS['authProxyEnabled'] && $GLOBALS['authProxyHeaderName'] !== '' && $GLOBALS['authProxyWhitelist'] !== ''){
+			if(isset(getallheaders()[$GLOBALS['authProxyHeaderName']])){
+				$usernameHeader = isset(getallheaders()[$GLOBALS['authProxyHeaderName']]) ? getallheaders()[$GLOBALS['authProxyHeaderName']] : $username;
+				writeLog('success', 'Auth Proxy Function - Starting Verification for IP: ' . userIP() . ' for request on: ' . $_SERVER['REMOTE_ADDR'] . ' against IP/Subnet: ' . $GLOBALS['authProxyWhitelist'], $usernameHeader);
+				$whitelistRange = analyzeIP($GLOBALS['authProxyWhitelist']);
+				$from = $whitelistRange['from'];
+				$to = $whitelistRange['to'];
+				$authProxy = authProxyRangeCheck($from,$to);
+				$username = ($authProxy) ? $usernameHeader : $username;
+				if($authProxy){
+					writeLog('success', 'Auth Proxy Function - IP: ' . userIP() . ' has been verified', $usernameHeader);
+				}else{
+					writeLog('error', 'Auth Proxy Function - IP: ' . userIP() . ' has failed verification', $usernameHeader);
+				}
+			}
+		}
 		$function = 'plugin_auth_' . $GLOBALS['authBackend'];
 		if (!$oAuth) {
 			$result = $database->fetch('SELECT * FROM users WHERE username = ? COLLATE NOCASE OR email = ? COLLATE NOCASE', $username, $username);
@@ -112,6 +129,7 @@ function login($array)
 						}
 					}
 			}
+			$authSuccess = ($authProxy) ? true : $authSuccess;
 		} else {
 			// Has oAuth Token!
 			switch ($oAuthType) {
@@ -139,7 +157,7 @@ function login($array)
 		if ($authSuccess) {
 			// Make sure user exists in database
 			$userExists = false;
-			$passwordMatches = ($oAuth) ? true : false;
+			$passwordMatches = ($oAuth || $authProxy) ? true : false;
 			$token = (is_array($authSuccess) && isset($authSuccess['token']) ? $authSuccess['token'] : '');
 			if ($result['username']) {
 				$userExists = true;
@@ -170,15 +188,22 @@ function login($array)
 				}
 				// 2FA might go here
 				if ($result['auth_service'] !== 'internal' && strpos($result['auth_service'], '::') !== false) {
-					$TFA = explode('::', $result['auth_service']);
-					// Is code with login info?
-					if ($tfaCode == '') {
-						return '2FA';
-					} else {
-						if (!verify2FA($TFA[1], $tfaCode, $TFA[0])) {
-							writeLoginLog($username, 'error');
-							writeLog('error', 'Login Function - Wrong 2FA', $username);
-							return '2FA-incorrect';
+					$tfaProceed = true;
+					// Add check for local or not
+					if($GLOBALS['ignoreTFALocal'] !== false) {
+						$tfaProceed = (isLocal()) ? false : true;
+					}
+					if($tfaProceed) {
+						$TFA = explode('::', $result['auth_service']);
+						// Is code with login info?
+						if ($tfaCode == '') {
+							return '2FA';
+						} else {
+							if (!verify2FA($TFA[1], $tfaCode, $TFA[0])) {
+								writeLoginLog($username, 'error');
+								writeLog('error', 'Login Function - Wrong 2FA', $username);
+								return '2FA-incorrect';
+							}
 						}
 					}
 				}

+ 21 - 18
api/functions/homepage-connect-functions.php

@@ -548,11 +548,11 @@ function plexConnect($action, $key = null)
 				$resolve = false;
 				break;
 			case 'recent':
-				//$url = $url . "/library/recentlyAdded?X-Plex-Token=" . $GLOBALS['plexToken'] . "&limit=" . $GLOBALS['homepageRecentLimit'];
-				$urls['movie'] = $url . "/hubs/home/recentlyAdded?X-Plex-Token=" . $GLOBALS['plexToken'] . "&X-Plex-Container-Start=0&X-Plex-Container-Size=" . $GLOBALS['homepageRecentLimit'] . "&type=1";
-				$urls['tv'] = $url . "/hubs/home/recentlyAdded?X-Plex-Token=" . $GLOBALS['plexToken'] . "&X-Plex-Container-Start=0&X-Plex-Container-Size=" . $GLOBALS['homepageRecentLimit'] . "&type=2";
-				$urls['music'] = $url . "/hubs/home/recentlyAdded?X-Plex-Token=" . $GLOBALS['plexToken'] . "&X-Plex-Container-Start=0&X-Plex-Container-Size=" . $GLOBALS['homepageRecentLimit'] . "&type=8";
-				$multipleURL = true;
+					//$url = $url . "/library/recentlyAdded?X-Plex-Token=" . $GLOBALS['plexToken'] . "&limit=" . $GLOBALS['homepageRecentLimit'];
+					$urls['movie'] = $url . "/hubs/home/recentlyAdded?X-Plex-Token=" . $GLOBALS['plexToken'] . "&X-Plex-Container-Start=0&X-Plex-Container-Size=" . $GLOBALS['homepageRecentLimit'] . "&type=1";
+					$urls['tv'] = $url . "/hubs/home/recentlyAdded?X-Plex-Token=" . $GLOBALS['plexToken'] . "&X-Plex-Container-Start=0&X-Plex-Container-Size=" . $GLOBALS['homepageRecentLimit'] . "&type=2";
+					$urls['music'] = $url . "/hubs/home/recentlyAdded?X-Plex-Token=" . $GLOBALS['plexToken'] . "&X-Plex-Container-Start=0&X-Plex-Container-Size=" . $GLOBALS['homepageRecentLimit'] . "&type=8";
+					$multipleURL = true;
 				break;
 			case 'metadata':
 				$url = $url . "/library/metadata/" . $key . "?X-Plex-Token=" . $GLOBALS['plexToken'];
@@ -739,7 +739,7 @@ function jdownloaderConnect()
 {
     if ($GLOBALS['homepageJdownloaderEnabled'] && !empty($GLOBALS['jdownloaderURL']) && qualifyRequest($GLOBALS['homepageJdownloaderAuth'])) {
         $url = qualifyURL($GLOBALS['jdownloaderURL']);
-        $url = $url . '/';
+
         try {
             $options = (localURL($url)) ? array('verify' => false) : array();
             $response = Requests::get($url, array(), $options);
@@ -748,23 +748,26 @@ function jdownloaderConnect()
                 $packages = $temp['packages'];
                 if ($packages['downloader']) {
                     $api['content']['queueItems'] = $packages['downloader'];
-                } else {
+                }else{
                     $api['content']['queueItems'] = [];
                 }
-                $grabbed = array();
                 if ($packages['linkgrabber_decrypted']) {
-                    $grabbed = array_merge($grabbed, $packages['linkgrabber_decrypted']);
+                    $api['content']['grabberItems'] = $packages['linkgrabber_decrypted'];
+                }else{
+                    $api['content']['grabberItems'] = [];
                 }
                 if ($packages['linkgrabber_failed']) {
-                    $grabbed = array_merge($grabbed, $packages['linkgrabber_failed']);
+                    $api['content']['encryptedItems'] = $packages['linkgrabber_failed'];
+                }else{
+                    $api['content']['encryptedItems'] = [];
                 }
                 if ($packages['linkgrabber_offline']) {
-                    $grabbed = array_merge($grabbed, $packages['linkgrabber_offline']);
+                    $api['content']['offlineItems'] = $packages['linkgrabber_offline'];
+                }else{
+                    $api['content']['offlineItems'] = [];
                 }
-                $api['content']['grabberItems'] = $grabbed;
 
-                $status = array($temp['downloader_state'], $temp['grabber_collecting'], $temp['update_ready']);
-                $api['content']['$status'] = $status;
+                $api['content']['$status'] = array($temp['downloader_state'], $temp['grabber_collecting'], $temp['update_ready']);
             }
         } catch (Requests_Exception $e) {
             writeLog('error', 'JDownloader Connect Function - Error: ' . $e->getMessage(), 'SYSTEM');
@@ -1274,7 +1277,7 @@ function getCalendar()
 			$icsEvents = getIcsEventsAsArray($value);
 			if (isset($icsEvents) && !empty($icsEvents)) {
 				$timeZone = isset($icsEvents [1] ['X-WR-TIMEZONE']) ? trim($icsEvents[1]['X-WR-TIMEZONE']) : date_default_timezone_get();
-				$originalTimeZone = isset($icsEvents [1] ['X-WR-TIMEZONE']) ? trim($icsEvents[1]['X-WR-TIMEZONE']) : false;
+				$originalTimeZone = isset($icsEvents [1] ['X-WR-TIMEZONE']) ? str_replace('"', '', trim($icsEvents[1]['X-WR-TIMEZONE'])) : false;
 				unset($icsEvents [1]);
 				foreach ($icsEvents as $icsEvent) {
 					$startKeys = array_filter_key($icsEvent, function ($key) {
@@ -1290,7 +1293,7 @@ function getCalendar()
 							$tzKey = array_keys($startKeys);
 							if (strpos($tzKey[0], 'TZID=') !== false) {
 								$originalTimeZone = explode('TZID=', (string)$tzKey[0]);
-								$originalTimeZone = (count($originalTimeZone) >= 2) ? $originalTimeZone[1] : false;
+								$originalTimeZone = (count($originalTimeZone) >= 2) ? str_replace('"', '', $originalTimeZone[1]) : false;
 							}
 						}
 						$start = reset($startKeys);
@@ -1618,6 +1621,8 @@ function getLidarrCalendar($array, $number)
 
 function getRadarrCalendar($array, $number, $url)
 {
+	$url = rtrim($url, '/'); //remove trailing slash
+	$url = $url . '/api';
 	$array = json_decode($array, true);
 	$gotCalendar = array();
 	$i = 0;
@@ -1648,8 +1653,6 @@ function getRadarrCalendar($array, $number, $url)
 			$banner = "/plugins/images/cache/no-np.png";
 			foreach ($child['images'] as $image) {
 				if ($image['coverType'] == "banner" || $image['coverType'] == "fanart") {
-					$url = rtrim($url, '/'); //remove trailing slash
-					$url = $url . '/api';
 					$imageUrl = $image['url'];
 					$urlParts = explode("/", $url);
 					$imageParts = explode("/", $image['url']);

+ 38 - 0
api/functions/homepage-functions.php

@@ -1017,6 +1017,7 @@ function getHomepageList()
                                 <div class="panel-body">
 									<ul class="list-icons">
                                         <li><i class="fa fa-chevron-right text-danger"></i> <a href="https://pypi.org/project/myjd-api/" target="_blank">Download [myjd-api] Module</a></li>
+                                        <li><i class="fa fa-chevron-right text-danger"></i> Add <b>/api/myjd</b> to the URL if you are using <a href="https://pypi.org/project/RSScrawler/" target="_blank">RSScrawler</a></li>
                                     </ul>
                                 </div>
                             </div>
@@ -2147,6 +2148,43 @@ function getHomepageList()
 						'help' => 'Use Ombi Alias Names instead of Usernames - If Alias is blank, Alias will fallback to Username'
 					)
 				),
+				'Default Filter' => array(
+					array(
+						'type' => 'switch',
+						'name' => 'ombiDefaultFilterAvailable',
+						'label' => 'Show Available',
+						'value' => $GLOBALS['ombiDefaultFilterAvailable'],
+						'help' => 'Show All Available Ombi Requests'
+					),
+					array(
+						'type' => 'switch',
+						'name' => 'ombiDefaultFilterUnavailable',
+						'label' => 'Show Unavailable',
+						'value' => $GLOBALS['ombiDefaultFilterUnavailable'],
+						'help' => 'Show All Unavailable Ombi Requests'
+					),
+					array(
+						'type' => 'switch',
+						'name' => 'ombiDefaultFilterApproved',
+						'label' => 'Show Approved',
+						'value' => $GLOBALS['ombiDefaultFilterApproved'],
+						'help' => 'Show All Approved Ombi Requests'
+					),
+					array(
+						'type' => 'switch',
+						'name' => 'ombiDefaultFilterUnapproved',
+						'label' => 'Show Unapproved',
+						'value' => $GLOBALS['ombiDefaultFilterUnapproved'],
+						'help' => 'Show All Unapproved Ombi Requests'
+					),
+					array(
+						'type' => 'switch',
+						'name' => 'ombiDefaultFilterDenied',
+						'label' => 'Show Denied',
+						'value' => $GLOBALS['ombiDefaultFilterDenied'],
+						'help' => 'Show All Denied Ombi Requests'
+					)
+				),
 				'Test Connection' => array(
 					array(
 						'type' => 'blank',

+ 5 - 0
api/functions/normal-functions.php

@@ -311,6 +311,11 @@ function getCert()
 {
 	$url = 'http://curl.haxx.se/ca/cacert.pem';
 	$file = __DIR__ . DIRECTORY_SEPARATOR . 'cert' . DIRECTORY_SEPARATOR . 'cacert.pem';
+	if($GLOBALS['selfSignedCert'] !== ''){
+		if(file_exists($GLOBALS['selfSignedCert'])){
+			return $GLOBALS['selfSignedCert'];
+		}
+	}
 	if (!file_exists($file)) {
 		file_put_contents($file, fopen($url, 'r'));
 	} elseif (file_exists($file) && time() - 2592000 > filemtime($file)) {

+ 145 - 2
api/functions/organizr-functions.php

@@ -35,6 +35,11 @@ function organizrSpecialSettings()
 				'sso' => ($GLOBALS['ssoOmbi']) ? true : false,
 				'cookie' => (isset($_COOKIE['Auth'])) ? true : false,
 				'alias' => ($GLOBALS['ombiAlias']) ? true : false,
+				'ombiDefaultFilterAvailable' => $GLOBALS['ombiDefaultFilterAvailable'] ? true : false,
+				'ombiDefaultFilterUnavailable' => $GLOBALS['ombiDefaultFilterUnavailable'] ? true : false,
+				'ombiDefaultFilterApproved' => $GLOBALS['ombiDefaultFilterApproved'] ? true : false,
+				'ombiDefaultFilterUnapproved' => $GLOBALS['ombiDefaultFilterUnapproved'] ? true : false,
+				'ombiDefaultFilterDenied' => $GLOBALS['ombiDefaultFilterDenied'] ? true : false
 			),
 			'options' => array(
 				'alternateHomepageHeaders' => $GLOBALS['alternateHomepageHeaders'],
@@ -506,7 +511,7 @@ function getSettingsMain()
 				'icon' => 'fa fa-download',
 				'text' => 'Retrieve',
 				'attr' => ($GLOBALS['docker']) ? 'title="You can just restart your docker to update"' : '',
-				'help' => ($GLOBALS['docker']) ? 'Since you are using the Official Docker image, You can just restart your docker to update' : 'This will re-download all of the source files for Organizr'
+				'help' => ($GLOBALS['docker']) ? 'Since you are using the official Docker image, you can just restart your Docker container to update Organizr' : 'This will re-download all of the source files for Organizr'
 			)
 		),
 		'API' => array(
@@ -599,6 +604,13 @@ function getSettingsMain()
 				'value' => $GLOBALS['plexStrictFriends'],
 				'help' => 'Enabling this will only allow Friends that have shares to the Machine ID entered above to login, Having this disabled will allow all Friends on your Friends list to login'
 			),
+			array(
+				'type' => 'switch',
+				'name' => 'ignoreTFALocal',
+				'label' => 'Ignore External 2FA on Local Subnet',
+				'value' => $GLOBALS['ignoreTFALocal'],
+				'help' => 'Enabling this will bypass external 2FA security if user is on local Subnet'
+			),
 			array(
 				'type' => 'input',
 				'name' => 'authBackendHost',
@@ -855,6 +867,31 @@ function getSettingsMain()
 				'help' => 'IPv4 only at the moment - This will set your login as local if your IP falls within the From and To'
 			),
 		),
+		'Auth Proxy' => array(
+			array(
+				'type' => 'switch',
+				'name' => 'authProxyEnabled',
+				'label' => 'Auth Proxy',
+				'help' => 'Enable option to set Auth Poxy Header Login',
+				'value' => $GLOBALS['authProxyEnabled'],
+			),
+			array(
+				'type' => 'input',
+				'name' => 'authProxyHeaderName',
+				'label' => 'Auth Proxy Header Name',
+				'value' => $GLOBALS['authProxyHeaderName'],
+				'placeholder' => 'i.e. X-Forwarded-User',
+				'help' => 'Please choose a unique value for added security'
+			),
+			array(
+				'type' => 'input',
+				'name' => 'authProxyWhitelist',
+				'label' => 'Auth Proxy Whitelist',
+				'value' => $GLOBALS['authProxyWhitelist'],
+				'placeholder' => 'i.e. 10.0.0.0/24 or 10.0.0.20',
+				'help' => 'IPv4 only at the moment - This must be set to work, will accept subnet or IP address'
+			),
+		),
 		'Ping' => array(
 			array(
 				'type' => 'select',
@@ -1107,7 +1144,8 @@ function getCustomizeAppearance()
 					'type' => 'input',
 					'name' => 'loginWallpaper',
 					'label' => 'Login Wallpaper',
-					'value' => $GLOBALS['loginWallpaper']
+					'value' => $GLOBALS['loginWallpaper'],
+					'help' => 'You may enter multiple URL\'s using the CSV format.  i.e. link#1,link#2,link#3'
 				),
 				array(
 					'type' => 'switch',
@@ -1940,6 +1978,34 @@ function downloader($array)
 					break;
 			}
 			break;
+        case 'jdownloader':
+            switch ($array['data']['action']) {
+                case 'start':
+                    jdownloaderAction($array['data']['action'], $array['data']['target']);
+                    break;
+                case 'stop':
+                    jdownloaderAction($array['data']['action'], $array['data']['target']);
+                    break;
+                case 'resume':
+                    jdownloaderAction($array['data']['action'], $array['data']['target']);
+                    break;
+                case 'pause':
+                    jdownloaderAction($array['data']['action'], $array['data']['target']);
+                    break;
+                case 'update':
+                    jdownloaderAction($array['data']['action'], $array['data']['target']);
+                    break;
+                case 'retry':
+                    jdownloaderAction($array['data']['action'], $array['data']['target']);
+                    break;
+                case 'remove':
+                    jdownloaderAction($array['data']['action'], $array['data']['target']);
+                    break;
+                default:
+                    # code...
+                    break;
+            }
+            break;
 		case 'nzbget':
 			break;
 		default:
@@ -1948,6 +2014,57 @@ function downloader($array)
 	}
 }
 
+function jdownloaderAction($action = null, $target = null)
+{
+    if ($GLOBALS['homepageJdownloaderEnabled'] && !empty($GLOBALS['jdownloaderURL']) && qualifyRequest($GLOBALS['homepageJdownloaderAuth'])) {
+        $url = qualifyURL($GLOBALS['jdownloaderURL']);
+
+        # This ensures compatibility with RSScrawler
+        $url = str_replace('/myjd', '', $url);
+        if(substr($url , -1)=='/') {
+            $url = substr_replace($url ,"",-1);
+        }
+
+        switch ($action) {
+            case 'start':
+                $url = $url . '/myjd_start/';
+                break;
+            case 'stop':
+                $url = $url . '/myjd_stop/';
+                break;
+            case 'resume':
+                $url = $url . '/myjd_pause/false';
+                break;
+            case 'pause':
+                $url = $url . '/myjd_pause/true';
+                break;
+            case 'update':
+                $url = $url . '/myjd_update';
+                break;
+            case 'retry':
+                # code...
+                break;
+            case 'remove':
+                # code...
+                break;
+            default:
+                # code...
+                break;
+        }
+        try {
+            $options = (localURL($url)) ? array('verify' => false) : array();
+            $response = Requests::post($url, array(), $options);
+            if ($response->success) {
+                $api['content'] = json_decode($response->body, true);
+            }
+        } catch (Requests_Exception $e) {
+            writeLog('error', 'JDownloader Connect Function - Error: ' . $e->getMessage(), 'SYSTEM');
+        };
+        $api['content'] = isset($api['content']) ? $api['content'] : false;
+        return $api;
+    }
+}
+
 function sabnzbdAction($action = null, $target = null)
 {
 	if ($GLOBALS['homepageSabnzbdEnabled'] && !empty($GLOBALS['sabnzbdURL']) && !empty($GLOBALS['sabnzbdToken']) && qualifyRequest($GLOBALS['homepageSabnzbdAuth'])) {
@@ -2434,3 +2551,29 @@ function checkHostPrefix($s)
 	}
 	return (substr($s, -1, 1) == '\\') ? $s : $s . '\\';
 }
+function analyzeIP($ip)
+{
+	if(strpos($ip,'/') !== false){
+		$explodeIP = explode('/', $ip);
+		$prefix = $explodeIP[1];
+		$start_ip = $explodeIP[0];
+		$ip_count = 1 << (32 - $prefix);
+		$start_ip_long = ip2long($start_ip);
+		$last_ip_long = ip2long($start_ip) + $ip_count - 1;
+	}elseif(substr_count($ip, '.') == 3){
+		$start_ip_long = ip2long($ip);
+		$last_ip_long = ip2long($ip);
+	}
+	return (isset($start_ip_long) && isset($last_ip_long)) ? array('from' => $start_ip_long, 'to' => $last_ip_long) : false;
+}
+function authProxyRangeCheck($from, $to)
+{
+	$approved = false;
+	$userIP = ip2long($_SERVER['REMOTE_ADDR']);
+	$low = $from;
+	$high = $to;
+	if ($userIP <= $high && $low <= $userIP) {
+		$approved = true;
+	}
+	return $approved;
+}

+ 1 - 1
api/functions/static-globals.php

@@ -1,7 +1,7 @@
 <?php
 // ===================================
 // Organizr Version
-$GLOBALS['installedVersion'] = '2.0.270';
+$GLOBALS['installedVersion'] = '2.0.291';
 // ===================================
 // Quick php Version check
 $GLOBALS['minimumPHP'] = '7.1.3';

+ 7 - 0
api/index.php

@@ -28,6 +28,7 @@ if (!in_array($function, $approvedFunctionsBypass)) {
 	if (isApprovedRequest($method) === false) {
 		$result['status'] = "error";
 		$result['statusText'] = "Not Authorized";
+		http_response_code(401);
 		writeLog('success', 'Killed Attack From [' . (isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : 'No Referer') . ']', $GLOBALS['organizrUser']['username']);
 		exit(json_encode($result));
 	}
@@ -1388,6 +1389,12 @@ if (!$result) {
 }
 $result['generationDate'] = $GLOBALS['currentTime'];
 $result['generationTime'] = formatSeconds(timeExecution());
+//Set HTTP Code
+if($result['statusText'] == "API/Token invalid or not set"){
+	http_response_code(401);
+}else{
+	http_response_code(200);
+}
 //return JSON array
 if ($pretty) {
 	echo '<pre>' . safe_json_encode($result, JSON_PRETTY_PRINT) . '</pre>';

+ 10 - 7
api/pages/settings-settings-logs.php

@@ -82,7 +82,11 @@ if(file_exists('config'.DIRECTORY_SEPARATOR.'config.php')){
                     }
                 },
                 { "data": "username" },
-                { "data": "ip" },
+                { data: \'ip\',
+                    render: function ( data, type, row ) {
+                        return ipInfoSpan(data);
+                    }
+                },
                 { data: \'auth_type\',
                     render: function ( data, type, row ) {
                         if ( type === \'display\' || type === \'filter\' ) {
@@ -99,19 +103,18 @@ if(file_exists('config'.DIRECTORY_SEPARATOR.'config.php')){
                 "columns": [
                 { data: \'utc_date\',
                     render: function ( data, type, row ) {
-                        // If display or filter data is requested, format the date
                         if ( type === \'display\' || type === \'filter\' ) {
                             var m = moment.tz(data, activeInfo.timezone);
                             return moment(m).format(\'LLL\');
                         }
-
-                    // Otherwise the data type requested (`type`) is type detection or
-                    // sorting data, for which we want to use the integer, so just return
-                    // that, unaltered
                     return data;}
                     },
                 { "data": "username" },
-                { "data": "ip" },
+                { data: \'ip\',
+                    render: function ( data, type, row ) {
+                        return ipInfoSpan(data);
+                    }
+                },
                 { "data": "message" },
                 { data: \'type\',
                     render: function ( data, type, row ) {

+ 1 - 0
api/plugins/config/php-mailer.php

@@ -1,6 +1,7 @@
 <?php
 return array(
 	'PHPMAILER-enabled' => false,
+	'PHPMAILER-debugTesting' => false,
 	'PHPMAILER-smtpHost' => '',
 	'PHPMAILER-smtpHostPort' => '',
 	'PHPMAILER-smtpHostAuth' => true,

+ 3 - 0
api/plugins/js/php-mailer.js

@@ -309,6 +309,9 @@ $(document).on('click', '.phpmSendTestEmail', function() {
         var response = JSON.parse(data);
         if(response.data == true){
             messageSingle('',window.lang.translate('Email Test Successful'),activeInfo.settings.notifications.position,'#FFF','success','5000');
+        }else if(response.data.indexOf('|||DEBUG|||') == 0) {
+            messageSingle('',window.lang.translate('Press F11 to check Console for output'),activeInfo.settings.notifications.position,'#FFF','warning','20000');
+            console.warn(response.data);
         }else{
             messageSingle('',response.data,activeInfo.settings.notifications.position,'#FFF','error','5000');
         }

+ 15 - 4
api/plugins/php-mailer.php

@@ -106,7 +106,10 @@ function phpmAdminSendEmail()
 	}
 	return false;
 }
-
+function phpmGetDebug($str, $level){
+	$GLOBALS['phpmOriginalDebug'] = $GLOBALS['phpmOriginalDebug'] . $str;
+	return $GLOBALS['phpmOriginalDebug'];
+}
 function phpmSendTestEmail()
 {
 	$emailTemplate = array(
@@ -118,10 +121,12 @@ function phpmSendTestEmail()
 		'inviteCode' => null,
 	);
 	$emailTemplate = phpmEmailTemplate($emailTemplate);
+	$GLOBALS['phpmOriginalDebug'] = '|||DEBUG|||';
 	try {
 		$mail = new PHPMailer\PHPMailer\PHPMailer(true);
+		$mail->SMTPDebug = 2;
 		$mail->isSMTP();
-		//$mail->SMTPDebug = 3;
+		$mail->Debugoutput = function($str, $level) {phpmGetDebug($str, $level);};
 		$mail->Host = $GLOBALS['PHPMAILER-smtpHost'];
 		$mail->Port = $GLOBALS['PHPMAILER-smtpHostPort'];
 		if ($GLOBALS['PHPMAILER-smtpHostType'] !== 'n/a') {
@@ -147,10 +152,10 @@ function phpmSendTestEmail()
 		$mail->Body = phpmBuildEmail($emailTemplate);
 		$mail->send();
 		writeLog('success', 'Mail Function -  E-Mail Test Sent', $GLOBALS['organizrUser']['username']);
-		return true;
+		return ($GLOBALS['PHPMAILER-debugTesting']) ? $GLOBALS['phpmOriginalDebug'] : true;
 	} catch (PHPMailer\PHPMailer\Exception $e) {
 		writeLog('error', 'Mail Function -  E-Mail Test Failed[' . $mail->ErrorInfo . ']', $GLOBALS['organizrUser']['username']);
-		return $e->errorMessage();
+		return ($GLOBALS['PHPMAILER-debugTesting']) ? $GLOBALS['phpmOriginalDebug'] : $e->errorMessage();
 	}
 	return false;
 }
@@ -302,6 +307,12 @@ function phpmGetSettings()
 				'icon' => 'fa fa-paper-plane',
 				'text' => 'Send'
 			),
+			array(
+				'type' => 'switch',
+				'name' => 'PHPMAILER-debugTesting',
+				'label' => 'Enable Debug Output on Email Test',
+				'value' => $GLOBALS['PHPMAILER-debugTesting'],
+			),
 			array(
 				'type' => 'input',
 				'name' => 'PHPMAILER-domain',

+ 161 - 76
css/simplebar.css

@@ -1,105 +1,190 @@
-/*!
- * 
- *         SimpleBar.js - v2.6.1
- *         Scrollbars, simpler.
- *         https://grsmto.github.io/simplebar/
- *         
- *         Made by Adrien Grsmto from a fork by Jonathan Nicol
- *         Under MIT License
- *       
- */
 [data-simplebar] {
-    position: relative;
-    z-index: 0;
-    overflow: hidden!important;
-    max-height: inherit;
-    -webkit-overflow-scrolling: touch; /* Trigger native scrolling for mobile, if not supported, plugin is used. */
+  position: relative;
+  flex-direction: column;
+  flex-wrap: wrap;
+  justify-content: flex-start;
+  align-content: flex-start;
+  align-items: flex-start;
 }
 
-[data-simplebar="init"] {
-    display: -webkit-box;
-    display: -ms-flexbox;
-    display: flex;
+.simplebar-wrapper {
+  overflow: hidden;
+  width: inherit;
+  height: inherit;
+  max-width: inherit;
+  max-height: inherit;
 }
 
-.simplebar-scroll-content {
-    overflow-x: hidden!important;
-    overflow-y: scroll;
-    min-width: 100%!important;
-    max-height: inherit!important;
-    -webkit-box-sizing: content-box!important;
-            box-sizing: content-box!important;
+.simplebar-mask {
+  direction: inherit;
+  position: absolute;
+  overflow: hidden;
+  padding: 0;
+  margin: 0;
+  left: 0;
+  top: 0;
+  bottom: 0;
+  right: 0;
+  width: auto !important;
+  height: auto !important;
+  z-index: 0;
 }
 
-.simplebar-content {
-    overflow-y: hidden!important;
-    overflow-x: scroll;
-    -webkit-box-sizing: border-box!important;
-            box-sizing: border-box!important;
-    min-height: 100%!important;
+.simplebar-offset {
+  direction: inherit !important;
+  box-sizing: inherit !important;
+  resize: none !important;
+  position: absolute;
+  top: 0;
+  left: 0;
+  bottom: 0;
+  right: 0;
+  padding: 0;
+  margin: 0;
+  -webkit-overflow-scrolling: touch;
+}
+
+.simplebar-content-wrapper {
+  direction: inherit;
+  box-sizing: border-box !important;
+  position: relative;
+  display: block;
+  height: 100%; /* Required for horizontal native scrollbar to not appear if parent is taller than natural height */
+  width: auto;
+  visibility: visible;
+  overflow: auto; /* Scroll on this element otherwise element can't have a padding applied properly */
+  max-width: 100%; /* Not required for horizontal scroll to trigger */
+  max-height: 100%; /* Needed for vertical scroll to trigger */
+}
+
+.simplebar-content:before,
+.simplebar-content:after {
+  content: ' ';
+  display: table;
+}
+
+.simplebar-placeholder {
+  max-height: 100%;
+  max-width: 100%;
+  width: 100%;
+  pointer-events: none;
+}
+
+.simplebar-height-auto-observer-wrapper {
+  box-sizing: inherit !important;
+  height: 100%;
+  width: 100%;
+  max-width: 1px;
+  position: relative;
+  float: left;
+  max-height: 1px;
+  overflow: hidden;
+  z-index: -1;
+  padding: 0;
+  margin: 0;
+  pointer-events: none;
+  flex-grow: inherit;
+  flex-shrink: 0;
+  flex-basis: 0;
+}
+
+.simplebar-height-auto-observer {
+  box-sizing: inherit;
+  display: block;
+  opacity: 0;
+  position: absolute;
+  top: 0;
+  left: 0;
+  height: 1000%;
+  width: 1000%;
+  min-height: 1px;
+  min-width: 1px;
+  overflow: hidden;
+  pointer-events: none;
+  z-index: -1;
 }
 
 .simplebar-track {
-    z-index: 1;
-    position: absolute;
-    right: 0;
-    bottom: 0;
-    width: 11px;
+  z-index: 1;
+  position: absolute;
+  right: 0;
+  bottom: 0;
+  pointer-events: none;
+  overflow: hidden;
+}
+
+[data-simplebar].simplebar-dragging .simplebar-track {
+  pointer-events: all;
 }
 
 .simplebar-scrollbar {
-    position: absolute;
-    right: 2px;
-    width: 7px;
-    min-height: 10px;
+  position: absolute;
+  right: 2px;
+  width: 7px;
+  min-height: 10px;
 }
 
 .simplebar-scrollbar:before {
-    position: absolute;
-    content: "";
-    background: black;
-    border-radius: 7px;
-    left: 0;
-    right: 0;
-    opacity: 0;
-    -webkit-transition: opacity 0.2s linear;
-    transition: opacity 0.2s linear;
+  position: absolute;
+  content: '';
+  background: black;
+  border-radius: 7px;
+  left: 0;
+  right: 0;
+  opacity: 0;
+  transition: opacity 0.2s linear;
+}
+
+.simplebar-track .simplebar-scrollbar.simplebar-visible:before {
+  /* When hovered, remove all transitions from drag handle */
+  opacity: 0.5;
+  transition: opacity 0s linear;
+}
+
+.simplebar-track.simplebar-vertical {
+  top: 0;
+  width: 11px;
 }
 
-.simplebar-track:hover .simplebar-scrollbar:before,
-.simplebar-track .simplebar-scrollbar.visible:before {
-    /* When hovered, remove all transitions from drag handle */
-    opacity: 0.5;
-    -webkit-transition: opacity 0 linear;
-    transition: opacity 0 linear;
+.simplebar-track.simplebar-vertical .simplebar-scrollbar:before {
+  top: 2px;
+  bottom: 2px;
 }
 
-.simplebar-track.vertical {
-    top: 0;
+.simplebar-track.simplebar-horizontal {
+  left: 0;
+  height: 11px;
 }
 
-.simplebar-track.vertical .simplebar-scrollbar:before {
-    top: 2px;
-    bottom: 2px;
+.simplebar-track.simplebar-horizontal .simplebar-scrollbar:before {
+  height: 100%;
+  left: 2px;
+  right: 2px;
 }
 
-.simplebar-track.horizontal {
-    left: 0;
-    width: auto;
-    height: 11px;
+.simplebar-track.simplebar-horizontal .simplebar-scrollbar {
+  right: auto;
+  left: 0;
+  top: 2px;
+  height: 7px;
+  min-height: 0;
+  min-width: 10px;
+  width: auto;
 }
 
-.simplebar-track.horizontal .simplebar-scrollbar:before {
-    height: 100%;
-    left: 2px;
-    right: 2px;
+/* Rtl support */
+[data-simplebar-direction='rtl'] .simplebar-track.simplebar-vertical {
+  right: auto;
+  left: 0;
 }
 
-.horizontal.simplebar-track .simplebar-scrollbar {
-    right: auto;
-    top: 2px;
-    height: 7px;
-    min-height: 0;
-    min-width: 10px;
-    width: auto;
+.hs-dummy-scrollbar-size {
+  direction: rtl;
+  position: fixed;
+  opacity: 0;
+  visibility: hidden;
+  height: 500px;
+  width: 500px;
+  overflow-y: hidden;
+  overflow-x: scroll;
 }

+ 38 - 0
js/custom.js

@@ -1939,4 +1939,42 @@ $(document).on('click', ".showMoreHealth", function(){
    var id = $(this).attr('data-id');
     $('.showMoreHealthDiv-'+id).toggleClass('d-none');
     $(this).find('.card-body').toggleClass('healthPosition');
+});
+//IP INFO
+$(document).on('click', ".ipInfo", function(){
+    $.getJSON("https://ipinfo.io/"+$(this).text()+"/?token=ddd0c072ad5021", function (response) {
+        var region = (typeof response.region == 'undefined') ? ' N/A' : response.region;
+        var ip = (typeof response.ip == 'undefined') ? ' N/A' : response.ip;
+        var hostname = (typeof response.hostname == 'undefined') ? ' N/A' : response.hostname;
+        var loc = (typeof response.loc == 'undefined') ? ' N/A' : response.loc;
+        var org = (typeof response.org == 'undefined') ? ' N/A' : response.org;
+        var city = (typeof response.city == 'undefined') ? ' N/A' : response.city;
+        var country = (typeof response.country == 'undefined') ? ' N/A' : response.country;
+        var phone = (typeof response.phone == 'undefined') ? ' N/A' : response.phone;
+        var div = '<div class="row">' +
+            '<div class="col-lg-12">' +
+            '<div class="white-box">' +
+            '<h3 class="box-title">'+ip+'</h3>' +
+            '<div class="table-responsive">' +
+            '<table class="table">' +
+            '<tbody>' +
+            '<tr><td class="text-left">Hostname</td><td class="txt-oflo text-right">'+hostname+'</td></tr>' +
+            '<tr><td class="text-left">Location</td><td class="txt-oflo text-right">'+loc+'</td></tr>' +
+            '<tr><td class="text-left">Org</td><td class="txt-oflo text-right">'+org+'</td></tr>' +
+            '<tr><td class="text-left">City</td><td class="txt-oflo text-right">'+city+'</td></tr>' +
+            '<tr><td class="text-left">Country</td><td class="txt-oflo text-right">'+country+'</td></tr>' +
+            '<tr><td class="text-left">Phone</td><td class="txt-oflo text-right">'+phone+'</td></tr>' +
+            '<tr><td class="text-left">Region</td><td class="txt-oflo text-right">'+region+'</td></tr>' +
+            '</tbody>' +
+            '</table>' +
+            '</div>' +
+            '</div>' +
+            '</div>' +
+            '</div>';
+        swal({
+            content: createElementFromHTML(div),
+            buttons: false,
+            className: 'bg-org'
+        });
+    });
 });

File diff suppressed because it is too large
+ 0 - 0
js/custom.min.js


+ 204 - 31
js/functions.js

@@ -13,6 +13,7 @@ lang.init({
 	},
 	allowCookieOverride: true
 });
+var OAuthLoginNeeded = false;
 var timeouts = {};
 var increment = 0;
 var tabInformation = {};
@@ -22,6 +23,9 @@ tabActionsList['close'] = [];
 
 // Start Organizr
 $(document).ready(function () {
+    if(getCookie('organizrOAuth')){
+        OAuthLoginNeeded = true
+    }
     launch();
     local('r','loggingIn');
 });
@@ -521,6 +525,9 @@ function swapBodyClass(tab){
     $('body').attr('data-active-tab', tab);
     $('body').addClass('active-tab-'+tab);
 }
+function editPageTitle(title){
+    document.title =  title + ' - ' + activeInfo.appearance.title;
+}
 function switchTab(tab, type){
     if(type !== 2){
         hideFrames();
@@ -540,6 +547,7 @@ function switchTab(tab, type){
 			var newTab = $('#internal-'+tab);
 			var tabURL = newTab.attr('data-url');
 			$('#menu-'+cleanClass(tab)).find('a').addClass("active");
+            editPageTitle(tab);
 			if(newTab.hasClass('loaded')){
 				console.log('Tab Function: Switching to tab: '+tab);
 				newTab.addClass("show").removeClass('hidden');
@@ -562,6 +570,7 @@ function switchTab(tab, type){
 			var newTab = $('#container-'+tab);
 			var tabURL = newTab.attr('data-url');
 			$('#menu-'+cleanClass(tab)).find('a').addClass("active");
+            editPageTitle(tab);
 			if(newTab.hasClass('loaded')){
 				console.log('Tab Function: Switching to tab: '+tab);
 				newTab.addClass("show").removeClass('hidden');
@@ -1894,7 +1903,7 @@ function checkTabHomepageItem(id, name, url, urlLocal){
         addEditHomepageItem(id,'Plex');
     }else if(name.includes('emby') || url.includes('emby') || urlLocal.includes('emby')){
         addEditHomepageItem(id,'Emby');
-    }else if(name.includes('jdownloader') || url.includes('jdownloader') || urlLocal.includes('jdownloader')){
+    }else if(name.includes('jdownloader') || url.includes('jdownloader') || urlLocal.includes('jdownloader') || name.includes('rsscrawler') || url.includes('rsscrawler') || urlLocal.includes('rsscrawler')){
         addEditHomepageItem(id,'jDownloader');
     }else if(name.includes('sab') || url.includes('sab') || urlLocal.includes('sab')){
         addEditHomepageItem(id,'SabNZBD');
@@ -3532,10 +3541,11 @@ function organizrAPI(type,path,data=null){
 	var timeout = 10000;
     switch(path){
         case 'api/?v1/windows/update':
+        case 'api/?v1/docker/update':
             timeout = 120000;
             break;
         default:
-            timeout = 10000;
+            timeout = 60000;
     }
 	switch (type) {
 		case 'get':
@@ -3889,12 +3899,12 @@ function loadAppearance(appearance){
 	if(appearance.loginWallpaper !== ''){
 		cssSettings += `
 		    .login-register {
-			    background: url(`+appearance.loginWallpaper+`) center center/cover no-repeat!important;
+			    background: url(`+randomCSV(appearance.loginWallpaper)+`) center center/cover no-repeat!important;
 			    height: 100%;
 			    position: fixed;
 		    }
 			.lock-screen {
-				background: url(`+appearance.loginWallpaper+`) center center/cover no-repeat!important;
+				background: url(`+randomCSV(appearance.loginWallpaper)+`) center center/cover no-repeat!important;
 			    height: 100%;
 			    position: fixed;
 			    z-index: 1001;
@@ -3918,6 +3928,18 @@ function loadAppearance(appearance){
         $('#custom-css').html(appearance.customCss);
     }
 }
+function randomCSV(values){
+    if(typeof values == 'string'){
+        if(values.includes(',')){
+            var csv = values.split(',');
+            var luckyNumber = Math.floor(Math.random() * csv.length);
+            return csv[luckyNumber];
+        }else{
+            return values;
+        }
+    }
+    return false;
+}
 function loadCustomJava(appearance){
     if(appearance.customThemeJava !== ''){
         $('#custom-theme-javascript').html(appearance.customThemeJava);
@@ -4902,8 +4924,8 @@ function requestList (list, type, page=1) {
 function buildDownloaderItem(array, source, type='none'){
     //console.log(array);
     var queue = '';
-    var history = '';
     var count = 0;
+    var history = '';
 	switch (source) {
         case 'jdownloader':
             if(array.content === false){
@@ -4911,40 +4933,45 @@ function buildDownloaderItem(array, source, type='none'){
                 break;
             }
 
-            /*
-            if(array.content.$status[0] != 'RUNNING'){
-                var state = `<a href="#"><span class="downloader mouse" data-source="jdownloader" data-action="resume" data-target="main"><i class="fa fa-play"></i></span></a>`;
-                var active = 'grayscale';
-            }else{
-                var state = `<a href="#"><span class="downloader mouse" data-source="jdownloader" data-action="pause" data-target="main"><i class="fa fa-pause"></i></span></a>`;
-                var active = '';
-            }
-            $('.jdownloader-downloader-action').html(state);
-            */
-
-            if(array.content.queueItems.length == 0){
+            if(array.content.queueItems.length == 0 && array.content.grabberItems.length == 0 && array.content.encryptedItems.length == 0 && array.content.offlineItems.length == 0){
                 queue = '<tr><td class="max-texts" lang="en">Nothing in queue</td></tr>';
+            }else{
+                if(array.content.$status[0] == 'RUNNING') {
+                    queue += `
+                        <tr><td>
+                            <a href="#"><span class="downloader mouse" data-source="jdownloader" data-action="pause" data-target="main"><i class="fa fa-pause"></i></span></a>
+                            <a href="#"><span class="downloader mouse" data-source="jdownloader" data-action="stop" data-target="main"><i class="fa fa-stop"></i></span></a>
+                        </td></tr>
+                        `;
+                }else if(array.content.$status[0] == 'PAUSE'){
+                    queue += `<tr><td><a href="#"><span class="downloader mouse" data-source="jdownloader" data-action="resume" data-target="main"><i class="fa fa-fast-forward"></i></span></a></td></tr>`;
+                }else{
+                    queue += `<tr><td><a href="#"><span class="downloader mouse" data-source="jdownloader" data-action="start" data-target="main"><i class="fa fa-play"></i></span></a></td></tr>`;
+                }
+                if(array.content.$status[1]) {
+                    queue += `<tr><td><a href="#"><span class="downloader mouse" data-source="jdownloader" data-action="update" data-target="main"><i class="fa fa-globe"></i></span></a></td></tr>`;
+                }
             }
             $.each(array.content.queueItems, function(i,v) {
                 count = count + 1;
                 if(v.speed == null){
-                    if(v.percentage == '100'){
-                        v.speed = '--';
-                    }else{
-                        v.speed = 'Stopped';
-                    }
+                    v.speed = 'Stopped';
                 }
                 if(v.eta == null){
                     if(v.percentage == '100'){
-                        v.eta = 'Complete';
+                        v.speed = 'Completed';
+                        v.eta = '--';
                     }else{
                         v.eta = '--';
                     }
                 }
+                if(v.enabled == null){
+                    v.speed = 'Disabled';
+                }
                 queue += `
                 <tr>
                     <td class="max-texts">`+v.name+`</td>
-                    <td class="hidden-xs">`+v.speed+`</td>
+                    <td>`+v.speed+`</td>
                     <td class="hidden-xs" alt="`+v.done+`">`+v.size+`</td>
                     <td class="hidden-xs">`+v.eta+`</td>
                     <td class="text-right">
@@ -4955,13 +4982,51 @@ function buildDownloaderItem(array, source, type='none'){
                 </tr>
                 `;
             });
-            if(array.content.grabberItems.length == 0){
-                history = '<tr><td class="max-texts" lang="en">Nothing in Linkgrabbber</td></tr>';
-            }
             $.each(array.content.grabberItems, function(i,v) {
-                history += `
+                count = count + 1;
+                queue += `
                 <tr>
-                    <td class="max-texts">`+ v.name+`</td>
+                    <td class="max-texts">`+v.name+`</td>
+                    <td>Online</td>
+                    <td class="hidden-xs"> -- </td>
+                    <td class="hidden-xs"> -- </td>
+                    <td class="text-right">
+                        <div class="progress progress-lg m-b-0">
+                            <div class="progress-bar progress-bar-info" style="width: 0%;" role="progressbar">0%</div>
+                        </div>
+                    </td>
+                </tr>
+                `;
+            });
+            $.each(array.content.encryptedItems, function(i,v) {
+                count = count + 1;
+                queue += `
+                <tr>
+                    <td class="max-texts">`+v.name+`</td>
+                    <td>Encrypted</td>
+                    <td class="hidden-xs"> -- </td>
+                    <td class="hidden-xs"> -- </td>
+                    <td class="text-right">
+                        <div class="progress progress-lg m-b-0">
+                            <div class="progress-bar progress-bar-info" style="width: 0%;" role="progressbar">0%</div>
+                        </div>
+                    </td>
+                </tr>
+                `;
+            });
+            $.each(array.content.offlineItems, function(i,v) {
+                count = count + 1;
+                queue += `
+                <tr>
+                    <td class="max-texts">`+v.name+`</td>
+                    <td>Offline</td>
+                    <td class="hidden-xs"> -- </td>
+                    <td class="hidden-xs"> -- </td>
+                    <td class="text-right">
+                        <div class="progress progress-lg m-b-0">
+                            <div class="progress-bar progress-bar-info" style="width: 0%;" role="progressbar">0%</div>
+                        </div>
+                    </td>
                 </tr>
                 `;
             });
@@ -5220,7 +5285,7 @@ function buildDownloaderItem(array, source, type='none'){
                 queue += `
                 <tr>
                     <td class="max-texts">`+v.name+`</td>
-                    <td class="hidden-xs">`+status+`</td>
+                    <td class="hidden-xs qbit-`+status+`">`+status+`</td>
                     <td class="hidden-xs">`+v.save_path+`</td>
                     <td class="hidden-xs">`+size+`</td>
                     <td class="text-right">
@@ -5280,6 +5345,10 @@ function buildDownloader(source){
     var historyButton = 'HISTORY';
     switch (source) {
         case 'jdownloader':
+            var queue = true;
+            var history = false;
+            queueButton = 'REFRESH';
+            break;
         case 'sabnzbd':
         case 'nzbget':
             var queue = true;
@@ -5376,6 +5445,10 @@ function buildDownloaderCombined(source){
     var historyButton = 'HISTORY';
     switch (source) {
         case 'jdownloader':
+            var queue = true;
+            var history = false;
+            queueButton = 'REFRESH';
+            break;
         case 'sabnzbd':
         case 'nzbget':
             var queue = true;
@@ -5846,6 +5919,20 @@ function homepagePlaylist(type, timeout=30000){
 		console.error("Organizr Function: API Connection Failed");
 	});
 }
+function defaultOmbiFilter(){
+    var defaultFilter = {
+        "request-filter-approved" : activeInfo.settings.homepage.ombi.ombiDefaultFilterApproved,
+        "request-filter-unapproved" : activeInfo.settings.homepage.ombi.ombiDefaultFilterUnapproved,
+        "request-filter-available" : activeInfo.settings.homepage.ombi.ombiDefaultFilterAvailable,
+        "request-filter-unavailable" : activeInfo.settings.homepage.ombi.ombiDefaultFilterUnavailable,
+        "request-filter-denied" : activeInfo.settings.homepage.ombi.ombiDefaultFilterDenied
+    };
+    $.each(defaultFilter, function(i,v) {
+        if(v == false){
+            $('#'+i).click();
+        }
+    });
+}
 function homepageRequests(timeout){
 	var timeout = (typeof timeout !== 'undefined') ? timeout : activeInfo.settings.homepage.refresh.ombiRefresh;
 	organizrAPI('POST','api/?v1/homepage/connect',{action:'getRequests'}).success(function(data) {
@@ -5868,6 +5955,8 @@ function homepageRequests(timeout){
 			autoWidth:true,
 			items:4
     	})
+        // Default Ombi Filter
+        defaultOmbiFilter();
 	}).fail(function(xhr) {
 		console.error("Organizr Function: API Connection Failed");
 	});
@@ -5941,6 +6030,13 @@ function getPlexHeaders(){
         'X-Plex-Product': activeInfo.appearance.title,
         'X-Plex-Version': '2.0',
         'X-Plex-Client-Identifier': activeInfo.settings.misc.uuid,
+        'X-Plex-Model': 'Plex OAuth',
+        'X-Plex-Platform': activeInfo.osName,
+        'X-Plex-Platform-Version': activeInfo.osVersion,
+        'X-Plex-Device': activeInfo.browserName,
+        'X-Plex-Device-Name': activeInfo.browserVersion,
+        'X-Plex-Device-Screen-Resolution': window.screen.width + 'x' + window.screen.height,
+        'X-Plex-Language': 'en'
     };
 }
 var plex_oauth_window = null;
@@ -6018,7 +6114,20 @@ function PlexOAuth(success, error, pre) {
         var x_plex_headers = getPlexHeaders();
         const pin = data.pin;
         const code = data.code;
-        plex_oauth_window.location = 'https://app.plex.tv/auth/#!?clientID=' + x_plex_headers['X-Plex-Client-Identifier'] + '&code=' + code;
+        var oauth_params = {
+            'clientID': x_plex_headers['X-Plex-Client-Identifier'],
+            'context[device][product]': x_plex_headers['X-Plex-Product'],
+            'context[device][version]': x_plex_headers['X-Plex-Version'],
+            'context[device][platform]': x_plex_headers['X-Plex-Platform'],
+            'context[device][platformVersion]': x_plex_headers['X-Plex-Platform-Version'],
+            'context[device][device]': x_plex_headers['X-Plex-Device'],
+            'context[device][deviceName]': x_plex_headers['X-Plex-Device-Name'],
+            'context[device][model]': x_plex_headers['X-Plex-Model'],
+            'context[device][screenResolution]': x_plex_headers['X-Plex-Device-Screen-Resolution'],
+            'context[device][layout]': 'desktop',
+            'code': code
+        };
+        plex_oauth_window.location = 'https://app.plex.tv/auth/#!?' + encodeData(oauth_params);
         polling = pin;
         (function poll() {
             $.ajax({
@@ -6056,6 +6165,11 @@ function PlexOAuth(success, error, pre) {
         }
     });
 }
+function encodeData(data) {
+    return Object.keys(data).map(function(key) {
+        return [key, data[key]].map(encodeURIComponent).join("=");
+    }).join("&");
+}
 function oAuthSuccess(type,token){
     switch(type) {
         case 'plex':
@@ -7002,6 +7116,64 @@ function showLDAPLoginTest(){
         className: 'bg-org'
     })
 }
+function oAuthLoginNeededCheck() {
+    if(OAuthLoginNeeded == false){
+        return false;
+    }else{
+        if(activeInfo.user.loggedin == true){
+            return false;
+        }
+    }
+    message('OAuth', ' Proceeding to login', activeInfo.settings.notifications.position, '#FFF', 'info', '10000');
+    organizrAPI('POST', 'api/?v1/login', '').success(function (data) {
+        var html = JSON.parse(data);
+        if (html.data == true) {
+            local('set', 'message', 'Welcome|Login Successful|success');
+            location.reload();
+        } else if (html.data == 'mismatch') {
+            $('div.login-box').unblock({});
+            message('Login Error', ' Wrong username/email/password combo', activeInfo.settings.notifications.position, '#FFF', 'warning', '10000');
+            console.error('Organizr Function: Login failed - wrong username/email/password');
+        } else if (html.data == 'lockout') {
+            $('div.login-box').block({
+                message: '<h5><i class="fa fa-close"></i> Locked Out!</h4>',
+                css: {
+                    color: '#fff',
+                    border: '1px solid #e91e63',
+                    backgroundColor: '#f44336'
+                }
+            });
+            message('Login Error', ' You have been Locked out', activeInfo.settings.notifications.position, '#FFF', 'error', '10000');
+            console.error('Organizr Function: Login failed - User has been locked out');
+            setTimeout(function () {
+                local('r', 'loggingIn');
+                location.reload()
+            }, 10000);
+        } else if (html.data == '2FA') {
+            $('div.login-box').unblock({});
+            $('#tfa-div').removeClass('hidden');
+            $('#loginform [name=tfaCode]').focus();
+        } else if (html.data == '2FA-incorrect') {
+            $('div.login-box').unblock({});
+            $('#tfa-div').removeClass('hidden');
+            $('#loginform [name=tfaCode]').focus();
+            message('Login Error', html.data, activeInfo.settings.notifications.position, '#FFF', 'warning', '10000');
+        } else {
+            $('div.login-box').unblock({});
+            message('Login Error', html.data, activeInfo.settings.notifications.position, '#FFF', 'warning', '10000');
+            console.error('Organizr Function: Login failed');
+        }
+        local('r', 'loggingIn');
+    }).fail(function (xhr) {
+        $('div.login-box').unblock({});
+        message('Login Error', 'API Connection Failed', activeInfo.settings.notifications.position, '#FFF', 'warning', '10000');
+        console.error("Organizr Function: API Connection Failed");
+        local('r', 'loggingIn');
+    });
+}
+function ipInfoSpan(ip){
+    return '<span class="ipInfo mouse">'+ip+'</span>';
+}
 function launch(){
 	organizrConnect('api/?v1/launch_organizr').success(function (data) {
         try {
@@ -7090,5 +7262,6 @@ function launch(){
 				console.error('Organizr Function: Action not set or defined');
 		}
 		console.log('Organizr DOM Fully loaded');
+        oAuthLoginNeededCheck();
 	});
 }

+ 13 - 13
js/langpack/pl[Polish].json

@@ -2,7 +2,7 @@
     "token": {
         "Navigation": "Nawigacja",
         "Date": "Data",
-        "Type": "Typ",
+        "Type": "Rodzaj",
         "IP Address": "Adres IP",
         "Username": "Nazwa użytkownika",
         "Message": "Wiadomość",
@@ -73,7 +73,7 @@
         "Business": "Komercyjna",
         "Personal": "Prywatna",
         "Choose License": "Wybór licencji:",
-        "Install Type": "Typ instalacji",
+        "Install Type": "Rodzaj instalacji",
         "Verify": "Zweryfikuj",
         "Database": "Baza danych",
         "Security": "Bezpieczeństwo",
@@ -123,7 +123,7 @@
         "Add New Tab": "Dodaj nową kartę",
         "SPLASH": "SPLASH",
         "ACTIVE": "AKTYWNE",
-        "TYPE": "TYP",
+        "TYPE": "RODZAJ",
         "GROUP": "GRUPA",
         "Delete": "Usuń",
         "No": "Nie",
@@ -233,7 +233,7 @@
         "Web Folder": "Folder sieciowy",
         "Dependencies Missing": "Brak zależności",
         "Organizr Dependency Check": "Sprawdzanie zależności Organizr",
-        "Please make sure both Token and Machine are filled in": "Proszę upewnić się, czy pola Token i ID urządzenia są wypełnione",
+        "Please make sure both Token and Machine are filled in": "Upewnij się, że pola Token i ID urządzenia są wypełnione",
         "Loading Requests...": "Wczytywanie zgłoszeń...",
         "Loading Recent...": "Wczytywanie ostatnich...",
         "Loading Now Playing...": "Wczytywanie teraz odtwarzanych...",
@@ -244,9 +244,9 @@
         "Become Sponsor": "Zostań sponsorem",
         "Splash Page": "Strona powitalna",
         "Lock Screen": "Ekran blokady",
-        "If you signed in with a Emby Acct... Please use the following link to change your password there:": "Jeśli zalogowano się za pomocą konta Emby... Użyj poniższego linku, aby zmienić hasło:",
+        "If you signed in with a Emby Acct... Please use the following link to change your password there:": "Jeśli zalogowano się za pomocą konta Emby - użyj poniższego linku, aby zmienić hasło:",
         "Password Notice": "Podpowiedź do hasla",
-        "If you signed in with a Plex Acct... Please use the following link to change your password there:": "Jeśli zalogowano się za pomocą konta Plex... Użyj poniższego linku, aby zmienić hasło:",
+        "If you signed in with a Plex Acct... Please use the following link to change your password there:": "Jeśli zalogowano się za pomocą konta Plex - użyj poniższego linku, aby zmienić hasło:",
         "Active Tokens": "Aktywne tokeny",
         "Deactivate": "Dezaktywuj",
         "Activate": "Aktywuj",
@@ -287,15 +287,15 @@
         "All": "Wszystko",
         "Choose Media Status": "Wybierz status multimediów",
         "Music": "Muzyka",
-        "Choose Media Type": "Wybierz typ multimediów",
+        "Choose Media Type": "Wybierz rodzaj multimediów",
         "PHP Version Check": "Sprawdzanie wersji PHP",
         "Don\\'t have an account?": "Nie masz konta?",
-        "The value of #987654 is just a placeholder, you can change to any value you like.": "Wartość #987654 jest tylko zastępcza, możesz zmienić ją na dowolną inną.",
+        "The value of #987654 is just a placeholder, you can change to any value you like.": "Wartość #987654 jest tylko zastępcza i możesz zmienić ją na dowolną inną.",
         "This is not the same as database authentication - i.e. Plex Authentication | Emby Authentication | FTP Authentication": "Nie jest to równoznaczne z uwierzytelnianiem bazy danych - np. Uwierzytelnianie Plex | Uwierzytelnianie Emby | Uwierzytelnianie FTP",
         "Status: [ ": "Status: [",
         "This module requires XMLRPC": "Ten moduł wymaga XMLRPC",
         "Misc Options": "Różne opcje",
-        "Search My Media": "Wyszukaj moje multimedia",
+        "Search My Media": "Wyszukaj multimedia",
         "Import Plex Users": "Importuj użytkowników Plex",
         "Import": "Import",
         "INSTALL": "INSTALUJ",
@@ -338,7 +338,7 @@
         "Use Get Token Button": "Użyj przycisku Pobierz token",
         "Plex Token": "Token Plex",
         "Authentication Backend": "Uwierzytelnianie backend",
-        "Authentication Type": "Typ uwierzytelniania",
+        "Authentication Type": "Rodzaj uwierzytelniania",
         "Generate": "Wygeneruj",
         "Generate New API Key": "Wygeneruj nowy klucz API",
         "Organizr API": "API Organizr",
@@ -406,7 +406,7 @@
         "iCal URL's": "URL iCal",
         "Enable iCal": "Włącz iCal",
         "Calendar": "Kalendarz",
-        "Theme Javascript": "Motyw JavaScript",
+        "Theme Javascript": "JavaScript motywu",
         "Custom Javascript": "Niestandardowy JavaScript",
         "Theme CSS [Can replace colors from above]": "Motyw CSS [Może zastąpić kolory z góry]",
         "Custom CSS [Can replace colors from above]": "Niestandardowy CSS [Może zastąpić kolory z góry]",
@@ -498,7 +498,7 @@
         "Close Error": "Zamknij błąd",
         "An Error Occured": "Wystąpił błąd",
         "Debug Area": "Obszar debugowania",
-        "Tab Local URL": "Lokalny URL karty",
+        "Tab Local URL": "URL karty (lokalny)",
         "PRELOAD": "PRELOAD",
         "Use Ombi Alias Names": "Użyj nazw aliasów Ombi",
         "TV Show Default Request": "Domyślne zgłoszenie serialu",
@@ -514,7 +514,7 @@
         "Account Suffix": "Sufiks konta",
         "Account prefix - i.e. Controller\\ from Controller\\Username for AD - uid= for OpenLDAP": "Prefiks konta - tj. Kontroler\\z Kontrolera\\Nazwa użytkownika dla AD - uid= dla OpenLDAP",
         "Account Prefix": "Prefiks konta",
-        "LDAP Backend Type": "Typ backendu LDAP",
+        "LDAP Backend Type": "Rodzaj backendu LDAP",
         "Strict Plex Friends": "Wyłącznie znajomi z Plex"
     }
 }

+ 520 - 0
js/langpack/sl[Slovenian].json

@@ -0,0 +1,520 @@
+{
+    "token": {
+        "Navigation": "Navigiraj",
+        "Date": "Datum",
+        "Type": "Tip",
+        "IP Address": "IP naslov",
+        "Username": "Uporabniško ime",
+        "Message": "Sporočilo",
+        "My Profile": "Moj profil",
+        "Account Settings": "Nastavitve računa",
+        "Login/Register": "Prijavi se / Registracija",
+        "Logout": "Odjava",
+        "Inbox": "Prejeto",
+        "Go Back": "Pojdi nazaj",
+        "Reset": "Razveljavi",
+        "Email": "e-pošta",
+        "Enter your Email and instructions will be sent to you!": "Vnesite svoj e-poštni naslov, kamor bodo poslana nadaljna navodila!",
+        "Register": "Registriraj se",
+        "Registration Password": "Registracijsko geslo",
+        "Recover Password": "Obnovitveno geslo",
+        "Password": "Geslo",
+        "Sign Up": "Prijavi se",
+        "Don't have an account?": "Nimaš računa?",
+        "Forgot pwd?": "Ste pozabili pwd?",
+        "Remember Me": "Zapomni si me",
+        "Login": "Prijava",
+        "Installed": "Nameščeno",
+        "Install Update": "Namesti posodobitve",
+        "Organizr Versions": "Verzije Organizr",
+        "About": "O stvari",
+        "Organizr Logs": "Dnevniki Organizr",
+        "Main Settings": "Glavne nastavitve",
+        "Updates": "Posodobitve",
+        "Logs": "Dnevniki",
+        "Main": "Glavno",
+        "Plugins": "Vtičniki",
+        "Manage Users": "Upravljal uporabnike",
+        "Customize Organizr": "Prilagodi Organizr",
+        "Edit Categories": "Uredi kategorije",
+        "Edit Tabs": "Uredi zavihtke",
+        "Tabs": "Zavihtek",
+        "System Settings": "Sistemske nastavitve",
+        "User Management": "Upravljanje uporabnikov",
+        "Customize": "Prilagodi",
+        "Tab Editor": "Urejevalnik zavihtkov",
+        "Settings": "Nastavitve",
+        "Organizr Settings": "Organizr nastavitve",
+        "Categories": "Kategorije",
+        "Login Logs": "Dnevniki prijav",
+        "Login Log": "Dnevnik prijav",
+        "Organizr Log": "Dnevnik Organizr",
+        "FIXED": "POPRAVLJENO",
+        "NEW": "NOVO",
+        "NOTE": "OPOMBA",
+        "Update Available": "Nadgradnja na voljo",
+        "is available, goto": "Je na voljo, pojdi",
+        "Update Tab": "Posodobi zavihtek",
+        "Database Name:": "Ime podatkovne baze:",
+        "Database Name": "Ime podatkovne baze",
+        "Database Location:": "Lokacija podatkovne baze:",
+        "Database Location": "Lokacija podatkovne baze",
+        "Hover to show": "Z miško lebni nad gumbom za prikaz",
+        "API Key:": "API ključ:",
+        "API Key": "API ključ",
+        "Registration Password:": "Registracijsko geslo:",
+        "Hash Key:": "Hash ključ:",
+        "Hash Key": "Hash ključ",
+        "Password:": "Geslo:",
+        "Attention": "POZOR",
+        "The Hash Key will be used to decrypt all passwords etc... on the server.": "Hash ključ bo uporabljen za dekripcijo vseh gesel in podobno... na tem serverju.",
+        "The API Key will be used for all calls to organizr for the UI. [Auto-Generated]": "API ključ bo uporabljen za vsa klicanja organizr na uporabnišem vmestniku (UI). [Samodejo generirano]",
+        "Notice": "Opazi",
+        "Business": "Poslovno",
+        "Personal": "Zasebno",
+        "Choose License": "Izberi licenco",
+        "Install Type": "Namestitveni tip",
+        "Verify": "Potrdi",
+        "Database": "Podatkovna baza",
+        "Security": "Varnost",
+        "Admin Info": "Administratorjeve informacije",
+        "Parent Directory:": "Nadrejena mapa",
+        "Admin Creation": "Stvaritev skrbnika",
+        "The Database will contain sensitive information.  Please place in directory outside of root Web Directory.": "Podakovna baza bo vsebovala senzitivne informacije. Prosimo, da svojo mapo premaknete izven glavne spletne mape (Web Directory)",
+        "MANAGE": "UPRAVLJAJ",
+        "CATEGORY": "KATEGORIJE",
+        "ADDED": "DODANO",
+        "NAME & EMAIL": "IME & E-POŠTA",
+        "MANAGE USERS": "UPRAVLJANJE UPORABNIKOV",
+        "Add User": "Dodaj uporabnike",
+        "Manage Groups": "Upravljaj skupine",
+        "Choose Language": "Izberi jezik",
+        "Welcome": "POZDRAVLJENI",
+        "License": "LICENCA",
+        "Webserver Version": "Verzija spletnega strežnika",
+        "PHP Version": "PHP verzija",
+        "Organizr Branch": "Veja Organizr",
+        "Organizr Version": "Verzija Organizr",
+        "Information": "Informacija",
+        "Below you will find all the links for everything that has to do with Organizr": "Spodaj boste našli povezave do vsega, kar ima veze z Organizr",
+        "Loading...": "Nalagam...",
+        "Donate": "Prispevaj",
+        "Edit Group": "Uredi skupino",
+        "For icons, use the following format:": "Za ikone, izberi naslednji format:",
+        "For images, use the following format:": "Za slike, izberi naslednji format",
+        "You may use an image or icon in this field": "Za to polje lahko uporabiš sliko ali ikono",
+        "Image Legend": "Legenda slike",
+        "Category Image": "Kategorija Slika",
+        "Category Name": "Kategorija Ime",
+        "Edit Category": "Uredi Kategorijo",
+        "Add Category": "Dodaj Kategorijo",
+        "Add New Category": "Dodaj novo Kategorijo",
+        "DELETE": "ODSTANI",
+        "EDIT": "UREDI",
+        "DEFAULT": "PRIVZETO",
+        "TABS": "ZAVIHTEK",
+        "NAME": "IME",
+        "Category Editor": "Urejevalnik kategorije",
+        "Tab Image": "Slika zavihtka",
+        "Tab URL": "URL zavihtka",
+        "Tab Name": "Ime zavihtka",
+        "Edit Tab": "Uredi zavihtek",
+        "Add Tab": "Dodaj zavihtek",
+        "Add New Tab": "Dodaj nov zavihtek",
+        "SPLASH": "SPLASH",
+        "ACTIVE": "AKTIVNO",
+        "TYPE": "TIP",
+        "GROUP": "SKUPINA",
+        "Delete": "Odstrani",
+        "No": "Ne",
+        "Yes": "Ja",
+        "Deleted Category": "Odstrani Kategorijo",
+        "Add New User": "Dodaj novega uporabnika",
+        "EMAIL": "E-POŠTA",
+        "Deleted User": "Odstrani uporabnika",
+        "Category Order Saved": "Vrstni red shranjevanja kategorij",
+        "Group Image": "Slika skupine",
+        "Group Name": "Ime skupine",
+        "Add Group": "Dodaj skupino",
+        "Add New Group": "Dodaj novo skupino",
+        "USERS": "UPORABNIKI",
+        "GROUP NAME": "IME SKUPINE",
+        "MANAGE GROUPS": "UPRAVLJAJ SKUPINE",
+        "Changed Language To": "Zamenjaj jezik na",
+        "Groups": "Skupine",
+        "Users": "Uporabniki",
+        "Appearance": "Izgled",
+        "Customize Appearance": "Prilagodi izgled",
+        "Request Me!": "Zaprosi me!",
+        "Would you like to Request it?": "Ali bi radi zaprosili zanj?",
+        "No Results for:": "Ni rezultatov za:",
+        "Nothing in queue": "V čakalni vrsti ni ničesar",
+        "Nothing in history": "V zgodovini ni ničesar",
+        "Nothing in hitsory": "V HITSORY ni ničesar",
+        "Load More": "Naloži več",
+        "Request": "Zaprosi",
+        "No Results": "Ni rezultatov",
+        "Airs Today TV": "Na TV se predvaja danes",
+        "Popular TV": "Popularna TV",
+        "Top TV": "Najboljša TV",
+        "Upcoming Movies": "Prihajajoči filmi",
+        "Popular Movies": "Popularni filmi",
+        "Top Movies": "Najboljši filmi",
+        "In Theatres": "V kinematografih",
+        "Suggestions": "Predlogi",
+        "TV": "TV",
+        "Movie": "Film",
+        "Denied": "Zavrnjeno",
+        "Unapproved": "Neudobreno",
+        "Approved": "Udobreno",
+        "Unavailable": "Ni na voljo",
+        "Available": "Na voljo",
+        "Recently Added": "Dodano pred kratkim",
+        "Active": "Aktivno",
+        "Requested By:": "Zanj zaprosil :",
+        "Request Options": "Opcije zaprošanja",
+        "Deny": "Zavrnjeno",
+        "OK": "OK",
+        "Seconds": "Sekunde",
+        "Verify Password": "Potrdi Geslo",
+        "Account Information": "Informacije računa",
+        "Inactive Plugins": "Neaktivni vtičniki",
+        "Active Plugins": "Aktivni vtičniki",
+        "Inactive": "Neaktivno",
+        "Everything Active": "Vse aktivno",
+        "Nothing Active": "Nič aktivno",
+        "Choose Plex Machine": "Izberi Plex server",
+        "Test Speed to Server": "Testiraj hitrost do serverja",
+        "Test Server Speed": "Testiraj serverjevo hitrost",
+        "Subject": "predmet",
+        "To:": "Za:",
+        "Email Users": "email uporabniki",
+        "E-Mail Center": "E-Poštni Center",
+        "You have been invited. Please goto ": "Ti si bil povabljan. Prosim pojdi na ",
+        "Use Invite Code": "Uporabi pozivno kodo",
+        "VALID": "Veljavno",
+        "IP ADDRESS": "IP naslov",
+        "USED BY": "UPORABLJA",
+        "DATE USED": "DATUM UPORABE",
+        "DATE SENT": "POSLAN DATUM",
+        "INVITE CODE": "NEVELJAVNA KODA",
+        "USERNAME": "UPORABNIŠKO IME",
+        "Manage Invites": "Uprabljaj povabila",
+        "No Invites": "Ni povabil",
+        "Create/Send Invite": "Ustvari/pošlji povabilo",
+        "Name or Username": "Ime ali Uporabniško ime",
+        "New Invite": "Novo povabilo",
+        "Hover to show ": "Z miško lebni nad gumbom za prikaz ",
+        "Parent Directory: ": "nadrejeni imenik",
+        "The Database will contain sensitive information. Please place in directory outside of root Web Directory.": "Podatkovna baza bo vsebovala senzitivne informacije. Prosimo da izberete mapo izven glavne spletne mape (Web Directory)",
+        "I Want to Help": " Želim pomagati",
+        "Head on over to POEditor and help us translate Organizr into your language": "Odpravi se do POEditor-ja in nam pomagaj prevesti Organizr v tvoj jezik",
+        "Want to help translate?": "Želiš pomagati prevajati?",
+        "Single Sign-On": "Enkratna prijava",
+        "Coming Soon...": "Prijaja kmalu",
+        "Homepage Order": "uredi domačo stran",
+        "Homepage Items": "predmeti na domači strani",
+        "Image Manager": "upravitelj slik",
+        "Edit User": "Uredi Uporabnika",
+        "Password Again": "Ponovno geslo",
+        "Template": "predloga",
+        "Plex Machine": "Plex strežnik",
+        "Get Plex Machine": "Pridobi Plex strežnik",
+        "Grab It": "Pograbi",
+        "Plex Password": "Plex Geslo",
+        "Plex Username": "Plex Uporabniško Ime",
+        "Enter Plex Details": "Vnesi podrobnosti o Plexu",
+        "Get Plex Token": "Pridobi Plex žeton",
+        "Upload Image": "Naloži sliko",
+        "View Images": "Preglej slike",
+        "Reload": "Ponovno naloži",
+        "Unlock": "Odkleni",
+        "Browser Information": "informacije brskalnika",
+        "Web Folder": "Spletna mapa",
+        "Dependencies Missing": "Manjkajoče odvisnosti",
+        "Organizr Dependency Check": "Oznaka odvisnosti Organizr",
+        "Please make sure both Token and Machine are filled in": "Prosimo, prepričajte se, da sta Žeton in Server izpolnjena",
+        "Loading Requests...": "Nalagam Prošnje...",
+        "Loading Recent...": "Nalagam Nedavno...",
+        "Loading Now Playing...": "Nalagam Zdaj v predvajanju...",
+        "Loading Playlists...": "Nalagam sezname predvajanja...",
+        "Loading Download Queue...": "Nalagam čakalno vrsto prenosov...",
+        "Organizr Mod Picks": "Izbira Moddov Organizr ",
+        "Requests": "Prošnje",
+        "Become Sponsor": "Postani sponzor",
+        "Splash Page": "Splash Page",
+        "Lock Screen": "Zaklenjen zaslon",
+        "If you signed in with a Emby Acct... Please use the following link to change your password there:": "Če si prijavljen z Emby računom... Prosimo, da sledite linku in spremenite svoje geslo tukaj:",
+        "Password Notice": "Opomba gesla",
+        "If you signed in with a Plex Acct... Please use the following link to change your password there:": "Če si prijavljen z Plex računom... Prosimo, da sledite linku in spremenite svoje geslo tukaj:",
+        "Active Tokens": "Aktivni žetoni",
+        "Deactivate": "Deaktiviraj",
+        "Activate": "Aktiviraj",
+        "Current": "Trenutno",
+        "Save": "Shrani",
+        "STATUS": "STATUS",
+        "PLUGIN": "VTIČNIK",
+        "Plugin Marketplace": "Trgvričnokov",
+        "Marketplace": "Trg",
+        "Chat": "Klepet",
+        "Current Directory: ": "Trenutni direktorij:",
+        "Suggested Directory: ": "Predlagan direktorij",
+        "The Registration Password will lockout the registration field with this password. {User-Generated]": "Registracijsko geslo bo odklenilo registracijsko polje z tem geslom. {Uporabnik-Generirano]",
+        "The Hash Key will be used to decrypt all passwords etc... on the server. {User-Generated]": "Hash ključ bo uporabljen za dekripcijo vseh gesel in ipd... na tem serverju. {Uporabnik-Generirano]",
+        "If using Plex or Emby - It is suggested that you use the username and email of the Admin account.": "Če uporavljate Plex ali Emby - Zaželjeno je, da uporabite uporabniško ime in geslo od Administrativnega računa",
+        "Business has Media items hidden [Plex, Emby etc...]": "Poslovno ima skrite Media izbire [Plex, Emby, ipd...]",
+        "Personal has everything unlocked - no restrictions": "Zasebno ima vse odklenjeno - brez omejitev",
+        "Continue To Website": "Nadaljuj na spletno stran",
+        "Patreon": "Patreon",
+        "Cryptos": "Kripto",
+        "Square Cash": "Square Cash",
+        "PayPal": "PayPal",
+        "Beerpay.io": "Beerpay.io",
+        "Sponsors": "Sponzorji",
+        "THEME": "TEME",
+        "Theme Marketplace": "Trg tem",
+        "Test Tab": "Testni zavihtek",
+        "Select or type Icon": "izberi ali napiši icon",
+        "Choose Icon": "Izberi ikono",
+        "Choose Image": "Izberi sliko",
+        "Ping URL": "Ping-aj URL",
+        "Please set tab as [New Window] on next screen": "Prosimo, da nastavite zavihtke kot [Novo okno] na naslenjem zaslonu",
+        "Tab can be set as iFrame": "Zavihtem je lahko nastavljen kot iFrame",
+        "Premier": "Premiera",
+        "Missing": "Manjka",
+        "Unaired": "Nepredvajano",
+        "Downloaded": "Naloženo",
+        "All": "Vse",
+        "Choose Media Status": "Izberi status medie",
+        "Music": "Glasba",
+        "Choose Media Type": "izberi tip medie",
+        "PHP Version Check": "Pregled PHP verzije",
+        "Don\\'t have an account?": "Še nimaš račnuna ?",
+        "The value of #987654 is just a placeholder, you can change to any value you like.": "Vrednost #987654 je le \"Placeholder\", lahko jo spremeniš v katerokoli vrednost želiš",
+        "This is not the same as database authentication - i.e. Plex Authentication | Emby Authentication | FTP Authentication": "To ni isto kot avtentikacija podatkovne baze- t.j. Preverjanje pristnosti Plex | Preverjanje pristnosti Emby | Preverjanje pristnosti FTP\n\n",
+        "Status: [ ": "Status:[ ",
+        "This module requires XMLRPC": "Ta model potrebuje XMLRPC",
+        "Misc Options": "Misc opcije",
+        "Search My Media": "Išči moj media",
+        "Import Plex Users": "uvozi plex uporabnike",
+        "Import": "uvozi",
+        "INSTALL": "NAMESTI",
+        "INFO": "Informacije",
+        "Day": "Dan",
+        "Week": "Teden",
+        "Month": "Mesec",
+        "List": "Lista",
+        "Streams": "tokovi",
+        "javascript:void(0)": "javascript:void(0)",
+        "Request Show or Movie": "Zaprosi za TV-serijo ali film",
+        "Mark as Unavailable": "Označi kot nedosegljivo",
+        "Mark as Available": "Označi kot dosegljivo",
+        "Approve": "Sprejmi",
+        "Start": "Start",
+        "Everyone Refresh Seconds": "Osvežitv po sekundah za vse",
+        "Admin Refresh Seconds": "Osvežitv po sekundah za Administratorja",
+        "Minimum Authentication for Time Display": "Minimalno preverjanje pristnosti za prikaz časa",
+        "Show Ping Time": "Prikaži Ping čas",
+        "Offline Sound": "\"offline\" zvok",
+        "Online Sound": "\"online\" zvok",
+        "Minimum Authentication for Message and Sound": "Minimalno preverjanje pristnosti za Sporočila in zvok",
+        "Minimum Authentication": "Minimalno preverjanje pristnosti",
+        "Nginx Auth Debug": "Nginx Auth razhroščevaje",
+        "Hide Registration": "Skrij registracijo",
+        "Lockout Groups To": "zakleni skupine k",
+        "Lockout Groups From": "zakleni skupine iz",
+        "Inactivity Lock": "zaklep v primeru neaktivnosti",
+        "Inactivity Timer [Minutes]": "čas neaktivnosti [v minutah]",
+        "Emby Token": "Emby Žeton",
+        "http(s)://hostname:port": "http(s)://hostname:port",
+        "Emby URL": "Emby URL",
+        "cn=%s,dc=sub,dc=domain,dc=com": "cn=%s,dc=sub,dc=domain,dc=com",
+        "Host Base DN": "Osnovni DN gostitelja",
+        "http{s) | ftp(s) | ldap(s)://hostname:port": "http{s) | ftp(s) | ldap(s)://hostname:port",
+        "Host Address": "Naslov strežnika",
+        "Enable Plex oAuth": "Omogoče Plex oAuth",
+        "Retrieve": "prikliči",
+        "Use Get Plex Machine Button": "uporabi Pridobi Plex Server gumb",
+        "Use Get Token Button": "Uporabi Pridobi Žeton gumb",
+        "Plex Token": "Plex Žeton",
+        "Authentication Backend": "Preverjanje pristnosti v ozadju",
+        "Authentication Type": "Tip preverjanja pristnosti",
+        "Generate": "Generiraj",
+        "Generate New API Key": "Generiraj nov API ključ",
+        "Organizr API": "Organizr API",
+        "Force Install Branch": "namesti vejo z silo",
+        "Branch": "veja",
+        "SSO": "SSO",
+        "Enable": "Omogoči",
+        "Tautulli URL": "Tautulli URL",
+        "Ombi URL": "Ombi URL",
+        "Plex Note": "Plex opombe",
+        "Admin username for Plex": "Administracijsko Uporabniško ime za Plex",
+        "Admin Username": "Administracijsko uporabniško ime",
+        "Click Main on the sub-menu above.": "V zgornjem podmeniju kliknite Main.",
+        "Important Information": "Pomembne informacije",
+        "PING": "PING",
+        "Custom HTML/JavaScript": "Custom HTML/JavaScript",
+        "CustomHTML-2": "CustomHTML-2",
+        "CustomHTML-1": "CustomHTML-1",
+        "Refresh Seconds": "Osvežitev sekund",
+        "Limit to User": "Omeji na uporabnika",
+        "Minimum Group to Request": "Minimalna skupina za pošnjo",
+        "Token": "žeton",
+        "URL": "URL",
+        "Ombi": "Ombi",
+        "Items Per Day": "Stvari na dan",
+        "Time Format": "Časovni format",
+        "Default View": "Privzeti pogled",
+        "Start Day": "dan začetka",
+        "SickRage": "SickRage",
+        "CouchPotato": "CouchPotato",
+        "Test Connection": "Testiraj povezavo",
+        "Please Save before Testing": "Prosimo, da shranite, preden Testirate",
+        "# of Days After": "# dni po",
+        "# of Days Before": "#dni za",
+        "Radarr": "Radar",
+        "Lidarr": "lidarr",
+        "Show Unmonitored": "ne spremljaj TV-Serije",
+        "Sonarr": "sonarr",
+        "Hide Completed": "Skrij končane",
+        "Hide Seeding": "Skrij sejanje",
+        "Deluge": "Deluge",
+        "Order": "razpored",
+        "Status: [": "Status:[",
+        "]": "]",
+        "rTorrent": "rTorrent",
+        "Reverse Sorting": "Reverzno sortiranje",
+        "qBittorrent": "qBittorrent",
+        "Transmission": "prenos",
+        "NZBGet": "NZBGet\n",
+        "SabNZBD": "SabNZBD",
+        "Image Cache Size": "Velikost predpomnilnika slike",
+        "Emby Tab WAN URL": "URL naslova Emby WAN",
+        "Only use if you have Emby in a reverse proxy": "Uporabi le, če imaš emby v \"Reverse proxy\"",
+        "Emby Tab Name": "Emby ime zavihtka",
+        "Item Limit": "Omejitev postavke",
+        "Minimum Authorization": "Minimalno preverjanje pristnosti",
+        "User Information": "Uporabniške informacije",
+        "Emby": "Emby",
+        "Plex Tab WAN URL": "URL naslova PLEX WAN",
+        "Only use if you have Plex in a reverse proxy": "Uporabi le. če imaš Plex v \"Reverse proxy\"",
+        "Plex Tab Name": "Plex ime zavihtka",
+        "Media Server": "Media strežnik",
+        "Plex": "Plex",
+        "separate by comma's": "Loči z vejicami",
+        "iCal URL's": "iCal URL's",
+        "Enable iCal": "omogoči iCal",
+        "Calendar": "Kolendar",
+        "Theme Javascript": "javascript tema",
+        "Custom Javascript": "Javascript po meri",
+        "Theme CSS [Can replace colors from above]": "CSS tema [lahko zamenjaš z barvami od zgoraj]",
+        "Custom CSS [Can replace colors from above]": "CSS po meri [lahko zamenjaš z barvami od zgoraj]",
+        "Copy code and paste inside left box": "Kopiraj kodo in jo prilepi znotraj levega polja",
+        "Download and unzip file and place in": "Naloži in odpakiraj mapo in jo postavi v",
+        "Click [Generate your Favicons and HTML code]": "Kliknite [Generiraj svoje Favicons in HTML kodo]",
+        "Enter this path": "vnesi pot",
+        "At bottom of page on [Favicon Generator Options] under [Path] choose [I cannot or I do not want to place favicon files at the root of my web site.]": "Na dnu strani [Favicon Generator Options] pod [Path] izberite [Ne morem ali ne želim, da bi datoteke favicon namestili v \"root\" moje spletne strani.]",
+        "Edit settings to your liking": "uredi nastavitve po svoji želji",
+        "Choose your image to use": "Izberi svojo sliko za uporabo",
+        "Click [Select your Favicon picture]": "Klikni [izberi svojo Favicon sliko]",
+        "Instructions": "Navodila",
+        "Fav Icon Code": "Fav Icon Koda",
+        "Test Message": "Testno sporočilo",
+        "Position": "Pozicija",
+        "Style": "Stil",
+        "Theme": "Tema",
+        "Button Text Color": "barva beseduka v gumbu",
+        "Button Color": "Barva gumba",
+        "Accent Text Color": "Priliv barve besedila",
+        "Accent Color": "Priliv barve",
+        "Side Bar Text Color": "Barva besedila v stranski vrstici",
+        "Side Bar Color": "barva stranske vrstice",
+        "Nav Bar Text Color": "barva besedila v navigacijski vrstici",
+        "Nav Bar Color": "barva navigacijske vrstice",
+        "Unsorted Tab Placement": "Umestitev nesortiranega zavihka",
+        "Alternate Homepage Titles": "Alternativni naslovi spletne strani",
+        "Minimal Login Screen": "Minimalno prijavno okno",
+        "Login Wallpaper": "Prijavno ozadje",
+        "Use Logo instead of Title": "Uporabi logo namesto naslova",
+        "Title": "naslov",
+        "Logo": "logo",
+        "Import Users": "uvozi uporabike",
+        "Drop files here to upload": "Za nalaganje spusti datoteke tukaj",
+        "SpeedTest Settings": "Nastavitve testa hitrosti",
+        "PHP Mailer Settings": "Nastavitve PHP Mailerja",
+        "Invites Settings": "Nastavitve povabil",
+        "Chat Settings": "Nastavitve klepeta",
+        "Delete ": "odstrani ",
+        "App Cluster": "Aplikacijski grozd",
+        "App ID": "Aplikacijski ID",
+        "API Secret": "API skrivnost",
+        "Auth Key": "Auth ključ",
+        "Use Pusher SSL": "Uporabi Pusher SSL",
+        "Message Sound": "Zvok sporočila",
+        "# of Previous Messages": "# prejšnjih sporočil",
+        "Note": "opomba",
+        "Libraries": "knjižnice",
+        "Body": "telo",
+        "Name": "ime",
+        "Template #4": "Template #4",
+        "Template #3": "Template #3",
+        "Template #2": "Template #2",
+        "Reminder Template": "Predloga za opomnike",
+        "Invite User": "povabi uporabnike",
+        "Reset Password": "Ponastavi geslo",
+        "New Registration": "Nova registracija",
+        "Edit Template": "Uredi predlogo",
+        "Send Welcome E-Mail": "pošlji Pozdravno sporočilo",
+        "Full URL": "polni URL",
+        "WAN Logo URL": "URL logotipa WAN",
+        "https://domain.com/": "https://domain.com/",
+        "Domain Link Override": "Povozi link domene",
+        "Send": "pošlji",
+        "Send Test": "pošlji test",
+        "i.e. same as username": "t.j. isto kot uporabniško ime",
+        "Sender Email": "pošiljateljeva e-pošta",
+        "Sender Name": "pošiljateljevo ime",
+        "Verify Certificate": "Preveri certifikat",
+        "Authentication": "Preverjanje pristnosti",
+        "SMTP Port": "SMTP Port",
+        "SMTP Host": "SMTP gostitelj",
+        "Results For cmd:": "Rezultati za cmd:",
+        "Organizr Information:": "Informacije Organizr:",
+        "DB Schema": "shema podatkovne baze",
+        "Misc SSO": "Misc SSO",
+        "Tautulli SSO": "Tautulli SSO",
+        "Plex SSO": "Plex SSO",
+        "Ombi SSO": "Ombi SSO",
+        "Commands": "Ukazi",
+        "Input Command": "vnesi ukaz",
+        "Organizr Debug Area": "Organizr razhroščevalno območje",
+        "Google Ads": "Googlovi oglasi",
+        "DB Folder": "Mapa podatkovne baze",
+        "API Folder": "API mapa",
+        "Root Folder": "Korenska mapa",
+        "Organizr Paths": "Poti Organizr",
+        "Organizr News": "novice  Organizr",
+        "Close Error": "zapri napako",
+        "An Error Occured": "Zgodila se je napaka",
+        "Debug Area": "razhroščevalno območje",
+        "Tab Local URL": "Zavihtek lokalnega URL",
+        "PRELOAD": "PREDNALOŽI",
+        "Use Ombi Alias Names": "Uporabite imena Alias ​​zahtevkov",
+        "TV Show Default Request": "privzeta zahteva TV oddaj",
+        "Add to Combined Downloader": "Dodaj v skupni downloader",
+        "http(s)://hostname:port/xmlrpc": "http(s)://hostname:port/xmlrpc\nLength: 30",
+        "rTorrent API URL Override": "Povozi rTorrent API URL",
+        "Enable Notify Sounds": "omogoči zvok obvestila",
+        "Remember Me Length": "Dolžina Zapomni si Me",
+        "Minimum Authentication for Debug Area": "Minimalno preverjanje pristnosti za razhroščevalno območje",
+        "Account DN": "DN računa",
+        "Bind Username": "veži uporabniško ime",
+        "Account suffix - start with comma - ,ou=people,dc=domain,dc=tld": "Pripona računa - začni z vejico - ,ou=ljudje,dc=domena,dc=tld",
+        "Account Suffix": "pripona računa",
+        "Account prefix - i.e. Controller\\ from Controller\\Username for AD - uid= for OpenLDAP": "Predpona računa -t.j. Kontroler\\ iz kontrolerja\\Uporabniško ime in AD -uid= zaOpenLDAP",
+        "Account Prefix": "Predpona računa",
+        "LDAP Backend Type": "LDAP vrsta orodja",
+        "Strict Plex Friends": "Strogo Prijatelji iz Plexa"
+    }
+}

+ 7 - 0
js/news.json

@@ -1,4 +1,11 @@
 [
+  {
+    "title": "Plex Oauth Issues - RESOLVED!",
+    "subTitle": "Let's make them bring back support correctly",
+    "date": "2019-07-17 15:20",
+    "body": "It seems like Plex has broken support for us using Oauth.  Currently there is a workaround but we would rather they fix the issue.  Please share your feedback in this thread: <a href=\"https:\/\/forums.plex.tv\/t\/plex-oauth-not-working-with-tautulli-ombi-etc\/433945\" target=\"_blank\">Plex Oauth Discussion<\/a>",
+    "author": "CauseFX"
+  },
   {
     "title": "Emby Discontinued Support",
     "subTitle": "Emby API - EmbyConnect Deprecated",

File diff suppressed because it is too large
+ 6 - 8
js/simplebar.js


+ 7 - 0
js/version.json

@@ -194,5 +194,12 @@
     "new": "Login lockout feature|JDownloader HP Support - rix|Coupon indicator on sponsor section|Sponsor API call|Custom Pages API endpoint|Theme analytics|SVG added to whitelist",
     "fixed": "Homepage Loading Sections Displaying for Disabled Items (#1204)|Healthchecks tag issue|Updated Evo logo|Updated Netdata logo|Updated Nextcloud logo|CSS fix for alt headers on HP|HP Downloader counter on MS Edge|Cache image button on recent items",
     "notes": "Please report bugs in GitHub issues page|Updated language translations"
+  },
+  "2.0.291": {
+    "date": "2019-08-31 16:40",
+    "title": "Sorry for late update",
+    "new": "Added Auth Proxy - Accept X-Forwarded-User (#1215)|Ombi default sort option|JDownloader Actions|Set of wallpapers for login page (#1214)|Logging to help with email debug|HTTP codes to API|IPInfo in settings logs|Plex oauth update - thanks swifty|Disable Google Authenticator for Local Access (#1232)",
+    "fixed": "Upgraded Simplebar|Homepage downloader variable being overwritten|Timeout on Docker update|Radarr Calendar Image URL",
+    "notes": "Please report bugs in GitHub issues page|Updated language translations"
   }
 }

Some files were not shown because too many files changed in this diff