Przeglądaj źródła

Merge branch 'v2-develop' into netdata-tweaks

Henry Whitaker 6 lat temu
rodzic
commit
e4a41d4751

+ 2 - 0
.gitignore

@@ -115,6 +115,8 @@ plugins/theme_files/*
 plugins/plugin_files/*
 plugins/plugin_files/*
 !plugins/theme_files/index.html
 !plugins/theme_files/index.html
 !plugins/plugin_files/index.html
 !plugins/plugin_files/index.html
+plugins/images/userTabs/*
+!plugins/images/userTabs/index.html
 # =========================
 # =========================
 # Plugin files
 # Plugin files
 # =========================
 # =========================

+ 8 - 2
api/config/default.php

@@ -8,6 +8,8 @@ return array(
 	'authBackendHostSuffix' => '',
 	'authBackendHostSuffix' => '',
 	'ldapBindUsername' => '',
 	'ldapBindUsername' => '',
 	'ldapBindPassword' => '',
 	'ldapBindPassword' => '',
+	'ldapSSL' => false,
+	'ldapTLS' => false,
 	'authBaseDN' => '',
 	'authBaseDN' => '',
 	'authBackendDomain' => '',
 	'authBackendDomain' => '',
 	'ldapType' => '1',
 	'ldapType' => '1',
@@ -316,7 +318,7 @@ return array(
 	'speedtestHeaderToggle' => true,
 	'speedtestHeaderToggle' => true,
 	'speedtestHeader' => 'Speedtest',
 	'speedtestHeader' => 'Speedtest',
 	'homepageNetdataEnabled' => false,
 	'homepageNetdataEnabled' => false,
-	'homepageNetdataRefresh' => '2500',
+	'homepageNetdataRefresh' => '10000',
 	'homepageNetdataAuth' => '1',
 	'homepageNetdataAuth' => '1',
 	'netdataURL' => '',
 	'netdataURL' => '',
 	'netdata1Title' => '',
 	'netdata1Title' => '',
@@ -384,5 +386,9 @@ return array(
 	'netdata7Enabled' => false,
 	'netdata7Enabled' => false,
 	'netdataCustom' => '{
 	'netdataCustom' => '{
     
     
-	}'
+	}',
+	'githubMenuLink' => true,
+	'organizrSupportMenuLink' => true,
+	'organizrDocsMenuLink' => true,
+	'organizrSignoutMenuLink' => true
 );
 );

+ 7 - 1
api/functions/api-functions.php

@@ -1154,6 +1154,12 @@ function importUsersType($array)
 			case 'plex':
 			case 'plex':
 				return importUsers(allPlexUsers(true));
 				return importUsers(allPlexUsers(true));
 				break;
 				break;
+			case 'jellyfin':
+				return importUsers(allJellyfinUsers(true));
+				break;
+			case 'emby':
+				return importUsers(allEmbyUsers(true));
+				break;
 			default:
 			default:
 				return false;
 				return false;
 		}
 		}
@@ -1288,7 +1294,7 @@ function youtubeSearch($query)
 	if (!$query) {
 	if (!$query) {
 		return 'no query provided!';
 		return 'no query provided!';
 	}
 	}
-	$keys = array('AIzaSyBsdt8nLJRMTwOq5PY5A5GLZ2q7scgn01w', 'AIzaSyD-8SHutB60GCcSM8q_Fle38rJUV7ujd8k', 'AIzaSyBzOpVBT6VII-b-8gWD0MOEosGg4hyhCsQ');
+	$keys = array('AIzaSyBsdt8nLJRMTwOq5PY5A5GLZ2q7scgn01w', 'AIzaSyD-8SHutB60GCcSM8q_Fle38rJUV7ujd8k', 'AIzaSyBzOpVBT6VII-b-8gWD0MOEosGg4hyhCsQ', 'AIzaSyBKnRe1P8fpfBHgooJpmT0WOsrdUtZ4cpk');
 	$randomKeyIndex = array_rand($keys);
 	$randomKeyIndex = array_rand($keys);
 	$key = $keys[$randomKeyIndex];
 	$key = $keys[$randomKeyIndex];
 	$apikey = ($GLOBALS['youtubeAPI'] !== '') ? $GLOBALS['youtubeAPI'] : $key;
 	$apikey = ($GLOBALS['youtubeAPI'] !== '') ? $GLOBALS['youtubeAPI'] : $key;

+ 129 - 17
api/functions/auth-functions.php

@@ -145,7 +145,87 @@ function allPlexUsers($newOnly = false)
 		}
 		}
 		return false;
 		return false;
 	} catch (Requests_Exception $e) {
 	} catch (Requests_Exception $e) {
-		writeLog('success', 'Plex User Function - Error: ' . $e->getMessage(), $username);
+		writeLog('success', 'Plex Import User Function - Error: ' . $e->getMessage(), 'SYSTEM');
+	}
+	return false;
+}
+
+function allJellyfinUsers($newOnly = false)
+{
+	try {
+		if (!empty($GLOBALS['embyURL']) && !empty($GLOBALS['embyToken'])) {
+			$url = qualifyURL($GLOBALS['embyURL']) . '/Users?api_key=' . $GLOBALS['embyToken'];
+			$headers = array();
+			$response = Requests::get($url, $headers);
+			if ($response->success) {
+				$users = json_decode($response->body, true);
+				if (is_array($users) || is_object($users)) {
+					$results = array();
+					foreach ($users as $child) {
+						// Jellyfin doesn't list emails for some reason
+						$email = random_ascii_string(10) . '@placeholder.eml';
+						if ($newOnly) {
+							$taken = usernameTaken((string)$child['Name'], $email);
+							if (!$taken) {
+								$results[] = array(
+									'username' => (string)$child['Name'],
+									'email' => $email
+								);
+							}
+						} else {
+							$results[] = array(
+								'username' => (string)$child['Name'],
+								'email' => $email,
+							);
+						}
+					}
+					return $results;
+				}
+			}
+		}
+		return false;
+	} catch (Requests_Exception $e) {
+		writeLog('success', 'Jellyfin Import User Function - Error: ' . $e->getMessage(), 'SYSTEM');
+	}
+	return false;
+}
+
+function allEmbyUsers($newOnly = false)
+{
+	try {
+		if (!empty($GLOBALS['embyURL']) && !empty($GLOBALS['embyToken'])) {
+			$url = qualifyURL($GLOBALS['embyURL']) . '/Users?api_key=' . $GLOBALS['embyToken'];
+			$headers = array();
+			$response = Requests::get($url, $headers);
+			if ($response->success) {
+				$users = json_decode($response->body, true);
+				if (is_array($users) || is_object($users)) {
+					$results = array();
+					foreach ($users as $child) {
+						// Emby doesn't list emails for some reason
+						$email = random_ascii_string(10) . '@placeholder.eml';
+						if ($newOnly) {
+							$taken = usernameTaken((string)$child['Name'], $email);
+							if (!$taken) {
+								$results[] = array(
+									'username' => (string)$child['Name'],
+									'email' => $email
+								);
+							}
+						} else {
+							$results[] = array(
+								'username' => (string)$child['Name'],
+								'email' => $email,
+							);
+						}
+					}
+					return $results;
+				}
+			}
+		}
+		return false;
+	} catch (Requests_Exception $e) {
+		writeLog('success', 'Emby Import User Function - Error: ' . $e->getMessage(), 'SYSTEM');
 	}
 	}
 	return false;
 	return false;
 }
 }
@@ -225,8 +305,8 @@ if (function_exists('ldap_connect')) {
 				'account_suffix' => (empty($GLOBALS['authBackendHostSuffix'])) ? null : $GLOBALS['authBackendHostSuffix'],
 				'account_suffix' => (empty($GLOBALS['authBackendHostSuffix'])) ? null : $GLOBALS['authBackendHostSuffix'],
 				'port' => $ldapPort,
 				'port' => $ldapPort,
 				'follow_referrals' => false,
 				'follow_referrals' => false,
-				'use_ssl' => false,
-				'use_tls' => false,
+				'use_ssl' => $GLOBALS['ldapSSL'],
+				'use_tls' => $GLOBALS['ldapTLS'],
 				'version' => 3,
 				'version' => 3,
 				'timeout' => 5,
 				'timeout' => 5,
 				// Custom LDAP Options
 				// Custom LDAP Options
@@ -337,32 +417,67 @@ function plugin_auth_emby_local($username, $password)
 	return false;
 	return false;
 }
 }
 
 
+// Pass credentials to JellyFin Backend
+function plugin_auth_jellyfin($username, $password)
+{
+	try {
+		$url = qualifyURL($GLOBALS['embyURL']) . '/Users/authenticatebyname';
+		$headers = array(
+			'X-Emby-Authorization' => 'MediaBrowser Client="Organizr Auth", Device="Organizr", DeviceId="orgv2", Version="2.0"',
+			'Content-Type' => 'application/json',
+		);
+		$data = array(
+			'Username' => $username,
+			'Pw' => $password
+		);
+		$response = Requests::post($url, $headers, json_encode($data));
+		if ($response->success) {
+			$json = json_decode($response->body, true);
+			if (is_array($json) && isset($json['SessionInfo']) && isset($json['User']) && $json['User']['HasPassword'] == true) {
+				writeLog('success', 'JellyFin Auth Function - Found User and Logged In', $username);
+				// Login Success - Now Logout JellyFin Session As We No Longer Need It
+				$headers = array(
+					'X-Emby-Authorization' => 'MediaBrowser Client="Organizr Auth", Device="Organizr", DeviceId="orgv2", Version="2.0", Token="' . $json['AccessToken'] . '"',
+					'Content-Type' => 'application/json',
+				);
+				$response = Requests::post(qualifyURL($GLOBALS['embyURL']) . '/Sessions/Logout', $headers, array());
+				if ($response->success) {
+					return true;
+				}
+			}
+		}
+		return false;
+	} catch (Requests_Exception $e) {
+		writeLog('error', 'JellyFin Auth Function - Error: ' . $e->getMessage(), $username);
+	}
+	return false;
+}
+
 // Authenticate against emby connect
 // Authenticate against emby connect
 function plugin_auth_emby_connect($username, $password)
 function plugin_auth_emby_connect($username, $password)
 {
 {
 	// Emby disabled EmbyConnect on their API
 	// Emby disabled EmbyConnect on their API
 	// https://github.com/MediaBrowser/Emby/issues/3553
 	// https://github.com/MediaBrowser/Emby/issues/3553
-	return plugin_auth_emby_local($username, $password);
-	/*
+	//return plugin_auth_emby_local($username, $password);
 	try {
 	try {
 		// Get A User
 		// Get A User
-		$connectId = '';
+		$connectUserName = '';
 		$url = qualifyURL($GLOBALS['embyURL']) . '/Users?api_key=' . $GLOBALS['embyToken'];
 		$url = qualifyURL($GLOBALS['embyURL']) . '/Users?api_key=' . $GLOBALS['embyToken'];
 		$response = Requests::get($url);
 		$response = Requests::get($url);
 		if ($response->success) {
 		if ($response->success) {
 			$json = json_decode($response->body, true);
 			$json = json_decode($response->body, true);
 			if (is_array($json)) {
 			if (is_array($json)) {
 				foreach ($json as $key => $value) { // Scan for this user
 				foreach ($json as $key => $value) { // Scan for this user
-					if (isset($value['ConnectUserName']) && isset($value['ConnectUserId'])) { // Qualify as connect account
-						if ($value['ConnectUserName'] == $username || $value['Name'] == $username) {
-							$connectId = $value['ConnectUserId'];
+					if (isset($value['ConnectUserName']) && isset($value['ConnectLinkType'])) { // Qualify as connect account
+						if (strtolower($value['ConnectUserName']) == $username || strtolower($value['Name']) == $username) {
+							$connectUserName = $value['ConnectUserName'];
 							writeLog('success', 'Emby Connect Auth Function - Found User', $username);
 							writeLog('success', 'Emby Connect Auth Function - Found User', $username);
 							break;
 							break;
 						}
 						}
 					}
 					}
 				}
 				}
-				if ($connectId) {
-					writeLog('success', 'Emby Connect Auth Function - Attempting to Login with Emby ID: ' . $connectId, $username);
+				if ($connectUserName) {
+					writeLog('success', 'Emby Connect Auth Function - Attempting to Login with Emby ID: ' . $connectUserName, $username);
 					$connectURL = 'https://connect.emby.media/service/user/authenticate';
 					$connectURL = 'https://connect.emby.media/service/user/authenticate';
 					$headers = array(
 					$headers = array(
 						'Accept' => 'application/json',
 						'Accept' => 'application/json',
@@ -375,10 +490,10 @@ function plugin_auth_emby_connect($username, $password)
 					$response = Requests::post($connectURL, $headers, $data);
 					$response = Requests::post($connectURL, $headers, $data);
 					if ($response->success) {
 					if ($response->success) {
 						$json = json_decode($response->body, true);
 						$json = json_decode($response->body, true);
-						if (is_array($json) && isset($json['AccessToken']) && isset($json['User']) && $json['User']['Id'] == $connectId) {
+						if (is_array($json) && isset($json['AccessToken']) && isset($json['User']) && $json['User']['Name'] == $connectUserName) {
 							return array(
 							return array(
 								'email' => $json['User']['Email'],
 								'email' => $json['User']['Email'],
-								'image' => $json['User']['ImageUrl'],
+								//'image' => $json['User']['ImageUrl'],
 							);
 							);
 						} else {
 						} else {
 							writeLog('error', 'Emby Connect Auth Function - Bad Response', $username);
 							writeLog('error', 'Emby Connect Auth Function - Bad Response', $username);
@@ -394,7 +509,6 @@ function plugin_auth_emby_connect($username, $password)
 		writeLog('error', 'Emby Connect Auth Function - Error: ' . $e->getMessage(), $username);
 		writeLog('error', 'Emby Connect Auth Function - Error: ' . $e->getMessage(), $username);
 		return false;
 		return false;
 	}
 	}
-	*/
 }
 }
 
 
 // Authenticate Against Emby Local (first) and Emby Connect
 // Authenticate Against Emby Local (first) and Emby Connect
@@ -403,12 +517,10 @@ function plugin_auth_emby_all($username, $password)
 	// Emby disabled EmbyConnect on their API
 	// Emby disabled EmbyConnect on their API
 	// https://github.com/MediaBrowser/Emby/issues/3553
 	// https://github.com/MediaBrowser/Emby/issues/3553
 	$localResult = plugin_auth_emby_local($username, $password);
 	$localResult = plugin_auth_emby_local($username, $password);
-	return $localResult;
-	/*
+	//return $localResult;
 	if ($localResult) {
 	if ($localResult) {
 		return $localResult;
 		return $localResult;
 	} else {
 	} else {
 		return plugin_auth_emby_connect($username, $password);
 		return plugin_auth_emby_connect($username, $password);
 	}
 	}
-	*/
 }
 }

+ 70 - 73
api/functions/homepage-connect-functions.php

@@ -795,7 +795,7 @@ function embyConnect($action, $key = 'Latest', $skip = false)
 				}
 				}
 				// Get A User
 				// Get A User
 				$userIds = $url . "/Users?api_key=" . $GLOBALS['embyToken'];
 				$userIds = $url . "/Users?api_key=" . $GLOBALS['embyToken'];
-				$showPlayed = true;
+				$showPlayed = false;
 				try {
 				try {
 					$options = (localURL($userIds)) ? array('verify' => false) : array();
 					$options = (localURL($userIds)) ? array('verify' => false) : array();
 					$response = Requests::get($userIds, array(), $options);
 					$response = Requests::get($userIds, array(), $options);
@@ -1535,43 +1535,42 @@ function calendarDaysCheck($entryStart, $entryEnd)
 
 
 function calendarStandardizeTimezone($timezone)
 function calendarStandardizeTimezone($timezone)
 {
 {
-    switch ($timezone) {
-        case('CST'):
-        case('Central Time'):
-        case('Central Standard Time'):
-            $timezone = 'America/Chicago';
-            break;
-        case('CET'):
-        case('Central European Time'):
-            $timezone = 'Europe/Berlin';
-            break;
-        case('EST'):
-        case('Eastern Time'):
-        case('Eastern Standard Time'):
-            $timezone = 'America/New_York';
-            break;
-        case('PST'):
-        case('Pacific Time'):
-        case('Pacific Standard Time'):
-            $timezone = 'America/Los_Angeles';
-            break;
-        case('China Time'):
-        case('China Standard Time'):
-            $timezone = 'Asia/Beijing';
-            break;
-        case('IST'):
-        case('India Time'):
-        case('India Standard Time'):
-            $timezone = 'Asia/New_Delhi';
-            break;
-        case('JST');
-        case('Japan Time'):
-        case('Japan Standard Time'):
-            $timezone = 'Asia/Tokyo';
-            break;
-    }
-
-    return $timezone;
+	switch ($timezone) {
+		case('CST'):
+		case('Central Time'):
+		case('Central Standard Time'):
+			$timezone = 'America/Chicago';
+			break;
+		case('CET'):
+		case('Central European Time'):
+			$timezone = 'Europe/Berlin';
+			break;
+		case('EST'):
+		case('Eastern Time'):
+		case('Eastern Standard Time'):
+			$timezone = 'America/New_York';
+			break;
+		case('PST'):
+		case('Pacific Time'):
+		case('Pacific Standard Time'):
+			$timezone = 'America/Los_Angeles';
+			break;
+		case('China Time'):
+		case('China Standard Time'):
+			$timezone = 'Asia/Beijing';
+			break;
+		case('IST'):
+		case('India Time'):
+		case('India Standard Time'):
+			$timezone = 'Asia/New_Delhi';
+			break;
+		case('JST');
+		case('Japan Time'):
+		case('Japan Standard Time'):
+			$timezone = 'Asia/Tokyo';
+			break;
+	}
+	return $timezone;
 }
 }
 
 
 function getCalenderRepeat($value)
 function getCalenderRepeat($value)
@@ -2636,21 +2635,12 @@ function getMonitorr()
 				// This section grabs the names of all services by regex
 				// This section grabs the names of all services by regex
 				$services = [];
 				$services = [];
 				$servicesMatch = [];
 				$servicesMatch = [];
-				$servicePattern = '/<div id="servicetitle"><div>(.*)<\/div><\/div><div class="btnonline">Online<\/div><\/a><\/div><\/div>|<div id="servicetitleoffline".*><div>(.*)<\/div><\/div><div class="btnoffline".*>Offline<\/div><\/div><\/div>|<div id="servicetitlenolink".*><div>(.*)<\/div><\/div><div class="btnonline".*>Online<\/div><\/div><\/div>/';
+				$servicePattern = '/<div id="servicetitle"><div>(.*)<\/div><\/div><div class="btnonline">Online<\/div><\/a><\/div><\/div>|<div id="servicetitleoffline".*><div>(.*)<\/div><\/div><div class="btnoffline".*>Offline<\/div><\/div><\/div>|<div id="servicetitlenolink".*><div>(.*)<\/div><\/div><div class="btnonline".*>Online<\/div><\/div><\/div>|<div id="servicetitle"><div>(.*)<\/div><\/div><div class="btnunknown">/';
 				preg_match_all($servicePattern, $html, $servicesMatch);
 				preg_match_all($servicePattern, $html, $servicesMatch);
-				unset($servicesMatch[0]);
-				$servicesMatch = array_values($servicesMatch);
-				foreach ($servicesMatch as $group) {
-					foreach ($group as $service) {
-						if ($service !== '') {
-							array_push($services, $service);
-						}
-					}
-				}
-				// This section then grabs the status and image of that service with regex
+				$services = array_filter($servicesMatch[1]) + array_filter($servicesMatch[2]) + array_filter($servicesMatch[3]) + array_filter($servicesMatch[4]);
 				$statuses = [];
 				$statuses = [];
-				foreach ($services as $service) {
-					$statusPattern = '/' . $service . '<\/div><\/div><div class="btnonline">(Online)<\/div>|' . $service . '<\/div><\/div><div class="btnoffline".*>(Offline)<\/div><\/div><\/div>/';
+				foreach ($services as $key => $service) {
+					$statusPattern = '/' . $service . '<\/div><\/div><div class="btnonline">(Online)<\/div>|' . $service . '<\/div><\/div><div class="btnoffline".*>(Offline)<\/div><\/div><\/div>|' . $service . '<\/div><\/div><div class="btnunknown">(.*)<\/div><\/a>/';
 					$status = [];
 					$status = [];
 					preg_match($statusPattern, $html, $status);
 					preg_match($statusPattern, $html, $status);
 					$statuses[$service] = $status;
 					$statuses[$service] = $status;
@@ -2663,8 +2653,13 @@ function getMonitorr()
 							$statuses[$service] = [
 							$statuses[$service] = [
 								'status' => false
 								'status' => false
 							];
 							];
+						} else if ($match == 'Unresponsive') {
+							$statuses[$service] = [
+								'status' => 'unresponsive'
+							];
 						}
 						}
 					}
 					}
+					$statuses[$service]['sort'] = $key;
 					$imageMatch = [];
 					$imageMatch = [];
 					$imgPattern = '/assets\/img\/\.\.(.*)" class="serviceimg" alt=.*><\/div><\/div><div id="servicetitle"><div>' . $service . '|assets\/img\/\.\.(.*)" class="serviceimg imgoffline" alt=.*><\/div><\/div><div id="servicetitleoffline".*><div>' . $service . '|assets\/img\/\.\.(.*)" class="serviceimg" alt=.*><\/div><\/div><div id="servicetitlenolink".*><div>' . $service . '/';
 					$imgPattern = '/assets\/img\/\.\.(.*)" class="serviceimg" alt=.*><\/div><\/div><div id="servicetitle"><div>' . $service . '|assets\/img\/\.\.(.*)" class="serviceimg imgoffline" alt=.*><\/div><\/div><div id="servicetitleoffline".*><div>' . $service . '|assets\/img\/\.\.(.*)" class="serviceimg" alt=.*><\/div><\/div><div id="servicetitlenolink".*><div>' . $service . '/';
 					preg_match($imgPattern, $html, $imageMatch);
 					preg_match($imgPattern, $html, $imageMatch);
@@ -2698,7 +2693,15 @@ function getMonitorr()
 						}
 						}
 					}
 					}
 				}
 				}
-				ksort($statuses);
+				foreach($statuses as $status){
+					foreach($status as $key=>$value){
+						if(!isset($sortArray[$key])){
+							$sortArray[$key] = array();
+						}
+						$sortArray[$key][] = $value;
+					}
+				}
+				array_multisort($sortArray['status'], SORT_ASC, $sortArray['sort'], SORT_ASC, $statuses);
 				$api['services'] = $statuses;
 				$api['services'] = $statuses;
 				$api['options'] = [
 				$api['options'] = [
 					'title' => $GLOBALS['monitorrHeader'],
 					'title' => $GLOBALS['monitorrHeader'],
@@ -2708,6 +2711,7 @@ function getMonitorr()
 			}
 			}
 		} catch (Requests_Exception $e) {
 		} catch (Requests_Exception $e) {
 			writeLog('error', 'Monitorr Connect Function - Error: ' . $e->getMessage(), 'SYSTEM');
 			writeLog('error', 'Monitorr Connect Function - Error: ' . $e->getMessage(), 'SYSTEM');
+			$api['error'] = $e->getMessage();
 		};
 		};
 		$api = isset($api) ? $api : false;
 		$api = isset($api) ? $api : false;
 		return $api;
 		return $api;
@@ -2724,13 +2728,11 @@ function getSpeedtest()
 			$response = Requests::get($dataUrl);
 			$response = Requests::get($dataUrl);
 			if ($response->success) {
 			if ($response->success) {
 				$json = json_decode($response->body, true);
 				$json = json_decode($response->body, true);
-
 				$api['data'] = [
 				$api['data'] = [
 					'current' => $json['data'],
 					'current' => $json['data'],
 					'average' => $json['average'],
 					'average' => $json['average'],
 					'max' => $json['max'],
 					'max' => $json['max'],
 				];
 				];
-
 				$api['options'] = [
 				$api['options'] = [
 					'title' => $GLOBALS['speedtestHeader'],
 					'title' => $GLOBALS['speedtestHeader'],
 					'titleToggle' => $GLOBALS['speedtestHeaderToggle'],
 					'titleToggle' => $GLOBALS['speedtestHeaderToggle'],
@@ -2751,12 +2753,10 @@ function getNetdata()
 		$api = [];
 		$api = [];
 		$api['data'] = [];
 		$api['data'] = [];
 		$api['url'] = $GLOBALS['netdataURL'];
 		$api['url'] = $GLOBALS['netdataURL'];
-
 		$url = qualifyURL($GLOBALS['netdataURL']);
 		$url = qualifyURL($GLOBALS['netdataURL']);
-
-		for($i = 1; $i < 8; $i++) {
-			if($GLOBALS['netdata'.($i).'Enabled']) {
-				switch($GLOBALS['netdata'.$i.'Data']) {
+		for ($i = 1; $i < 8; $i++) {
+			if ($GLOBALS['netdata' . ($i) . 'Enabled']) {
+				switch ($GLOBALS['netdata' . $i . 'Data']) {
 					case 'disk-read':
 					case 'disk-read':
 						$data = disk('in', $url);
 						$data = disk('in', $url);
 						break;
 						break;
@@ -2812,19 +2812,16 @@ function getNetdata()
 						];
 						];
 						break;
 						break;
 				}
 				}
-
-				$data['title'] = $GLOBALS['netdata'.$i.'Title'];
-				$data['colour'] = $GLOBALS['netdata'.$i.'Colour'];
-				$data['chart'] = $GLOBALS['netdata'.$i.'Chart'];
-				$data['size'] = $GLOBALS['netdata'.$i.'Size'];
-				$data['lg'] = $GLOBALS['netdata'.($i).'lg'];
-				$data['md'] = $GLOBALS['netdata'.($i).'md'];
-				$data['sm'] = $GLOBALS['netdata'.($i).'sm'];
-
+				$data['title'] = $GLOBALS['netdata' . $i . 'Title'];
+				$data['colour'] = $GLOBALS['netdata' . $i . 'Colour'];
+				$data['chart'] = $GLOBALS['netdata' . $i . 'Chart'];
+				$data['size'] = $GLOBALS['netdata' . $i . 'Size'];
+				$data['lg'] = $GLOBALS['netdata' . ($i) . 'lg'];
+				$data['md'] = $GLOBALS['netdata' . ($i) . 'md'];
+				$data['sm'] = $GLOBALS['netdata' . ($i) . 'sm'];
 				array_push($api['data'], $data);
 				array_push($api['data'], $data);
 			}
 			}
 		}
 		}
-
 		$api = isset($api) ? $api : false;
 		$api = isset($api) ? $api : false;
 		return $api;
 		return $api;
 	}
 	}
@@ -3152,8 +3149,8 @@ function testAPIConnection($array)
 					'account_suffix' => (empty($GLOBALS['authBackendHostSuffix'])) ? null : $GLOBALS['authBackendHostSuffix'],
 					'account_suffix' => (empty($GLOBALS['authBackendHostSuffix'])) ? null : $GLOBALS['authBackendHostSuffix'],
 					'port' => $ldapPort,
 					'port' => $ldapPort,
 					'follow_referrals' => false,
 					'follow_referrals' => false,
-					'use_ssl' => false,
-					'use_tls' => false,
+					'use_ssl' => $GLOBALS['ldapSSL'],
+					'use_tls' => $GLOBALS['ldapTLS'],
 					'version' => 3,
 					'version' => 3,
 					'timeout' => 5,
 					'timeout' => 5,
 					// Custom LDAP Options
 					// Custom LDAP Options
@@ -3230,8 +3227,8 @@ function testAPIConnection($array)
 					'account_suffix' => '',
 					'account_suffix' => '',
 					'port' => $ldapPort,
 					'port' => $ldapPort,
 					'follow_referrals' => false,
 					'follow_referrals' => false,
-					'use_ssl' => false,
-					'use_tls' => false,
+					'use_ssl' => $GLOBALS['ldapSSL'],
+					'use_tls' => $GLOBALS['ldapTLS'],
 					'version' => 3,
 					'version' => 3,
 					'timeout' => 5,
 					'timeout' => 5,
 					// Custom LDAP Options
 					// Custom LDAP Options

+ 2 - 2
api/functions/homepage-functions.php

@@ -2619,7 +2619,7 @@ function getHomepageList()
 		),
 		),
 		array(
 		array(
 			'name' => 'Tautulli',
 			'name' => 'Tautulli',
-			'enabled' => true,
+			'enabled' => (strpos('personal', $GLOBALS['license']) !== false) ? true : false,
 			'image' => 'plugins/images/tabs/tautulli.png',
 			'image' => 'plugins/images/tabs/tautulli.png',
 			'category' => 'Monitor',
 			'category' => 'Monitor',
 			'settings' => array(
 			'settings' => array(
@@ -3157,7 +3157,7 @@ function buildHomepageSettings()
 				break;
 				break;
 		}
 		}
 		$homepageList .= '
 		$homepageList .= '
-		<div class="col-md-3 col-xs-12 sort-homepage m-t-10 hvr-grow">
+		<div class="col-md-3 col-xs-12 sort-homepage m-t-10 hvr-grow clearfix">
 			<div class="homepage-drag fc-event ' . $class . ' lazyload"  data-src="' . $image . '">
 			<div class="homepage-drag fc-event ' . $class . ' lazyload"  data-src="' . $image . '">
 				<span class="ordinal-position text-uppercase badge bg-org homepage-number" data-link="' . $key . '" style="float:left;width: 30px;">' . $val . '</span>
 				<span class="ordinal-position text-uppercase badge bg-org homepage-number" data-link="' . $key . '" style="float:left;width: 30px;">' . $val . '</span>
 				<span class="homepage-text">&nbsp; ' . strtoupper(substr($key, 13)) . '</span>
 				<span class="homepage-text">&nbsp; ' . strtoupper(substr($key, 13)) . '</span>

+ 386 - 408
api/functions/netdata-functions.php

@@ -1,448 +1,426 @@
 <?php
 <?php
-
 function netdataSettngsArray()
 function netdataSettngsArray()
 {
 {
-    $array = array(
-        'name' => 'Netdata',
-        'enabled' => true,
-        'image' => 'plugins/images/tabs/netdata.png',
-        'category' => 'Monitor',
-        'settings' => array(
-            'Enable' => array(
-                array(
-                    'type' => 'switch',
-                    'name' => 'homepageNetdataEnabled',
-                    'label' => 'Enable',
-                    'value' => $GLOBALS['homepageNetdataEnabled']
-                ),
-                array(
-                    'type' => 'select',
-                    'name' => 'homepageNetdataAuth',
-                    'label' => 'Minimum Authentication',
-                    'value' => $GLOBALS['homepageNetdataAuth'],
-                    'options' => groupSelect()
-                )
-            ),
-            'Connection' => array(
-                array(
-                    'type' => 'html',
-                    'override' => 12,
-                    'label' => 'Info',
-                    'html' => 'The URL needs to be on the same domain as your Organizr, and be proxied by subdomain. E.g. If Organizr is accessed at: https://domain.com, then your URL for netdata should be: https://netdata.domain.com'
-                ),
-                array(
-                    'type' => 'input',
-                    'name' => 'netdataURL',
-                    'label' => 'URL',
-                    'value' => $GLOBALS['netdataURL'],
-                    'help' => 'Please enter the local IP:PORT of your netdata instance'
-                ),
-                array(
-                    'type' => 'blank',
-                    'label' => ''
-                ),
-            ),
-        )
-    );
-
-    for($i = 1; $i <= 7; $i++) {
-        $array['settings']['Chart '.$i] = array(
-            array(
-                'type' => 'switch',
-                'name' => 'netdata'.$i.'Enabled',
-                'label' => 'Enable',
-                'value' => $GLOBALS['netdata'.$i.'Enabled']
-            ),
-            array(
-                'type' => 'blank',
-                'label' => ''
-            ),
-            array(
-                'type' => 'input',
-                'name' => 'netdata'.$i.'Title',
-                'label' => 'Title',
-                'value' => $GLOBALS['netdata'.$i.'Title'],
-                'help' => 'Title for the netdata graph'
-            ),
-            array(
-                'type' => 'select',
-                'name' => 'netdata'.$i.'Data',
-                'label' => 'Data',
-                'value' => $GLOBALS['netdata'.$i.'Data'],
-                'options' => netdataOptions(),
-            ),
-            array(
-                'type' => 'select',
-                'name' => 'netdata'.$i.'Chart',
-                'label' => 'Chart',
-                'value' => $GLOBALS['netdata'.$i.'Chart'],
-                'options' => netdataChartOptions(),
-            ),
-            array(
-                'type' => 'select',
-                'name' => 'netdata'.$i.'Colour',
-                'label' => 'Colour',
-                'value' => $GLOBALS['netdata'.$i.'Colour'],
-                'options' => netdataColourOptions(),
-            ),
-            array(
-                'type' => 'select',
-                'name' => 'netdata'.$i.'Size',
-                'label' => 'Size',
-                'value' => $GLOBALS['netdata'.$i.'Size'],
-                'options' => netdataSizeOptions(),
-            ),
-            array(
-                'type' => 'blank',
-                'label' => ''
-            ),
-            array(
-                'type' => 'switch',
-                'name' => 'netdata'.$i.'lg',
-                'label' => 'Show on large screens',
-                'value' => $GLOBALS['netdata'.$i.'lg']
-            ),
-            array(
-                'type' => 'switch',
-                'name' => 'netdata'.$i.'md',
-                'label' => 'Show on medium screens',
-                'value' => $GLOBALS['netdata'.$i.'md']
-            ),
-            array(
-                'type' => 'switch',
-                'name' => 'netdata'.$i.'sm',
-                'label' => 'Show on small screens',
-                'value' => $GLOBALS['netdata'.$i.'sm']
-            ),
-        );
-    }
-
-    $array['settings']['Custom data'] = [
-        [
-            'type' => 'html',
-            'label' => '',
-            'override' => 12,
-            'html' => <<<HTML
-            <div>
-                <p>This is where you can define custom data sources for your netdata charts. To use a custom source, you need to select 'Custom' in the data field for the chart.</p>
-                <p>To define a custom data source, you need to add an entry to the JSON below, where the key is the chart number you want the custom data to be used for. Here is an example to set chart 1's custom data source to RAM percentage:</p>
-                <pre>{
-                "1": {
-                    "url": "/api/v1/data?chart=system.ram&format=array&points=540&group=average&gtime=0&options=absolute|percentage|jsonwrap|nonzero&after=-540&dimensions=used|buffers|active|wired",
-                    "value": "result,0",
-                    "units": "%",
-                    "max": 100
-                }
-            }</pre>
-                <p>The URL is appended to your netdata URL and returns JSON formatted data. The value field tells Organizr how to return the value you want from the netdata API. This should be formatted as comma-separated keys to access the desired value.</p>
-                <table class="table table-striped">
-                    <thead>
-                        <tr>
-                            <th>Parameter</th>
-                            <th>Description</th>
-                            <th>Required</th>
-                        </tr>
-                    </thead>
-                    <tbody>
-                        <tr>
-                            <td>url</td>
-                            <td>Specifies the netdata API endpoint</td>
-                            <td><i class="fa fa-check text-success" aria-hidden="true"></i></td>
-                        </tr>
-                        <tr>
-                            <td>value</td>
-                            <td>Specifies the selector used to get the data form the netdata response</td>
-                            <td><i class="fa fa-check text-success" aria-hidden="true"></i></td>
-                        </tr>
-                        <tr>
-                            <td>units</td>
-                            <td>Specifies the units shown in the graph/chart. Defaults to %</td>
-                            <td><i class="fa fa-times text-danger" aria-hidden="true"></i></td>
-                        </tr>
-                        <tr>
-                            <td>max</td>
-                            <td>Specifies the maximum possible value for the data. Defaults to 100</td>
-                            <td><i class="fa fa-times text-danger" aria-hidden="true"></i></td>
-                        </tr>
-                        <tr>
-                            <td>mutator</td>
-                            <td>Used to perform simple mathematical operations on the result (+, -, /, *). For example: dividing the result by 1000 would be '/1000'. These operations can be chained together by putting them in a comma-seprated format.</td>
-                            <td><i class="fa fa-times text-danger" aria-hidden="true"></i></td>
-                        </tr>
-                        <tr>
-                            <td>netdata</td>
-                            <td>Can be used to override the netdata instance data is retrieved from (in the format: http://IP:PORT)</td>
-                            <td><i class="fa fa-times text-danger" aria-hidden="true"></i></td>
-                        </tr>
-                    </tbody>
-                </table>
-            </div>
-            HTML
-        ],
-        [
-            'type' => 'html',
-            'name' => 'netdataCustomTextAce',
-            'class' => 'jsonTextarea hidden',
-            'label' => 'Custom definitions',
-            'override' => 12,
-            'html' => '<div id="netdataCustomTextAce" style="height: 300px;">' . htmlentities($GLOBALS['netdataCustom']) . '</div>',
-        ],
-        [
-            'type' => 'textbox',
-            'name' => 'netdataCustom',
-            'class' => 'jsonTextarea hidden',
-            'id' => 'netdataCustomText',
-            'label' => '',
-            'value' => $GLOBALS['netdataCustom'],
-        ]
-    ];
-
-    $array['settings']['Options'] =  array(
-        array(
-            'type' => 'select',
-            'name' => 'homepageNetdataRefresh',
-            'label' => 'Refresh Seconds',
-            'value' => $GLOBALS['homepageNetdataRefresh'],
-            'options' => optionTime()
-        ),
-    );
-
-    return $array;
+	$array = array(
+		'name' => 'Netdata',
+		'enabled' => true,
+		'image' => 'plugins/images/tabs/netdata.png',
+		'category' => 'Monitor',
+		'settings' => array(
+			'Enable' => array(
+				array(
+					'type' => 'switch',
+					'name' => 'homepageNetdataEnabled',
+					'label' => 'Enable',
+					'value' => $GLOBALS['homepageNetdataEnabled']
+				),
+				array(
+					'type' => 'select',
+					'name' => 'homepageNetdataAuth',
+					'label' => 'Minimum Authentication',
+					'value' => $GLOBALS['homepageNetdataAuth'],
+					'options' => groupSelect()
+				)
+			),
+			'Connection' => array(
+				array(
+					'type' => 'html',
+					'override' => 12,
+					'label' => 'Info',
+					'html' => 'The URL needs to be on the same domain as your Organizr, and be proxied by subdomain. E.g. If Organizr is accessed at: https://domain.com, then your URL for netdata should be: https://netdata.domain.com'
+				),
+				array(
+					'type' => 'input',
+					'name' => 'netdataURL',
+					'label' => 'URL',
+					'value' => $GLOBALS['netdataURL'],
+					'help' => 'Please enter the local IP:PORT of your netdata instance'
+				),
+				array(
+					'type' => 'blank',
+					'label' => ''
+				),
+			),
+		)
+	);
+	for ($i = 1; $i <= 7; $i++) {
+		$array['settings']['Chart ' . $i] = array(
+			array(
+				'type' => 'switch',
+				'name' => 'netdata' . $i . 'Enabled',
+				'label' => 'Enable',
+				'value' => $GLOBALS['netdata' . $i . 'Enabled']
+			),
+			array(
+				'type' => 'blank',
+				'label' => ''
+			),
+			array(
+				'type' => 'input',
+				'name' => 'netdata' . $i . 'Title',
+				'label' => 'Title',
+				'value' => $GLOBALS['netdata' . $i . 'Title'],
+				'help' => 'Title for the netdata graph'
+			),
+			array(
+				'type' => 'select',
+				'name' => 'netdata' . $i . 'Data',
+				'label' => 'Data',
+				'value' => $GLOBALS['netdata' . $i . 'Data'],
+				'options' => netdataOptions(),
+			),
+			array(
+				'type' => 'select',
+				'name' => 'netdata' . $i . 'Chart',
+				'label' => 'Chart',
+				'value' => $GLOBALS['netdata' . $i . 'Chart'],
+				'options' => netdataChartOptions(),
+			),
+			array(
+				'type' => 'select',
+				'name' => 'netdata' . $i . 'Colour',
+				'label' => 'Colour',
+				'value' => $GLOBALS['netdata' . $i . 'Colour'],
+				'options' => netdataColourOptions(),
+			),
+			array(
+				'type' => 'select',
+				'name' => 'netdata' . $i . 'Size',
+				'label' => 'Size',
+				'value' => $GLOBALS['netdata' . $i . 'Size'],
+				'options' => netdataSizeOptions(),
+			),
+			array(
+				'type' => 'blank',
+				'label' => ''
+			),
+			array(
+				'type' => 'switch',
+				'name' => 'netdata' . $i . 'lg',
+				'label' => 'Show on large screens',
+				'value' => $GLOBALS['netdata' . $i . 'lg']
+			),
+			array(
+				'type' => 'switch',
+				'name' => 'netdata' . $i . 'md',
+				'label' => 'Show on medium screens',
+				'value' => $GLOBALS['netdata' . $i . 'md']
+			),
+			array(
+				'type' => 'switch',
+				'name' => 'netdata' . $i . 'sm',
+				'label' => 'Show on small screens',
+				'value' => $GLOBALS['netdata' . $i . 'sm']
+			),
+		);
+	}
+	$array['settings']['Custom data'] = array(
+		array(
+			'type' => 'html',
+			'label' => '',
+			'override' => 12,
+			'html' => '
+			<div>
+			    <p>This is where you can define custom data sources for your netdata charts. To use a custom source, you need to select "Custom" in the data field for the chart.</p>
+			    <p>To define a custom data source, you need to add an entry to the JSON below, where the key is the chart number you want the custom data to be used for. Here is an example to set chart 1 custom data source to RAM percentage:</p>
+			    <pre>{
+			    "1": {
+			        "url": "/api/v1/data?chart=system.ram&format=array&points=540&group=average&gtime=0&options=absolute|percentage|jsonwrap|nonzero&after=-540&dimensions=used|buffers|active|wired",
+			        "value": "result,0",
+			        "units": "%",
+			        "max": 100
+			    }
+			}</pre>
+			    <p>The URL is appended to your netdata URL and returns JSON formatted data. The value field tells Organizr how to return the value you want from the netdata API. This should be formatted as comma-separated keys to access the desired value.</p>
+			    <table class="table table-striped">
+			        <thead>
+			            <tr>
+			                <th>Parameter</th>
+			                <th>Description</th>
+			                <th>Required</th>
+			            </tr>
+			        </thead>
+			        <tbody>
+			            <tr>
+			                <td>url</td>
+			                <td>Specifies the netdata API endpoint</td>
+			                <td><i class="fa fa-check text-success" aria-hidden="true"></i></td>
+			            </tr>
+			            <tr>
+			                <td>value</td>
+			                <td>Specifies the selector used to get the data form the netdata response</td>
+			                <td><i class="fa fa-check text-success" aria-hidden="true"></i></td>
+			            </tr>
+			            <tr>
+			                <td>units</td>
+			                <td>Specifies the units shown in the graph/chart. Defaults to %</td>
+			                <td><i class="fa fa-times text-danger" aria-hidden="true"></i></td>
+			            </tr>
+			            <tr>
+			                <td>max</td>
+			                <td>Specifies the maximum possible value for the data. Defaults to 100</td>
+			                <td><i class="fa fa-times text-danger" aria-hidden="true"></i></td>
+			            </tr>
+			            <tr>
+			                <td>mutator</td>
+			                <td>Used to perform simple mathematical operations on the result (+, -, /, *). For example: dividing the result by 1000 would be "/1000". These operations can be chained together by putting them in a comma-seprated format.</td>
+			                <td><i class="fa fa-times text-danger" aria-hidden="true"></i></td>
+			            </tr>
+			            <tr>
+			                <td>netdata</td>
+			                <td>Can be used to override the netdata instance data is retrieved from (in the format: http://IP:PORT)</td>
+			                <td><i class="fa fa-times text-danger" aria-hidden="true"></i></td>
+			            </tr>
+			        </tbody>
+			    </table>
+			</div>'
+		),
+		array(
+			'type' => 'html',
+			'name' => 'netdataCustomTextAce',
+			'class' => 'jsonTextarea hidden',
+			'label' => 'Custom definitions',
+			'override' => 12,
+			'html' => '<div id="netdataCustomTextAce" style="height: 300px;">' . htmlentities($GLOBALS['netdataCustom']) . '</div>',
+		),
+		array(
+			'type' => 'textbox',
+			'name' => 'netdataCustom',
+			'class' => 'jsonTextarea hidden',
+			'id' => 'netdataCustomText',
+			'label' => '',
+			'value' => $GLOBALS['netdataCustom'],
+		)
+	);
+	$array['settings']['Options'] = array(
+		array(
+			'type' => 'select',
+			'name' => 'homepageNetdataRefresh',
+			'label' => 'Refresh Seconds',
+			'value' => $GLOBALS['homepageNetdataRefresh'],
+			'options' => optionTime()
+		),
+	);
+	return $array;
 }
 }
 
 
 function disk($dimension, $url)
 function disk($dimension, $url)
 {
 {
-    $data = [];
-    // Get Data
-    $dataUrl = $url . '/api/v1/data?chart=system.io&dimensions='.$dimension.'&format=array&points=540&group=average&gtime=0&options=absolute|jsonwrap|nonzero&after=-540';
-    try {
-        $response = Requests::get($dataUrl);
-        if ($response->success) {
-            $json = json_decode($response->body, true);
-            $data['value'] = $json['latest_values'][0] / 1000;
-            $data['percent'] =  getPercent($json['latest_values'][0], $json['max']);
-            $data['units'] = 'MiB/s';
-            $data['max'] = $json['max'];
-        }
-    } catch (Requests_Exception $e) {
-        writeLog('error', 'Netdata Connect Function - Error: ' . $e->getMessage(), 'SYSTEM');
-    };
-
-    return $data;
+	$data = [];
+	// Get Data
+	$dataUrl = $url . '/api/v1/data?chart=system.io&dimensions=' . $dimension . '&format=array&points=540&group=average&gtime=0&options=absolute|jsonwrap|nonzero&after=-540';
+	try {
+		$response = Requests::get($dataUrl);
+		if ($response->success) {
+			$json = json_decode($response->body, true);
+			$data['value'] = $json['latest_values'][0] / 1000;
+			$data['percent'] = getPercent($json['latest_values'][0], $json['max']);
+			$data['units'] = 'MiB/s';
+			$data['max'] = $json['max'];
+		}
+	} catch (Requests_Exception $e) {
+		writeLog('error', 'Netdata Connect Function - Error: ' . $e->getMessage(), 'SYSTEM');
+	};
+	return $data;
 }
 }
 
 
 function diskSpace($dimension, $url)
 function diskSpace($dimension, $url)
 {
 {
-    $data = [];
-    // Get Data
-    $dataUrl = $url . '/api/v1/data?chart=disk_space._&format=json&points=509&group=average&gtime=0&options=ms|jsonwrap|nonzero&after=-540&dimension='.$dimension;
-    try {
-        $response = Requests::get($dataUrl);
-        if ($response->success) {
-            $json = json_decode($response->body, true);
-            $data['value'] = $json['result']['data'][0][1];
-            $data['percent'] = $data['value'];
-            $data['units'] = '%';
-            $data['max'] = 100;
-        }
-    } catch (Requests_Exception $e) {
-        writeLog('error', 'Netdata Connect Function - Error: ' . $e->getMessage(), 'SYSTEM');
-    };
-
-    return $data;
+	$data = [];
+	// Get Data
+	$dataUrl = $url . '/api/v1/data?chart=disk_space._&format=json&points=509&group=average&gtime=0&options=ms|jsonwrap|nonzero&after=-540&dimension=' . $dimension;
+	try {
+		$response = Requests::get($dataUrl);
+		if ($response->success) {
+			$json = json_decode($response->body, true);
+			$data['value'] = $json['result']['data'][0][1];
+			$data['percent'] = $data['value'];
+			$data['units'] = '%';
+			$data['max'] = 100;
+		}
+	} catch (Requests_Exception $e) {
+		writeLog('error', 'Netdata Connect Function - Error: ' . $e->getMessage(), 'SYSTEM');
+	};
+	return $data;
 }
 }
 
 
 function net($dimension, $url)
 function net($dimension, $url)
 {
 {
-    $data = [];
-
-    // Get Data
-    $dataUrl = $url . '/api/v1/data?chart=system.net&dimensions='.$dimension.'&format=array&points=540&group=average&gtime=0&options=absolute|jsonwrap|nonzero&after=-540';
-    try {
-        $response = Requests::get($dataUrl);
-        if ($response->success) {
-            $json = json_decode($response->body, true);
-            $data['value'] = $json['latest_values'][0] / 1000;
-            $data['percent'] = getPercent($json['latest_values'][0], $json['max']);
-            $data['units'] = 'Mbit/s';
-            $data['max'] = $json['max'];
-        }
-    } catch (Requests_Exception $e) {
-        writeLog('error', 'Netdata Connect Function - Error: ' . $e->getMessage(), 'SYSTEM');
-    };
-
-    return $data;
+	$data = [];
+	// Get Data
+	$dataUrl = $url . '/api/v1/data?chart=system.net&dimensions=' . $dimension . '&format=array&points=540&group=average&gtime=0&options=absolute|jsonwrap|nonzero&after=-540';
+	try {
+		$response = Requests::get($dataUrl);
+		if ($response->success) {
+			$json = json_decode($response->body, true);
+			$data['value'] = $json['latest_values'][0] / 1000;
+			$data['percent'] = getPercent($json['latest_values'][0], $json['max']);
+			$data['units'] = 'Mbit/s';
+			$data['max'] = $json['max'];
+		}
+	} catch (Requests_Exception $e) {
+		writeLog('error', 'Netdata Connect Function - Error: ' . $e->getMessage(), 'SYSTEM');
+	};
+	return $data;
 }
 }
 
 
 function cpu($url)
 function cpu($url)
 {
 {
-    $data = [];
-    $dataUrl = $url . '/api/v1/data?chart=system.cpu&format=array';
-    try {
-        $response = Requests::get($dataUrl);
-        if ($response->success) {
-            $json = json_decode($response->body, true);
-            $data['value'] = $json[0];
-            $data['percent'] = $data['value'];
-            $data['max'] = 100;
-            $data['units'] = '%';
-        }
-    } catch (Requests_Exception $e) {
-        writeLog('error', 'Netdata Connect Function - Error: ' . $e->getMessage(), 'SYSTEM');
-    };
-
-    return $data;
+	$data = [];
+	$dataUrl = $url . '/api/v1/data?chart=system.cpu&format=array';
+	try {
+		$response = Requests::get($dataUrl);
+		if ($response->success) {
+			$json = json_decode($response->body, true);
+			$data['value'] = $json[0];
+			$data['percent'] = $data['value'];
+			$data['max'] = 100;
+			$data['units'] = '%';
+		}
+	} catch (Requests_Exception $e) {
+		writeLog('error', 'Netdata Connect Function - Error: ' . $e->getMessage(), 'SYSTEM');
+	};
+	return $data;
 }
 }
 
 
 function ram($url)
 function ram($url)
 {
 {
-    $data = [];
-    $dataUrl = $url . '/api/v1/data?chart=system.ram&format=array&points=540&group=average&gtime=0&options=absolute|percentage|jsonwrap|nonzero&after=-540&dimensions=used|buffers|active|wired';
-    try {
-        $response = Requests::get($dataUrl);
-        if ($response->success) {
-            $json = json_decode($response->body, true);
-            $data['value'] = $json['result'][0];
-            $data['percent'] = $data['value'];
-            $data['max'] = 100;
-            $data['units'] = '%';
-        }
-    } catch (Requests_Exception $e) {
-        writeLog('error', 'Netdata Connect Function - Error: ' . $e->getMessage(), 'SYSTEM');
-    };
-
-    return $data;
+	$data = [];
+	$dataUrl = $url . '/api/v1/data?chart=system.ram&format=array&points=540&group=average&gtime=0&options=absolute|percentage|jsonwrap|nonzero&after=-540&dimensions=used|buffers|active|wired';
+	try {
+		$response = Requests::get($dataUrl);
+		if ($response->success) {
+			$json = json_decode($response->body, true);
+			$data['value'] = $json['result'][0];
+			$data['percent'] = $data['value'];
+			$data['max'] = 100;
+			$data['units'] = '%';
+		}
+	} catch (Requests_Exception $e) {
+		writeLog('error', 'Netdata Connect Function - Error: ' . $e->getMessage(), 'SYSTEM');
+	};
+	return $data;
 }
 }
 
 
 function swap($url)
 function swap($url)
 {
 {
-    $data = [];
-    $dataUrl = $url . '/api/v1/data?chart=system.swap&format=array&points=540&group=average&gtime=0&options=absolute|percentage|jsonwrap|nonzero&after=-540&dimensions=used';
-    try {
-        $response = Requests::get($dataUrl);
-        if ($response->success) {
-            $json = json_decode($response->body, true);
-            $data['value'] = $json['result'][0];
-            $data['percent'] = $data['value'];
-            $data['max'] = 100;
-            $data['units'] = '%';
-        }
-    } catch (Requests_Exception $e) {
-        writeLog('error', 'Netdata Connect Function - Error: ' . $e->getMessage(), 'SYSTEM');
-    };
-
-    return $data;
+	$data = [];
+	$dataUrl = $url . '/api/v1/data?chart=system.swap&format=array&points=540&group=average&gtime=0&options=absolute|percentage|jsonwrap|nonzero&after=-540&dimensions=used';
+	try {
+		$response = Requests::get($dataUrl);
+		if ($response->success) {
+			$json = json_decode($response->body, true);
+			$data['value'] = $json['result'][0];
+			$data['percent'] = $data['value'];
+			$data['max'] = 100;
+			$data['units'] = '%';
+		}
+	} catch (Requests_Exception $e) {
+		writeLog('error', 'Netdata Connect Function - Error: ' . $e->getMessage(), 'SYSTEM');
+	};
+	return $data;
 }
 }
 
 
 function getPercent($val, $max)
 function getPercent($val, $max)
 {
 {
-    if($max == 0) {
-        return 0;
-    } else {
-        return ( $val / $max ) * 100;
-    }
+	if ($max == 0) {
+		return 0;
+	} else {
+		return ($val / $max) * 100;
+	}
 }
 }
 
 
 function customNetdata($url, $id)
 function customNetdata($url, $id)
 {
 {
-    try {
-        $customs = json_decode($GLOBALS['netdataCustom'], true, 512, JSON_THROW_ON_ERROR);
-    } catch(Exception $e) {
-        $customs = false;
-    }
-        
-    if($customs == false) {
-        return [
-            'error' => 'unable to parse custom JSON'
-        ];
-    } else if(!isset($customs[$id])) {
-        return [
-            'error' => 'custom definition not found'
-        ];
-    } else {
-        $data = [];
-        $custom = $customs[$id];
-
-        if( isset($custom['url']) && isset($custom['value']) ) {
-            if( isset($custom['netdata']) && $custom['netdata'] != '' ) {
-                $url = qualifyURL($custom['netdata']);
-            }
-            $dataUrl = $url . '/' . $custom['url'];
-            try {
-                $response = Requests::get($dataUrl);
-                if ($response->success) {
-                    $json = json_decode($response->body, true);
-
-                    if( !isset($custom['max']) || $custom['max'] == '' ) {
-                        $custom['max'] = 100;
-                    }
-                    $data['max'] = $custom['max'];
-
-                    if( !isset($custom['units']) || $custom['units'] == '' ) {
-                        $custom['units'] = '%';
-                    }
-                    $data['units'] = $custom['units'];
-    
-                    $selectors = explode(',', $custom['value']);
-                    foreach($selectors as $selector) {
-                        if(is_numeric($selector)) {
-                            $selector = (int) $selector;
-                        }
-                        if(!isset($data['value'])) {
-                            $data['value'] = $json[$selector];
-                        } else {
-                            $data['value'] = $data['value'][$selector];
-                        }
-                    }
-
-                    if(isset($custom['mutator'])) {
-                        $data['value'] = parseMutators($data['value'], $custom['mutator']);
-                    }
-    
-                    if($data['max'] == 0) {
-                        $data['percent'] = 0;
-                    } else {
-                        $data['percent'] = ( $data['value'] / $data['max'] ) * 100;
-                    }
-                }
-            } catch (Requests_Exception $e) {
-                writeLog('error', 'Netdata Connect Function - Error: ' . $e->getMessage(), 'SYSTEM');
-            };
-        } else {
-            $data['error'] = 'custom definition incomplete';
-        }
-    
-        return $data;
-    }
+	try {
+		$customs = json_decode($GLOBALS['netdataCustom'], true, 512, JSON_THROW_ON_ERROR);
+	} catch (Exception $e) {
+		$customs = false;
+	}
+	if ($customs == false) {
+		return [
+			'error' => 'unable to parse custom JSON'
+		];
+	} else if (!isset($customs[$id])) {
+		return [
+			'error' => 'custom definition not found'
+		];
+	} else {
+		$data = [];
+		$custom = $customs[$id];
+		if (isset($custom['url']) && isset($custom['value'])) {
+			if (isset($custom['netdata']) && $custom['netdata'] != '') {
+				$url = qualifyURL($custom['netdata']);
+			}
+			$dataUrl = $url . '/' . $custom['url'];
+			try {
+				$response = Requests::get($dataUrl);
+				if ($response->success) {
+					$json = json_decode($response->body, true);
+					if (!isset($custom['max']) || $custom['max'] == '') {
+						$custom['max'] = 100;
+					}
+					$data['max'] = $custom['max'];
+					if (!isset($custom['units']) || $custom['units'] == '') {
+						$custom['units'] = '%';
+					}
+					$data['units'] = $custom['units'];
+					$selectors = explode(',', $custom['value']);
+					foreach ($selectors as $selector) {
+						if (is_numeric($selector)) {
+							$selector = (int)$selector;
+						}
+						if (!isset($data['value'])) {
+							$data['value'] = $json[$selector];
+						} else {
+							$data['value'] = $data['value'][$selector];
+						}
+					}
+					if (isset($custom['mutator'])) {
+						$data['value'] = parseMutators($data['value'], $custom['mutator']);
+					}
+					if ($data['max'] == 0) {
+						$data['percent'] = 0;
+					} else {
+						$data['percent'] = ($data['value'] / $data['max']) * 100;
+					}
+				}
+			} catch (Requests_Exception $e) {
+				writeLog('error', 'Netdata Connect Function - Error: ' . $e->getMessage(), 'SYSTEM');
+			};
+		} else {
+			$data['error'] = 'custom definition incomplete';
+		}
+		return $data;
+	}
 }
 }
 
 
 function parseMutators($val, $mutators)
 function parseMutators($val, $mutators)
 {
 {
-    $mutators = explode(',', $mutators);
-    foreach($mutators as $m) {
-        $op = $m[0];
-        try {
-            $m = (float) substr($m, 1);
-            switch($op) {
-                case '+':
-                    $val = $val + $m;
-                    break;
-                case '-':
-                    $val = $val - $m;
-                    break;
-                case '/':
-                    $val = $val / $m;
-                    break;
-                case '*':
-                    $val = $val * $m;
-                    break;
-                default:
-                    break;
-            }
-        } catch(Exception $e) {
-            //
-        }
-    }
-
-    return $val;
+	$mutators = explode(',', $mutators);
+	foreach ($mutators as $m) {
+		$op = $m[0];
+		try {
+			$m = (float)substr($m, 1);
+			switch ($op) {
+				case '+':
+					$val = $val + $m;
+					break;
+				case '-':
+					$val = $val - $m;
+					break;
+				case '/':
+					$val = $val / $m;
+					break;
+				case '*':
+					$val = $val * $m;
+					break;
+				default:
+					break;
+			}
+		} catch (Exception $e) {
+			//
+		}
+	}
+	return $val;
 }
 }

+ 11 - 12
api/functions/normal-functions.php

@@ -77,8 +77,9 @@ function parseDomain($value, $force = false)
 }
 }
 
 
 // Cookie Custom Function
 // Cookie Custom Function
-function coookie($type, $name, $value = '', $days = -1, $http = true)
+function coookie($type, $name, $value = '', $days = -1, $http = true, $path = '/')
 {
 {
+	$days = ($days > 365) ? 365 : $days;
 	if (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] == "https") {
 	if (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] == "https") {
 		$Secure = true;
 		$Secure = true;
 		$HTTPOnly = true;
 		$HTTPOnly = true;
@@ -92,20 +93,19 @@ function coookie($type, $name, $value = '', $days = -1, $http = true)
 	if (!$http) {
 	if (!$http) {
 		$HTTPOnly = false;
 		$HTTPOnly = false;
 	}
 	}
-	$Path = '/';
 	$Domain = parseDomain($_SERVER['HTTP_HOST']);
 	$Domain = parseDomain($_SERVER['HTTP_HOST']);
 	$DomainTest = parseDomain($_SERVER['HTTP_HOST'], true);
 	$DomainTest = parseDomain($_SERVER['HTTP_HOST'], true);
 	if ($type == 'set') {
 	if ($type == 'set') {
 		$_COOKIE[$name] = $value;
 		$_COOKIE[$name] = $value;
 		header('Set-Cookie: ' . rawurlencode($name) . '=' . rawurlencode($value)
 		header('Set-Cookie: ' . rawurlencode($name) . '=' . rawurlencode($value)
 			. (empty($days) ? '' : '; expires=' . gmdate('D, d-M-Y H:i:s', time() + (86400 * $days)) . ' GMT')
 			. (empty($days) ? '' : '; expires=' . gmdate('D, d-M-Y H:i:s', time() + (86400 * $days)) . ' GMT')
-			. (empty($Path) ? '' : '; path=' . $Path)
+			. (empty($path) ? '' : '; path=' . $path)
 			. (empty($Domain) ? '' : '; domain=' . $Domain)
 			. (empty($Domain) ? '' : '; domain=' . $Domain)
 			. (!$Secure ? '' : '; SameSite=None; Secure')
 			. (!$Secure ? '' : '; SameSite=None; Secure')
 			. (!$HTTPOnly ? '' : '; HttpOnly'), false);
 			. (!$HTTPOnly ? '' : '; HttpOnly'), false);
 		header('Set-Cookie: ' . rawurlencode($name) . '=' . rawurlencode($value)
 		header('Set-Cookie: ' . rawurlencode($name) . '=' . rawurlencode($value)
 			. (empty($days) ? '' : '; expires=' . gmdate('D, d-M-Y H:i:s', time() + (86400 * $days)) . ' GMT')
 			. (empty($days) ? '' : '; expires=' . gmdate('D, d-M-Y H:i:s', time() + (86400 * $days)) . ' GMT')
-			. (empty($Path) ? '' : '; path=' . $Path)
+			. (empty($path) ? '' : '; path=' . $path)
 			. (empty($Domain) ? '' : '; domain=' . $DomainTest)
 			. (empty($Domain) ? '' : '; domain=' . $DomainTest)
 			. (!$Secure ? '' : '; SameSite=None; Secure')
 			. (!$Secure ? '' : '; SameSite=None; Secure')
 			. (!$HTTPOnly ? '' : '; HttpOnly'), false);
 			. (!$HTTPOnly ? '' : '; HttpOnly'), false);
@@ -113,20 +113,20 @@ function coookie($type, $name, $value = '', $days = -1, $http = true)
 		unset($_COOKIE[$name]);
 		unset($_COOKIE[$name]);
 		header('Set-Cookie: ' . rawurlencode($name) . '=' . rawurlencode($value)
 		header('Set-Cookie: ' . rawurlencode($name) . '=' . rawurlencode($value)
 			. (empty($days) ? '' : '; expires=' . gmdate('D, d-M-Y H:i:s', time() - 3600) . ' GMT')
 			. (empty($days) ? '' : '; expires=' . gmdate('D, d-M-Y H:i:s', time() - 3600) . ' GMT')
-			. (empty($Path) ? '' : '; path=' . $Path)
+			. (empty($path) ? '' : '; path=' . $path)
 			. (empty($Domain) ? '' : '; domain=' . $Domain)
 			. (empty($Domain) ? '' : '; domain=' . $Domain)
 			. (!$Secure ? '' : '; SameSite=None; Secure')
 			. (!$Secure ? '' : '; SameSite=None; Secure')
 			. (!$HTTPOnly ? '' : '; HttpOnly'), false);
 			. (!$HTTPOnly ? '' : '; HttpOnly'), false);
 		header('Set-Cookie: ' . rawurlencode($name) . '=' . rawurlencode($value)
 		header('Set-Cookie: ' . rawurlencode($name) . '=' . rawurlencode($value)
 			. (empty($days) ? '' : '; expires=' . gmdate('D, d-M-Y H:i:s', time() - 3600) . ' GMT')
 			. (empty($days) ? '' : '; expires=' . gmdate('D, d-M-Y H:i:s', time() - 3600) . ' GMT')
-			. (empty($Path) ? '' : '; path=' . $Path)
+			. (empty($path) ? '' : '; path=' . $path)
 			. (empty($Domain) ? '' : '; domain=' . $DomainTest)
 			. (empty($Domain) ? '' : '; domain=' . $DomainTest)
 			. (!$Secure ? '' : '; SameSite=None; Secure')
 			. (!$Secure ? '' : '; SameSite=None; Secure')
 			. (!$HTTPOnly ? '' : '; HttpOnly'), false);
 			. (!$HTTPOnly ? '' : '; HttpOnly'), false);
 	}
 	}
 }
 }
 
 
-function coookieSeconds($type, $name, $value = '', $ms, $http = true)
+function coookieSeconds($type, $name, $value = '', $ms, $http = true, $path = '/')
 {
 {
 	if (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] == "https") {
 	if (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] == "https") {
 		$Secure = true;
 		$Secure = true;
@@ -141,20 +141,19 @@ function coookieSeconds($type, $name, $value = '', $ms, $http = true)
 	if (!$http) {
 	if (!$http) {
 		$HTTPOnly = false;
 		$HTTPOnly = false;
 	}
 	}
-	$Path = '/';
 	$Domain = parseDomain($_SERVER['HTTP_HOST']);
 	$Domain = parseDomain($_SERVER['HTTP_HOST']);
 	$DomainTest = parseDomain($_SERVER['HTTP_HOST'], true);
 	$DomainTest = parseDomain($_SERVER['HTTP_HOST'], true);
 	if ($type == 'set') {
 	if ($type == 'set') {
 		$_COOKIE[$name] = $value;
 		$_COOKIE[$name] = $value;
 		header('Set-Cookie: ' . rawurlencode($name) . '=' . rawurlencode($value)
 		header('Set-Cookie: ' . rawurlencode($name) . '=' . rawurlencode($value)
 			. (empty($ms) ? '' : '; expires=' . gmdate('D, d-M-Y H:i:s', time() + ($ms / 1000)) . ' GMT')
 			. (empty($ms) ? '' : '; expires=' . gmdate('D, d-M-Y H:i:s', time() + ($ms / 1000)) . ' GMT')
-			. (empty($Path) ? '' : '; path=' . $Path)
+			. (empty($path) ? '' : '; path=' . $path)
 			. (empty($Domain) ? '' : '; domain=' . $Domain)
 			. (empty($Domain) ? '' : '; domain=' . $Domain)
 			. (!$Secure ? '' : '; SameSite=None; Secure')
 			. (!$Secure ? '' : '; SameSite=None; Secure')
 			. (!$HTTPOnly ? '' : '; HttpOnly'), false);
 			. (!$HTTPOnly ? '' : '; HttpOnly'), false);
 		header('Set-Cookie: ' . rawurlencode($name) . '=' . rawurlencode($value)
 		header('Set-Cookie: ' . rawurlencode($name) . '=' . rawurlencode($value)
 			. (empty($ms) ? '' : '; expires=' . gmdate('D, d-M-Y H:i:s', time() + ($ms / 1000)) . ' GMT')
 			. (empty($ms) ? '' : '; expires=' . gmdate('D, d-M-Y H:i:s', time() + ($ms / 1000)) . ' GMT')
-			. (empty($Path) ? '' : '; path=' . $Path)
+			. (empty($path) ? '' : '; path=' . $path)
 			. (empty($Domain) ? '' : '; domain=' . $DomainTest)
 			. (empty($Domain) ? '' : '; domain=' . $DomainTest)
 			. (!$Secure ? '' : '; SameSite=None; Secure')
 			. (!$Secure ? '' : '; SameSite=None; Secure')
 			. (!$HTTPOnly ? '' : '; HttpOnly'), false);
 			. (!$HTTPOnly ? '' : '; HttpOnly'), false);
@@ -162,13 +161,13 @@ function coookieSeconds($type, $name, $value = '', $ms, $http = true)
 		unset($_COOKIE[$name]);
 		unset($_COOKIE[$name]);
 		header('Set-Cookie: ' . rawurlencode($name) . '=' . rawurlencode($value)
 		header('Set-Cookie: ' . rawurlencode($name) . '=' . rawurlencode($value)
 			. (empty($ms) ? '' : '; expires=' . gmdate('D, d-M-Y H:i:s', time() - 3600) . ' GMT')
 			. (empty($ms) ? '' : '; expires=' . gmdate('D, d-M-Y H:i:s', time() - 3600) . ' GMT')
-			. (empty($Path) ? '' : '; path=' . $Path)
+			. (empty($path) ? '' : '; path=' . $path)
 			. (empty($Domain) ? '' : '; domain=' . $Domain)
 			. (empty($Domain) ? '' : '; domain=' . $Domain)
 			. (!$Secure ? '' : '; SameSite=None; Secure')
 			. (!$Secure ? '' : '; SameSite=None; Secure')
 			. (!$HTTPOnly ? '' : '; HttpOnly'), false);
 			. (!$HTTPOnly ? '' : '; HttpOnly'), false);
 		header('Set-Cookie: ' . rawurlencode($name) . '=' . rawurlencode($value)
 		header('Set-Cookie: ' . rawurlencode($name) . '=' . rawurlencode($value)
 			. (empty($ms) ? '' : '; expires=' . gmdate('D, d-M-Y H:i:s', time() - 3600) . ' GMT')
 			. (empty($ms) ? '' : '; expires=' . gmdate('D, d-M-Y H:i:s', time() - 3600) . ' GMT')
-			. (empty($Path) ? '' : '; path=' . $Path)
+			. (empty($path) ? '' : '; path=' . $path)
 			. (empty($Domain) ? '' : '; domain=' . $DomainTest)
 			. (empty($Domain) ? '' : '; domain=' . $DomainTest)
 			. (!$Secure ? '' : '; SameSite=None; Secure')
 			. (!$Secure ? '' : '; SameSite=None; Secure')
 			. (!$HTTPOnly ? '' : '; HttpOnly'), false);
 			. (!$HTTPOnly ? '' : '; HttpOnly'), false);

+ 87 - 11
api/functions/organizr-functions.php

@@ -131,6 +131,12 @@ function organizrSpecialSettings()
 			'debugArea' => qualifyRequest($GLOBALS['debugAreaAuth']),
 			'debugArea' => qualifyRequest($GLOBALS['debugAreaAuth']),
 			'debugErrors' => $GLOBALS['debugErrors'],
 			'debugErrors' => $GLOBALS['debugErrors'],
 			'sandbox' => $GLOBALS['sandbox'],
 			'sandbox' => $GLOBALS['sandbox'],
+		),
+		'menuLink' => array(
+			'githubMenuLink' => $GLOBALS['githubMenuLink'],
+			'organizrSupportMenuLink' => $GLOBALS['organizrSupportMenuLink'],
+			'organizrDocsMenuLink' => $GLOBALS['organizrDocsMenuLink'],
+			'organizrSignoutMenuLink' => $GLOBALS['organizrSignoutMenuLink']
 		)
 		)
 	);
 	);
 }
 }
@@ -160,6 +166,7 @@ function wizardConfig($array)
 		'organizrHash' => $hashKey,
 		'organizrHash' => $hashKey,
 		'organizrAPI' => $api,
 		'organizrAPI' => $api,
 		'registrationPassword' => $registrationPassword,
 		'registrationPassword' => $registrationPassword,
+		'uuid' => gen_uuid()
 	);
 	);
 	// Create Config
 	// Create Config
 	$GLOBALS['dbLocation'] = $location;
 	$GLOBALS['dbLocation'] = $location;
@@ -684,6 +691,22 @@ function getSettingsMain()
 				'label' => 'Account DN',
 				'label' => 'Account DN',
 				'html' => '<span id="accountDN" class="ldapAuth switchAuth">' . $GLOBALS['authBackendHostPrefix'] . 'TestAcct' . $GLOBALS['authBackendHostSuffix'] . '</span>'
 				'html' => '<span id="accountDN" class="ldapAuth switchAuth">' . $GLOBALS['authBackendHostPrefix'] . 'TestAcct' . $GLOBALS['authBackendHostSuffix'] . '</span>'
 			),
 			),
+			array(
+				'type' => 'switch',
+				'name' => 'ldapSSL',
+				'class' => 'ldapAuth switchAuth',
+				'label' => 'Enable LDAP SSL',
+				'value' => $GLOBALS['ldapSSL'],
+				'help' => 'This will enable the use of SSL for LDAP connections'
+			),
+			array(
+				'type' => 'switch',
+				'name' => 'ldapSSL',
+				'class' => 'ldapAuth switchAuth',
+				'label' => 'Enable LDAP TLS',
+				'value' => $GLOBALS['ldapTLS'],
+				'help' => 'This will enable the use of TLS for LDAP connections'
+			),
 			array(
 			array(
 				'type' => 'button',
 				'type' => 'button',
 				'name' => 'test-button-ldap',
 				'name' => 'test-button-ldap',
@@ -707,7 +730,7 @@ function getSettingsMain()
 				'type' => 'input',
 				'type' => 'input',
 				'name' => 'embyURL',
 				'name' => 'embyURL',
 				'class' => 'embyAuth switchAuth',
 				'class' => 'embyAuth switchAuth',
-				'label' => 'Emby URL',
+				'label' => 'Emby/Jellyfin URL',
 				'value' => $GLOBALS['embyURL'],
 				'value' => $GLOBALS['embyURL'],
 				'help' => 'Please make sure to use local IP address and port - You also may use local dns name too.',
 				'help' => 'Please make sure to use local IP address and port - You also may use local dns name too.',
 				'placeholder' => 'http(s)://hostname:port'
 				'placeholder' => 'http(s)://hostname:port'
@@ -716,7 +739,7 @@ function getSettingsMain()
 				'type' => 'password-alt',
 				'type' => 'password-alt',
 				'name' => 'embyToken',
 				'name' => 'embyToken',
 				'class' => 'embyAuth switchAuth',
 				'class' => 'embyAuth switchAuth',
-				'label' => 'Emby Token',
+				'label' => 'Emby/Jellyin Token',
 				'value' => $GLOBALS['embyToken'],
 				'value' => $GLOBALS['embyToken'],
 				'placeholder' => ''
 				'placeholder' => ''
 			),
 			),
@@ -825,6 +848,10 @@ function getSettingsMain()
 						'name' => 'Allow Top Navigation',
 						'name' => 'Allow Top Navigation',
 						'value' => 'allow-top-navigation'
 						'value' => 'allow-top-navigation'
 					),
 					),
+					array(
+						'name' => 'Allow Downloads',
+						'value' => 'allow-downloads'
+					),
 				)
 				)
 			),
 			),
 			array(
 			array(
@@ -1236,6 +1263,30 @@ function getCustomizeAppearance()
 					'label' => 'Show Debug Errors',
 					'label' => 'Show Debug Errors',
 					'value' => $GLOBALS['debugErrors']
 					'value' => $GLOBALS['debugErrors']
 				),
 				),
+				array(
+					'type' => 'switch',
+					'name' => 'githubMenuLink',
+					'label' => 'Show GitHub Repo Link',
+					'value' => $GLOBALS['githubMenuLink']
+				),
+				array(
+					'type' => 'switch',
+					'name' => 'organizrSupportMenuLink',
+					'label' => 'Show Organizr Support Link',
+					'value' => $GLOBALS['organizrSupportMenuLink']
+				),
+				array(
+					'type' => 'switch',
+					'name' => 'organizrDocsMenuLink',
+					'label' => 'Show Organizr Docs Link',
+					'value' => $GLOBALS['organizrDocsMenuLink']
+				),
+				array(
+					'type' => 'switch',
+					'name' => 'organizrSignoutMenuLink',
+					'label' => 'Show Organizr Sign out & in Button on Sidebar',
+					'value' => $GLOBALS['organizrSignoutMenuLink']
+				),
 				array(
 				array(
 					'type' => 'select',
 					'type' => 'select',
 					'name' => 'unsortedTabs',
 					'name' => 'unsortedTabs',
@@ -1750,18 +1801,18 @@ function showLogin()
 
 
 function checkoAuth()
 function checkoAuth()
 {
 {
-	return ($GLOBALS['plexoAuth'] && $GLOBALS['authType'] !== 'internal') ? true : false;
+	return ($GLOBALS['plexoAuth'] && $GLOBALS['authBackend'] == 'plex' && $GLOBALS['authType'] !== 'internal') ? true : false;
 }
 }
 
 
 function checkoAuthOnly()
 function checkoAuthOnly()
 {
 {
-	return ($GLOBALS['plexoAuth'] && $GLOBALS['authType'] == 'external') ? true : false;
+	return ($GLOBALS['plexoAuth'] && $GLOBALS['authBackend'] == 'plex' && $GLOBALS['authType'] == 'external') ? true : false;
 }
 }
 
 
 function showoAuth()
 function showoAuth()
 {
 {
 	$buttons = '';
 	$buttons = '';
-	if ($GLOBALS['plexoAuth'] && $GLOBALS['authType'] !== 'internal') {
+	if ($GLOBALS['plexoAuth'] && $GLOBALS['authBackend'] == 'plex' && $GLOBALS['authType'] !== 'internal') {
 		$buttons .= '<a href="javascript:void(0)" onclick="oAuthStart(\'plex\')" class="btn btn-lg btn-block text-uppercase waves-effect waves-light bg-plex text-muted" data-toggle="tooltip" title="" data-original-title="Login with Plex"> <span>Login</span><i aria-hidden="true" class="mdi mdi-plex m-l-5"></i> </a>';
 		$buttons .= '<a href="javascript:void(0)" onclick="oAuthStart(\'plex\')" class="btn btn-lg btn-block text-uppercase waves-effect waves-light bg-plex text-muted" data-toggle="tooltip" title="" data-original-title="Login with Plex"> <span>Login</span><i aria-hidden="true" class="mdi mdi-plex m-l-5"></i> </a>';
 	}
 	}
 	return ($buttons) ? '
 	return ($buttons) ? '
@@ -1787,16 +1838,35 @@ function showoAuth()
 
 
 function getImages()
 function getImages()
 {
 {
+	$allIconsPrep = array();
+	$allIcons = array();
+	$ignore = array(".", "..", "._.DS_Store", ".DS_Store", ".pydio_id", "index.html");
 	$dirname = dirname(__DIR__, 2) . DIRECTORY_SEPARATOR . 'plugins' . DIRECTORY_SEPARATOR . 'images' . DIRECTORY_SEPARATOR . 'tabs' . DIRECTORY_SEPARATOR;
 	$dirname = dirname(__DIR__, 2) . DIRECTORY_SEPARATOR . 'plugins' . DIRECTORY_SEPARATOR . 'images' . DIRECTORY_SEPARATOR . 'tabs' . DIRECTORY_SEPARATOR;
 	$path = 'plugins/images/tabs/';
 	$path = 'plugins/images/tabs/';
 	$images = scandir($dirname);
 	$images = scandir($dirname);
-	$ignore = array(".", "..", "._.DS_Store", ".DS_Store", ".pydio_id");
-	$allIcons = array();
 	foreach ($images as $image) {
 	foreach ($images as $image) {
 		if (!in_array($image, $ignore)) {
 		if (!in_array($image, $ignore)) {
-			$allIcons[] = $path . $image;
+			$allIconsPrep[$image] = array(
+				'path' => $path,
+				'name' => $image
+			);
 		}
 		}
 	}
 	}
+	$dirname = dirname(__DIR__, 2) . DIRECTORY_SEPARATOR . 'plugins' . DIRECTORY_SEPARATOR . 'images' . DIRECTORY_SEPARATOR . 'userTabs' . DIRECTORY_SEPARATOR;
+	$path = 'plugins/images/userTabs/';
+	$images = scandir($dirname);
+	foreach ($images as $image) {
+		if (!in_array($image, $ignore)) {
+			$allIconsPrep[$image] = array(
+				'path' => $path,
+				'name' => $image
+			);
+		}
+	}
+	ksort($allIconsPrep);
+	foreach ($allIconsPrep as $item) {
+		$allIcons[] = $item['path'] . $item['name'];
+	}
 	return $allIcons;
 	return $allIcons;
 }
 }
 
 
@@ -1817,7 +1887,7 @@ function editImages()
 	$array = array();
 	$array = array();
 	$postCheck = array_filter($_POST);
 	$postCheck = array_filter($_POST);
 	$filesCheck = array_filter($_FILES);
 	$filesCheck = array_filter($_FILES);
-	$approvedPath = 'plugins/images/tabs/';
+	$approvedPath = 'plugins/images/userTabs/';
 	if (!empty($postCheck)) {
 	if (!empty($postCheck)) {
 		$removeImage = $approvedPath . pathinfo($_POST['data']['imagePath'], PATHINFO_BASENAME);
 		$removeImage = $approvedPath . pathinfo($_POST['data']['imagePath'], PATHINFO_BASENAME);
 		if ($_POST['data']['action'] == 'deleteImage' && approvedFileExtension($removeImage)) {
 		if ($_POST['data']['action'] == 'deleteImage' && approvedFileExtension($removeImage)) {
@@ -1831,7 +1901,7 @@ function editImages()
 		ini_set('upload_max_filesize', '10M');
 		ini_set('upload_max_filesize', '10M');
 		ini_set('post_max_size', '10M');
 		ini_set('post_max_size', '10M');
 		$tempFile = $_FILES['file']['tmp_name'];
 		$tempFile = $_FILES['file']['tmp_name'];
-		$targetPath = dirname(__DIR__, 2) . DIRECTORY_SEPARATOR . 'plugins' . DIRECTORY_SEPARATOR . 'images' . DIRECTORY_SEPARATOR . 'tabs' . DIRECTORY_SEPARATOR;
+		$targetPath = dirname(__DIR__, 2) . DIRECTORY_SEPARATOR . 'plugins' . DIRECTORY_SEPARATOR . 'images' . DIRECTORY_SEPARATOR . 'userTabs' . DIRECTORY_SEPARATOR;
 		$targetFile = $targetPath . $_FILES['file']['name'];
 		$targetFile = $targetPath . $_FILES['file']['name'];
 		return (move_uploaded_file($tempFile, $targetFile)) ? true : false;
 		return (move_uploaded_file($tempFile, $targetFile)) ? true : false;
 	}
 	}
@@ -2616,7 +2686,13 @@ function importUserButtons()
 	';
 	';
 	$buttons = '';
 	$buttons = '';
 	if (!empty($GLOBALS['plexToken'])) {
 	if (!empty($GLOBALS['plexToken'])) {
-		$buttons .= '<button class="btn bg-plex text-muted waves-effect waves-light importUsersButton" onclick="importUsers(\'plex\')" type="button"><span class="btn-label"><i class="mdi mdi-plex"></i></span><span lang="en">Import Plex Users</span></button>';
+		$buttons .= '<button class="btn m-b-20 m-r-20 bg-plex text-muted waves-effect waves-light importUsersButton" onclick="importUsers(\'plex\')" type="button"><span class="btn-label"><i class="mdi mdi-plex"></i></span><span lang="en">Import Plex Users</span></button>';
+	}
+	if (!empty($GLOBALS['embyURL']) && !empty($GLOBALS['embyToken']) && (strpos($GLOBALS['embyURL'], 'jellyfin') !== false)) {
+		$buttons .= '<button class="btn m-b-20 m-r-20 bg-primary text-muted waves-effect waves-light importUsersButton" onclick="importUsers(\'jellyfin\')" type="button"><span class="btn-label"><i class="mdi mdi-fish"></i></span><span lang="en">Import Jellyfin Users</span></button>';
+	}
+	if (!empty($GLOBALS['embyURL']) && !empty($GLOBALS['embyToken']) && (strpos($GLOBALS['embyURL'], 'jellyfin') === false)) {
+		$buttons .= '<button class="btn m-b-20 m-r-20 bg-emby text-muted waves-effect waves-light importUsersButton" onclick="importUsers(\'emby\')" type="button"><span class="btn-label"><i class="mdi mdi-emby"></i></span><span lang="en">Import Jellyfin Users</span></button>';
 	}
 	}
 	return ($buttons !== '') ? $buttons : $emptyButtons;
 	return ($buttons !== '') ? $buttons : $emptyButtons;
 }
 }

+ 4 - 1
api/functions/sso-functions.php

@@ -15,7 +15,7 @@ function ssoCheck($username, $password, $token = null)
 		$tautulliToken = getTautulliToken($username, $password, $token);
 		$tautulliToken = getTautulliToken($username, $password, $token);
 		if ($tautulliToken) {
 		if ($tautulliToken) {
 			foreach ($tautulliToken as $key => $value) {
 			foreach ($tautulliToken as $key => $value) {
-				coookie('set', 'tautulli_token_' . $value['uuid'], $value['token'], $GLOBALS['rememberMeDays']);
+				coookie('set', 'tautulli_token_' . $value['uuid'], $value['token'], $GLOBALS['rememberMeDays'], true, $value['path']);
 			}
 			}
 		}
 		}
 	}
 	}
@@ -74,8 +74,11 @@ function getTautulliToken($username, $password, $plexToken = null)
 				$options = (localURL($url)) ? array('verify' => false) : array();
 				$options = (localURL($url)) ? array('verify' => false) : array();
 				$response = Requests::post($url . '/auth/signin', $headers, $data, $options);
 				$response = Requests::post($url . '/auth/signin', $headers, $data, $options);
 				if ($response->success) {
 				if ($response->success) {
+					$qualifiedURL = qualifyURL($url, true);
+					$path = ($qualifiedURL['path']) ? $qualifiedURL['path'] : '/';
 					$token[$key]['token'] = json_decode($response->body, true)['token'];
 					$token[$key]['token'] = json_decode($response->body, true)['token'];
 					$token[$key]['uuid'] = json_decode($response->body, true)['uuid'];
 					$token[$key]['uuid'] = json_decode($response->body, true)['uuid'];
+					$token[$key]['path'] = $path;
 					writeLog('success', 'Tautulli Token Function - Grabbed token from: ' . $url, $username);
 					writeLog('success', 'Tautulli Token Function - Grabbed token from: ' . $url, $username);
 				} else {
 				} else {
 					writeLog('error', 'Tautulli Token Function - Error on URL: ' . $url, $username);
 					writeLog('error', 'Tautulli Token Function - Error on URL: ' . $url, $username);

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

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

+ 2 - 1
api/functions/token-functions.php

@@ -54,6 +54,7 @@ function createToken($username, $email, $image, $group, $groupID, $key, $days =
 	if (!isset($GLOBALS['dbLocation']) || !isset($GLOBALS['dbName'])) {
 	if (!isset($GLOBALS['dbLocation']) || !isset($GLOBALS['dbName'])) {
 		return false;
 		return false;
 	}
 	}
+	$days = ($days > 365) ? 365 : $days;
 	//Quick get user ID
 	//Quick get user ID
 	try {
 	try {
 		$database = new Dibi\Connection([
 		$database = new Dibi\Connection([
@@ -138,7 +139,7 @@ function validateToken($token, $global = false)
 			} catch (Dibi\Exception $e) {
 			} catch (Dibi\Exception $e) {
 				$GLOBALS['organizrUser'] = false;
 				$GLOBALS['organizrUser'] = false;
 			}
 			}
-		}else{
+		} else {
 			// Delete cookie & reload page
 			// Delete cookie & reload page
 			coookie('delete', $GLOBALS['cookieName']);
 			coookie('delete', $GLOBALS['cookieName']);
 			$GLOBALS['organizrUser'] = false;
 			$GLOBALS['organizrUser'] = false;

+ 1 - 1
api/pages/settings-tab-editor-homepage-order.php

@@ -3,7 +3,7 @@ if (file_exists('config' . DIRECTORY_SEPARATOR . 'config.php')) {
 	$pageSettingsTabEditorHomepageOrder = '
 	$pageSettingsTabEditorHomepageOrder = '
 <script>
 <script>
     $("#homepage-items-sort").sortable({
     $("#homepage-items-sort").sortable({
-	    placeholder:    "sort-placeholder col-md-3 col-xs-12 clearfix",
+	    placeholder:    "sort-placeholder col-md-3 col-xs-12 m-t-10 clearfix",
 	    forcePlaceholderSize: true,
 	    forcePlaceholderSize: true,
 	    start: function( e, ui ){
 	    start: function( e, ui ){
 	        ui.item.data( "start-pos", ui.item.index()+1 );
 	        ui.item.data( "start-pos", ui.item.index()+1 );

+ 2 - 2
api/pages/settings.php

@@ -8,7 +8,7 @@ if (file_exists('config' . DIRECTORY_SEPARATOR . 'config.php')) {
         sponsorLoad();
         sponsorLoad();
         newsLoad();
         newsLoad();
         checkCommitLoad();
         checkCommitLoad();
-        [].slice.call(document.querySelectorAll(\'.sttabs\')).forEach(function(el) {
+        [].slice.call(document.querySelectorAll(\'.sttabs-main-settings-div\')).forEach(function(el) {
             new CBPFWTabs(el);
             new CBPFWTabs(el);
         });
         });
     })();
     })();
@@ -30,7 +30,7 @@ if (file_exists('config' . DIRECTORY_SEPARATOR . 'config.php')) {
     <div class="row">
     <div class="row">
         <!-- Tab style start -->
         <!-- Tab style start -->
         <section class="">
         <section class="">
-            <div class="sttabs tabs-style-flip">
+            <div class="sttabs sttabs-main-settings-div tabs-style-flip">
                 <nav>
                 <nav>
                     <ul>
                     <ul>
                         <li onclick="changeSettingsMenu(\'Settings::Tab Editor\')" id="settings-main-tab-editor-anchor"><a href="#settings-main-tab-editor" class="sticon ti-layout-tab-v"><span lang="en">Tab Editor</span></a></li>
                         <li onclick="changeSettingsMenu(\'Settings::Tab Editor\')" id="settings-main-tab-editor-anchor"><a href="#settings-main-tab-editor" class="sticon ti-layout-tab-v"><span lang="en">Tab Editor</span></a></li>

+ 26 - 1
css/organizr.css

@@ -1070,9 +1070,15 @@ input.has-success {
 ul.nav.customtab.nav-tabs.nav-low-margin {
 ul.nav.customtab.nav-tabs.nav-low-margin {
     margin: -25px -25px 0px -25px !important;
     margin: -25px -25px 0px -25px !important;
 }
 }
-i.fa.fa-life-ring.fa-fw {
+#menu-Organizr-Support i {
     color: #C62828;
     color: #C62828;
 }
 }
+#menu-GitHub-Repo i {
+    color: #2cabe3;
+}
+#menu-Organizr-Docs i {
+    color: #707cd2;
+}
 .ping {
 .ping {
     position: relative;
     position: relative;
     margin-top: 0;
     margin-top: 0;
@@ -4387,3 +4393,22 @@ html {
 .noty_layout [data-name~="mojs-shape"] {
 .noty_layout [data-name~="mojs-shape"] {
     pointer-events:none
     pointer-events:none
 }
 }
+.bottom-close-splash {
+    position: absolute;
+    width: 130px;
+    right: 20px;
+    bottom: 20px;
+}
+.sort-placeholder {
+    background-size: contain;
+    height: 60px;
+    border-radius: 10px;
+    border: 3px dotted whitesmoke;
+    opacity: .5;
+    font-size: 13px;
+    margin: 1px 0 -9px;
+    text-align: left;
+    background: #1f1f1f;
+    padding-right: 0px !important;
+    padding-left: 0px !important;
+}

Plik diff jest za duży
+ 0 - 0
css/organizr.min.css


+ 3 - 3
index.php

@@ -1,4 +1,4 @@
-<?php include 'api/functions/static-globals.php';?>
+<?php include 'api/functions/static-globals.php'; ?>
 <!DOCTYPE html>
 <!DOCTYPE html>
 <html lang="en" ontouchmove>
 <html lang="en" ontouchmove>
 
 
@@ -256,7 +256,7 @@
 <script src="plugins/bower_components/jquery-wizard-master/libs/formvalidation/bootstrap.min.js"></script>
 <script src="plugins/bower_components/jquery-wizard-master/libs/formvalidation/bootstrap.min.js"></script>
 <script src="js/bowser.min.js"></script>
 <script src="js/bowser.min.js"></script>
 <script src="js/jasny-bootstrap.js"></script>
 <script src="js/jasny-bootstrap.js"></script>
-<script src="js/cbpFWTabs.js"></script>
+<script src="js/cbpFWTabs.js?v=<?php echo $GLOBALS['fileHash']; ?>"></script>
 <script src="js/js.cookie.js"></script>
 <script src="js/js.cookie.js"></script>
 <script src="js/jquery-lang.js"></script>
 <script src="js/jquery-lang.js"></script>
 <script src="js/jquery-ui.min.js"></script>
 <script src="js/jquery-ui.min.js"></script>
@@ -287,7 +287,7 @@
 <script src="plugins/bower_components/mousetrap/mousetrap.min.js"></script>
 <script src="plugins/bower_components/mousetrap/mousetrap.min.js"></script>
 <script src="plugins/bower_components/bootstrap-treeview-master/dist/bootstrap-treeview.min.js"></script>
 <script src="plugins/bower_components/bootstrap-treeview-master/dist/bootstrap-treeview.min.js"></script>
 <script src="plugins/bower_components/jquery.easy-pie-chart/dist/jquery.easypiechart.min.js"></script>
 <script src="plugins/bower_components/jquery.easy-pie-chart/dist/jquery.easypiechart.min.js"></script>
-<script src="/js/gauge.min.js"></script>
+<script src="js/gauge.min.js"></script>
 <script src="js/jquery.mousewheel.min.js"></script>
 <script src="js/jquery.mousewheel.min.js"></script>
 <script src="js/ua-parser.min.js"></script>
 <script src="js/ua-parser.min.js"></script>
 <script src="js/plyr.js"></script>
 <script src="js/plyr.js"></script>

+ 9 - 5
js/cbpFWTabs.js

@@ -40,11 +40,15 @@
 		// current index
 		// current index
 		this.current = -1;
 		this.current = -1;
 		// show current content item
 		// show current content item
-        if(this.tabs[0].innerHTML.indexOf('#settings') >= 0){
-            this._show(5);
-        }else{
-            this._show();
-        }
+		try{
+			if(this.tabs[0].innerHTML.indexOf('#settings') >= 0){
+				this._show(5);
+			}else{
+				this._show();
+			}
+		}catch{
+			this._show();
+		}
 		// init events
 		// init events
 		this._initEvents();
 		this._initEvents();
 	};
 	};

+ 1 - 1
js/custom.js

@@ -1993,7 +1993,7 @@ $(document).on('click', 'li a[aria-controls="Custom data"]', function() {
     var resizeEditor = function(jsonEditor) {
     var resizeEditor = function(jsonEditor) {
         const aceEditor = jsonEditor;
         const aceEditor = jsonEditor;
         const newHeight = aceEditor.getSession().getScreenLength() * (aceEditor.renderer.lineHeight + aceEditor.renderer.scrollBar.getWidth());
         const newHeight = aceEditor.getSession().getScreenLength() * (aceEditor.renderer.lineHeight + aceEditor.renderer.scrollBar.getWidth());
-        aceEditor.container.style.height = `${newHeight}px`;
+        aceEditor.container.style.height = newHeight + 'px';
         aceEditor.resize();
         aceEditor.resize();
     }
     }
 
 

Plik diff jest za duży
+ 0 - 0
js/custom.min.js


+ 95 - 61
js/functions.js

@@ -204,7 +204,7 @@ function isNumberKey(evt) {
     return true;
     return true;
 }
 }
 function setTabInfo(tab,action,value){
 function setTabInfo(tab,action,value){
-    if(tab == 'Organizr-Support'){
+    if(tab == 'Organizr-Support' || tab == 'Organizr-Docs'){
         return false;
         return false;
     }
     }
     if(tab !== null && action !== null && value !== null){
     if(tab !== null && action !== null && value !== null){
@@ -2582,20 +2582,23 @@ function userMenu(user){
 }
 }
 function menuExtras(active){
 function menuExtras(active){
     var supportFrame = buildFrameContainer('Organizr Support','https://organizr.app/support',1);
     var supportFrame = buildFrameContainer('Organizr Support','https://organizr.app/support',1);
-    var adminMenu = (activeInfo.user.groupID <= 1) ? buildMenuList('Organizr Support','https://organizr.app/support',1,'fontawesome::life-ring'): '';
+    var docsFrame = buildFrameContainer('Organizr Docs','https://docs.organizr.app',1);
+    var adminMenu = '<li class="devider"></li>';
+    adminMenu += (activeInfo.user.groupID <= 1 && activeInfo.settings.menuLink.githubMenuLink) ? buildMenuList('GitHub Repo','https://github.com/causefx/organizr',2,'fontawesome::github') : '';
+    adminMenu += (activeInfo.user.groupID <= 1 && activeInfo.settings.menuLink.organizrSupportMenuLink) ? buildMenuList('Organizr Support','https://organizr.app/support',1,'fontawesome::life-ring') : '';
+    adminMenu += (activeInfo.user.groupID <= 1 && activeInfo.settings.menuLink.organizrDocsMenuLink) ? buildMenuList('Organizr Docs','https://docs.organizr.app',1,'simpleline::docs') : '';
     $(supportFrame).appendTo($('.iFrame-listing'));
     $(supportFrame).appendTo($('.iFrame-listing'));
+    $(docsFrame).appendTo($('.iFrame-listing'));
 	if(active === true){
 	if(active === true){
-		return `
+		return (activeInfo.settings.menuLink.organizrSignoutMenuLink) ? `
 			<li class="devider"></li>
 			<li class="devider"></li>
 			<li id="sign-out"><a class="waves-effect" onclick="logout();"><i class="fa fa-sign-out fa-fw"></i> <span class="hide-menu" lang="en">Logout</span></a></li>
 			<li id="sign-out"><a class="waves-effect" onclick="logout();"><i class="fa fa-sign-out fa-fw"></i> <span class="hide-menu" lang="en">Logout</span></a></li>
-			<li class="devider"></li>
-			<li id="github"><a href="https://github.com/causefx/organizr" target="_blank" class="waves-effect"><i class="fa fa-github fa-fw text-success"></i> <span class="hide-menu">GitHub</span></a></li>
-		`+adminMenu;
+		` + adminMenu : '' + adminMenu;
 	}else{
 	}else{
-		return `
+		return (activeInfo.settings.menuLink.organizrSignoutMenuLink) ? `
 			<li class="devider"></li>
 			<li class="devider"></li>
 			<li id="menu-login"><a class="waves-effect show-login" href="javascript:void(0)"><i class="mdi mdi-login fa-fw"></i> <span class="hide-menu" lang="en">Login/Register</span></a></li>
 			<li id="menu-login"><a class="waves-effect show-login" href="javascript:void(0)"><i class="mdi mdi-login fa-fw"></i> <span class="hide-menu" lang="en">Login/Register</span></a></li>
-		`;
+		` : '';
 	}
 	}
 }
 }
 function categoryProcess(arrayItems){
 function categoryProcess(arrayItems){
@@ -2795,7 +2798,7 @@ function buildSplashScreen(json){
         <section id="splashScreen" class="lock-screen splash-screen fade in">
         <section id="splashScreen" class="lock-screen splash-screen fade in">
             <div class="row p-20 flexbox">`+items+`</div>
             <div class="row p-20 flexbox">`+items+`</div>
             <div class="row p-20 p-t-0 flexbox">
             <div class="row p-20 p-t-0 flexbox">
-                <div class="col-xs-12 col-sm-12 col-md-12 col-lg-12 col-xl-12 mouse hvr-wobble-bottom" onclick="$('.splash-screen').addClass('hidden').removeClass('in')">
+                <div class="col-xs-12 col-sm-12 col-md-12 col-lg-12 col-xl-12 mouse hvr-wobble-bottom bottom-close-splash" onclick="$('.splash-screen').addClass('hidden').removeClass('in')">
                     <div class="homepage-drag fc-event bg-danger lazyload"  data-src="">
                     <div class="homepage-drag fc-event bg-danger lazyload"  data-src="">
                         <span class="homepage-text">&nbsp; Close Splash</span>
                         <span class="homepage-text">&nbsp; Close Splash</span>
                     </div>
                     </div>
@@ -3302,7 +3305,10 @@ function newsLoad(){
         try {
         try {
             var response = JSON.parse(data);
             var response = JSON.parse(data);
             var items = [];
             var items = [];
+            var limit = 5;
+            var count = 0;
             $.each(response, function(i,v) {
             $.each(response, function(i,v) {
+                count++;
                 var newBody = `
                 var newBody = `
                 <h5 class="pull-left">`+moment(v.date).format('LLL')+`</h5>
                 <h5 class="pull-left">`+moment(v.date).format('LLL')+`</h5>
                 <h5 class="pull-right">`+v.author+`</h5>
                 <h5 class="pull-right">`+v.author+`</h5>
@@ -3310,9 +3316,11 @@ function newsLoad(){
                 `+((v.subTitle) ? '<h5>' + v.subTitle + '</h5>' : '' )+`
                 `+((v.subTitle) ? '<h5>' + v.subTitle + '</h5>' : '' )+`
                 <p>`+v.body+`</p>
                 <p>`+v.body+`</p>
                 `;
                 `;
-                items[i] = {
-                    title:v.title,
-                    body:newBody
+                if(count <= limit){
+                    items[i] = {
+                        title:v.title,
+                        body:newBody
+                    }
                 }
                 }
             });
             });
             var body = buildAccordion(items, true);
             var body = buildAccordion(items, true);
@@ -3731,7 +3739,7 @@ function marketplaceJSON(type) {
 }
 }
 function allIcons() {
 function allIcons() {
     return $.ajax({
     return $.ajax({
-        url: "/js/icons.json",
+        url: "js/icons.json",
     });
     });
 }
 }
 function organizrConnect(path){
 function organizrConnect(path){
@@ -4816,7 +4824,7 @@ function buildRequest(array){
 			<div class="white-box m-b-0 search-div resultBox-outside">
 			<div class="white-box m-b-0 search-div resultBox-outside">
 				<div class="form-group m-b-0">
 				<div class="form-group m-b-0">
 					<div id="request-input-div" class="input-group">
 					<div id="request-input-div" class="input-group">
-						<input id="request-input" lang="en" placeholder="Request Show or Movie" type="text" class="form-control inline-focus">
+						<input id="request-input" lang="en" placeholder="Request a Show or Movie" type="text" class="form-control inline-focus">
                         <input id="request-page" type="hidden" class="form-control">
                         <input id="request-page" type="hidden" class="form-control">
                         <div class="input-group-btn">
                         <div class="input-group-btn">
                             <button type="button" class="btn waves-effect waves-light btn-info dropdown-toggle" data-toggle="dropdown" aria-expanded="false"><span lang="en">Suggestions</span> <span class="caret"></span></button>
                             <button type="button" class="btn waves-effect waves-light btn-info dropdown-toggle" data-toggle="dropdown" aria-expanded="false"><span lang="en">Suggestions</span> <span class="caret"></span></button>
@@ -6094,9 +6102,12 @@ function buildPiholeItem(array){
             <div class="card text-white mb-3 pihole-stat bg-green">
             <div class="card text-white mb-3 pihole-stat bg-green">
                 <div class="card-body">
                 <div class="card-body">
                     <div class="inline-block">
                     <div class="inline-block">
-                        <p>Total queries</p>`;
+                        <p class="d-inline mr-1">Total queries</p>`;
         for(var key in data) {
         for(var key in data) {
             var e = data[key];
             var e = data[key];
+            if(length > 1 && !combine) {
+                card += `<p class="d-inline text-muted">(`+key+`)</p>`;
+            }
             card += `<h3 data-toggle="tooltip" data-placement="right" title="`+key+`">`+e['dns_queries_today'].toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",")+`</h3>`;
             card += `<h3 data-toggle="tooltip" data-placement="right" title="`+key+`">`+e['dns_queries_today'].toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",")+`</h3>`;
         };
         };
         card += `
         card += `
@@ -6114,9 +6125,12 @@ function buildPiholeItem(array){
             <div class="card bg-inverse text-white mb-3 pihole-stat bg-aqua">
             <div class="card bg-inverse text-white mb-3 pihole-stat bg-aqua">
                 <div class="card-body">
                 <div class="card-body">
                     <div class="inline-block">
                     <div class="inline-block">
-                        <p>Queries Blocked</p>`;
+                        <p class="d-inline mr-1">Queries Blocked</p>`;
         for(var key in data) {
         for(var key in data) {
             var e = data[key];
             var e = data[key];
+            if(length > 1 && !combine) {
+                card += `<p class="d-inline text-muted">(`+key+`)</p>`;
+            }
             card += `<h3 data-toggle="tooltip" data-placement="right" title="`+key+`">`+e['ads_blocked_today'].toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",")+`</h3>`;
             card += `<h3 data-toggle="tooltip" data-placement="right" title="`+key+`">`+e['ads_blocked_today'].toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",")+`</h3>`;
         };
         };
         card += `
         card += `
@@ -6134,9 +6148,12 @@ function buildPiholeItem(array){
             <div class="card bg-inverse text-white mb-3 pihole-stat bg-yellow">
             <div class="card bg-inverse text-white mb-3 pihole-stat bg-yellow">
                 <div class="card-body">
                 <div class="card-body">
                     <div class="inline-block">
                     <div class="inline-block">
-                        <p>Percent Blocked</p>`;
+                        <p class="d-inline mr-1">Percent Blocked</p>`;
         for(var key in data) {
         for(var key in data) {
             var e = data[key];
             var e = data[key];
+            if(length > 1 && !combine) {
+                card += `<p class="d-inline text-muted">(`+key+`)</p>`;
+            }
             card += `<h3 data-toggle="tooltip" data-placement="right" title="`+key+`">`+e['ads_percentage_today'].toFixed(1)+`%</h3>`
             card += `<h3 data-toggle="tooltip" data-placement="right" title="`+key+`">`+e['ads_percentage_today'].toFixed(1)+`%</h3>`
         };
         };
         card += `
         card += `
@@ -6154,9 +6171,12 @@ function buildPiholeItem(array){
             <div class="card bg-inverse text-white mb-3 pihole-stat bg-red">
             <div class="card bg-inverse text-white mb-3 pihole-stat bg-red">
                 <div class="card-body">
                 <div class="card-body">
                     <div class="inline-block">
                     <div class="inline-block">
-                        <p>Domains on Blocklist</p>`;
+                        <p class="d-inline mr-1">Domains on Blocklist</p>`;
         for(var key in data) {
         for(var key in data) {
             var e = data[key];
             var e = data[key];
+            if(length > 1 && !combine) {
+                card += `<p class="d-inline text-muted">(`+key+`)</p>`;
+            }
             card += `<h3 data-toggle="tooltip" data-placement="right" title="`+key+`">`+e['domains_being_blocked'].toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",")+`</h3>`;
             card += `<h3 data-toggle="tooltip" data-placement="right" title="`+key+`">`+e['domains_being_blocked'].toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",")+`</h3>`;
         };
         };
         card += `
         card += `
@@ -6177,15 +6197,6 @@ function buildPiholeItem(array){
         stats += '</div>';
         stats += '</div>';
     } else {
     } else {
         for(var key in array['data']) {
         for(var key in array['data']) {
-            if(length > 1) {
-                stats += `
-                <div class="row mb-2">
-                    <div class="col-sm-12">
-                        `+key+`
-                    </div>
-                </div>
-                `;
-            }
             var data = array['data'][key];
             var data = array['data'][key];
             obj = {};
             obj = {};
             obj[key] = data;
             obj[key] = data;
@@ -6588,13 +6599,13 @@ function buildTautulliItem(array){
             var section_name = null;
             var section_name = null;
             if(type == 'movie'){
             if(type == 'movie'){
                 extraField = 'Movies';
                 extraField = 'Movies';
-                section_name = 'Movie Libaries';
+                section_name = 'Movie Libraries';
             }else if(type == 'show'){
             }else if(type == 'show'){
                 extraField = 'Shows/Seasons/Episodes';
                 extraField = 'Shows/Seasons/Episodes';
-                section_name = 'TV Show Libaries';
+                section_name = 'TV Show Libraries';
             }else if(type == 'artist'){
             }else if(type == 'artist'){
                 extraField = 'Artists/Albums/Tracks';
                 extraField = 'Artists/Albums/Tracks';
-                section_name = 'Music Libaries';
+                section_name = 'Music Libraries';
             }
             }
             var cardTitle = '<th><span class="pull-left cardTitle">'+section_name.toUpperCase()+'</span><span class="pull-right cardCountType">'+extraField.toUpperCase()+'</th>';
             var cardTitle = '<th><span class="pull-left cardTitle">'+section_name.toUpperCase()+'</span><span class="pull-right cardCountType">'+extraField.toUpperCase()+'</th>';
             var card = `
             var card = `
@@ -7039,7 +7050,7 @@ var html = `
         <div class="white-box text-white p-0">
         <div class="white-box text-white p-0">
             <!-- Tabstyle start -->
             <!-- Tabstyle start -->
             <section class="">
             <section class="">
-                <div class="sttabs tabs-style-iconbox">
+                <div class="sttabs sttabs-main-weather-health-div tabs-style-iconbox">
                     <nav>
                     <nav>
                         <ul>${healthHeader}</ul>
                         <ul>${healthHeader}</ul>
                     </nav>
                     </nav>
@@ -7053,7 +7064,7 @@ var html = `
     </div>
     </div>
     <script>
     <script>
         (function() {
         (function() {
-            [].slice.call(document.querySelectorAll('.sttabs')).forEach(function(el) {
+            [].slice.call(document.querySelectorAll('.sttabs-main-weather-health-div')).forEach(function(el) {
                 new CBPFWTabs(el);
                 new CBPFWTabs(el);
             });
             });
         })();
         })();
@@ -7081,7 +7092,7 @@ function buildPollutant(array){
         <div class="white-box text-white p-0">
         <div class="white-box text-white p-0">
             <!-- Tabstyle start -->
             <!-- Tabstyle start -->
             <section class="">
             <section class="">
-                <div class="sttabs tabs-style-iconbox">
+                <div class="sttabs sttabs-main-weather-pollutant-div tabs-style-iconbox">
                     <nav>
                     <nav>
                         <ul>${pollutantHeader}</ul>
                         <ul>${pollutantHeader}</ul>
                     </nav>
                     </nav>
@@ -7095,7 +7106,7 @@ function buildPollutant(array){
     </div>
     </div>
     <script>
     <script>
         (function() {
         (function() {
-            [].slice.call(document.querySelectorAll('.sttabs')).forEach(function(el) {
+            [].slice.call(document.querySelectorAll('.sttabs-main-weather-pollutant-div')).forEach(function(el) {
                 new CBPFWTabs(el);
                 new CBPFWTabs(el);
             });
             });
         })();
         })();
@@ -7127,10 +7138,22 @@ function buildMonitorrItem(array){
     var cards = '';
     var cards = '';
     var options = array['options'];
     var options = array['options'];
     var services = array['services'];
     var services = array['services'];
+    var tabName = '';
 
 
     var buildCard = function(name, data) {
     var buildCard = function(name, data) {
-        if(data.status) { var statusColor = 'success'; var imageText = 'fa fa-check-circle text-success' } 
-            else { var statusColor = 'danger animated-3 loop-animation flash'; var imageText = 'fa fa-times-circle text-danger'}
+        if(data.status == true) {
+            var statusColor = 'success'; var imageText = 'fa fa-check-circle text-success'
+        } else if (data.status == 'unresponsive') {
+            var statusColor = 'warning animated-3 loop-animation flash'; var imageText = 'fa fa-times-circle text-warning'
+        } else {
+            var statusColor = 'danger animated-3 loop-animation flash'; var imageText = 'fa fa-times-circle text-danger'
+        }
+        if(typeof data.link !== 'undefined' && data.link.includes('#')) {
+            tabName = data.link.substring(data.link.indexOf('#')+1);
+            monitorrLink = '<a href="javascript:void(0)" onclick="tabActions(event,\''+tabName+'\',1)">';
+        } else if(typeof data.link !== 'undefined') {
+            monitorrLink = '<a href="'+data.link+'" target="_blank">'
+        }
         if(options['compact']) {
         if(options['compact']) {
             var card = `
             var card = `
             <div class="col-xl-2 col-lg-3 col-md-4 col-sm-6 col-xs-12">
             <div class="col-xl-2 col-lg-3 col-md-4 col-sm-6 col-xs-12">
@@ -7140,7 +7163,7 @@ function buildMonitorrItem(array){
                             <div class="left-health bg-`+statusColor+`"></div>
                             <div class="left-health bg-`+statusColor+`"></div>
                             <div class="ml-1 w-100">
                             <div class="ml-1 w-100">
                                 <i class="`+imageText+` font-20 pull-right mt-3 mb-2"></i>
                                 <i class="`+imageText+` font-20 pull-right mt-3 mb-2"></i>
-                                `; if (typeof data.link !== 'undefined') { card +=`<a href="`+data.link+`" target="_blank">`; }
+                                `; if (typeof data.link !== 'undefined') { card += monitorrLink; }
                                 card += `<h3 class="d-flex no-block align-items-center mt-2 mb-2"><img class="lazyload loginTitle" src="`+data.image+`">&nbsp;`+name+`</h3>
                                 card += `<h3 class="d-flex no-block align-items-center mt-2 mb-2"><img class="lazyload loginTitle" src="`+data.image+`">&nbsp;`+name+`</h3>
                                 `; if (typeof data.link !== 'undefined') { card +=`</a>`; }
                                 `; if (typeof data.link !== 'undefined') { card +=`</a>`; }
                                 card += `<div class="clearfix"></div>
                                 card += `<div class="clearfix"></div>
@@ -7160,7 +7183,7 @@ function buildMonitorrItem(array){
                             <img class="monitorrImage" src="`+data.image+`" alt="service icon">
                             <img class="monitorrImage" src="`+data.image+`" alt="service icon">
                         </div>
                         </div>
                         <div class="d-inline-block mt-4 py-2 px-4 badge indicator bg-`+statusColor+`">
                         <div class="d-inline-block mt-4 py-2 px-4 badge indicator bg-`+statusColor+`">
-                            <p class="mb-0">`; if(data.status) { card += 'ONLINE' } else { card += 'OFFLINE' } card+=`</p>
+                            <p class="mb-0">`; if(data.status == true) { card += 'ONLINE' } else if(data.status == 'unresponsive') { card += 'UNRESPONSIVE' } else { card += 'OFFLINE' } card+=`</p>
                         </div>
                         </div>
                         `; if (typeof data.link !== 'undefined') { card +=`</a>`; }
                         `; if (typeof data.link !== 'undefined') { card +=`</a>`; }
                         card += `</div>
                         card += `</div>
@@ -7177,27 +7200,31 @@ function buildMonitorrItem(array){
 }
 }
 function buildMonitorr(array){
 function buildMonitorr(array){
     if(array === false){ return ''; }
     if(array === false){ return ''; }
-    var services = (typeof array.services !== 'undefined') ? Object.keys(array.services).length : false;
-    var html = `
-    <div id="allMonitorr">
-		<div class="el-element-overlay row">`
-    if(array['options']['titleToggle']) {
+    if(array.error != undefined) {
+        console.log('Monitorr error: ' + array.error);
+    } else {
+        var services = (typeof array.services !== 'undefined') ? Object.keys(array.services).length : false;
+        var html = `
+        <div id="allMonitorr">
+            <div class="el-element-overlay row">`
+        if(array['options']['titleToggle']) {
+            html += `
+                <div class="col-md-12">
+                    <h4 class="pull-left homepage-element-title"><span lang="en">`+array['options']['title']+`</span> : </h4><h4 class="pull-left">&nbsp;<span class="label label-info m-l-20 checkbox-circle good-monitorr-services mouse" onclick="homepageMonitorr()">`+services+`</span></h4></h4>
+                    <hr class="hidden-xs ml-2">
+                </div>
+                <div class="clearfix"></div>
+            `;
+        }
         html += `
         html += `
-            <div class="col-md-12">
-                <h4 class="pull-left homepage-element-title"><span lang="en">`+array['options']['title']+`</span> : </h4><h4 class="pull-left">&nbsp;<span class="label label-info m-l-20 checkbox-circle good-monitorr-services mouse" onclick="homepageMonitorr()">`+services+`</span></h4></h4>
-                <hr class="hidden-xs ml-2">
+                <div class="monitorrCards">
+                    `+buildMonitorrItem(array)+`
+                </div>
             </div>
             </div>
-            <div class="clearfix"></div>
+        </div>
+        <div class="clearfix"></div>
         `;
         `;
     }
     }
-    html += `
-            <div class="monitorrCards">
-                `+buildMonitorrItem(array)+`
-			</div>
-		</div>
-    </div>
-    <div class="clearfix"></div>
-    `;
     return (array) ? html : '';
     return (array) ? html : '';
 }
 }
 function homepageMonitorr(timeout){
 function homepageMonitorr(timeout){
@@ -8119,15 +8146,21 @@ function youtubeSearch(searchQuery) {
 function youtubeCheck(title,link){
 function youtubeCheck(title,link){
 	youtubeSearch(title).success(function(data) {
 	youtubeSearch(title).success(function(data) {
         var response = JSON.parse(data);
         var response = JSON.parse(data);
-		inlineLoad();
-		var id = response.data.items["0"].id.videoId;
-		var div = `
+        console.log(data)
+		if(response.data){
+			inlineLoad();
+			var id = response.data.items["0"].id.videoId;
+			var div = `
 		<div id="player-`+link+`" data-plyr-provider="youtube" data-plyr-embed-id="`+id+`"></div>
 		<div id="player-`+link+`" data-plyr-provider="youtube" data-plyr-embed-id="`+id+`"></div>
 		<div class="clearfix"></div>
 		<div class="clearfix"></div>
 		`;
 		`;
-		$('.youtube-div').html(div);
-		$('.'+link).trigger('click');
-		player = new Plyr('#player-'+link);
+			$('.youtube-div').html(div);
+			$('.'+link).trigger('click');
+			player = new Plyr('#player-'+link);
+		}else{
+			messageSingle('API Limit Reached','YouTube API Error',activeInfo.settings.notifications.position,'#FFF','error','5000');
+		}
+
 	}).fail(function(xhr) {
 	}).fail(function(xhr) {
 		console.error("Organizr Function: YouTube Connection Failed");
 		console.error("Organizr Function: YouTube Connection Failed");
 	});
 	});
@@ -8232,6 +8265,7 @@ function changeAuth(){
         case 'emby_local':
         case 'emby_local':
         case 'emby_connect':
         case 'emby_connect':
         case 'emby_all':
         case 'emby_all':
+	    case 'jellyfin':
             $('.switchAuth').parent().parent().parent().hide();
             $('.switchAuth').parent().parent().parent().hide();
             $('.backendAuth').parent().parent().parent().show();
             $('.backendAuth').parent().parent().parent().show();
             $('.embyAuth').parent().parent().parent().show();
             $('.embyAuth').parent().parent().parent().show();

+ 10 - 3
js/news.json

@@ -1,23 +1,30 @@
 [
 [
+  {
+    "title": "Plex OAuth Patch",
+    "subTitle": "You server hostname will now be listed on OAuth box",
+    "date": "2020-06-18 16:30",
+    "body": "Plex has now fixed the 3 open CVE's pertaining to the ability for an attacker to phish for an Plex Admins token.  Please follow this link to read more about it.  <a href=\"https:\/\/www.bleepingcomputer.com\/news\/security\/plex-fixes-media-server-bugs-allowing-full-system-takeover\/\" target=\"_blank\" rel=\"noopener noreferrer\">Plex Vulnerabilities Patched<\/a>",
+    "author": "CauseFX"
+  },
   {
   {
     "title": "Tab Redirect Loops and SameSite Cookie Issues",
     "title": "Tab Redirect Loops and SameSite Cookie Issues",
     "subTitle": "New Browser Restrictions",
     "subTitle": "New Browser Restrictions",
     "date": "2020-04-01 19:30",
     "date": "2020-04-01 19:30",
-    "body": "It seems there are a lot of issues happening with redirect Loops and SameSite cookie issues.  If you are having issues, please read this document.  <a href=\"https:\/\/docs.organizr.app\/books\/troubleshooting\/page\/redirect-looping---samesite-errors\" target=\"_blank\">Organizr SameSite Docs<\/a>",
+    "body": "It seems there are a lot of issues happening with redirect Loops and SameSite cookie issues.  If you are having issues, please read this document.  <a href=\"https:\/\/docs.organizr.app\/books\/troubleshooting\/page\/redirect-looping---samesite-errors\" target=\"_blank\" rel=\"noopener noreferrer\">Organizr SameSite Docs<\/a>",
     "author": "CauseFX"
     "author": "CauseFX"
   },
   },
   {
   {
     "title": "Plex Oauth Issues - RESOLVED!",
     "title": "Plex Oauth Issues - RESOLVED!",
     "subTitle": "Let's make them bring back support correctly",
     "subTitle": "Let's make them bring back support correctly",
     "date": "2019-07-17 15:20",
     "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>",
+    "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\" rel=\"noopener noreferrer\">Plex Oauth Discussion<\/a>",
     "author": "CauseFX"
     "author": "CauseFX"
   },
   },
   {
   {
     "title": "Emby Discontinued Support",
     "title": "Emby Discontinued Support",
     "subTitle": "Emby API - EmbyConnect Deprecated",
     "subTitle": "Emby API - EmbyConnect Deprecated",
     "date": "2019-05-14 19:15",
     "date": "2019-05-14 19:15",
-    "body": "Emby has discontinued support for matching users against Emby Connect via API.  Therefore, we will no longer be supporting this method of Authentication for Organizr.  If Emby decides to support this again, I will re-enable it once more.  You can find more information here: <a href=\"https:\/\/github.com\/MediaBrowser\/Emby\/issues\/3553\" target=\"_blank\">Emby API Discussion<\/a>",
+    "body": "Emby has discontinued support for matching users against Emby Connect via API.  Therefore, we will no longer be supporting this method of Authentication for Organizr.  If Emby decides to support this again, I will re-enable it once more.  You can find more information here: <a href=\"https:\/\/github.com\/MediaBrowser\/Emby\/issues\/3553\" target=\"_blank\" rel=\"noopener noreferrer\">Emby API Discussion<\/a>",
     "author": "CauseFX"
     "author": "CauseFX"
   },
   },
   {
   {

+ 21 - 0
js/version.json

@@ -250,5 +250,26 @@
     "new": "Weather Homepage Item|Pi-Hole Homepage Item|Tautulli Homepage Item|Monitorr Homepage Item|Netdata Homepage Item|SpeedTest Homepage Item|HealthChecks Plugin|Bandwidth info to Now Playing|Config files can now contain arrays|Scrape API endpoint|New API Docs WIP located at /api/docs",
     "new": "Weather Homepage Item|Pi-Hole Homepage Item|Tautulli Homepage Item|Monitorr Homepage Item|Netdata Homepage Item|SpeedTest Homepage Item|HealthChecks Plugin|Bandwidth info to Now Playing|Config files can now contain arrays|Scrape API endpoint|New API Docs WIP located at /api/docs",
     "fixed": "iOS icon issue|Language Bug|Cache Image timing|fix hardcoded plugins directory for root (#1341)|Ombi Posters|Plex Timeouts|Indent cleanup|Homepage Item shortcut in Tab Editor broken (#1368)|Empty login form submitting|Sanitize username going into log (#1359)|Ombi Div for tv items|edit sttabs for reflect more than just settings tabs|Emby HomePage Add-in - Item Details URL Needs updating (#1290)|Fix JQuery error on settings save (#1356)",
     "fixed": "iOS icon issue|Language Bug|Cache Image timing|fix hardcoded plugins directory for root (#1341)|Ombi Posters|Plex Timeouts|Indent cleanup|Homepage Item shortcut in Tab Editor broken (#1368)|Empty login form submitting|Sanitize username going into log (#1359)|Ombi Div for tv items|edit sttabs for reflect more than just settings tabs|Emby HomePage Add-in - Item Details URL Needs updating (#1290)|Fix JQuery error on settings save (#1356)",
     "notes": "Added note about Samesite issues|New Radarr Logo|Please report bugs in GitHub issues page"
     "notes": "Added note about Samesite issues|New Radarr Logo|Please report bugs in GitHub issues page"
+  },
+  "2.0.631": {
+    "date": "2020-06-19 19:15",
+    "title": "Whoa - sorry for the wait",
+    "new": "Added css bottom-close-splash",
+    "fixed": "Netadata fixes|Docker update fixes|Tautulli Fixes|Hardcode fixes for days beyond one year|Typos|NZBHydra tab image|fix stray / in index.php and js/functions.php (#1408)|LDAP with STARTTLS support (#1411)",
+    "notes": "Please report bugs in GitHub issues page"
+  },
+  "2.0.633": {
+    "date": "2020-06-20 02:01",
+    "title": "Small Bugfix",
+    "new": "",
+    "fixed": "Fix heredoc error on php versions under 7.3",
+    "notes": "Please report bugs in GitHub issues page"
+  },
+  "2.0.650": {
+    "date": "2020-07-03 20:55",
+    "title": "Weekly Updates",
+    "new": "Added Jellyfin Auth function to log into Organizr|Re-Enable EmbyConnect backend option|Option to disable logout/login button on sidebar",
+    "fixed": "Plex oAuth login enabled after switching to a different backend (#1416)|losing access to settings (#1418)|Youtube Error catch and added another API Key(#1419)|Update coookie function to include path|Update Tautulli cookie to include path|News.json fixes|homepage sort issue on right side|add uuid on wizard path",
+    "notes": "Please report bugs in GitHub issues page"
   }
   }
 }
 }

BIN
plugins/images/tabs/nzbhydra.png


BIN
plugins/images/tabs/requestrr.png


BIN
plugins/images/tabs/xbackbone.png


+ 10 - 0
plugins/images/userTabs/index.html

@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <title>Image folder for User</title>
+</head>
+<body>
+Tab images go here
+</body>
+</html>

Niektóre pliki nie zostały wyświetlone z powodu dużej ilości zmienionych plików