Ver Fonte

Merge branch 'v2-develop' into feature/bookmark_plugin

leet1994 há 5 anos atrás
pai
commit
e2ede00728

+ 1 - 0
.gitignore

@@ -137,6 +137,7 @@ api/plugins/*
 !api/plugins/api/healthChecks.php
 !api/plugins/config/healthChecks.php
 !api/plugins/js/healthChecks.js
+!api/plugins/js/healthChecks-settings.js
 !api/plugins/php-mailer.php
 !api/plugins/api/php-mailer.php
 !api/plugins/config/php-mailer.php

+ 57 - 4
api/classes/organizr.class.php

@@ -60,7 +60,7 @@ class Organizr
 	
 	// ===================================
 	// Organizr Version
-	public $version = '2.1.184';
+	public $version = '2.1.195';
 	// ===================================
 	// Quick php Version check
 	public $minimumPHP = '7.2';
@@ -395,6 +395,7 @@ class Organizr
 		return ($encode) ? json_encode($files) : $files;
 	}
 	
+	/* Old function
 	public function pluginFiles($type)
 	{
 		$files = '';
@@ -414,6 +415,50 @@ class Organizr
 		}
 		return $files;
 	}
+	*/
+	public function pluginFiles($type, $settings = false)
+	{
+		$files = '';
+		switch ($type) {
+			case 'js':
+				foreach (glob(dirname(__DIR__, 1) . DIRECTORY_SEPARATOR . 'plugins' . DIRECTORY_SEPARATOR . 'js' . DIRECTORY_SEPARATOR . '*.js') as $filename) {
+					$keyOriginal = strtoupper(basename($filename, '.js'));
+					$key = str_replace('-SETTINGS', '', $keyOriginal);
+					$continue = false;
+					if ($settings) {
+						if (stripos($keyOriginal, '-SETTINGS') !== false) {
+							$continue = true;
+						}
+					} else {
+						if (stripos($keyOriginal, '-SETTINGS') == false) {
+							$continue = true;
+						}
+					}
+					switch ($key) {
+						case 'PHP-MAILER':
+							$key = 'PHPMAILER';
+							break;
+						default:
+							$key = $key;
+					}
+					if ($this->config[$key . '-enabled'] || $settings) {
+						if ($continue) {
+							$files .= '<script src="api/plugins/js/' . basename($filename) . '?v=' . $this->fileHash . '" defer="true"></script>';
+						}
+					}
+					
+				}
+				break;
+			case 'css':
+				foreach (glob(dirname(__DIR__, 1) . DIRECTORY_SEPARATOR . 'plugins' . DIRECTORY_SEPARATOR . 'css' . DIRECTORY_SEPARATOR . '*.css') as $filename) {
+					$files .= '<link href="api/plugins/css/' . basename($filename) . '?v=' . $this->fileHash . '" rel="stylesheet">';
+				}
+				break;
+			default:
+				break;
+		}
+		return $files;
+	}
 	
 	public function formKey($script = true)
 	{
@@ -2375,11 +2420,19 @@ class Organizr
 				array(
 					'type' => 'input',
 					'name' => 'jellyfinURL',
-					'label' => 'Jellyfin URL',
+					'label' => 'Jellyfin API URL',
 					'value' => $this->config['jellyfinURL'],
-					'help' => 'Please make sure to use the same (sub)domain to access Jellyfin as Organizr\'s',
+					'help' => 'Please make sure to use the local address to the API',
 					'placeholder' => 'http(s)://hostname:port'
 				),
+				array(
+					'type' => 'input',
+					'name' => 'jellyfinSSOURL',
+					'label' => 'Jellyfin SSO URL',
+					'value' => $this->config['jellyfinSSOURL'],
+					'help' => 'Please make sure to use the same (sub)domain to access Jellyfin as Organizr\'s',
+					'placeholder' => 'http(s)://domain.com'
+				),
 				array(
 					'type' => 'switch',
 					'name' => 'ssoJellyfin',
@@ -2877,7 +2930,7 @@ class Organizr
 		->issuedAt(time())// Configures the time that the token was issue (iat claim)
 		->expiresAt(time() + (86400 * $days))// Configures the expiration time of the token (exp claim)
 		//->withClaim('username', $result['username'])// Configures a new claim, called "username"
-		//->withClaim('group', $result['group'])// Configures a new claim, called "group"
+		->withClaim('group', $result['group'])// Configures a new claim, called "group"
 		//->withClaim('groupID', $result['group_id'])// Configures a new claim, called "groupID"
 		//->withClaim('email', $result['email'])// Configures a new claim, called "email"
 		//->withClaim('image', $result['image'])// Configures a new claim, called "image"

+ 3 - 0
api/config/default.php

@@ -46,6 +46,7 @@ return array(
 	'jellyfinTabName' => '',
 	'jellyfinURL' => '',
 	'jellyfinToken' => '',
+	'jellyfinSSOURL' => '',
 	'plexID' => '',
 	'tautulliURL' => '',
 	'ombiURL' => '',
@@ -98,6 +99,7 @@ return array(
 	'transmissionHideSeeding' => false,
 	'transmissionHideCompleted' => false,
 	'transmissionCombine' => false,
+	'transmissionDisableCertCheck' => false,
 	'delugeURL' => '',
 	'delugePassword' => '',
 	'delugeHideSeeding' => false,
@@ -112,6 +114,7 @@ return array(
 	'qBittorrentReverseSorting' => false,
 	'qBittorrentCombine' => false,
 	'qBittorrentApiVersion' => '1',
+	'qBittorrentDisableCertCheck' => false,
 	'rTorrentURL' => '',
 	'rTorrentURLOverride' => '',
 	'rTorrentUsername' => '',

+ 20 - 6
api/functions/log-functions.php

@@ -2,9 +2,23 @@
 
 trait LogFunctions
 {
-
-}
-
-
-
-
+	public function info($msg, $username = null)
+	{
+		$this->writeLog('info', $msg, $username);
+	}
+	
+	public function error($msg, $username = null)
+	{
+		$this->writeLog('error', $msg, $username);
+	}
+	
+	public function warning($msg, $username = null)
+	{
+		$this->writeLog('warning', $msg, $username);
+	}
+	
+	public function debug($msg, $username = null)
+	{
+		$this->writeLog('debug', $msg, $username);
+	}
+}

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

@@ -53,6 +53,7 @@ trait SSOFunctions
 		$token = null;
 		try {
 			$url = $this->qualifyURL($this->config['jellyfinURL']);
+			$ssoUrl = $this->qualifyURL($this->config['jellyfinSSOURL']);
 			$headers = array(
 				'X-Emby-Authorization' => 'MediaBrowser Client="Organizr Jellyfin Tab", Device="Organizr_PHP", DeviceId="Organizr_SSO", Version="1.0"',
 				"Accept" => "application/json",
@@ -68,13 +69,14 @@ trait SSOFunctions
 			if ($response->success) {
 				$token = json_decode($response->body, true);
 				$this->writeLog('success', 'Jellyfin Token Function - Grabbed token.', $username);
+				return '{"Servers":[{"ManualAddress":"' . $ssoUrl . '","Id":"' . $token['ServerId'] . '","UserId":"' . $token['User']['Id'] . '","AccessToken":"' . $token['AccessToken'] . '"}]}';
 			} else {
 				$this->writeLog('error', 'Jellyfin Token Function - Jellyfin did not return Token', $username);
 			}
 		} catch (Requests_Exception $e) {
 			$this->writeLog('error', 'Jellyfin Token Function - Error: ' . $e->getMessage(), $username);
 		}
-		return '{"Servers":[{"ManualAddress":"' . $url . '","Id":"' . $token['ServerId'] . '","UserId":"' . $token['User']['Id'] . '","AccessToken":"' . $token['AccessToken'] . '"}]}';
+		return false;
 	}
 	
 	public function getOmbiToken($username, $password, $oAuthToken = null, $fallback = false)

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

@@ -26,7 +26,7 @@ trait TokenFunctions
 				if ($jwttoken->validate($data)) {
 					$result['valid'] = true;
 					//$result['username'] = $jwttoken->getClaim('username');
-					//$result['group'] = $jwttoken->getClaim('group');
+					$result['group'] = ($jwttoken->hasClaim('group')) ? $jwttoken->getClaim('group') : 'N/A';
 					//$result['groupID'] = $jwttoken->getClaim('groupID');
 					$result['userID'] = $jwttoken->getClaim('userID');
 					//$result['email'] = $jwttoken->getClaim('email');

+ 8 - 2
api/homepage/qbittorrent.php

@@ -34,6 +34,12 @@ trait QBitTorrentHomepageItem
 						'help' => 'Please make sure to use local IP address and port - You also may use local dns name too.',
 						'placeholder' => 'http(s)://hostname:port'
 					),
+					array(
+						'type' => 'switch',
+						'name' => 'qBittorrentDisableCertCheck',
+						'label' => 'Disable Certificate Check',
+						'value' => $this->config['qBittorrentDisableCertCheck']
+					),
 					array(
 						'type' => 'select',
 						'name' => 'qBittorrentApiVersion',
@@ -123,7 +129,7 @@ trait QBitTorrentHomepageItem
 		$apiVersionQuery = ($this->config['qBittorrentApiVersion'] == '1') ? '/query/torrents?sort=' : '/api/v2/torrents/info?sort=';
 		$url = $digest['scheme'] . '://' . $digest['host'] . $digest['port'] . $digest['path'] . $apiVersionLogin;
 		try {
-			$options = ($this->localURL($this->config['qBittorrentURL'])) ? array('verify' => false) : array();
+			$options = $this->requestOptions($this->config['qBittorrentURL'], $this->config['qBittorrentDisableCertCheck'], $this->config['homepageDownloadRefresh']);
 			$response = Requests::post($url, array(), $data, $options);
 			$reflection = new ReflectionClass($response->cookies);
 			$cookie = $reflection->getProperty("cookies");
@@ -215,7 +221,7 @@ trait QBitTorrentHomepageItem
 		$apiVersionQuery = ($this->config['qBittorrentApiVersion'] == '1') ? '/query/torrents?sort=' : '/api/v2/torrents/info?sort=';
 		$url = $digest['scheme'] . '://' . $digest['host'] . $digest['port'] . $digest['path'] . $apiVersionLogin;
 		try {
-			$options = ($this->localURL($this->config['qBittorrentURL'])) ? array('verify' => false) : array();
+			$options = $this->requestOptions($this->config['qBittorrentURL'], $this->config['qBittorrentDisableCertCheck'], $this->config['homepageDownloadRefresh']);
 			$response = Requests::post($url, array(), $data, $options);
 			$reflection = new ReflectionClass($response->cookies);
 			$cookie = $reflection->getProperty("cookies");

+ 9 - 3
api/homepage/transmission.php

@@ -34,6 +34,12 @@ trait TransmissionHomepageItem
 						'help' => 'Please do not included /web in URL.  Please make sure to use local IP address and port - You also may use local dns name too.',
 						'placeholder' => 'http(s)://hostname:port'
 					),
+					array(
+						'type' => 'switch',
+						'name' => 'transmissionDisableCertCheck',
+						'label' => 'Disable Certificate Check',
+						'value' => $this->config['transmissionDisableCertCheck']
+					),
 					array(
 						'type' => 'input',
 						'name' => 'transmissionUsername',
@@ -101,7 +107,7 @@ trait TransmissionHomepageItem
 		$passwordInclude = ($this->config['transmissionUsername'] != '' && $this->config['transmissionPassword'] != '') ? $this->config['transmissionUsername'] . ':' . $this->decrypt($this->config['transmissionPassword']) . "@" : '';
 		$url = $digest['scheme'] . '://' . $passwordInclude . $digest['host'] . $digest['port'] . $digest['path'] . '/rpc';
 		try {
-			$options = ($this->localURL($this->config['transmissionURL'])) ? array('verify' => false) : array();
+			$options = $this->requestOptions($this->config['transmissionURL'], $this->config['transmissionDisableCertCheck'], $this->config['homepageDownloadRefresh']);
 			$response = Requests::get($url, array(), $options);
 			if ($response->headers['x-transmission-session-id']) {
 				$headers = array(
@@ -189,7 +195,7 @@ trait TransmissionHomepageItem
 		$passwordInclude = ($this->config['transmissionUsername'] != '' && $this->config['transmissionPassword'] != '') ? $this->config['transmissionUsername'] . ':' . $this->decrypt($this->config['transmissionPassword']) . "@" : '';
 		$url = $digest['scheme'] . '://' . $passwordInclude . $digest['host'] . $digest['port'] . $digest['path'] . '/rpc';
 		try {
-			$options = ($this->localURL($this->config['transmissionURL'])) ? array('verify' => false) : array();
+			$options = $this->requestOptions($this->config['transmissionURL'], $this->config['transmissionDisableCertCheck'], $this->config['homepageDownloadRefresh']);
 			$response = Requests::get($url, array(), $options);
 			if ($response->headers['x-transmission-session-id']) {
 				$headers = array(
@@ -245,4 +251,4 @@ trait TransmissionHomepageItem
 		$this->setAPIResponse('success', null, 200, $api);
 		return $api;
 	}
-}
+}

+ 131 - 70
api/pages/settings-tab-editor-categories.php

@@ -11,86 +11,147 @@ function get_page_settings_tab_editor_categories($Organizr)
 	if (!$Organizr->qualifyRequest(1, true)) {
 		return false;
 	}
+	$iconSelectors = '
+		$(".categoryIconIconList").select2({
+			ajax: {
+				url: \'api/v2/icon\',
+				data: function (params) {
+					var query = {
+						search: params.term,
+						page: params.page || 1
+					}
+					return query;
+				},
+				processResults: function (data, params) {
+					params.page = params.page || 1;
+					return {
+						results: data.response.data.results,
+						pagination: {
+							more: (params.page * 20) < data.response.data.total
+						}
+					};
+				},
+				//cache: true
+			},
+			placeholder: \'Search for an icon\',
+			templateResult: formatIcon,
+			templateSelection: formatIcon
+		});
+		
+		$(".categoryIconImageList").select2({
+			 ajax: {
+				url: \'api/v2/image/select\',
+				data: function (params) {
+					var query = {
+						search: params.term,
+						page: params.page || 1
+					}
+					return query;
+				},
+				processResults: function (data, params) {
+					params.page = params.page || 1;
+					return {
+						results: data.response.data.results,
+						pagination: {
+							more: (params.page * 20) < data.response.data.total
+						}
+					};
+				},
+				//cache: true
+			},
+			placeholder: \'Search for an image\',
+			templateResult: formatImage,
+			templateSelection: formatImage
+		});
+	';
 	return '
 <script>
 buildCategoryEditor();
 $( \'#categoryEditorTable\' ).sortable({
-    stop: function () {
-        var inputs = $(\'input.order\');
-        var nbElems = inputs.length;
-        inputs.each(function(idx) {
-            $(this).val(idx + 1);
-        });
-        submitCategoryOrder();
-    }
+	stop: function () {
+		var inputs = $(\'input.order\');
+		var nbElems = inputs.length;
+		inputs.each(function(idx) {
+			$(this).val(idx + 1);
+		});
+		submitCategoryOrder();
+	}
 });
+' . $iconSelectors . '
 </script>
 <div class="panel bg-org panel-info">
-    <div class="panel-heading">
-        <span lang="en">Category Editor</span>
-        <button type="button" class="btn btn-success btn-circle pull-right popup-with-form m-r-5" href="#new-category-form" data-effect="mfp-3d-unfold"><i class="fa fa-plus"></i> </button>
-    </div>
-    <div class="table-responsive">
-        <form id="submit-categories-form" onsubmit="return false;">
-            <table class="table table-hover manage-u-table">
-                <thead>
-                    <tr>
-                        <th width="70" class="text-center">#</th>
-                        <th lang="en">NAME</th>
-                        <th lang="en" style="text-align:center">TABS</th>
-                        <th lang="en" style="text-align:center">DEFAULT</th>
-                        <th lang="en" style="text-align:center">EDIT</th>
-                        <th lang="en" style="text-align:center">DELETE</th>
-                    </tr>
-                </thead>
-                <tbody id="categoryEditorTable"><td class="text-center" colspan="6"><i class="fa fa-spin fa-spinner"></i></td></tbody>
-            </table>
-        </form>
-    </div>
+	<div class="panel-heading">
+		<span lang="en">Category Editor</span>
+		<button type="button" class="btn btn-success btn-circle pull-right popup-with-form m-r-5" href="#new-category-form" data-effect="mfp-3d-unfold"><i class="fa fa-plus"></i> </button>
+	</div>
+	<div class="table-responsive">
+		<form id="submit-categories-form" onsubmit="return false;">
+			<table class="table table-hover manage-u-table">
+				<thead>
+					<tr>
+						<th width="70" class="text-center">#</th>
+						<th lang="en">NAME</th>
+						<th lang="en" style="text-align:center">TABS</th>
+						<th lang="en" style="text-align:center">DEFAULT</th>
+						<th lang="en" style="text-align:center">EDIT</th>
+						<th lang="en" style="text-align:center">DELETE</th>
+					</tr>
+				</thead>
+				<tbody id="categoryEditorTable"><td class="text-center" colspan="6"><i class="fa fa-spin fa-spinner"></i></td></tbody>
+			</table>
+		</form>
+	</div>
 </div>
 <form id="new-category-form" class="mfp-hide white-popup-block mfp-with-anim">
-    <h1 lang="en">Add New Category</h1>
-    <fieldset style="border:0;">
-        <div class="form-group">
-            <label class="control-label" for="new-category-form-inputNameNew" lang="en">Category Name</label>
-            <input type="text" class="form-control" id="new-category-form-inputNameNew" name="category" required="" autofocus>
-        </div>
-        <div class="form-group">
-            <label class="control-label" for="new-category-form-inputImageNew" lang="en">Category Image</label>
-            <input type="text" class="form-control" id="new-category-form-inputImageNew" name="image"  required="">
-        </div>
-    </fieldset>
-    <button class="btn btn-sm btn-info btn-rounded waves-effect waves-light pull-right row b-none addNewCategory" type="button"><span class="btn-label"><i class="fa fa-plus"></i></span><span lang="en">Add Category</span></button>
-    <div class="clearfix"></div>
+	<h1 lang="en">Add New Category</h1>
+	<fieldset style="border:0;">
+		<div class="form-group">
+			<label class="control-label" for="new-category-form-inputNameNew" lang="en">Category Name</label>
+			<input type="text" class="form-control" id="new-category-form-inputNameNew" name="category" required="" autofocus>
+		</div>
+		<div class="row">
+			<div class="form-group col-lg-6">
+				<label class="control-label" for="new-category-form-chooseImage" lang="en">Choose Image</label>
+				<select class="form-control categoryIconImageList" id="new-category-form-chooseImage" name="chooseImage"><option lang="en">Select or type Image</option></select>
+			</div>
+			<div class="form-group col-lg-6">
+				<label class="control-label" for="new-category-form-chooseIcon" lang="en">Choose Icon</label>
+				<select class="form-control categoryIconIconList" id="new-category-form-chooseIcon" name="chooseIcon"><option lang="en">Select or type Icon</option></select>
+			</div>
+		</div>
+		<div class="form-group">
+			<label class="control-label" for="new-category-form-inputImageNew" lang="en">Category Image</label>
+			<input type="text" class="form-control" id="new-category-form-inputImageNew" name="image"  required="">
+		</div>
+	</fieldset>
+	<button class="btn btn-sm btn-info btn-rounded waves-effect waves-light pull-right row b-none addNewCategory" type="button"><span class="btn-label"><i class="fa fa-plus"></i></span><span lang="en">Add Category</span></button>
+	<div class="clearfix"></div>
 </form>
 <form id="edit-category-form" class="mfp-hide white-popup-block mfp-with-anim">
-    <input type="hidden" name="id" value="">
-    <h1 lang="en">Edit Category</h1>
-    <fieldset style="border:0;">
-        <div class="form-group">
-            <label class="control-label" for="edit-category-form-inputName" lang="en">Category Name</label>
-            <input type="text" class="form-control" id="edit-category-form-inputName" name="category" required="" autofocus>
-        </div>
-        <div class="form-group">
-            <label class="control-label" for="edit-category-form-inputImage" lang="en">Category Image</label>
-            <div class="panel panel-info">
-                <div class="panel-heading">
-                    <span lang="en">Image Legend</span>
-                    <div class="pull-right"><a href="#" data-perform="panel-collapse"><i class="ti-plus"></i></a></div>
-                </div>
-                <div class="panel-wrapper collapse" aria-expanded="false">
-                    <div class="panel-body">
-                        <p lang="en">You may use an image or icon in this field</p>
-                        <p lang="en">For images, use the following format:</p><code>url::path/to/image</code>
-                        <p lang="en">For icons, use the following format:</p><code>icon-type::icon-name</code> i.e. <code>fontawesome::home</code>
-                    </div>
-                </div>
-            </div>
-            <input type="text" class="form-control" id="edit-category-form-inputImage" name="image"  required="">
-        </div>
-    </fieldset>
-    <button class="btn btn-sm btn-info btn-rounded waves-effect waves-light pull-right row b-none editCategory" type="button"><span class="btn-label"><i class="fa fa-plus"></i></span><span lang="en">Edit Category</span></button>
-    <div class="clearfix"></div>
+	<input type="hidden" name="id" value="">
+	<h1 lang="en">Edit Category</h1>
+	<fieldset style="border:0;">
+		<div class="form-group">
+			<label class="control-label" for="edit-category-form-inputName" lang="en">Category Name</label>
+			<input type="text" class="form-control" id="edit-category-form-inputName" name="category" required="" autofocus>
+		</div>
+		<div class="row">
+			<div class="form-group col-lg-6">
+				<label class="control-label" for="edit-category-form-chooseImage" lang="en">Choose Image</label>
+				<select class="form-control categoryIconImageList" id="edit-category-form-chooseImage" name="chooseImage"><option lang="en">Select or type Image</option></select>
+			</div>
+			<div class="form-group col-lg-6">
+				<label class="control-label" for="edit-category-form-chooseIcon" lang="en">Choose Icon</label>
+				<select class="form-control categoryIconIconList" id="edit-category-form-chooseIcon" name="chooseIcon"><option lang="en">Select or type Icon</option></select>
+			</div>
+		</div>
+		<div class="form-group">
+			<label class="control-label" for="edit-category-form-inputImage" lang="en">Category Image</label>
+			<input type="text" class="form-control" id="edit-category-form-inputImage" name="image"  required="">
+		</div>
+	</fieldset>
+	<button class="btn btn-sm btn-info btn-rounded waves-effect waves-light pull-right row b-none editCategory" type="button"><span class="btn-label"><i class="fa fa-plus"></i></span><span lang="en">Edit Category</span></button>
+	<div class="clearfix"></div>
 </form>
 ';
-}
+}

+ 175 - 176
api/pages/settings-tab-editor-tabs.php

@@ -12,17 +12,16 @@ function get_page_settings_tab_editor_tabs($Organizr)
 		return false;
 	}
 	$iconSelectors = '
-
-	    $(".tabIconIconList").select2({
-	        ajax: {
-			    url: \'api/v2/icon\',
-			    data: function (params) {
+		$(".tabIconIconList").select2({
+			ajax: {
+				url: \'api/v2/icon\',
+				data: function (params) {
 					var query = {
 						search: params.term,
 						page: params.page || 1
 					}
 					return query;
-			    },
+				},
 				processResults: function (data, params) {
 					params.page = params.page || 1;
 					return {
@@ -32,7 +31,7 @@ function get_page_settings_tab_editor_tabs($Organizr)
 						}
 					};
 				},
-			    //cache: true
+				//cache: true
 			},
 			placeholder: \'Search for an icon\',
 			templateResult: formatIcon,
@@ -41,14 +40,14 @@ function get_page_settings_tab_editor_tabs($Organizr)
 		
 		$(".tabIconImageList").select2({
 			 ajax: {
-			    url: \'api/v2/image/select\',
-			    data: function (params) {
+				url: \'api/v2/image/select\',
+				data: function (params) {
 					var query = {
 						search: params.term,
 						page: params.page || 1
 					}
 					return query;
-			    },
+				},
 				processResults: function (data, params) {
 					params.page = params.page || 1;
 					return {
@@ -58,7 +57,7 @@ function get_page_settings_tab_editor_tabs($Organizr)
 						}
 					};
 				},
-			    //cache: true
+				//cache: true
 			},
 			placeholder: \'Search for an image\',
 			templateResult: formatImage,
@@ -70,185 +69,185 @@ function get_page_settings_tab_editor_tabs($Organizr)
 	buildTabEditor();
 	!function(a){function f(a,b){if(!(a.originalEvent.touches.length>1)){a.preventDefault();var c=a.originalEvent.changedTouches[0],d=document.createEvent("MouseEvents");d.initMouseEvent(b,!0,!0,window,1,c.screenX,c.screenY,c.clientX,c.clientY,!1,!1,!1,!1,0,null),a.target.dispatchEvent(d)}}if(a.support.touch="ontouchend"in document,a.support.touch){var e,b=a.ui.mouse.prototype,c=b._mouseInit,d=b._mouseDestroy;b._touchStart=function(a){var b=this;!e&&b._mouseCapture(a.originalEvent.changedTouches[0])&&(e=!0,b._touchMoved=!1,f(a,"mouseover"),f(a,"mousemove"),f(a,"mousedown"))},b._touchMove=function(a){e&&(this._touchMoved=!0,f(a,"mousemove"))},b._touchEnd=function(a){e&&(f(a,"mouseup"),f(a,"mouseout"),this._touchMoved||f(a,"click"),e=!1)},b._mouseInit=function(){var b=this;b.element.bind({touchstart:a.proxy(b,"_touchStart"),touchmove:a.proxy(b,"_touchMove"),touchend:a.proxy(b,"_touchEnd")}),c.call(b)},b._mouseDestroy=function(){var b=this;b.element.unbind({touchstart:a.proxy(b,"_touchStart"),touchmove:a.proxy(b,"_touchMove"),touchend:a.proxy(b,"_touchEnd")}),d.call(b)}}}(jQuery);
 	$( \'#tabEditorTable\' ).sortable({
-	    stop: function () {
-	        $(\'input.order\').each(function(idx) {
-	            $(this).val(idx + 1);
-	        });
-	        var newTabs = $( "#submit-tabs-form" ).serializeToJSON();
-	        newTabsGlobal = newTabs;
-	        $(\'.saveTabOrderButton\').removeClass(\'hidden\');
-	        //submitTabOrder(newTabs);
-	    }
+		stop: function () {
+			$(\'input.order\').each(function(idx) {
+				$(this).val(idx + 1);
+			});
+			var newTabs = $( "#submit-tabs-form" ).serializeToJSON();
+			newTabsGlobal = newTabs;
+			$(\'.saveTabOrderButton\').removeClass(\'hidden\');
+			//submitTabOrder(newTabs);
+		}
 	});
 	$( \'#tabEditorTable\' ).disableSelection();
 	' . $iconSelectors . '
 	</script>
 	<div class="panel bg-org panel-info">
-	    <div class="panel-heading">
-	        <span lang="en">Tab Editor</span>
-	        <button type="button" class="btn btn-info btn-circle pull-right popup-with-form m-r-5" href="#new-tab-form" data-effect="mfp-3d-unfold"><i class="fa fa-plus"></i> </button>
-	        <button type="button" class="btn btn-info btn-circle pull-right m-r-5 help-modal" data-modal="tabs"><i class="fa fa-question-circle"></i> </button>
-	    	<button onclick="submitTabOrder(newTabsGlobal)" class="btn btn-sm btn-info btn-rounded waves-effect waves-light pull-right animated loop-animation rubberBand m-r-20 saveTabOrderButton hidden" type="button"><span class="btn-label"><i class="fa fa-save"></i></span><span lang="en">Save Tab Order</span></button>
-	    </div>
-	    <div class="table-responsive">
-	        <form id="submit-tabs-form" onsubmit="return false;">
-	            <table class="table table-hover manage-u-table">
-	                <thead>
-	                    <tr>
-	                        <th width="70" class="text-center">#</th>
-	                        <th lang="en">NAME</th>
-	                        <th lang="en">CATEGORY</th>
-	                        <th lang="en">GROUP</th>
-	                        <th lang="en">TYPE</th>
-	                        <th lang="en" style="text-align:center">DEFAULT</th>
-	                        <th lang="en" style="text-align:center">ACTIVE</th>
-	                        <th lang="en" style="text-align:center">SPLASH</th>
-	                        <th lang="en" style="text-align:center">PING</th>
-	                        <th lang="en" style="text-align:center">PRELOAD</th>
-	                        <th lang="en" style="text-align:center">EDIT</th>
-	                        <th lang="en" style="text-align:center">DELETE</th>
-	                    </tr>
-	                </thead>
-	                <tbody id="tabEditorTable">
-	                	<td class="text-center" colspan="12"><i class="fa fa-spin fa-spinner"></i></td>
+		<div class="panel-heading">
+			<span lang="en">Tab Editor</span>
+			<button type="button" class="btn btn-info btn-circle pull-right popup-with-form m-r-5" href="#new-tab-form" data-effect="mfp-3d-unfold"><i class="fa fa-plus"></i> </button>
+			<button type="button" class="btn btn-info btn-circle pull-right m-r-5 help-modal" data-modal="tabs"><i class="fa fa-question-circle"></i> </button>
+			<button onclick="submitTabOrder(newTabsGlobal)" class="btn btn-sm btn-info btn-rounded waves-effect waves-light pull-right animated loop-animation rubberBand m-r-20 saveTabOrderButton hidden" type="button"><span class="btn-label"><i class="fa fa-save"></i></span><span lang="en">Save Tab Order</span></button>
+		</div>
+		<div class="table-responsive">
+			<form id="submit-tabs-form" onsubmit="return false;">
+				<table class="table table-hover manage-u-table">
+					<thead>
+						<tr>
+							<th width="70" class="text-center">#</th>
+							<th lang="en">NAME</th>
+							<th lang="en">CATEGORY</th>
+							<th lang="en">GROUP</th>
+							<th lang="en">TYPE</th>
+							<th lang="en" style="text-align:center">DEFAULT</th>
+							<th lang="en" style="text-align:center">ACTIVE</th>
+							<th lang="en" style="text-align:center">SPLASH</th>
+							<th lang="en" style="text-align:center">PING</th>
+							<th lang="en" style="text-align:center">PRELOAD</th>
+							<th lang="en" style="text-align:center">EDIT</th>
+							<th lang="en" style="text-align:center">DELETE</th>
+						</tr>
+					</thead>
+					<tbody id="tabEditorTable">
+						<td class="text-center" colspan="12"><i class="fa fa-spin fa-spinner"></i></td>
 					</tbody>
-	            </table>
-	        </form>
-	    </div>
+				</table>
+			</form>
+		</div>
 	</div>
 	<form id="new-tab-form" class="mfp-hide white-popup-block mfp-with-anim">
-	    <h1 lang="en">Add New Tab</h1>
-	    <fieldset style="border:0;">
-	        <div class="alert alert-success alert-dismissable tabTestMessage hidden">
-	            <button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button>
-	            <span lang="en">Tab can be set as iFrame</span>
-	        </div>
-	        <div class="alert alert-danger alert-dismissable tabTestMessage hidden">
-	            <button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button>
-	            <span lang="en">Please set tab as [New Window] on next screen</span>
-	        </div>
-	        <div class="form-group">
-	            <label class="control-label" for="new-tab-form-inputNameNew" lang="en">Tab Name</label>
-	            <input type="text" class="form-control" id="new-tab-form-inputNameNew" name="name" required="" autofocus>
-	        </div>
-	        <div class="form-group">
-	            <label class="control-label" for="new-tab-form-inputURLNew" lang="en">Tab URL</label>
-	            <input type="text" class="form-control" id="new-tab-form-inputURLNew" name="url"  required="">
-	        </div>
-	        <div class="form-group">
-	            <label class="control-label" for="new-tab-form-inputURLLocalNew" lang="en">Tab Local URL</label>
-	            <input type="text" class="form-control" id="new-tab-form-inputURLLocalNew" name="url_local">
-	        </div>
-	        <div class="form-group">
-	            <label class="control-label" for="new-tab-form-inputPingURLNew" lang="en">Ping URL</label>
-	            <input type="text" class="form-control" id="new-tab-form-inputPingURLNew" name="ping_url"  placeholder="host/ip:port">
-	        </div>
-	        <div class="row">
-		        <div class="form-group col-lg-6">
-		            <label class="control-label" for="new-tab-form-inputTabActionTypeNew" lang="en">Tab Auto Action</label>
-		                <select class="form-control" id="new-tab-form-inputTabActionTypeNew" name="timeout">
-		                    <option value="null">None</option>
-		                    <option value="1">Auto Close</option>
-		                    <option value="2">Auto Reload</option>
+		<h1 lang="en">Add New Tab</h1>
+		<fieldset style="border:0;">
+			<div class="alert alert-success alert-dismissable tabTestMessage hidden">
+				<button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button>
+				<span lang="en">Tab can be set as iFrame</span>
+			</div>
+			<div class="alert alert-danger alert-dismissable tabTestMessage hidden">
+				<button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button>
+				<span lang="en">Please set tab as [New Window] on next screen</span>
+			</div>
+			<div class="form-group">
+				<label class="control-label" for="new-tab-form-inputNameNew" lang="en">Tab Name</label>
+				<input type="text" class="form-control" id="new-tab-form-inputNameNew" name="name" required="" autofocus>
+			</div>
+			<div class="form-group">
+				<label class="control-label" for="new-tab-form-inputURLNew" lang="en">Tab URL</label>
+				<input type="text" class="form-control" id="new-tab-form-inputURLNew" name="url"  required="">
+			</div>
+			<div class="form-group">
+				<label class="control-label" for="new-tab-form-inputURLLocalNew" lang="en">Tab Local URL</label>
+				<input type="text" class="form-control" id="new-tab-form-inputURLLocalNew" name="url_local">
+			</div>
+			<div class="form-group">
+				<label class="control-label" for="new-tab-form-inputPingURLNew" lang="en">Ping URL</label>
+				<input type="text" class="form-control" id="new-tab-form-inputPingURLNew" name="ping_url"  placeholder="host/ip:port">
+			</div>
+			<div class="row">
+				<div class="form-group col-lg-6">
+					<label class="control-label" for="new-tab-form-inputTabActionTypeNew" lang="en">Tab Auto Action</label>
+						<select class="form-control" id="new-tab-form-inputTabActionTypeNew" name="timeout">
+							<option value="null">None</option>
+							<option value="1">Auto Close</option>
+							<option value="2">Auto Reload</option>
 						</select>
-		        </div>
-		        <div class="form-group col-lg-6">
-		            <label class="control-label" for="new-tab-form-inputTabActionTimeNew" lang="en">Tab Auto Action Minutes</label>
-		                <input type="number" class="form-control" id="new-tab-form-inputTabActionTimeNew" name="timeout_ms"  placeholder="0">
-		        </div>
-		    </div>
-	        <div class="row">
-		        <div class="form-group col-lg-4">
-		            <label class="control-label" for="new-tab-form-chooseImage" lang="en">Choose Image</label>
-		            <select class="form-control tabIconImageList" id="new-tab-form-chooseImage" name="chooseImage"><option lang="en">Select or type Image</option></select>
-		        </div>
-		        <div class="form-group col-lg-4">
-		            <label class="control-label" for="new-tab-form-chooseIcon" lang="en">Choose Icon</label>
+				</div>
+				<div class="form-group col-lg-6">
+					<label class="control-label" for="new-tab-form-inputTabActionTimeNew" lang="en">Tab Auto Action Minutes</label>
+						<input type="number" class="form-control" id="new-tab-form-inputTabActionTimeNew" name="timeout_ms"  placeholder="0">
+				</div>
+			</div>
+			<div class="row">
+				<div class="form-group col-lg-4">
+					<label class="control-label" for="new-tab-form-chooseImage" lang="en">Choose Image</label>
+					<select class="form-control tabIconImageList" id="new-tab-form-chooseImage" name="chooseImage"><option lang="en">Select or type Image</option></select>
+				</div>
+				<div class="form-group col-lg-4">
+					<label class="control-label" for="new-tab-form-chooseIcon" lang="en">Choose Icon</label>
 					<select class="form-control tabIconIconList" id="new-tab-form-chooseIcon" name="chooseIcon"><option lang="en">Select or type Icon</option></select>
-		        </div>
-		        <div class="form-group col-lg-4">
-		            <label class="control-label" for="new-tab-form-chooseBlackberry" lang="en">Choose Blackberry Theme Icon</label>
-		            <button id="new-tab-form-chooseBlackberry" class="btn btn-xs btn-primary waves-effect waves-light form-control" onclick="showBlackberryThemes(\'new-tab-form-inputImageNew\');" type="button">
-		                <i class="fa fa-search"></i>&nbsp; <span lang="en">Choose</span>
-		            </button>
-		        </div>
-		    </div>
-	        <div class="form-group">
-	            <label class="control-label" for="new-tab-form-inputImageNew" lang="en">Tab Image</label>
-	            <input type="text" class="form-control" id="new-tab-form-inputImageNew" name="image"  required="">
-	        </div>
-	    </fieldset>
-	    <button class="btn btn-sm btn-info btn-rounded waves-effect waves-light row b-none testTab" type="button"><span class="btn-label"><i class="fa fa-flask"></i></span><span lang="en">Test Tab</span></button>
-	    <button class="btn btn-sm btn-info btn-rounded waves-effect waves-light pull-right row b-none addNewTab" type="button"><span class="btn-label"><i class="fa fa-plus"></i></span><span lang="en">Add Tab</span></button>
-	    <div class="clearfix"></div>
+				</div>
+				<div class="form-group col-lg-4">
+					<label class="control-label" for="new-tab-form-chooseBlackberry" lang="en">Choose Blackberry Theme Icon</label>
+					<button id="new-tab-form-chooseBlackberry" class="btn btn-xs btn-primary waves-effect waves-light form-control" onclick="showBlackberryThemes(\'new-tab-form-inputImageNew\');" type="button">
+						<i class="fa fa-search"></i>&nbsp; <span lang="en">Choose</span>
+					</button>
+				</div>
+			</div>
+			<div class="form-group">
+				<label class="control-label" for="new-tab-form-inputImageNew" lang="en">Tab Image</label>
+				<input type="text" class="form-control" id="new-tab-form-inputImageNew" name="image"  required="">
+			</div>
+		</fieldset>
+		<button class="btn btn-sm btn-info btn-rounded waves-effect waves-light row b-none testTab" type="button"><span class="btn-label"><i class="fa fa-flask"></i></span><span lang="en">Test Tab</span></button>
+		<button class="btn btn-sm btn-info btn-rounded waves-effect waves-light pull-right row b-none addNewTab" type="button"><span class="btn-label"><i class="fa fa-plus"></i></span><span lang="en">Add Tab</span></button>
+		<div class="clearfix"></div>
 	</form>
 	<form id="edit-tab-form" class="mfp-hide white-popup-block mfp-with-anim">
-	    <input type="hidden" name="id" value="x">
-	    <span class="hidden" id="originalTabName"></span>
-	    <h1 lang="en">Edit Tab</h1>
-	    <fieldset style="border:0;">
-	        <div class="alert alert-success alert-dismissable tabEditTestMessage hidden">
-	            <button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button>
-	            <span lang="en">Tab can be set as iFrame</span>
-	        </div>
-	        <div class="alert alert-danger alert-dismissable tabEditTestMessage hidden">
-	            <button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button>
-	            <span lang="en">Please set tab as [New Window] on next screen</span>
-	        </div>
-	        <div class="form-group">
-	            <label class="control-label" for="edit-tab-form-inputName" lang="en">Tab Name</label>
-	            <input type="text" class="form-control" id="edit-tab-form-inputName" name="name" required="" autofocus>
-	        </div>
-	        <div class="form-group">
-	            <label class="control-label" for="edit-tab-form-inputURL" lang="en">Tab URL</label>
-	            <input type="text" class="form-control" id="edit-tab-form-inputURL" name="url"  required="">
-	        </div>
-	        <div class="form-group">
-	            <label class="control-label" for="edit-tab-form-inputLocalURL" lang="en">Tab Local URL</label>
-	            <input type="text" class="form-control" id="edit-tab-form-inputLocalURL" name="url_local">
-	        </div>
-	        <div class="form-group">
-	            <label class="control-label" for="edit-tab-form-pingURL" lang="en">Ping URL</label>
-	            <input type="text" class="form-control" id="edit-tab-form-pingURL" name="ping_url" placeholder="host/ip:port">
-	        </div>
-	        <div class="row">
-		        <div class="form-group col-lg-6">
-		            <label class="control-label" for="edit-tab-form-inputTabActionTypeNew" lang="en">Tab Auto Action</label>
-		                <select class="form-control" id="edit-tab-form-inputTabActionTypeNew" name="timeout">
-		                    <option value="null">None</option>
-		                    <option value="1">Auto Close</option>
-		                    <option value="2">Auto Reload</option>
+		<input type="hidden" name="id" value="x">
+		<span class="hidden" id="originalTabName"></span>
+		<h1 lang="en">Edit Tab</h1>
+		<fieldset style="border:0;">
+			<div class="alert alert-success alert-dismissable tabEditTestMessage hidden">
+				<button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button>
+				<span lang="en">Tab can be set as iFrame</span>
+			</div>
+			<div class="alert alert-danger alert-dismissable tabEditTestMessage hidden">
+				<button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button>
+				<span lang="en">Please set tab as [New Window] on next screen</span>
+			</div>
+			<div class="form-group">
+				<label class="control-label" for="edit-tab-form-inputName" lang="en">Tab Name</label>
+				<input type="text" class="form-control" id="edit-tab-form-inputName" name="name" required="" autofocus>
+			</div>
+			<div class="form-group">
+				<label class="control-label" for="edit-tab-form-inputURL" lang="en">Tab URL</label>
+				<input type="text" class="form-control" id="edit-tab-form-inputURL" name="url"  required="">
+			</div>
+			<div class="form-group">
+				<label class="control-label" for="edit-tab-form-inputLocalURL" lang="en">Tab Local URL</label>
+				<input type="text" class="form-control" id="edit-tab-form-inputLocalURL" name="url_local">
+			</div>
+			<div class="form-group">
+				<label class="control-label" for="edit-tab-form-pingURL" lang="en">Ping URL</label>
+				<input type="text" class="form-control" id="edit-tab-form-pingURL" name="ping_url" placeholder="host/ip:port">
+			</div>
+			<div class="row">
+				<div class="form-group col-lg-6">
+					<label class="control-label" for="edit-tab-form-inputTabActionTypeNew" lang="en">Tab Auto Action</label>
+						<select class="form-control" id="edit-tab-form-inputTabActionTypeNew" name="timeout">
+							<option value="null">None</option>
+							<option value="1">Auto Close</option>
+							<option value="2">Auto Reload</option>
 						</select>
-		        </div>
-		        <div class="form-group col-lg-6">
-		            <label class="control-label" for="edit-tab-form-inputTabActionTimeNew" lang="en">Tab Auto Action Minutes</label>
-		                <input type="number" class="form-control" id="edit-tab-form-inputTabActionTimeNew" name="timeout_ms">
-		        </div>
-		    </div>
-	        <div class="row">
-		        <div class="form-group col-lg-4">
-		            <label class="control-label" for="edit-tab-form-chooseImage" lang="en">Choose Image</label>
-		            <select class="form-control tabIconImageList" id="edit-tab-form-chooseImage" name="chooseImage"><option lang="en">Select or type Image</option></select>
-		        </div>
-		        <div class="form-group col-lg-4">
-		            <label class="control-label" for="edit-tab-form-chooseIcon" lang="en">Choose Icon</label>
+				</div>
+				<div class="form-group col-lg-6">
+					<label class="control-label" for="edit-tab-form-inputTabActionTimeNew" lang="en">Tab Auto Action Minutes</label>
+						<input type="number" class="form-control" id="edit-tab-form-inputTabActionTimeNew" name="timeout_ms">
+				</div>
+			</div>
+			<div class="row">
+				<div class="form-group col-lg-4">
+					<label class="control-label" for="edit-tab-form-chooseImage" lang="en">Choose Image</label>
+					<select class="form-control tabIconImageList" id="edit-tab-form-chooseImage" name="chooseImage"><option lang="en">Select or type Image</option></select>
+				</div>
+				<div class="form-group col-lg-4">
+					<label class="control-label" for="edit-tab-form-chooseIcon" lang="en">Choose Icon</label>
 					<select class="form-control tabIconIconList" id="edit-tab-form-chooseIcon" name="chooseIcon"><option lang="en">Select or type Icon</option></select>
-		        </div>
-		        <div class="form-group col-lg-4">
-		            <label class="control-label" for="edit-tab-form-chooseBlackberry" lang="en">Choose Blackberry Theme Icon</label>
-		            <button id="edit-tab-form-chooseBlackberry" class="btn btn-xs btn-primary waves-effect waves-light form-control" onclick="showBlackberryThemes(\'edit-tab-form-inputImage\');" type="button">
-		                <i class="fa fa-search"></i>&nbsp; <span lang="en">Choose</span>
-		            </button>
-		        </div>
-		    </div>
-	        <div class="form-group">
-	            <label class="control-label" for="edit-tab-form-inputImage" lang="en">Tab Image</label>
-	            <input type="text" class="form-control" id="edit-tab-form-inputImage" name="image"  required="">
-	        </div>
-	    </fieldset>
-	    <button class="btn btn-sm btn-info btn-rounded waves-effect waves-light row b-none testEditTab" type="button"><span class="btn-label"><i class="fa fa-flask"></i></span><span lang="en">Test Tab</span></button>
-	    <button class="btn btn-sm btn-info btn-rounded waves-effect waves-light pull-right row b-none editTab" type="button"><span class="btn-label"><i class="fa fa-check"></i></span><span lang="en">Edit Tab</span></button>
-	    <div class="clearfix"></div>
+				</div>
+				<div class="form-group col-lg-4">
+					<label class="control-label" for="edit-tab-form-chooseBlackberry" lang="en">Choose Blackberry Theme Icon</label>
+					<button id="edit-tab-form-chooseBlackberry" class="btn btn-xs btn-primary waves-effect waves-light form-control" onclick="showBlackberryThemes(\'edit-tab-form-inputImage\');" type="button">
+						<i class="fa fa-search"></i>&nbsp; <span lang="en">Choose</span>
+					</button>
+				</div>
+			</div>
+			<div class="form-group">
+				<label class="control-label" for="edit-tab-form-inputImage" lang="en">Tab Image</label>
+				<input type="text" class="form-control" id="edit-tab-form-inputImage" name="image"  required="">
+			</div>
+		</fieldset>
+		<button class="btn btn-sm btn-info btn-rounded waves-effect waves-light row b-none testEditTab" type="button"><span class="btn-label"><i class="fa fa-flask"></i></span><span lang="en">Test Tab</span></button>
+		<button class="btn btn-sm btn-info btn-rounded waves-effect waves-light pull-right row b-none editTab" type="button"><span class="btn-label"><i class="fa fa-check"></i></span><span lang="en">Edit Tab</span></button>
+		<div class="clearfix"></div>
 	</form>
 	';
 }

+ 1 - 1
api/pages/settings-user-manage-users.php

@@ -303,4 +303,4 @@ function get_page_settings_user_manage_users($Organizr)
     <div class="clearfix"></div>
 </form>
 ';
-}
+}

+ 1 - 1
api/pages/settings.php

@@ -12,7 +12,7 @@ function get_page_settings($Organizr)
 		return false;
 	}
 	$Organizr->writeLog('success', 'Admin Function -  Accessed Settings Page', $Organizr->user['username']);
-	return '
+	return $Organizr->pluginFiles('js', true) . '
 <script>
     (function() {
         updateCheck();

+ 35 - 0
api/pages/tabs.php

@@ -0,0 +1,35 @@
+<?php
+$GLOBALS['organizrPages'][] = 'tabs';
+function get_page_tabs($Organizr)
+{
+	if (!$Organizr) {
+		$Organizr = new Organizr();
+	}
+	return '
+<script>
+</script>
+<div class="container-fluid">
+    <div class="row bg-title">
+        <div class="col-lg-3 col-md-4 col-sm-4 col-xs-12">
+            <h4 class="page-title" lang="en">No Tabs Available</h4>
+        </div>
+        <!-- /.col-lg-12 -->
+    </div>
+    <!--.row-->
+    <div class="row">
+        <div class="col-lg-12">
+            <div class="panel panel-warning">
+                <div class="panel-heading"> <i class="ti-alert fa-fw"></i> <span lang="en">No Tabs Available</span></div>
+                <div class="panel-wrapper collapse in" aria-expanded="true">
+                    <div class="panel-body">
+                        <p lang="en">There are no available tabs for your group - please contact the Administrator</p>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+    <!--./row-->
+</div>
+<!-- /.container-fluid -->
+';
+}

+ 3 - 3
api/pages/wizard.php

@@ -24,8 +24,8 @@ function get_page_wizard($Organizr)
                                     message: \'The username must be more than 2 and less than 30 characters long\'
                                 },
                                 regexp: {
-                                    regexp: /^[a-zA-Z0-9_\.]+$/,
-                                    message: \'The username can only consist of alphabetical, number, dot and underscore\'
+                                    regexp: /^[a-zA-Z0-9_\.\@]+$/,
+                                    message: \'The username can only consist of alphabetical, number, at sign, dot and underscore\'
                                 }
                             }
                         },
@@ -386,4 +386,4 @@ function get_page_wizard($Organizr)
 </div>
 <!-- /.container-fluid -->
 ';
-}
+}

+ 3 - 1
api/plugins/chat.php

@@ -12,7 +12,9 @@ $GLOBALS['plugins'][]['chat'] = array( // Plugin Name
 	'configPrefix' => 'CHAT', // config file prefix for array items without the hypen
 	'version' => '1.0.0', // SemVer of plugin
 	'image' => 'plugins/images/chat.png', // 1:1 non transparent image for plugin
-	'settings' => true, // does plugin need a settings page? true or false
+	'settings' => true, // does plugin need a settings modal?
+	'bind' => true, // use default bind to make settings page - true or false
+	'api' => 'api/v2/plugins/chat/settings', // api route for settings page
 	'homepage' => false // Is plugin for use on homepage? true or false
 );
 

+ 4 - 2
api/plugins/healthChecks.php

@@ -10,7 +10,9 @@ $GLOBALS['plugins'][]['healthChecks'] = array( // Plugin Name
 	'configPrefix' => 'HEALTHCHECKS', // config file prefix for array items without the hyphen
 	'version' => '1.0.0', // SemVer of plugin
 	'image' => 'plugins/images/healthchecksio.png', // 1:1 non transparent image for plugin
-	'settings' => true, // does plugin need a settings page? true or false
+	'settings' => true, // does plugin need a settings modal?
+	'bind' => false, // use default bind to make settings page - true or false
+	'api' => false, // api route for settings page
 	'homepage' => false // Is plugin for use on homepage? true or false
 );
 
@@ -139,4 +141,4 @@ class HealthChecks extends Organizr
 			$this->setAPIResponse('error', 'User does not have access', 401);
 		}
 	}
-}
+}

+ 4 - 2
api/plugins/invites.php

@@ -10,7 +10,9 @@ $GLOBALS['plugins'][]['Invites'] = array( // Plugin Name
 	'configPrefix' => 'INVITES', // config file prefix for array items without the hypen
 	'version' => '1.0.0', // SemVer of plugin
 	'image' => 'plugins/images/invites.png', // 1:1 non transparent image for plugin
-	'settings' => true, // does plugin need a settings page? true or false
+	'settings' => true, // does plugin need a settings modal?
+	'bind' => true, // use default bind to make settings page - true or false
+	'api' => 'api/v2/plugins/invites/settings', // api route for settings page
 	'homepage' => false // Is plugin for use on homepage? true or false
 );
 
@@ -470,4 +472,4 @@ class Invites extends Organizr
 		return (!empty($plexUser) ? $plexUser : null);
 	}
 	
-}
+}

+ 168 - 182
api/plugins/js/chat.js

@@ -1,200 +1,186 @@
 // FUNCTIONS FOR CHAT
-chatLaunch()
+$('body').arrive('#activeInfo', {onceOnly: true}, function() {
+	chatLaunch();
+});
 function chatLaunch(){
-    if(typeof activeInfo == 'undefined'){
-        setTimeout(function () {
-            chatLaunch();
-        }, 1000);
-    }else{
-        if(activeInfo.plugins["CHAT-enabled"] == true && activeInfo.plugins.includes["CHAT-authKey-include"] !== '' && activeInfo.plugins.includes["CHAT-appID-include"] !== '' && activeInfo.plugins.includes["CHAT-cluster-include"] !== ''){
-            if (activeInfo.user.groupID <= activeInfo.plugins.includes["CHAT-Auth-include"]) {
-                var menuList = `<li><a class=""  href="javascript:void(0)" onclick="tabActions(event,'chat','plugin');chatEntry();"><i class="fa fa-comments-o fa-fw"></i> <span lang="en">Chat</span><small class="chat-counter label label-rouded label-info pull-right hidden">0</small></a></li>`;
-				var htmlDOM = `
-                <div id="container-plugin-chat" class="plugin-container hidden">
-                    <div class="chat-main-box bg-org">
-                        <!-- .chat-left-panel -->
-                        <div class="chat-left-aside">
-                            <div class="open-panel"><i class="ti-angle-right"></i></div>
-                            <div class="chat-left-inner bg-org"><ul class="chatonline style-none "></ul></div>
-                        </div>
-                        <!-- .chat-left-panel -->
-                        <!-- .chat-right-panel -->
-                        <div class="chat-right-aside">
-                            <div class="chat-box">
-                                <ul class="chat-list p-t-30"></ul>
-                                <div class="row send-chat-box">
-                                    <div class="col-sm-12">
-                                        <textarea class="form-control chat-input-send" placeholder="Type your message" lang="en"></textarea>
-                                        <div class="custom-send">
-                                            <button type="button" class="btn btn-info btn-lg custom-send-button"><i class="fa fa-paper-plane fa-2x"></i> </button>
-                                        </div>
-                                    </div>
-                                </div>
-                            </div>
-                        </div>
-                        <!-- .chat-right-panel -->
-                    </div>
-                </div>
-		    	`;
-				$('.append-menu').after(menuList);
-	            $('.plugin-listing').append(htmlDOM);
-	            pageLoad();
-                // Enable pusher logging - don't include this in production
-                //Pusher.logToConsole = true;
-                // Add API Key & cluster here to make the connection
-                var pusher = new Pusher(activeInfo.plugins.includes["CHAT-authKey-include"], {
-                    cluster: activeInfo.plugins.includes["CHAT-cluster-include"],
-                    encrypted: true
-                });
-                // Enter a unique channel you wish your users to be subscribed in.
-                var channel = pusher.subscribe('org_channel');
-                // bind the server event to get the response data and append it to the message div
-                channel.bind('my-event',
-                    function(data) {
-                        formatMessage(data);
-                        $('.chat-list').append(formatMessage(data));
-                        $('.custom-send').html('<button type="button" class="btn btn-info btn-lg custom-send-button"><i class="fa fa-paper-plane fa-2x"></i> </button>');
-                        $(".chat-list").scrollTop($(".chat-list")[0].scrollHeight);
-                        if($('#container-plugin-chat').hasClass('hidden')){
-                            var chatSound =  new Audio(activeInfo.plugins.includes["CHAT-newMessageSound-include"]);
-                            chatSound.play();
-                            message(data.username,data.message,activeInfo.settings.notifications.position,"#FFF","success","20000");
-                            $('.profile-image').addClass('animated loop-animation rubberBand');
-                            $('.chat-counter').removeClass('hidden').html(parseInt($('.chat-counter').text()) + 1);
-                        }
-                    });
-                // check if the user is subscribed to the above channel
-                channel.bind('pusher:subscription_succeeded', function(members) {
-	                organizrConsole('Plugin Function','Chat Websocket Connected!');
-	                organizrConsole('Plugin Function','Connecting to Organizr Chat DB');
-                    getMessagesAndUsers(activeInfo.settings.homepage.refresh["CHAT-userRefreshTimeout"], true);
-                });
-                /*jslint browser: true*/
-                /*global $, jQuery, alert*/
-                $(document).ready(function () {
-                    "use strict";
-                    $('.chat-left-inner > .chatonline').slimScroll({
-                        height: '100%',
-                        position: 'right',
-                        size: "0px",
-                        color: '#dcdcdc'
-
-                    });
-                    $('.chat-list').slimScroll({
-                        height: '100%',
-                        position: 'right',
-                        size: "0px",
-                        color: '#dcdcdc',
-                        start: 'bottom',
-                    });
-                    $(".open-panel").on("click", function () {
-                        $(".chat-left-aside").toggleClass("open-pnl");
-                        $(".open-panel i").toggleClass("ti-angle-left");
-                    });
-                });
-			}
-        }
-    }
+	if(activeInfo.plugins["CHAT-enabled"] == true && activeInfo.plugins.includes["CHAT-authKey-include"] !== '' && activeInfo.plugins.includes["CHAT-appID-include"] !== '' && activeInfo.plugins.includes["CHAT-cluster-include"] !== ''){
+		if (activeInfo.user.groupID <= activeInfo.plugins.includes["CHAT-Auth-include"]) {
+			var menuList = `<li><a class=""  href="javascript:void(0)" onclick="tabActions(event,'chat','plugin');chatEntry();"><i class="fa fa-comments-o fa-fw"></i> <span lang="en">Chat</span><small class="chat-counter label label-rouded label-info pull-right hidden">0</small></a></li>`;
+			var htmlDOM = `
+			<div id="container-plugin-chat" class="plugin-container hidden">
+				<div class="chat-main-box bg-org">
+					<!-- .chat-left-panel -->
+					<div class="chat-left-aside">
+						<div class="open-panel"><i class="ti-angle-right"></i></div>
+						<div class="chat-left-inner bg-org"><ul class="chatonline style-none "></ul></div>
+					</div>
+					<!-- .chat-left-panel -->
+					<!-- .chat-right-panel -->
+					<div class="chat-right-aside">
+						<div class="chat-box">
+							<ul class="chat-list p-t-30"></ul>
+							<div class="row send-chat-box">
+								<div class="col-sm-12">
+									<textarea class="form-control chat-input-send" placeholder="Type your message" lang="en"></textarea>
+									<div class="custom-send">
+										<button type="button" class="btn btn-info btn-lg custom-send-button"><i class="fa fa-paper-plane fa-2x"></i> </button>
+									</div>
+								</div>
+							</div>
+						</div>
+					</div>
+					<!-- .chat-right-panel -->
+				</div>
+			</div>
+			`;
+			$('.append-menu').after(menuList);
+			$('.plugin-listing').append(htmlDOM);
+			pageLoad();
+			// Enable pusher logging - don't include this in production
+			//Pusher.logToConsole = true;
+			// Add API Key & cluster here to make the connection
+			var pusher = new Pusher(activeInfo.plugins.includes["CHAT-authKey-include"], {
+				cluster: activeInfo.plugins.includes["CHAT-cluster-include"],
+				encrypted: true
+			});
+			// Enter a unique channel you wish your users to be subscribed in.
+			var channel = pusher.subscribe('org_channel');
+			// bind the server event to get the response data and append it to the message div
+			channel.bind('my-event',
+				function(data) {
+					formatMessage(data);
+					$('.chat-list').append(formatMessage(data));
+					$('.custom-send').html('<button type="button" class="btn btn-info btn-lg custom-send-button"><i class="fa fa-paper-plane fa-2x"></i> </button>');
+					$(".chat-list").scrollTop($(".chat-list")[0].scrollHeight);
+					if($('#container-plugin-chat').hasClass('hidden')){
+						var chatSound =  new Audio(activeInfo.plugins.includes["CHAT-newMessageSound-include"]);
+						chatSound.play();
+						message(data.username,data.message,activeInfo.settings.notifications.position,"#FFF","success","20000");
+						$('.profile-image').addClass('animated loop-animation rubberBand');
+						$('.chat-counter').removeClass('hidden').html(parseInt($('.chat-counter').text()) + 1);
+					}
+				});
+			// check if the user is subscribed to the above channel
+			channel.bind('pusher:subscription_succeeded', function(members) {
+				organizrConsole('Plugin Function','Chat Websocket Connected!');
+				organizrConsole('Plugin Function','Connecting to Organizr Chat DB');
+				getMessagesAndUsers(activeInfo.settings.homepage.refresh["CHAT-userRefreshTimeout"], true);
+			});
+			/*jslint browser: true*/
+			/*global $, jQuery, alert*/
+			$(document).ready(function () {
+				"use strict";
+				$('.chat-left-inner > .chatonline').slimScroll({
+					height: '100%',
+					position: 'right',
+					size: "0px",
+					color: '#dcdcdc'
+				});
+				$('.chat-list').slimScroll({
+					height: '100%',
+					position: 'right',
+					size: "0px",
+					color: '#dcdcdc',
+					start: 'bottom',
+				});
+				$(".open-panel").on("click", function () {
+					$(".chat-left-aside").toggleClass("open-pnl");
+					$(".open-panel i").toggleClass("ti-angle-left");
+				});
+			});
+		}
+	}
 }
-$(document).on('click', '#CHAT-settings-button', function() {
-    ajaxloader(".content-wrap","in");
-	organizrAPI2('GET','api/v2/plugins/chat/settings').success(function(data) {
-        var response = data.response;
-        $('#CHAT-settings-items').html(buildFormGroup(response.data));
-    }).fail(function(xhr) {
-        console.error("Organizr Function: API Connection Failed");
-    });
-    ajaxloader();
-});
+
 //Chat functions!
 $(document).on('keypress', '.chat-input-send', function(ev) {
-    var keycode = (ev.keyCode ? ev.keyCode : ev.which);
-    if (keycode == '13') {
-        ev.preventDefault();
-        $('.custom-send-button').click();
-    }
+	var keycode = (ev.keyCode ? ev.keyCode : ev.which);
+	if (keycode == '13') {
+		ev.preventDefault();
+		$('.custom-send-button').click();
+	}
 });
 // Send the Message enter by User
 $('body').on('click', '.custom-send-button', function(e) {
-    e.preventDefault();
-    var message = $('.chat-input-send').val();
-    // Validate Name field
-    if (message !== '') {
-        organizrAPI2('POST','api/v2/plugins/chat/message',{ message : message }).success(function(data) {
-            // Nada yet
-        }).fail(function(xhr) {
-            console.error("Organizr Function: API Connection Failed");
-        });
-        // Clear the message input field
-        $('.chat-input-send').val('');
-        // Show a loading image while sending
-        $('.custom-send').html('<button type="button" class="btn btn-info btn-lg custom-send-button" disabled><i class="fa fa-spinner fa-pulse fa-2x"></i> </button>');
-    }
+	e.preventDefault();
+	var message = $('.chat-input-send').val();
+	// Validate Name field
+	if (message !== '') {
+		organizrAPI2('POST','api/v2/plugins/chat/message',{ message : message }).success(function(data) {
+			// Nada yet
+		}).fail(function(xhr) {
+			console.error("Organizr Function: API Connection Failed");
+		});
+		// Clear the message input field
+		$('.chat-input-send').val('');
+		// Show a loading image while sending
+		$('.custom-send').html('<button type="button" class="btn btn-info btn-lg custom-send-button" disabled><i class="fa fa-spinner fa-pulse fa-2x"></i> </button>');
+	}
 });
 function formatMessage(msg){
-    var className = 'odd';
-    if(msg.username == activeInfo.user.username){
-        if(activeInfo.user.username == 'Guest' && activeInfo.user.uid !== msg.uid){
-            className = '';
-        }
-    }else{
-        className = '';
-    }
-    return `
-        <li class="`+className+`">
-            <div class="chat-image"> <img alt="male" src="`+msg.gravatar+`"> </div>
-            <div class="chat-body">
-                <div class="chat-text">
-                    <h4>`+msg.username+`</h4>
-                    <p> `+msg.message+` </p> <b>`+moment.utc(msg.date, "YYYY-MM-DD hh:mm").local().format('LLL')+`</b> </div>
-            </div>
-        </li>
-    `;
+	var className = 'odd';
+	if(msg.username == activeInfo.user.username){
+		if(activeInfo.user.username == 'Guest' && activeInfo.user.uid !== msg.uid){
+			className = '';
+		}
+	}else{
+		className = '';
+	}
+	return `
+		<li class="`+className+`">
+			<div class="chat-image"> <img alt="male" src="`+msg.gravatar+`"> </div>
+			<div class="chat-body">
+				<div class="chat-text">
+					<h4>`+msg.username+`</h4>
+					<p> `+msg.message+` </p> <b>`+moment.utc(msg.date, "YYYY-MM-DD hh:mm").local().format('LLL')+`</b> </div>
+			</div>
+		</li>
+	`;
 }
 function formatUsers(array){
-    var users = {};
-    var userList = '';
-    array.reverse();
-    $.each(array, function (i, v){
-        if(!users.hasOwnProperty(v.username)){
-            users[v.username] = {
-                'last':v.date,
-                'gravatar':v.gravatar
-            }
-        }
-    });
-    $.each(users, function (i, v) {
-        userList += `
-            <li>
-                <a href="javascript:void(0)"><img src="`+v.gravatar+`" alt="user-img" class="img-circle"> <span>`+i+`<small class="text-success">`+moment.utc(v.last, "YYYY-MM-DD hh:mm[Z]").local().fromNow()+`</small></span></a>
-            </li>
-        `;
-    });
-    userList += '<li class="p-20"></li>';
-    return userList;
+	var users = {};
+	var userList = '';
+	array.reverse();
+	$.each(array, function (i, v){
+		if(!users.hasOwnProperty(v.username)){
+			users[v.username] = {
+				'last':v.date,
+				'gravatar':v.gravatar
+			}
+		}
+	});
+	$.each(users, function (i, v) {
+		userList += `
+			<li>
+				<a href="javascript:void(0)"><img src="`+v.gravatar+`" alt="user-img" class="img-circle"> <span>`+i+`<small class="text-success">`+moment.utc(v.last, "YYYY-MM-DD hh:mm[Z]").local().fromNow()+`</small></span></a>
+			</li>
+		`;
+	});
+	userList += '<li class="p-20"></li>';
+	return userList;
 }
 function chatEntry(){
-    $(".chat-list").scrollTop($(".chat-list")[0].scrollHeight);
-    $('.chat-input-send').focus();
-    $('.chat-counter').addClass('hidden').html('0');
+	$(".chat-list").scrollTop($(".chat-list")[0].scrollHeight);
+	$('.chat-input-send').focus();
+	$('.chat-counter').addClass('hidden').html('0');
 }
 function getMessagesAndUsers(timeout, initial = false){
-    var timeout = (typeof timeout !== 'undefined') ? timeout : activeInfo.settings.homepage.refresh["CHAT-userRefreshTimeout"];
-    organizrAPI2('GET','api/v2/plugins/chat/message').success(function(data) {
-        var response = data.response;
-        if(initial == true){
-            $.each(response.data, function (i, v){
-                $('.chat-list').append(formatMessage(v));
-            });
-        }
-        $('.chatonline').html(formatUsers(response.data));
-    }).fail(function(xhr) {
-        console.error("Organizr Function: API Connection Failed");
-    });
-    var timeoutTitle = 'ChatUserList';
-    if(typeof timeouts[timeoutTitle] !== 'undefined'){ clearTimeout(timeouts[timeoutTitle]); }
-    timeouts[timeoutTitle] = setTimeout(function(){ getMessagesAndUsers(timeout, false); }, timeout);
+	var timeout = (typeof timeout !== 'undefined') ? timeout : activeInfo.settings.homepage.refresh["CHAT-userRefreshTimeout"];
+	organizrAPI2('GET','api/v2/plugins/chat/message').success(function(data) {
+		var response = data.response;
+		if(initial == true){
+			$.each(response.data, function (i, v){
+				$('.chat-list').append(formatMessage(v));
+			});
+		}
+		$('.chatonline').html(formatUsers(response.data));
+	}).fail(function(xhr) {
+		console.error("Organizr Function: API Connection Failed");
+	});
+	var timeoutTitle = 'ChatUserList';
+	if(typeof timeouts[timeoutTitle] !== 'undefined'){ clearTimeout(timeouts[timeoutTitle]); }
+	timeouts[timeoutTitle] = setTimeout(function(){ getMessagesAndUsers(timeout, false); }, timeout);
 }
 $(document).on('click', '.profile-pic', function(e) {
-    $('.profile-image').removeClass('animated loop-animation rubberBand');
-});
+	$('.profile-image').removeClass('animated loop-animation rubberBand');
+});

+ 110 - 0
api/plugins/js/healthChecks-settings.js

@@ -0,0 +1,110 @@
+/* HEALTHCHECKS.IO JS FILE */
+
+// FUNCTIONS
+
+// EVENTS and LISTENERS
+
+// CHANGE CUSTOMIZE Options
+//
+$(document).on('click', '#HEALTHCHECKS-settings-button', function() {
+    ajaxloader(".content-wrap","in");
+    organizrAPI2('GET','api/v2/plugins/healthchecks/settings').success(function(data) {
+        var response = data.response;
+        $('#HEALTHCHECKS-settings-items').html(buildFormGroup(response.data));
+        var elAddButtonStart = $('#HEALTHCHECKS-settings-page [id*="Services"] .row.start');
+        var testone = $('#HEALTHCHECKS-settings-page [id*="Services"] .row.m-b-40').first('span')
+        var testtwo = $('#HEALTHCHECKS-settings-page [id*="Services"] .row.m-b-40 span')
+        $(elAddButtonStart).after('<div class="row"><button type="button" class="btn btn-info pull-right m-r-20 addNewHCService" ><i class="fa fa-plus"></i> Add New Service</button></div>');
+        $.each(testtwo, function(key,val) {
+            var el = $(val);
+            var text = el.text();
+            if(text === 'Service Name'){
+                $(this).after('&nbsp;<div class="pull-right text-danger removeHCService mouse"><i class="fa fa-close text-danger"></i></div>');
+            }
+        })
+
+    }).fail(function(xhr) {
+        console.error("Organizr Function: API Connection Failed");
+    });
+    ajaxloader();
+});
+$(document).on('click', '.addNewHCService', function() {
+    var lastEl = $('#HEALTHCHECKS-settings-page [name*="HEALTHCHECKS-all-items"]').last().attr('name');
+    var newNum = 0;
+    if(typeof lastEl !== 'undefined'){
+        lastEl = Number($('#HEALTHCHECKS-settings-page [name*="HEALTHCHECKS-all-items"]').last().attr('name').replace(/\D/g, ''));
+        newNum = lastEl + 1;
+    }
+    var copyEl = '' +
+        '<div class="row m-b-40">\n' +
+        '\t<!-- INPUT BOX  Yes Multiple -->\n' +
+        '\t<div class="col-md-6 p-b-10">\n' +
+        '\t\t<div class="form-group">\n' +
+        '\t\t\t<label class="control-label col-md-12"><span lang="en">Service Name</span>&nbsp;<div class="pull-right text-danger removeHCService mouse"><i class="fa fa-close text-danger"></i></div></label>\n' +
+        '\t\t\t<div class="col-md-12"> <input data-changed="false" lang="en" type="text" class="form-control" value="" name="HEALTHCHECKS-all-items[999999].name" data-type="input" data-label="Service Name" autocomplete="new-password"> </div> <!-- end div -->\n' +
+        '\t\t</div>\n' +
+        '\t</div>\n' +
+        '\t<!--/ INPUT BOX -->\n' +
+        '\n' +
+        '\t<!-- INPUT BOX  Yes Multiple -->\n' +
+        '\t<div class="col-md-6 p-b-10">\n' +
+        '\t\t<div class="form-group">\n' +
+        '\t\t\t<label class="control-label col-md-12"><span lang="en">UUID</span></label>\n' +
+        '\t\t\t<div class="col-md-12"> <input data-changed="false" lang="en" type="text" class="form-control" value="" name="HEALTHCHECKS-all-items[999999].uuid" data-type="input" data-label="UUID" autocomplete="new-password"> </div> <!-- end div -->\n' +
+        '\t\t</div>\n' +
+        '\t</div>\n' +
+        '\t<!--/ INPUT BOX -->\n' +
+        '\n' +
+        '\t<!-- INPUT BOX  Yes Multiple -->\n' +
+        '\t<div class="col-md-6 p-b-10">\n' +
+        '\t\t<div class="form-group">\n' +
+        '\t\t\t<label class="control-label col-md-12"><span lang="en">External URL</span></label>\n' +
+        '\t\t\t<div class="col-md-12"> <input data-changed="false" lang="en" type="text" class="form-control" value="" name="HEALTHCHECKS-all-items[999999].external" data-type="input" data-label="External URL" autocomplete="new-password"> </div> <!-- end div -->\n' +
+        '\t\t</div>\n' +
+        '\t</div>\n' +
+        '\t<!--/ INPUT BOX -->\n' +
+        '\n' +
+        '\t<!-- INPUT BOX  Yes Multiple -->\n' +
+        '\t<div class="col-md-6 p-b-10">\n' +
+        '\t\t<div class="form-group">\n' +
+        '\t\t\t<label class="control-label col-md-12"><span lang="en">Internal URL</span></label>\n' +
+        '\t\t\t<div class="col-md-12"> <input data-changed="false" lang="en" type="text" class="form-control" value="" name="HEALTHCHECKS-all-items[999999].internal" data-type="input" data-label="Internal URL" autocomplete="new-password"> </div> <!-- end div -->\n' +
+        '\t\t</div>\n' +
+        '\t</div>\n' +
+        '\t<!--/ INPUT BOX -->\n' +
+        '\n' +
+        '\t<!-- INPUT BOX  Yes Multiple -->\n' +
+        '\t<div class="col-md-6 p-b-10">\n' +
+        '\t\t<div class="form-group">\n' +
+        '\t\t\t<label class="control-label col-md-12"><span lang="en">Enabled</span></label>\n' +
+        '\t\t\t<div class="col-md-12"> <input data-changed="false" type="checkbox" class="js-switch" data-size="small" data-color="#99d683" data-secondary-color="#f96262" name="HEALTHCHECKS-all-items[999999].enabled" value="" checked="" data-type="switch" data-label="Enabled"><input data-changed="false" type="hidden" name="HEALTHCHECKS-all-items[999999].enabled" value=""> </div> <!-- end div -->\n' +
+        '\t\t</div>\n' +
+        '\t</div>\n' +
+        '\t<!--/ INPUT BOX -->\n' +
+        '</div>'
+//smallLabel+'<input data-changed="false" type="checkbox" class="js-switch'+extraClass+'" data-size="small" data-color="#99d683" data-secondary-color="#f96262"'+name+value+tof(item.value,'c')+id+disabled+type+label+attr+' /><input data-changed="false" type="hidden"'+name+'value="false">';
+    var elAddButtonStart = $('#HEALTHCHECKS-settings-page [id*="Services"] .row.start');
+    var copiedEl = $(copyEl).clone();
+    copiedEl.find("input").each(function() {
+        var currentName = $(this).attr("name");
+        var newName = currentName.replace('999999', newNum);
+        $(this).attr("name", newName);
+        $(this).attr("value", "");
+    });
+    $(copiedEl).appendTo(elAddButtonStart);
+    $(function () {
+        // Switchery
+        var elems = Array.prototype.slice.call(document.querySelectorAll('.js-switch'));
+        $('.js-switch').each(function() {
+            if ($(this).attr('data-switchery') !== 'true'){
+                new Switchery($(this)[0], $(this).data());
+            }
+        });
+    });
+
+});
+
+$(document).on('click', '.removeHCService', function() {
+    $(this).closest('.row').remove();
+    $('#HEALTHCHECKS-settings-page-save').removeClass('hidden');
+});

+ 1 - 110
api/plugins/js/healthChecks.js

@@ -1,110 +1 @@
-/* HEALTHCHECKS.IO JS FILE */
-
-// FUNCTIONS
-
-// EVENTS and LISTENERS
-
-// CHANGE CUSTOMIZE Options
-//
-$(document).on('click', '#HEALTHCHECKS-settings-button', function() {
-    ajaxloader(".content-wrap","in");
-    organizrAPI2('GET','api/v2/plugins/healthchecks/settings').success(function(data) {
-        var response = data.response;
-        $('#HEALTHCHECKS-settings-items').html(buildFormGroup(response.data));
-        var elAddButtonStart = $('#HEALTHCHECKS-settings-page [id*="Services"] .row.start');
-        var testone = $('#HEALTHCHECKS-settings-page [id*="Services"] .row.m-b-40').first('span')
-        var testtwo = $('#HEALTHCHECKS-settings-page [id*="Services"] .row.m-b-40 span')
-        $(elAddButtonStart).after('<div class="row"><button type="button" class="btn btn-info pull-right m-r-20 addNewHCService" ><i class="fa fa-plus"></i> Add New Service</button></div>');
-        $.each(testtwo, function(key,val) {
-            var el = $(val);
-            var text = el.text();
-            if(text === 'Service Name'){
-                $(this).after('&nbsp;<div class="pull-right text-danger removeHCService mouse"><i class="fa fa-close text-danger"></i></div>');
-            }
-        })
-
-    }).fail(function(xhr) {
-        console.error("Organizr Function: API Connection Failed");
-    });
-    ajaxloader();
-});
-$(document).on('click', '.addNewHCService', function() {
-    var lastEl = $('#HEALTHCHECKS-settings-page [name*="HEALTHCHECKS-all-items"]').last().attr('name');
-    var newNum = 0;
-    if(typeof lastEl !== 'undefined'){
-        lastEl = Number($('#HEALTHCHECKS-settings-page [name*="HEALTHCHECKS-all-items"]').last().attr('name').replace(/\D/g, ''));
-        newNum = lastEl + 1;
-    }
-    var copyEl = '' +
-        '<div class="row m-b-40">\n' +
-        '\t<!-- INPUT BOX  Yes Multiple -->\n' +
-        '\t<div class="col-md-6 p-b-10">\n' +
-        '\t\t<div class="form-group">\n' +
-        '\t\t\t<label class="control-label col-md-12"><span lang="en">Service Name</span>&nbsp;<div class="pull-right text-danger removeHCService mouse"><i class="fa fa-close text-danger"></i></div></label>\n' +
-        '\t\t\t<div class="col-md-12"> <input data-changed="false" lang="en" type="text" class="form-control" value="" name="HEALTHCHECKS-all-items[999999].name" data-type="input" data-label="Service Name" autocomplete="new-password"> </div> <!-- end div -->\n' +
-        '\t\t</div>\n' +
-        '\t</div>\n' +
-        '\t<!--/ INPUT BOX -->\n' +
-        '\n' +
-        '\t<!-- INPUT BOX  Yes Multiple -->\n' +
-        '\t<div class="col-md-6 p-b-10">\n' +
-        '\t\t<div class="form-group">\n' +
-        '\t\t\t<label class="control-label col-md-12"><span lang="en">UUID</span></label>\n' +
-        '\t\t\t<div class="col-md-12"> <input data-changed="false" lang="en" type="text" class="form-control" value="" name="HEALTHCHECKS-all-items[999999].uuid" data-type="input" data-label="UUID" autocomplete="new-password"> </div> <!-- end div -->\n' +
-        '\t\t</div>\n' +
-        '\t</div>\n' +
-        '\t<!--/ INPUT BOX -->\n' +
-        '\n' +
-        '\t<!-- INPUT BOX  Yes Multiple -->\n' +
-        '\t<div class="col-md-6 p-b-10">\n' +
-        '\t\t<div class="form-group">\n' +
-        '\t\t\t<label class="control-label col-md-12"><span lang="en">External URL</span></label>\n' +
-        '\t\t\t<div class="col-md-12"> <input data-changed="false" lang="en" type="text" class="form-control" value="" name="HEALTHCHECKS-all-items[999999].external" data-type="input" data-label="External URL" autocomplete="new-password"> </div> <!-- end div -->\n' +
-        '\t\t</div>\n' +
-        '\t</div>\n' +
-        '\t<!--/ INPUT BOX -->\n' +
-        '\n' +
-        '\t<!-- INPUT BOX  Yes Multiple -->\n' +
-        '\t<div class="col-md-6 p-b-10">\n' +
-        '\t\t<div class="form-group">\n' +
-        '\t\t\t<label class="control-label col-md-12"><span lang="en">Internal URL</span></label>\n' +
-        '\t\t\t<div class="col-md-12"> <input data-changed="false" lang="en" type="text" class="form-control" value="" name="HEALTHCHECKS-all-items[999999].internal" data-type="input" data-label="Internal URL" autocomplete="new-password"> </div> <!-- end div -->\n' +
-        '\t\t</div>\n' +
-        '\t</div>\n' +
-        '\t<!--/ INPUT BOX -->\n' +
-        '\n' +
-        '\t<!-- INPUT BOX  Yes Multiple -->\n' +
-        '\t<div class="col-md-6 p-b-10">\n' +
-        '\t\t<div class="form-group">\n' +
-        '\t\t\t<label class="control-label col-md-12"><span lang="en">Enabled</span></label>\n' +
-        '\t\t\t<div class="col-md-12"> <input data-changed="false" type="checkbox" class="js-switch" data-size="small" data-color="#99d683" data-secondary-color="#f96262" name="HEALTHCHECKS-all-items[999999].enabled" value="" checked="" data-type="switch" data-label="Enabled"><input data-changed="false" type="hidden" name="HEALTHCHECKS-all-items[999999].enabled" value=""> </div> <!-- end div -->\n' +
-        '\t\t</div>\n' +
-        '\t</div>\n' +
-        '\t<!--/ INPUT BOX -->\n' +
-        '</div>'
-//smallLabel+'<input data-changed="false" type="checkbox" class="js-switch'+extraClass+'" data-size="small" data-color="#99d683" data-secondary-color="#f96262"'+name+value+tof(item.value,'c')+id+disabled+type+label+attr+' /><input data-changed="false" type="hidden"'+name+'value="false">';
-    var elAddButtonStart = $('#HEALTHCHECKS-settings-page [id*="Services"] .row.start');
-    var copiedEl = $(copyEl).clone();
-    copiedEl.find("input").each(function() {
-        var currentName = $(this).attr("name");
-        var newName = currentName.replace('999999', newNum);
-        $(this).attr("name", newName);
-        $(this).attr("value", "");
-    });
-    $(copiedEl).appendTo(elAddButtonStart);
-    $(function () {
-        // Switchery
-        var elems = Array.prototype.slice.call(document.querySelectorAll('.js-switch'));
-        $('.js-switch').each(function() {
-            if ($(this).attr('data-switchery') !== 'true'){
-                new Switchery($(this)[0], $(this).data());
-            }
-        });
-    });
-
-});
-
-$(document).on('click', '.removeHCService', function() {
-    $(this).closest('.row').remove();
-    $('#HEALTHCHECKS-settings-page-save').removeClass('hidden');
-});
+/* HEALTHCHECKS.IO JS FILE IS NO LONGER NEEDED OR USED */

+ 397 - 421
api/plugins/js/invites.js

@@ -1,231 +1,223 @@
 /* INVITES JS FILE */
+$('body').arrive('#activeInfo', {onceOnly: true}, function() {
+	inviteLaunch();
+});
 // FUNCTIONS
-inviteLaunch()
 function inviteLaunch(){
-    if(typeof activeInfo == 'undefined'){
-        setTimeout(function () {
-            inviteLaunch();
-        }, 1000);
-    }else{
-        var menuList = '';
-    	var htmlDOM = `
-    	<div id="invite-area" class="white-popup mfp-with-anim mfp-hide">
-    		<div class="col-md-10 col-md-offset-1">
-    			<div class="invite-div"></div>
-    		</div>
-    	</div>
-    	`;
-        if(activeInfo.plugins["INVITES-enabled"] == true){
-            if (activeInfo.user.loggedin === true && activeInfo.user.groupID <= 1) {
-                menuList = `<li><a class="inline-popups inviteModal" href="#invite-area" data-effect="mfp-zoom-out"><i class="fa fa-ticket fa-fw"></i> <span lang="en">Manage Invites</span></a></li>`;
-                htmlDOM += `
-            	<div id="new-invite-area" class="white-popup mfp-with-anim mfp-hide">
-            		<div class="col-md-10 col-md-offset-1">
-                        <div class="col-md-12">
-                            <div class="panel panel-info m-b-0">
-                                <div class="panel-heading" lang="en">New Invite</div>
-                                <div class="panel-wrapper collapse in" aria-expanded="true">
-                                    <div class="panel-body">
-
-                                        <form id="new-invite-form">
-                                            <fieldset style="border:0;">
-                                            <div class="form-group">
-                                                <label class="control-label" for="new-invite-form-inputUsername" lang="en">Name or Username</label>
-                                                <input type="text" class="form-control" id="new-invite-form-inputUsername" name="username" required="" autofocus="">
-                                            </div>
-                                            <div class="form-group">
-                                                <label class="control-label" for="new-invite-form-inputEmail" lang="en">Email</label>
-                                                <input type="text" class="form-control" id="new-invite-form-inputEmail" name="email" required="" autofocus="">
-                                            </div>
-                                            </fieldset>
-                                            <button class="btn btn-sm btn-info btn-rounded waves-effect waves-light pull-right row b-none" onclick="createNewInvite();" type="button"><span class="btn-label"><i class="fa fa-plus"></i></span><span lang="en">Create/Send Invite</span></button>
-                                            <div class="clearfix"></div>
-                                        </form>
-
-
-
-                                    </div>
-                                </div>
-                            </div>
-                        </div>
-                        <div class="clearfix"></div>
-            		</div>
-            	</div>`;
-            }else if (activeInfo.user.loggedin === false){
-                menuList = `<li><a class="inline-popups inviteModal" href="#invite-area" data-effect="mfp-zoom-out"><i class="fa fa-ticket fa-fw"></i> <span lang="en">Use Invite Code</span></a></li>`;
-            }
-            $('.append-menu').after(menuList);
-            $('.organizr-area').after(htmlDOM);
-            pageLoad();
-            getInvite();
-        }
-    }
+	var menuList = '';
+	var htmlDOM = `
+	<div id="invite-area" class="white-popup mfp-with-anim mfp-hide">
+		<div class="col-md-10 col-md-offset-1">
+			<div class="invite-div"></div>
+		</div>
+	</div>
+	`;
+	if(activeInfo.plugins["INVITES-enabled"] == true){
+		if (activeInfo.user.loggedin === true && activeInfo.user.groupID <= 1) {
+			menuList = `<li><a class="inline-popups inviteModal" href="#invite-area" data-effect="mfp-zoom-out"><i class="fa fa-ticket fa-fw"></i> <span lang="en">Manage Invites</span></a></li>`;
+			htmlDOM += `
+			<div id="new-invite-area" class="white-popup mfp-with-anim mfp-hide">
+				<div class="col-md-10 col-md-offset-1">
+					<div class="col-md-12">
+						<div class="panel panel-info m-b-0">
+							<div class="panel-heading" lang="en">New Invite</div>
+							<div class="panel-wrapper collapse in" aria-expanded="true">
+								<div class="panel-body">
+									<form id="new-invite-form">
+										<fieldset style="border:0;">
+										<div class="form-group">
+											<label class="control-label" for="new-invite-form-inputUsername" lang="en">Name or Username</label>
+											<input type="text" class="form-control" id="new-invite-form-inputUsername" name="username" required="" autofocus="">
+										</div>
+										<div class="form-group">
+											<label class="control-label" for="new-invite-form-inputEmail" lang="en">Email</label>
+											<input type="text" class="form-control" id="new-invite-form-inputEmail" name="email" required="" autofocus="">
+										</div>
+										</fieldset>
+										<button class="btn btn-sm btn-info btn-rounded waves-effect waves-light pull-right row b-none" onclick="createNewInvite();" type="button"><span class="btn-label"><i class="fa fa-plus"></i></span><span lang="en">Create/Send Invite</span></button>
+										<div class="clearfix"></div>
+									</form>
+								</div>
+							</div>
+						</div>
+					</div>
+					<div class="clearfix"></div>
+				</div>
+			</div>`;
+		}else if (activeInfo.user.loggedin === false){
+			menuList = `<li><a class="inline-popups inviteModal" href="#invite-area" data-effect="mfp-zoom-out"><i class="fa fa-ticket fa-fw"></i> <span lang="en">Use Invite Code</span></a></li>`;
+		}
+		$('.append-menu').after(menuList);
+		$('.organizr-area').after(htmlDOM);
+		pageLoad();
+		getInvite();
+	}
 }
 function joinPlex(){
-    var username = $('#invitePlexJoinUsername');
-    var email = $('#invitePlexJoinEmail');
-    var password = $('#invitePlexJoinPassword');
-    if(username.val() == ''){
-        username.focus();
-        message('Invite Error',' Please Enter Username',activeInfo.settings.notifications.position,'#FFF','warning','5000');
-    }else if(email.val() == ''){
-        email.focus();
-        message('Invite Error',' Please Enter Email',activeInfo.settings.notifications.position,'#FFF','warning','5000');
-    }else if(password.val() == ''){
-        password.focus();
-        message('Invite Error',' Please Enter Password',activeInfo.settings.notifications.position,'#FFF','warning','5000');
-    }
-    if(email.val() !== '' && username.val() !== '' && password.val() !== ''){
-        organizrAPI2('POST','api/v2/plex/register',{username:username.val(), email:email.val(), password:password.val()}).success(function(data) {
-    		var response = data.response;
-            if(response.result === 'success'){
-                $('.invite-step-3-plex-no').toggleClass('hidden');
-                $('.invite-step-3-plex-yes').toggleClass('hidden');
-                message('Invite Function',' User Created',activeInfo.settings.notifications.position,'#FFF','success','5000');
-                $('#inviteUsernameInvite').val(username.val());
-                hasPlexUsername();
-            }else{
-                message('Invite Error',' '+response.message,activeInfo.settings.notifications.position,'#FFF','warning','5000');
-            }
-    	}).fail(function(xhr) {
-	        OrganizrApiError(xhr, 'Plex Signup Error');
-    	});
-    }
+	var username = $('#invitePlexJoinUsername');
+	var email = $('#invitePlexJoinEmail');
+	var password = $('#invitePlexJoinPassword');
+	if(username.val() == ''){
+		username.focus();
+		message('Invite Error',' Please Enter Username',activeInfo.settings.notifications.position,'#FFF','warning','5000');
+	}else if(email.val() == ''){
+		email.focus();
+		message('Invite Error',' Please Enter Email',activeInfo.settings.notifications.position,'#FFF','warning','5000');
+	}else if(password.val() == ''){
+		password.focus();
+		message('Invite Error',' Please Enter Password',activeInfo.settings.notifications.position,'#FFF','warning','5000');
+	}
+	if(email.val() !== '' && username.val() !== '' && password.val() !== ''){
+		organizrAPI2('POST','api/v2/plex/register',{username:username.val(), email:email.val(), password:password.val()}).success(function(data) {
+			var response = data.response;
+			if(response.result === 'success'){
+				$('.invite-step-3-plex-no').toggleClass('hidden');
+				$('.invite-step-3-plex-yes').toggleClass('hidden');
+				message('Invite Function',' User Created',activeInfo.settings.notifications.position,'#FFF','success','5000');
+				$('#inviteUsernameInvite').val(username.val());
+				hasPlexUsername();
+			}else{
+				message('Invite Error',' '+response.message,activeInfo.settings.notifications.position,'#FFF','warning','5000');
+			}
+		}).fail(function(xhr) {
+			OrganizrApiError(xhr, 'Plex Signup Error');
+		});
+	}
 }
 
 function joinEmby(){
-    var username = $('#inviteEmbyJoinUsername');
-    var email = $('#inviteEmbyJoinEmail');
-    var password = $('#inviteEmbyJoinPassword');
-    if(username.val() == ''){
-        username.focus();
-        message('Invite Error',' Please Enter Username',activeInfo.settings.notifications.position,'#FFF','warning','5000');
-    }else if(email.val() == ''){
-        email.focus();
-        message('Invite Error',' Please Enter Email',activeInfo.settings.notifications.position,'#FFF','warning','5000');
-    }else if(password.val() == ''){
-        password.focus();
-        message('Invite Error',' Please Enter Password',activeInfo.settings.notifications.position,'#FFF','warning','5000');
-    }
-    if(email.val() !== '' && username.val() !== '' && password.val() !== ''){
-        organizrAPI2('POST','api/v2/emby/register',{username:username.val(), email:email.val(), password:password.val()}).success(function(data) {
-    		var response = data.response;
-            if(response.result === 'success'){
-                $('.invite-step-3-emby-no').toggleClass('hidden');
-                $('.invite-step-3-emby-yes').toggleClass('hidden');
-                message('Invite Function',' User Created',activeInfo.settings.notifications.position,'#FFF','success','5000');
-                $('#inviteUsernameInviteEmby').val(username.val());
-                hasEmbyUsername();
-            }else{
-                message('Invite Error',' '+response.message,activeInfo.settings.notifications.position,'#FFF','warning','5000');
-            }
-    	}).fail(function(xhr) {
-	        OrganizrApiError(xhr, 'Emby Signup Error');
-    	});
-    }
+	var username = $('#inviteEmbyJoinUsername');
+	var email = $('#inviteEmbyJoinEmail');
+	var password = $('#inviteEmbyJoinPassword');
+	if(username.val() == ''){
+		username.focus();
+		message('Invite Error',' Please Enter Username',activeInfo.settings.notifications.position,'#FFF','warning','5000');
+	}else if(email.val() == ''){
+		email.focus();
+		message('Invite Error',' Please Enter Email',activeInfo.settings.notifications.position,'#FFF','warning','5000');
+	}else if(password.val() == ''){
+		password.focus();
+		message('Invite Error',' Please Enter Password',activeInfo.settings.notifications.position,'#FFF','warning','5000');
+	}
+	if(email.val() !== '' && username.val() !== '' && password.val() !== ''){
+		organizrAPI2('POST','api/v2/emby/register',{username:username.val(), email:email.val(), password:password.val()}).success(function(data) {
+			var response = data.response;
+			if(response.result === 'success'){
+				$('.invite-step-3-emby-no').toggleClass('hidden');
+				$('.invite-step-3-emby-yes').toggleClass('hidden');
+				message('Invite Function',' User Created',activeInfo.settings.notifications.position,'#FFF','success','5000');
+				$('#inviteUsernameInviteEmby').val(username.val());
+				hasEmbyUsername();
+			}else{
+				message('Invite Error',' '+response.message,activeInfo.settings.notifications.position,'#FFF','warning','5000');
+			}
+		}).fail(function(xhr) {
+			OrganizrApiError(xhr, 'Emby Signup Error');
+		});
+	}
 }
 
 function inviteHasAccount(type,value){
-    switch (type) {
-        case 'plex':
-            if(value){
-                $('.invite-step-2').toggleClass('hidden');
-                $('.invite-step-3-plex-yes').toggleClass('hidden');
-            }else{
-                $('.invite-step-2').toggleClass('hidden');
-                $('.invite-step-3-plex-no').toggleClass('hidden');
-            }
-            break;
-        case 'emby' :
-          if(value){
-            $('.invite-step-2').toggleClass('hidden');
-            $('.invite-step-3-emby-yes').toggleClass('hidden');
-          }else{
-            $('.invite-step-2').toggleClass('hidden');
-            $('.invite-step-3-emby-no').toggleClass('hidden');
-          }
-          break;
-        default:
-        alert(type+' is not set up yet');
-    }
+	switch (type) {
+		case 'plex':
+			if(value){
+				$('.invite-step-2').toggleClass('hidden');
+				$('.invite-step-3-plex-yes').toggleClass('hidden');
+			}else{
+				$('.invite-step-2').toggleClass('hidden');
+				$('.invite-step-3-plex-no').toggleClass('hidden');
+			}
+			break;
+		case 'emby' :
+		  if(value){
+			$('.invite-step-2').toggleClass('hidden');
+			$('.invite-step-3-emby-yes').toggleClass('hidden');
+		  }else{
+			$('.invite-step-2').toggleClass('hidden');
+			$('.invite-step-3-emby-no').toggleClass('hidden');
+		  }
+		  break;
+		default:
+		alert(type+' is not set up yet');
+	}
 }
 function hasPlexUsername(){
-    var code = $('#inviteCodeInput').val().toUpperCase();
-    var username = $('#inviteUsernameInvite');
-    if(username.val() == ''){
-        username.focus();
-        message('Invite Error',' Please Enter Username',activeInfo.settings.notifications.position,'#FFF','warning','5000');
-    }else{
-        var post = {
-            usedby:username.val()
-        };
-        ajaxloader(".content-wrap","in");
-        organizrAPI2('POST','api/v2/plugins/invites/' + code,post).success(function(data) {
-            var response = data.response;
-            if(response.result === 'success'){
-                $('.invite-step-3-plex-yes').toggleClass('hidden');
-                $('.invite-step-4-plex-accept').toggleClass('hidden');
-                if(local('get', 'invite')){
-            		local('remove', 'invite');
-            	}
-            }else{
-                message('Invite Error',response.message,activeInfo.settings.notifications.position,'#FFF','warning','5000');
-            }
-            ajaxloader();;
-        }).fail(function(xhr) {
-	        OrganizrApiError(xhr);
-            ajaxloader();
-        });
-    }
+	var code = $('#inviteCodeInput').val().toUpperCase();
+	var username = $('#inviteUsernameInvite');
+	if(username.val() == ''){
+		username.focus();
+		message('Invite Error',' Please Enter Username',activeInfo.settings.notifications.position,'#FFF','warning','5000');
+	}else{
+		var post = {
+			usedby:username.val()
+		};
+		ajaxloader(".content-wrap","in");
+		organizrAPI2('POST','api/v2/plugins/invites/' + code,post).success(function(data) {
+			var response = data.response;
+			if(response.result === 'success'){
+				$('.invite-step-3-plex-yes').toggleClass('hidden');
+				$('.invite-step-4-plex-accept').toggleClass('hidden');
+				if(local('get', 'invite')){
+					local('remove', 'invite');
+				}
+			}else{
+				message('Invite Error',response.message,activeInfo.settings.notifications.position,'#FFF','warning','5000');
+			}
+			ajaxloader();;
+		}).fail(function(xhr) {
+			OrganizrApiError(xhr);
+			ajaxloader();
+		});
+	}
 }
 function hasEmbyUsername(){
-    var code = $('#inviteCodeInput').val().toUpperCase();
-    var username = $('#inviteUsernameInviteEmby');
-    if(username.val() == ''){
-        username.focus();
-        message('Invite Error',' Please Enter Username',activeInfo.settings.notifications.position,'#FFF','warning','5000');
-    }else{
-        var post = {
-            usedby:username.val()
-        };
-        ajaxloader(".content-wrap","in");
-        organizrAPI2('POST','api/v2/plugins/invites/' + code,post).success(function(data) {
-	        var response = data.response;
-	        if(response.result === 'success'){
-                $('.invite-step-3-emby-yes').toggleClass('hidden');
-                $('.invite-step-4-emby-accept').toggleClass('hidden');
-                if(local('get', 'invite')){
-            		local('remove', 'invite');
-            	}
-            }else{
-                message('Invite Error',response.message,activeInfo.settings.notifications.position,'#FFF','warning','5000');
-            }
-            ajaxloader();;
-        }).fail(function(xhr) {
-	        OrganizrApiError(xhr);
-            ajaxloader();
-        });
-    }
+	var code = $('#inviteCodeInput').val().toUpperCase();
+	var username = $('#inviteUsernameInviteEmby');
+	if(username.val() == ''){
+		username.focus();
+		message('Invite Error',' Please Enter Username',activeInfo.settings.notifications.position,'#FFF','warning','5000');
+	}else{
+		var post = {
+			usedby:username.val()
+		};
+		ajaxloader(".content-wrap","in");
+		organizrAPI2('POST','api/v2/plugins/invites/' + code,post).success(function(data) {
+			var response = data.response;
+			if(response.result === 'success'){
+				$('.invite-step-3-emby-yes').toggleClass('hidden');
+				$('.invite-step-4-emby-accept').toggleClass('hidden');
+				if(local('get', 'invite')){
+					local('remove', 'invite');
+				}
+			}else{
+				message('Invite Error',response.message,activeInfo.settings.notifications.position,'#FFF','warning','5000');
+			}
+			ajaxloader();;
+		}).fail(function(xhr) {
+			OrganizrApiError(xhr);
+			ajaxloader();
+		});
+	}
 }
 function verifyInvite(){
-    var code = $('#inviteCodeInput').val().toUpperCase();
-    ajaxloader(".content-wrap","in");
-    organizrAPI2('GET','api/v2/plugins/invites/'+code).success(function(data) {
-        var response = data.response;
-        if(response.result === 'success'){
-            $('.invite-step-1').toggleClass('hidden');
-            $('.invite-step-2').toggleClass('hidden');
-        }else{
-            message('Invite Error',response.message,activeInfo.settings.notifications.position,'#FFF','warning','5000');
-        }
-        if(local('get', 'invite')){
-            local('remove', 'invite');
-        }
-        ajaxloader();;
-    }).fail(function(xhr) {
-	    OrganizrApiError(xhr);
-        ajaxloader();
-    });
+	var code = $('#inviteCodeInput').val().toUpperCase();
+	ajaxloader(".content-wrap","in");
+	organizrAPI2('GET','api/v2/plugins/invites/'+code).success(function(data) {
+		var response = data.response;
+		if(response.result === 'success'){
+			$('.invite-step-1').toggleClass('hidden');
+			$('.invite-step-2').toggleClass('hidden');
+		}else{
+			message('Invite Error',response.message,activeInfo.settings.notifications.position,'#FFF','warning','5000');
+		}
+		if(local('get', 'invite')){
+			local('remove', 'invite');
+		}
+		ajaxloader();;
+	}).fail(function(xhr) {
+		OrganizrApiError(xhr);
+		ajaxloader();
+	});
 }
 function getInvite(invite=null){
 	if(invite){
@@ -237,232 +229,216 @@ function getInvite(invite=null){
 	if(local('get', 'invite')){
 		//show error page
 		$('.inviteModal').trigger('click');
-        $('#inviteCodeInput').val(local('get', 'invite'));
+		$('#inviteCodeInput').val(local('get', 'invite'));
 		window.history.pushState({}, document.title, "./" );
-        local('remove', 'invite');
+		local('remove', 'invite');
 	}
 
 }
 function createNewInvite(){
-    var username = $('#new-invite-form-inputUsername');
-    var email = $('#new-invite-form-inputEmail');
-    if(username.val() == ''){
-        username.focus();
-        message('Invite Error',' Please Enter Username',activeInfo.settings.notifications.position,'#FFF','warning','5000');
-    }else if(email.val() == ''){
-        email.focus();
-        message('Invite Error',' Please Enter Email',activeInfo.settings.notifications.position,'#FFF','warning','5000');
-    }
+	var username = $('#new-invite-form-inputUsername');
+	var email = $('#new-invite-form-inputEmail');
+	if(username.val() == ''){
+		username.focus();
+		message('Invite Error',' Please Enter Username',activeInfo.settings.notifications.position,'#FFF','warning','5000');
+	}else if(email.val() == ''){
+		email.focus();
+		message('Invite Error',' Please Enter Email',activeInfo.settings.notifications.position,'#FFF','warning','5000');
+	}
 
-    if(email.val() !== '' && username.val() !== ''){
-        var post = {
-            code:createRandomString(6).toUpperCase(),
-            email:email.val(),
-            username:username.val(),
-        };
-        ajaxloader(".content-wrap","in");
-        organizrAPI2('POST','api/v2/plugins/invites',post).success(function(data) {
-            var response = data.response;
-            $.magnificPopup.close();
-            ajaxloader();
-            message('Invite',' Invite Created',activeInfo.settings.notifications.position,'#FFF','success','5000');
-        }).fail(function(xhr) {
-	        OrganizrApiError(xhr);
-            ajaxloader();
-            message('Invite Error',' An Error Occured',activeInfo.settings.notifications.position,'#FFF','error','5000');
-        });
-    }
+	if(email.val() !== '' && username.val() !== ''){
+		var post = {
+			code:createRandomString(6).toUpperCase(),
+			email:email.val(),
+			username:username.val(),
+		};
+		ajaxloader(".content-wrap","in");
+		organizrAPI2('POST','api/v2/plugins/invites',post).success(function(data) {
+			var response = data.response;
+			$.magnificPopup.close();
+			ajaxloader();
+			message('Invite',' Invite Created',activeInfo.settings.notifications.position,'#FFF','success','5000');
+		}).fail(function(xhr) {
+			OrganizrApiError(xhr);
+			ajaxloader();
+			message('Invite Error',' An Error Occured',activeInfo.settings.notifications.position,'#FFF','error','5000');
+		});
+	}
 
 }
 function deleteInvite(code, id){
-    ajaxloader(".content-wrap","in");
-    organizrAPI2('DELETE','api/v2/plugins/invites/' + code).success(function(data) {
-        var response = data.response;
-        $('#inviteItem-'+id).remove();
-        //$.magnificPopup.close();
-        ajaxloader();
-        message('Invite',' Invite Deleted',activeInfo.settings.notifications.position,'#FFF','success','5000');
-    }).fail(function(xhr) {
-        console.error("Organizr Function: API Connection Failed");
-        ajaxloader();
-        message('Invite Error',' An Error Occured',activeInfo.settings.notifications.position,'#FFF','error','5000');
-    });
+	ajaxloader(".content-wrap","in");
+	organizrAPI2('DELETE','api/v2/plugins/invites/' + code).success(function(data) {
+		var response = data.response;
+		$('#inviteItem-'+id).remove();
+		//$.magnificPopup.close();
+		ajaxloader();
+		message('Invite',' Invite Deleted',activeInfo.settings.notifications.position,'#FFF','success','5000');
+	}).fail(function(xhr) {
+		console.error("Organizr Function: API Connection Failed");
+		ajaxloader();
+		message('Invite Error',' An Error Occured',activeInfo.settings.notifications.position,'#FFF','error','5000');
+	});
 
 }
 // EVENTS and LISTENERS
 function buildInvites(array){
-    if(array.length == 0){
+	if(array.length == 0){
 		return '<h2 class="text-center" lang="en">No Invites</h2>';
 	}
-    var invites = '';
+	var invites = '';
 	$.each(array, function(i,v) {
-        v.dateused = (v.dateused) ? v.dateused : '-';
-        v.usedby = (v.usedby) ? v.usedby : '-';
-        v.ip = (v.ip) ? v.ip : '-';
-        invites += `
-        <tr id="inviteItem-`+v.id+`">
-            <td class="text-center">`+v.id+`</td>
-            <td>`+v.username+`</td>
-            <td>`+v.email+`</td>
-            <td>`+v.code+`</td>
-            <td>`+v.date+`</td>
-            <td>`+v.dateused+`</td>
-            <td>`+v.usedby+`</td>
-            <td>`+v.ip+`</td>
-            <td>`+v.valid+`</td>
-            <td><button type="button" class="btn btn-danger btn-outline btn-circle btn-lg m-r-5" onclick="deleteInvite('`+v.code+`','`+v.id+`');"><i class="ti-trash"></i></button></td>
-        </tr>
-        `;
-    });
-    return invites;
+		v.dateused = (v.dateused) ? v.dateused : '-';
+		v.usedby = (v.usedby) ? v.usedby : '-';
+		v.ip = (v.ip) ? v.ip : '-';
+		invites += `
+		<tr id="inviteItem-`+v.id+`">
+			<td class="text-center">`+v.id+`</td>
+			<td>`+v.username+`</td>
+			<td>`+v.email+`</td>
+			<td>`+v.code+`</td>
+			<td>`+v.date+`</td>
+			<td>`+v.dateused+`</td>
+			<td>`+v.usedby+`</td>
+			<td>`+v.ip+`</td>
+			<td>`+v.valid+`</td>
+			<td><button type="button" class="btn btn-danger btn-outline btn-circle btn-lg m-r-5" onclick="deleteInvite('`+v.code+`','`+v.id+`');"><i class="ti-trash"></i></button></td>
+		</tr>
+		`;
+	});
+	return invites;
 }
 $(document).on('click', '.inviteModal', function() {
-    var htmlDOM = '';
-    if (activeInfo.user.loggedin === true && activeInfo.user.groupID <= 1) {
-        ajaxloader(".content-wrap","in");
-        organizrAPI2('GET','api/v2/plugins/invites').success(function(data) {
-            var response = data.response;
-            var htmlDOM = '';
-            htmlDOM = `
-            <div class="col-md-12">
-                <div class="panel bg-org panel-info">
-                    <div class="panel-heading">
-                        <span lang="en">Manage Invites</span>
-                        <button type="button" class="btn btn-info btn-circle pull-right popup-with-form" href="#new-invite-area" data-effect="mfp-3d-unfold"><i class="fa fa-plus"></i> </button>
-                    </div>
-                    <div class="table-responsive">
-                        <table class="table table-hover manage-u-table">
-                            <thead>
-                                <tr>
-                                    <th width="70" class="text-center">#</th>
-                                    <th lang="en">USERNAME</th>
-                                    <th lang="en">EMAIL</th>
-                                    <th lang="en">INVITE CODE</th>
-                                    <th lang="en">DATE SENT</th>
-                                    <th lang="en">DATE USED</th>
-                                    <th lang="en">USED BY</th>
-                                    <th lang="en">IP ADDRESS</th>
-                                    <th lang="en">VALID</th>
-                                    <th lang="en">DELETE</th>
-                                </tr>
-                            </thead>
-                            <tbody id="manageInviteTable">
-                                `+buildInvites(response.data)+`
-                            </tbody>
-                        </table>
-                    </div>
-                </div>
-            </div>
-            <div class="clearfix"></div>
-            `;
-            $('.invite-div').html(htmlDOM);
-        }).fail(function(xhr) {
-            console.error("Organizr Function: API Connection Failed");
-        });
-        ajaxloader();
-    }else if (activeInfo.user.loggedin === false){
-        htmlDOM = `
-        <div class="col-md-12">
-            <div class="panel panel-info m-b-0">
-                <div class="panel-heading" lang="en">Use Invite Code</div>
-                <div class="panel-wrapper collapse in" aria-expanded="true">
-                    <div class="panel-body">
-                        <div class="form-group invite-step-1">
-                            <div class="input-group" style="width: 100%;">
-                                <div class="input-group-addon hidden-xs"><i class="ti-lock"></i></div>
-                                <input type="text" class="form-control text-uppercase" id="inviteCodeInput" placeholder="Code" autocomplete="off" autocorrect="off" autocapitalize="off" maxlength="6" spellcheck="false" autofocus="" required="">
-                            </div>
-                            <br />
-                            <button class="btn btn-block btn-info" onclick="verifyInvite();">Verify</button>
-
-                        </div>
-                        <div class="form-group invite-step-2 hidden">
-
-
-                            <div class="row">
-                                <h2 class="text-center" lang="en">Do you have a `+activeInfo.plugins.includes["INVITES-type-include"].toUpperCase()+` account?</h2>
-                                <div class="col-lg-6">
-                                    <button class="btn btn-block btn-info m-b-10" onclick="inviteHasAccount('`+activeInfo.plugins.includes["INVITES-type-include"]+`',true);" lang="en">Yes</button>
-                                </div>
-                                <div class="col-lg-6">
-                                    <button class="btn btn-block btn-primary m-b-10" onclick="inviteHasAccount('`+activeInfo.plugins.includes["INVITES-type-include"]+`',false);" lang="en">No</button>
-                                </div>
-                            </div>
-
-                        </div>
-                        <div class="form-group invite-step-3-plex-yes hidden">
-                            <div class="input-group" style="width: 100%;">
-                                <div class="input-group-addon hidden-xs"><i class="ti-user"></i></div>
-                                <input type="text" class="form-control" id="inviteUsernameInvite" placeholder="Plex Username or Email" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" autofocus="" required="">
-                            </div>
-                            <br />
-                            <button class="btn btn-block btn-info" onclick="hasPlexUsername();">Submit</button>
-                        </div>
-                        <div class="form-group invite-step-3-plex-no hidden">
-                            <div class="input-group" style="width: 100%;">
-                                <div class="input-group-addon hidden-xs"><i class="ti-user"></i></div>
-                                <input type="text" class="form-control" id="invitePlexJoinUsername" lang="en" placeholder="Username" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" autofocus="" required="">
-                            </div>
-                            <div class="input-group" style="width: 100%;">
-                                <div class="input-group-addon hidden-xs"><i class="ti-email"></i></div>
-                                <input type="text" class="form-control" id="invitePlexJoinEmail" lang="en" placeholder="E-Mail" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" required="">
-                            </div>
-                            <div class="input-group" style="width: 100%;">
-                                <div class="input-group-addon hidden-xs"><i class="ti-user"></i></div>
-                                <input type="password" class="form-control" id="invitePlexJoinPassword" lang="en" placeholder="Password" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false"  required="">
-                            </div>
-                            <br />
-                            <button class="btn btn-block btn-info" onclick="joinPlex();">Submit</button>
-                        </div>
-                        <div class="form-group invite-step-4-plex-accept hidden">
-                            <h4 class="" lang="en">You have been invited.  Please check your email or goto <a href="https://plex.tv" target="_blank">PLEX.TV</a> and login to accept the invite.  Once you have done that, you may head back here and login with your credentials.</h4>
-                        </div>
-                        <!-- Begin Emby Invites -->
-                        <div class="form-group invite-step-3-emby-yes hidden">
-                            <div class="input-group" style="width: 100%;">
-                                <div class="input-group-addon hidden-xs"><i class="ti-user"></i></div>
-                                <input type="text" class="form-control" id="inviteUsernameInviteEmby" placeholder="Emby Username" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" autofocus="" required="">
-                            </div>
-                            <br />
-                            <button class="btn btn-block btn-info" onclick="hasEmbyUsername();">Submit</button>
-                        </div>
-                        <div class="form-group invite-step-3-emby-no hidden">
-                            <div class="input-group" style="width: 100%;">
-                                <div class="input-group-addon hidden-xs"><i class="ti-user"></i></div>
-                                <input type="text" class="form-control" id="inviteEmbyJoinUsername" lang="en" placeholder="Username" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" autofocus="" required="">
-                            </div>
-                            <div class="input-group" style="width: 100%;">
-                                <div class="input-group-addon hidden-xs"><i class="ti-email"></i></div>
-                                <input type="text" class="form-control" id="inviteEmbyJoinEmail" lang="en" placeholder="E-Mail" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" required="">
-                            </div>
-                            <div class="input-group" style="width: 100%;">
-                                <div class="input-group-addon hidden-xs"><i class="ti-user"></i></div>
-                                <input type="password" class="form-control" id="inviteEmbyJoinPassword" lang="en" placeholder="Password" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false"  required="">
-                            </div>
-                            <br />
-                            <button class="btn btn-block btn-info" onclick="joinEmby();">Submit</button>
-                        </div>
-                        <div class="form-group invite-step-4-emby-accept hidden">
-                            <h4 class="" lang="en">You Have been added to emby!</h4>
-                        </div>
-                    </div>
-                </div>
-            </div>
-        </div>
-        <div class="clearfix"></div>
-        `;
-        $('.invite-div').html(htmlDOM);
-    }
-});
-
-$(document).on('click', '#INVITES-settings-button', function() {
-    ajaxloader(".content-wrap","in");
-    organizrAPI2('GET','api/v2/plugins/invites/settings').success(function(data) {
-        var response = data.response;
-        $('#INVITES-settings-items').html(buildFormGroup(response.data));
-        $('.selectpicker').selectpicker();
-    }).fail(function(xhr) {
-        console.error("Organizr Function: API Connection Failed");
-    });
-    ajaxloader();
+	var htmlDOM = '';
+	if (activeInfo.user.loggedin === true && activeInfo.user.groupID <= 1) {
+		ajaxloader(".content-wrap","in");
+		organizrAPI2('GET','api/v2/plugins/invites').success(function(data) {
+			var response = data.response;
+			var htmlDOM = '';
+			htmlDOM = `
+			<div class="col-md-12">
+				<div class="panel bg-org panel-info">
+					<div class="panel-heading">
+						<span lang="en">Manage Invites</span>
+						<button type="button" class="btn btn-info btn-circle pull-right popup-with-form" href="#new-invite-area" data-effect="mfp-3d-unfold"><i class="fa fa-plus"></i> </button>
+					</div>
+					<div class="table-responsive">
+						<table class="table table-hover manage-u-table">
+							<thead>
+								<tr>
+									<th width="70" class="text-center">#</th>
+									<th lang="en">USERNAME</th>
+									<th lang="en">EMAIL</th>
+									<th lang="en">INVITE CODE</th>
+									<th lang="en">DATE SENT</th>
+									<th lang="en">DATE USED</th>
+									<th lang="en">USED BY</th>
+									<th lang="en">IP ADDRESS</th>
+									<th lang="en">VALID</th>
+									<th lang="en">DELETE</th>
+								</tr>
+							</thead>
+							<tbody id="manageInviteTable">
+								`+buildInvites(response.data)+`
+							</tbody>
+						</table>
+					</div>
+				</div>
+			</div>
+			<div class="clearfix"></div>
+			`;
+			$('.invite-div').html(htmlDOM);
+		}).fail(function(xhr) {
+			console.error("Organizr Function: API Connection Failed");
+		});
+		ajaxloader();
+	}else if (activeInfo.user.loggedin === false){
+		htmlDOM = `
+		<div class="col-md-12">
+			<div class="panel panel-info m-b-0">
+				<div class="panel-heading" lang="en">Use Invite Code</div>
+				<div class="panel-wrapper collapse in" aria-expanded="true">
+					<div class="panel-body">
+						<div class="form-group invite-step-1">
+							<div class="input-group" style="width: 100%;">
+								<div class="input-group-addon hidden-xs"><i class="ti-lock"></i></div>
+								<input type="text" class="form-control text-uppercase" id="inviteCodeInput" placeholder="Code" autocomplete="off" autocorrect="off" autocapitalize="off" maxlength="6" spellcheck="false" autofocus="" required="">
+							</div>
+							<br />
+							<button class="btn btn-block btn-info" onclick="verifyInvite();">Verify</button>
+						</div>
+						<div class="form-group invite-step-2 hidden">
+							<div class="row">
+								<h2 class="text-center" lang="en">Do you have a `+activeInfo.plugins.includes["INVITES-type-include"].toUpperCase()+` account?</h2>
+								<div class="col-lg-6">
+									<button class="btn btn-block btn-info m-b-10" onclick="inviteHasAccount('`+activeInfo.plugins.includes["INVITES-type-include"]+`',true);" lang="en">Yes</button>
+								</div>
+								<div class="col-lg-6">
+									<button class="btn btn-block btn-primary m-b-10" onclick="inviteHasAccount('`+activeInfo.plugins.includes["INVITES-type-include"]+`',false);" lang="en">No</button>
+								</div>
+							</div>
+						</div>
+						<div class="form-group invite-step-3-plex-yes hidden">
+							<div class="input-group" style="width: 100%;">
+								<div class="input-group-addon hidden-xs"><i class="ti-user"></i></div>
+								<input type="text" class="form-control" id="inviteUsernameInvite" placeholder="Plex Username or Email" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" autofocus="" required="">
+							</div>
+							<br />
+							<button class="btn btn-block btn-info" onclick="hasPlexUsername();">Submit</button>
+						</div>
+						<div class="form-group invite-step-3-plex-no hidden">
+							<div class="input-group" style="width: 100%;">
+								<div class="input-group-addon hidden-xs"><i class="ti-user"></i></div>
+								<input type="text" class="form-control" id="invitePlexJoinUsername" lang="en" placeholder="Username" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" autofocus="" required="">
+							</div>
+							<div class="input-group" style="width: 100%;">
+								<div class="input-group-addon hidden-xs"><i class="ti-email"></i></div>
+								<input type="text" class="form-control" id="invitePlexJoinEmail" lang="en" placeholder="E-Mail" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" required="">
+							</div>
+							<div class="input-group" style="width: 100%;">
+								<div class="input-group-addon hidden-xs"><i class="ti-user"></i></div>
+								<input type="password" class="form-control" id="invitePlexJoinPassword" lang="en" placeholder="Password" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false"  required="">
+							</div>
+							<br />
+							<button class="btn btn-block btn-info" onclick="joinPlex();">Submit</button>
+						</div>
+						<div class="form-group invite-step-4-plex-accept hidden">
+							<h4 class="" lang="en">You have been invited.  Please check your email or goto <a href="https://plex.tv" target="_blank">PLEX.TV</a> and login to accept the invite.  Once you have done that, you may head back here and login with your credentials.</h4>
+						</div>
+						<!-- Begin Emby Invites -->
+						<div class="form-group invite-step-3-emby-yes hidden">
+							<div class="input-group" style="width: 100%;">
+								<div class="input-group-addon hidden-xs"><i class="ti-user"></i></div>
+								<input type="text" class="form-control" id="inviteUsernameInviteEmby" placeholder="Emby Username" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" autofocus="" required="">
+							</div>
+							<br />
+							<button class="btn btn-block btn-info" onclick="hasEmbyUsername();">Submit</button>
+						</div>
+						<div class="form-group invite-step-3-emby-no hidden">
+							<div class="input-group" style="width: 100%;">
+								<div class="input-group-addon hidden-xs"><i class="ti-user"></i></div>
+								<input type="text" class="form-control" id="inviteEmbyJoinUsername" lang="en" placeholder="Username" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" autofocus="" required="">
+							</div>
+							<div class="input-group" style="width: 100%;">
+								<div class="input-group-addon hidden-xs"><i class="ti-email"></i></div>
+								<input type="text" class="form-control" id="inviteEmbyJoinEmail" lang="en" placeholder="E-Mail" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" required="">
+							</div>
+							<div class="input-group" style="width: 100%;">
+								<div class="input-group-addon hidden-xs"><i class="ti-user"></i></div>
+								<input type="password" class="form-control" id="inviteEmbyJoinPassword" lang="en" placeholder="Password" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false"  required="">
+							</div>
+							<br />
+							<button class="btn btn-block btn-info" onclick="joinEmby();">Submit</button>
+						</div>
+						<div class="form-group invite-step-4-emby-accept hidden">
+							<h4 class="" lang="en">You Have been added to emby!</h4>
+						</div>
+					</div>
+				</div>
+			</div>
+		</div>
+		<div class="clearfix"></div>
+		`;
+		$('.invite-div').html(htmlDOM);
+	}
 });

+ 205 - 223
api/plugins/js/php-mailer.js

@@ -1,262 +1,244 @@
 /* PHP MAILER JS FILE */
-
+$('body').arrive('#activeInfo', {onceOnly: true}, function() {
+	phpmLaunch();
+});
 // FUNCTIONS
-phpmLaunch();
+
 function phpmLaunch(){
-    if(typeof activeInfo == 'undefined'){
-        setTimeout(function () {
-            phpmLaunch();
-        }, 1000);
-    }else{
-        if(activeInfo.plugins["PHPMAILER-enabled"] == true){
-            if (activeInfo.user.loggedin === true && activeInfo.user.groupID <= 1) {
-                var menuList = `<li><a class="inline-popups emailModal" href="#email-area" data-effect="mfp-zoom-out"><i class="fa fa-envelope fa-fw"></i> <span lang="en">E-Mail Center</span></a></li>`;
-                var htmlDOM = `
-            	<div id="email-area" class="white-popup mfp-with-anim mfp-hide">
-            		<div class="col-md-10 col-md-offset-1">
-            			<div class="email-div"></div>
-            		</div>
-            	</div>
-            	`;
-                $('.organizr-area').after(htmlDOM);
-                $('.append-menu').after(menuList);
-                pageLoad();
-            }
-        }
-    }
+	if(activeInfo.plugins["PHPMAILER-enabled"] == true){
+		if (activeInfo.user.loggedin === true && activeInfo.user.groupID <= 1) {
+			var menuList = `<li><a class="inline-popups emailModal" href="#email-area" data-effect="mfp-zoom-out"><i class="fa fa-envelope fa-fw"></i> <span lang="en">E-Mail Center</span></a></li>`;
+			var htmlDOM = `
+			<div id="email-area" class="white-popup mfp-with-anim mfp-hide">
+				<div class="col-md-10 col-md-offset-1">
+					<div class="email-div"></div>
+				</div>
+			</div>
+			`;
+			$('.organizr-area').after(htmlDOM);
+			$('.append-menu').after(menuList);
+			pageLoad();
+		}
+	}
+
 }
 function sendMail(){
-    var to = $('#sendEmailToInput').val();
-    var subject = $('#sendEmailSubjectInput').val();
-    var body = tinyMCE.get('sendEmail').getContent();
-    if(to == ''){
-        messageSingle('','Please Enter Email',activeInfo.settings.notifications.position,'#FFF','error','5000');
-    }else if(subject == ''){
-        messageSingle('','Please Enter Subject',activeInfo.settings.notifications.position,'#FFF','error','5000');
-    }else if(body == ''){
-        messageSingle('','Please Enter Body',activeInfo.settings.notifications.position,'#FFF','error','5000');
-    }else{
-	    messageSingle('','Sending Message',activeInfo.settings.notifications.position,'#FFF','success','5000');
-    }
-    if(to !== '' && subject !== '' && body !== ''){
-        var post = {
-            bcc:to,
-            subject:subject,
-            body:body
-        };
-        ajaxloader(".content-wrap","in");
-        organizrAPI2('POST','api/v2/plugins/php-mailer/email/send',post).success(function(data) {
-            var response = data.response;
-            if(response.result == 'success'){
-                $.magnificPopup.close();
-                messageSingle('',window.lang.translate('Email Sent Successful'),activeInfo.settings.notifications.position,'#FFF','success','5000');
-            }else{
-                messageSingle('',response.message,activeInfo.settings.notifications.position,'#FFF','error','5000');
-            }
-        }).fail(function(xhr) {
-	        OrganizrApiError(xhr);
-        });
-        ajaxloader();
-    }
+	var to = $('#sendEmailToInput').val();
+	var subject = $('#sendEmailSubjectInput').val();
+	var body = tinyMCE.get('sendEmail').getContent();
+	if(to == ''){
+		messageSingle('','Please Enter Email',activeInfo.settings.notifications.position,'#FFF','error','5000');
+	}else if(subject == ''){
+		messageSingle('','Please Enter Subject',activeInfo.settings.notifications.position,'#FFF','error','5000');
+	}else if(body == ''){
+		messageSingle('','Please Enter Body',activeInfo.settings.notifications.position,'#FFF','error','5000');
+	}else{
+		messageSingle('','Sending Message',activeInfo.settings.notifications.position,'#FFF','success','5000');
+	}
+	if(to !== '' && subject !== '' && body !== ''){
+		var post = {
+			bcc:to,
+			subject:subject,
+			body:body
+		};
+		ajaxloader(".content-wrap","in");
+		organizrAPI2('POST','api/v2/plugins/php-mailer/email/send',post).success(function(data) {
+			var response = data.response;
+			if(response.result == 'success'){
+				$.magnificPopup.close();
+				messageSingle('',window.lang.translate('Email Sent Successful'),activeInfo.settings.notifications.position,'#FFF','success','5000');
+			}else{
+				messageSingle('',response.message,activeInfo.settings.notifications.position,'#FFF','error','5000');
+			}
+		}).fail(function(xhr) {
+			OrganizrApiError(xhr);
+		});
+		ajaxloader();
+	}
 }
 function buildUserList(array){
-    var users = '';
-    var htmlDOM = '';
+	var users = '';
+	var htmlDOM = '';
 	$.each(array, function(i,v) {
-        users += '<option value="'+v+'">'+i+'</option>';
-    });
-    htmlDOM = `
-    <select multiple id="email-user-list" name="email-user-list[]">`+users+`</select>
-    <div class="button-box m-t-20">
-        <a id="select-all-users-list" class="btn btn-danger btn-outline" href="#">select all</a>
-        <a id="deselect-all-users-list" class="btn btn-info btn-outline" href="#">deselect all</a>
-        <a id="minimize-users-list" class="btn btn-primary btn-outline" href="#">minimize</a>
-    </div>`;
-    return htmlDOM;
+		users += '<option value="'+v+'">'+i+'</option>';
+	});
+	htmlDOM = `
+	<select multiple id="email-user-list" name="email-user-list[]">`+users+`</select>
+	<div class="button-box m-t-20">
+		<a id="select-all-users-list" class="btn btn-danger btn-outline" href="#">select all</a>
+		<a id="deselect-all-users-list" class="btn btn-info btn-outline" href="#">deselect all</a>
+		<a id="minimize-users-list" class="btn btn-primary btn-outline" href="#">minimize</a>
+	</div>`;
+	return htmlDOM;
 }
 function buildEmailModal(){
-    var htmlDOM = `
-    <div class="row">
-        <div class="col-md-12">
-            <div class="panel panel-info m-0">
-                <div class="panel-heading">
-                    <span lang="en">Email Users</span>
-                    <div class="btn-group pull-right">
-
+	var htmlDOM = `
+	<div class="row">
+		<div class="col-md-12">
+			<div class="panel panel-info m-0">
+				<div class="panel-heading">
+					<span lang="en">Email Users</span>
+					<div class="btn-group pull-right">
 						<button class="btn btn-info waves-effect waves-light loadUserList" type="button">
 							<i class="fa fa-user"></i>
 						</button>
-                        <button class="btn btn-info waves-effect waves-light" type="button" onclick="$('.mce-i-template').trigger('click');">
+						<button class="btn btn-info waves-effect waves-light" type="button" onclick="$('.mce-i-template').trigger('click');">
 							<i class="fa fa-files-o"></i>
 						</button>
-                        <button class="btn btn-info waves-effect waves-light unhide-user-list hidden" type="button">
+						<button class="btn btn-info waves-effect waves-light unhide-user-list hidden" type="button">
 							<i class="fa fa-eye"></i>
 						</button>
 						<button class="btn btn-info waves-effect waves-light" onclick="sendMail();"><i class="fa fa-paper-plane"></i></button>
-
-	                </div>
-                </div>
-                <div class="panel-wrapper collapse in main-email-panel" aria-expanded="true">
-                    <div class="panel-body">
-                        <div class="form-body">
-                            <div class="row">
-                                <div class="col-md-6">
-                                    <div class="form-group">
-                                        <label class="control-label" lang="en">To:</label>
-                                        <input type="text" id="sendEmailToInput" class="form-control"></div>
-                                </div>
-                                <div class="col-md-6">
-                                    <div class="form-group">
-                                        <label class="control-label" lang="en">Subject</label>
-                                        <input type="text" id="sendEmailSubjectInput" class="form-control"></div>
-                                </div>
-                                <div class="col-md-12" id="user-list-div">
-
-
-                                </div>
-                            </div>
-                            <!--/row-->
-                        </div>
-                    </div>
-                </div>
-            </div>
-        </div>
-    </div>
-    <textarea id="sendEmail" name="area"></textarea>
-    `;
-    $('.email-div').html(htmlDOM);
-    if ($("#sendEmail").length > 0) {
-        var templates = [];
-        if(activeInfo.plugins.includes["PHPMAILER-emailTemplateCustom-include-One"] !== ''){
-            templates.push(
-                {
-                    title: activeInfo.plugins.includes["PHPMAILER-emailTemplateCustom-include-OneName"],
-                    description: activeInfo.plugins.includes["PHPMAILER-emailTemplateCustom-include-OneSubject"],
-                    content: activeInfo.plugins.includes["PHPMAILER-emailTemplateCustom-include-One"],
-                }
-            )
-        }
-        if(activeInfo.plugins.includes["PHPMAILER-emailTemplateCustom-include-Two"] !== ''){
-            templates.push(
-                {
-                    title: activeInfo.plugins.includes["PHPMAILER-emailTemplateCustom-include-TwoName"],
-                    description: activeInfo.plugins.includes["PHPMAILER-emailTemplateCustom-include-TwoSubject"],
-                    content: activeInfo.plugins.includes["PHPMAILER-emailTemplateCustom-include-Two"],
-                }
-            )
-        }
-        if(activeInfo.plugins.includes["PHPMAILER-emailTemplateCustom-include-Three"] !== ''){
-            templates.push(
-                {
-                    title: activeInfo.plugins.includes["PHPMAILER-emailTemplateCustom-include-ThreeName"],
-                    description: activeInfo.plugins.includes["PHPMAILER-emailTemplateCustom-include-ThreeSubject"],
-                    content: activeInfo.plugins.includes["PHPMAILER-emailTemplateCustom-include-Three"],
-                }
-            )
-        }
-        if(activeInfo.plugins.includes["PHPMAILER-emailTemplateCustom-include-Four"] !== ''){
-            templates.push(
-                {
-                    title: activeInfo.plugins.includes["PHPMAILER-emailTemplateCustom-include-FourName"],
-                    description: activeInfo.plugins.includes["PHPMAILER-emailTemplateCustom-include-FourSubject"],
-                    content: activeInfo.plugins.includes["PHPMAILER-emailTemplateCustom-include-Four"],
-                }
-            )
-        }
-        tinymce.init({
-            selector: "textarea#sendEmail",
-            theme: "modern",
-            height: 300,
-            plugins: [
-                "advlist autolink link image lists charmap print preview hr anchor pagebreak spellchecker", "searchreplace wordcount visualblocks visualchars code fullscreen insertdatetime media nonbreaking", "save table contextmenu directionality emoticons template paste textcolor"
-            ],
-            toolbar: "insertfile template undo redo | styleselect | bold italic | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | link image | print preview media fullpage | forecolor backcolor",
-            templates: templates,
-            init_instance_callback: function (editor) {
-                editor.on('BeforeSetContent', function (e) {
-                    //tinyMCE.get('sendEmail').execCommand('selectAll');
-                    //tinyMCE.get('sendEmail').execCommand('delete');
-                    $.each(e.target.settings.templates, function(i,v) {
-                        if($.trim(v.content) == $.trim(e.content)){
-                            $('#sendEmailSubjectInput').val(v.description);
-                        }
-                    });
-                });
-              }
-        });
-    }
+					</div>
+				</div>
+				<div class="panel-wrapper collapse in main-email-panel" aria-expanded="true">
+					<div class="panel-body">
+						<div class="form-body">
+							<div class="row">
+								<div class="col-md-6">
+									<div class="form-group">
+										<label class="control-label" lang="en">To:</label>
+										<input type="text" id="sendEmailToInput" class="form-control"></div>
+								</div>
+								<div class="col-md-6">
+									<div class="form-group">
+										<label class="control-label" lang="en">Subject</label>
+										<input type="text" id="sendEmailSubjectInput" class="form-control"></div>
+								</div>
+								<div class="col-md-12" id="user-list-div"></div>
+							</div>
+							<!--/row-->
+						</div>
+					</div>
+				</div>
+			</div>
+		</div>
+	</div>
+	<textarea id="sendEmail" name="area"></textarea>
+	`;
+	$('.email-div').html(htmlDOM);
+	if ($("#sendEmail").length > 0) {
+		var templates = [];
+		if(activeInfo.plugins.includes["PHPMAILER-emailTemplateCustom-include-One"] !== ''){
+			templates.push(
+				{
+					title: activeInfo.plugins.includes["PHPMAILER-emailTemplateCustom-include-OneName"],
+					description: activeInfo.plugins.includes["PHPMAILER-emailTemplateCustom-include-OneSubject"],
+					content: activeInfo.plugins.includes["PHPMAILER-emailTemplateCustom-include-One"],
+				}
+			)
+		}
+		if(activeInfo.plugins.includes["PHPMAILER-emailTemplateCustom-include-Two"] !== ''){
+			templates.push(
+				{
+					title: activeInfo.plugins.includes["PHPMAILER-emailTemplateCustom-include-TwoName"],
+					description: activeInfo.plugins.includes["PHPMAILER-emailTemplateCustom-include-TwoSubject"],
+					content: activeInfo.plugins.includes["PHPMAILER-emailTemplateCustom-include-Two"],
+				}
+			)
+		}
+		if(activeInfo.plugins.includes["PHPMAILER-emailTemplateCustom-include-Three"] !== ''){
+			templates.push(
+				{
+					title: activeInfo.plugins.includes["PHPMAILER-emailTemplateCustom-include-ThreeName"],
+					description: activeInfo.plugins.includes["PHPMAILER-emailTemplateCustom-include-ThreeSubject"],
+					content: activeInfo.plugins.includes["PHPMAILER-emailTemplateCustom-include-Three"],
+				}
+			)
+		}
+		if(activeInfo.plugins.includes["PHPMAILER-emailTemplateCustom-include-Four"] !== ''){
+			templates.push(
+				{
+					title: activeInfo.plugins.includes["PHPMAILER-emailTemplateCustom-include-FourName"],
+					description: activeInfo.plugins.includes["PHPMAILER-emailTemplateCustom-include-FourSubject"],
+					content: activeInfo.plugins.includes["PHPMAILER-emailTemplateCustom-include-Four"],
+				}
+			)
+		}
+		tinymce.init({
+			selector: "textarea#sendEmail",
+			theme: "modern",
+			height: 300,
+			plugins: [
+				"advlist autolink link image lists charmap print preview hr anchor pagebreak spellchecker", "searchreplace wordcount visualblocks visualchars code fullscreen insertdatetime media nonbreaking", "save table contextmenu directionality emoticons template paste textcolor"
+			],
+			toolbar: "insertfile template undo redo | styleselect | bold italic | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | link image | print preview media fullpage | forecolor backcolor",
+			templates: templates,
+			init_instance_callback: function (editor) {
+				editor.on('BeforeSetContent', function (e) {
+					//tinyMCE.get('sendEmail').execCommand('selectAll');
+					//tinyMCE.get('sendEmail').execCommand('delete');
+					$.each(e.target.settings.templates, function(i,v) {
+						if($.trim(v.content) == $.trim(e.content)){
+							$('#sendEmailSubjectInput').val(v.description);
+						}
+					});
+				});
+			  }
+		});
+	}
 
 }
 // EVENTS and LISTENERS
 $(document).on("change", "#email-user-list", function () {
-    $('#sendEmailToInput').val($('#email-user-list').val());
+	$('#sendEmailToInput').val($('#email-user-list').val());
 });
 $(document).on('click', '.loadUserList', function() {
-    ajaxloader(".content-wrap","in");
-    organizrAPI2('GET','api/v2/plugins/php-mailer/email/list').success(function(data) {
-        var response = data.response;
-        $('#user-list-div').html(buildUserList(response.data));
-        $('#email-user-list').multiSelect();
-    }).fail(function(xhr) {
-	    OrganizrApiError(xhr);
-    });
-    ajaxloader();
+	ajaxloader(".content-wrap","in");
+	organizrAPI2('GET','api/v2/plugins/php-mailer/email/list').success(function(data) {
+		var response = data.response;
+		$('#user-list-div').html(buildUserList(response.data));
+		$('#email-user-list').multiSelect();
+	}).fail(function(xhr) {
+		OrganizrApiError(xhr);
+	});
+	ajaxloader();
 });
 $(document).on("click", ".emailModal", function(e) {
-    buildEmailModal();
+	buildEmailModal();
 });
 $(document).on("click", ".show-login", function(e) {
-    setTimeout(addForgotPassword, 1000);
+	setTimeout(addForgotPassword, 1000);
 });
 $(document).on("click", "#select-all-users-list", function(e) {
-    $('#email-user-list').multiSelect('select_all');
-    return false;
+	$('#email-user-list').multiSelect('select_all');
+	return false;
 });
 $(document).on("click", "#deselect-all-users-list", function(e) {
-    $('#email-user-list').multiSelect('deselect_all');
-    return false;
+	$('#email-user-list').multiSelect('deselect_all');
+	return false;
 });
 $(document).on("click", "#minimize-users-list, .unhide-user-list", function(e) {
-    $('.main-email-panel').toggleClass('hidden');
-    $('.loadUserList').toggleClass('hidden');
-    $('.unhide-user-list').toggleClass('hidden');
-    return false;
+	$('.main-email-panel').toggleClass('hidden');
+	$('.loadUserList').toggleClass('hidden');
+	$('.unhide-user-list').toggleClass('hidden');
+	return false;
 });
 function addForgotPassword(){
-    var item = '';
-    if(activeInfo.plugins["PHPMAILER-enabled"] == true){
-        if (activeInfo.user.loggedin === false) {
-            item = `<a href="javascript:void(0)" id="to-recover" class="text-dark pull-right"><i class="fa fa-lock m-r-5"></i> <span lang="en">Forgot pwd?</span></a>`;
-            $('.remember-me').after(item);
-        }
-    }
+	var item = '';
+	if(activeInfo.plugins["PHPMAILER-enabled"] == true){
+		if (activeInfo.user.loggedin === false) {
+			item = `<a href="javascript:void(0)" id="to-recover" class="text-dark pull-right"><i class="fa fa-lock m-r-5"></i> <span lang="en">Forgot pwd?</span></a>`;
+			$('.remember-me').after(item);
+		}
+	}
 }
-$(document).on('click', '#PHPMAILER-settings-button', function() {
-    ajaxloader(".content-wrap","in");
-    organizrAPI2('GET','api/v2/plugins/php-mailer/settings').success(function(data) {
-        var response = data.response;
-        $('#PHPMAILER-settings-items').html(buildFormGroup(response.data));
-    }).fail(function(xhr) {
-	    OrganizrApiError(xhr);
-    });
-    ajaxloader();
-});
 // SEND TEST EMAIL
 $(document).on('click', '.phpmSendTestEmail', function() {
-    messageSingle('',window.lang.translate('Sending Test E-Mail'),activeInfo.settings.notifications.position,'#FFF','info','5000');
-    ajaxloader(".content-wrap","in");
-    organizrAPI2('GET','api/v2/plugins/php-mailer/email/test').success(function(data) {
-        var response = data.response;
-        if(response.message !== null && response.message.indexOf('|||DEBUG|||') == 0){
-            messageSingle('',window.lang.translate('Press F11 to check Console for output'),activeInfo.settings.notifications.position,'#FFF','warning','5000');
-	        console.warn(response.message);
-        }else if(response.result == 'success') {
-            messageSingle('',window.lang.translate('Email Test Successful'),activeInfo.settings.notifications.position,'#FFF','success','20000');
-        }else{
-            messageSingle('',response.message,activeInfo.settings.notifications.position,'#FFF','error','5000');
-        }
-    }).fail(function(xhr, data) {
-	    OrganizrApiError(xhr, 'Mailer Error');
-    });
-    ajaxloader();
-});
+	messageSingle('',window.lang.translate('Sending Test E-Mail'),activeInfo.settings.notifications.position,'#FFF','info','5000');
+	ajaxloader(".content-wrap","in");
+	organizrAPI2('GET','api/v2/plugins/php-mailer/email/test').success(function(data) {
+		var response = data.response;
+		if(response.message !== null && response.message.indexOf('|||DEBUG|||') == 0){
+			messageSingle('',window.lang.translate('Press F11 to check Console for output'),activeInfo.settings.notifications.position,'#FFF','warning','5000');
+			console.warn(response.message);
+		}else if(response.result == 'success') {
+			messageSingle('',window.lang.translate('Email Test Successful'),activeInfo.settings.notifications.position,'#FFF','success','20000');
+		}else{
+			messageSingle('',response.message,activeInfo.settings.notifications.position,'#FFF','error','5000');
+		}
+	}).fail(function(xhr, data) {
+		OrganizrApiError(xhr, 'Mailer Error');
+	});
+	ajaxloader();
+});

+ 57 - 72
api/plugins/js/speedTest.js

@@ -1,4 +1,7 @@
 /* SPEEDTEST JS FILE */
+$('body').arrive('#activeInfo', {onceOnly: true}, function() {
+	speedTestLaunch();
+});
 function clamp(num, min, max) {
   return num <= min ? min : num >= max ? max : num;
 }
@@ -56,82 +59,64 @@ function initUI(){
 	$('#uploadPercent').attr('class', 'css-bar css-bar-0 css-bar-lg css-bar-warning pull-right').attr('data-label', '0Mbps');
 }
 // FUNCTIONS
-speedTestLaunch()
 function speedTestLaunch(){
-    if(typeof activeInfo == 'undefined'){
-        setTimeout(function () {
-            speedTestLaunch();
-        }, 1000);
-    }else{
-        if(activeInfo.plugins["SPEEDTEST-enabled"] == true){
-            if (activeInfo.user.groupID <= activeInfo.plugins.includes["SPEEDTEST-Auth-include"]) {
-                var menuList = `<li><a class="inline-popups speedTestModal" href="#speedtest-area" data-effect="mfp-zoom-out"><i class="fa fa-rocket fa-fw"></i> <span lang="en">Test Server Speed</span></a></li>`;
-				var htmlDOM = `
-		    	<div id="speedtest-area" class="white-popup mfp-with-anim mfp-hide">
-		    		<div class="col-md-4 col-md-offset-4">
-						<div class="panel bg-org panel-info">
-							<div class="panel-heading">
-								<span lang="en">Test Speed to Server</span>
-								<button id="startStopBtn" onclick="startStop()" class="btn btn-info waves-effect waves-light pull-right"><span lang="en" id="speedTestButtonText">Start</span> <i class="fa fa-rocket m-l-5"></i></button>
-							</div>
-							<div class="panel-body">
-								<div id="test">
-									<div class="row hidden-xs">
-										<div class="col-md-6 col-xs-6"><div id="downloadPercent" data-label="0Mbps" style="font-size: 15px;"></div></div>
-										<div class="col-md-6 col-xs-6"><div id="uploadPercent" data-label="0Mbps" style="font-size: 15px;"></div></div>
+	if(activeInfo.plugins["SPEEDTEST-enabled"] == true){
+		if (activeInfo.user.groupID <= activeInfo.plugins.includes["SPEEDTEST-Auth-include"]) {
+			var menuList = `<li><a class="inline-popups speedTestModal" href="#speedtest-area" data-effect="mfp-zoom-out"><i class="fa fa-rocket fa-fw"></i> <span lang="en">Test Server Speed</span></a></li>`;
+			var htmlDOM = `
+			<div id="speedtest-area" class="white-popup mfp-with-anim mfp-hide">
+				<div class="col-md-4 col-md-offset-4">
+					<div class="panel bg-org panel-info">
+						<div class="panel-heading">
+							<span lang="en">Test Speed to Server</span>
+							<button id="startStopBtn" onclick="startStop()" class="btn btn-info waves-effect waves-light pull-right"><span lang="en" id="speedTestButtonText">Start</span> <i class="fa fa-rocket m-l-5"></i></button>
+						</div>
+						<div class="panel-body">
+							<div id="test">
+								<div class="row hidden-xs">
+									<div class="col-md-6 col-xs-6"><div id="downloadPercent" data-label="0Mbps" style="font-size: 15px;"></div></div>
+									<div class="col-md-6 col-xs-6"><div id="uploadPercent" data-label="0Mbps" style="font-size: 15px;"></div></div>
+								</div>
+								<div class="progress progress-sm">
+									<div id="progress" class="progress-bar progress-bar-info active progress-bar-striped" role="progressbar" aria-valuenow="40" aria-valuemin="0" aria-valuemax="100" style="width: 0%">
+										<span class="sr-only">0% Complete (success)</span>
 									</div>
-									<div class="progress progress-sm">
-										<div id="progress" class="progress-bar progress-bar-info active progress-bar-striped" role="progressbar" aria-valuenow="40" aria-valuemin="0" aria-valuemax="100" style="width: 0%">
-											<span class="sr-only">0% Complete (success)</span>
+								</div>
+								<div class="white-box m-b-0">
+									<div class="user-btm-box">
+										<div class="col-md-3 col-xs-6 p-l-0 p-r-0 text-center">
+											<p class="text-success"><i class="ti-download fa-2x"></i></p>
+											<h1 id="dlText"></h1>
+											<h4 class="">Mbps</h4>
+										</div>
+										<div class="col-md-3 col-xs-6 p-l-0 p-r-0 text-center">
+											<p class="text-warning"><i class="ti-upload fa-2x"></i></p>
+											<h1 id="ulText"></h1>
+											<h4 class="">Mbps</h4>
+										</div>
+										<div class="col-md-3 col-xs-6 p-l-0 p-r-0 text-center">
+											<p class="text-purple"><i class="ti-direction-alt fa-2x"></i></p>
+											<h1 id="pingText"></h1>
+											<h4 class="">ms</h4>
+										</div>
+										<div class="col-md-3 col-xs-6 p-l-0 p-r-0 text-center">
+											<p class="text-info"><i class="ti-pulse fa-2x"></i></p>
+											<h1 id="jitText"></h1>
+											<h4 class="">ms</h4>
 										</div>
 									</div>
-				                    <div class="white-box m-b-0">
-				                        <div class="user-btm-box">
-				                            <div class="col-md-3 col-xs-6 p-l-0 p-r-0 text-center">
-				                                <p class="text-success"><i class="ti-download fa-2x"></i></p>
-				                                <h1 id="dlText"></h1>
-												<h4 class="">Mbps</h4>
-											</div>
-				                            <div class="col-md-3 col-xs-6 p-l-0 p-r-0 text-center">
-				                                <p class="text-warning"><i class="ti-upload fa-2x"></i></p>
-				                                <h1 id="ulText"></h1>
-												<h4 class="">Mbps</h4>
-											</div>
-				                            <div class="col-md-3 col-xs-6 p-l-0 p-r-0 text-center">
-				                                <p class="text-purple"><i class="ti-direction-alt fa-2x"></i></p>
-				                                <h1 id="pingText"></h1>
-												<h4 class="">ms</h4>
-											</div>
-				                            <div class="col-md-3 col-xs-6 p-l-0 p-r-0 text-center">
-				                                <p class="text-info"><i class="ti-pulse fa-2x"></i></p>
-				                                <h1 id="jitText"></h1>
-												<h4 class="">ms</h4>
-											</div>
-				                        </div>
-				                    </div>
 								</div>
-								<script type="text/javascript">initUI();</script>
 							</div>
-							<div class="panel-footer"> IP Address: <span id="ip"></span> </div>
+							<script type="text/javascript">initUI();</script>
 						</div>
-		    		</div>
-		    	</div>
-		    	`;
-				$('.append-menu').after(menuList);
-	            $('.organizr-area').after(htmlDOM);
-	            pageLoad();
-			}
-        }
-    }
-}
-
-$(document).on('click', '#SPEEDTEST-settings-button', function() {
-    ajaxloader(".content-wrap","in");
-    organizrAPI2('GET','api/v2/plugins/speedtest/settings').success(function(data) {
-        var response = data.response;
-        $('#SPEEDTEST-settings-items').html(buildFormGroup(response.data));
-    }).fail(function(xhr) {
-        console.error("Organizr Function: API Connection Failed");
-    });
-    ajaxloader();
-});
+						<div class="panel-footer"> IP Address: <span id="ip"></span> </div>
+					</div>
+				</div>
+			</div>
+			`;
+			$('.append-menu').after(menuList);
+			$('.organizr-area').after(htmlDOM);
+			pageLoad();
+		}
+	}
+}

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

@@ -10,7 +10,9 @@ $GLOBALS['plugins'][]['PHP Mailer'] = array( // Plugin Name
 	'configPrefix' => 'PHPMAILER', // config file prefix for array items without the hyphen
 	'version' => '1.0.0', // SemVer of plugin
 	'image' => 'plugins/images/php-mailer.png', // 1:1 non transparent image for plugin
-	'settings' => true, // does plugin need a settings page? true or false
+	'settings' => true, // does plugin need a settings modal?
+	'bind' => true, // use default bind to make settings page - true or false
+	'api' => 'api/v2/plugins/php-mailer/settings', // api route for settings page
 	'homepage' => false // Is plugin for use on homepage? true or false
 );
 
@@ -535,4 +537,4 @@ class PhpMailer extends Organizr
 			)
 		);
 	}
-}
+}

+ 5 - 3
api/plugins/speedTest.php

@@ -4,13 +4,15 @@ $GLOBALS['plugins'][]['SpeedTest'] = array( // Plugin Name
 	'name' => 'SpeedTest', // Plugin Name
 	'author' => 'CauseFX', // Who wrote the plugin
 	'category' => 'Utilities', // One to Two Word Description
-	'link' => 'https://github.com/PHPMailer/PHPMailer', // Link to plugin info
+	'link' => '', // Link to plugin info
 	'license' => 'personal,business', // License Type use , for multiple
 	'idPrefix' => 'SPEEDTEST', // html element id prefix
 	'configPrefix' => 'SPEEDTEST', // config file prefix for array items without the hypen
 	'version' => '1.0.0', // SemVer of plugin
 	'image' => 'plugins/images/speedtest.png', // 1:1 non transparent image for plugin
-	'settings' => true, // does plugin need a settings page? true or false
+	'settings' => true, // does plugin need a settings modal?
+	'bind' => true, // use default bind to make settings page - true or false
+	'api' => 'api/v2/plugins/speedtest/settings', // api route for settings page
 	'homepage' => false // Is plugin for use on homepage? true or false
 );
 
@@ -30,4 +32,4 @@ class SpeedTest extends Organizr
 			)
 		);
 	}
-}
+}

+ 4 - 42
css/organizr.css

@@ -122,6 +122,7 @@ span.jsgrid-pager-page.jsgrid-pager-current-page.btn.btn-primary{
 .organizr-area-right {
     height: calc(100vh - 40px);
     position: absolute;
+    width: 100%;
 }
 /* Larger Images */
 
@@ -1216,6 +1217,9 @@ ul.nav.customtab.nav-tabs.nav-low-margin {
     -webkit-animation-iteration-count: infinite;
     animation-iteration-count: infinite;
 }
+.heartbit.depend-heartbit {
+    top: -37.5px;
+}
 .ping .point {
     width: 6px;
     height: 6px;
@@ -4326,48 +4330,6 @@ html {
 .accordion > .card .card-header {
     margin-bottom: 0px; }
 
-@keyframes spinner-border {
-    to {
-        transform: rotate(360deg); } }
-
-.spinner-border {
-    display: inline-block;
-    width: 2rem;
-    height: 2rem;
-    vertical-align: text-bottom;
-    border: 0.25em solid currentColor;
-    border-right-color: transparent;
-    border-radius: 50%;
-    animation: spinner-border .75s linear infinite; }
-
-.spinner-border-sm {
-    width: 1rem;
-    height: 1rem;
-    border-width: 0.2em; }
-
-@keyframes spinner-grow {
-    0% {
-        transform: scale(0);
-    }
-    50% {
-        opacity: 1;
-    }
-}
-
-.spinner-grow {
-    display: inline-block;
-    width: 2rem;
-    height: 2rem;
-    vertical-align: text-bottom;
-    background-color: currentColor;
-    border-radius: 50%;
-    opacity: 0;
-    animation: spinner-grow .75s linear infinite; }
-
-.spinner-grow-sm {
-    width: 1rem;
-    height: 1rem; }
-
 .pollen-wrapper {
     display: flex;
     flex-direction: column;

Diff do ficheiro suprimidas por serem muito extensas
+ 0 - 0
css/organizr.min.css


+ 2 - 1
index.php

@@ -139,7 +139,7 @@ $Organizr = new Organizr();
     <div class="error-page bg-org"></div>
     <div class="login-area hidden"></div>
     <div class="p-0" id="page-wrapper">
-        <div class="organizr-area"></div>
+        <div class="organizr-area hidden"></div>
         <div class="plugin-listing p-0 hidden"></div>
         <div class="internal-listing p-0 hidden"></div>
         <div class="iFrame-listing p-0 hidden"></div>
@@ -230,6 +230,7 @@ $Organizr = new Organizr();
 <script src="js/ua-parser.min.js"></script>
 <script src="js/plyr.js"></script>
 <script src="js/simplebar.js"></script>
+<script src="js/arrive.min.js"></script>
 <script src="https://apis.google.com/js/client.js?onload=googleApiClientReady"></script>
 <script src="js/functions.js?v=<?php echo $Organizr->fileHash; ?>"></script>
 <script src="js/custom.min.js?v=<?php echo $Organizr->fileHash; ?>"></script>

+ 461 - 0
js/arrive.js

@@ -0,0 +1,461 @@
+/*globals jQuery,Window,HTMLElement,HTMLDocument,HTMLCollection,NodeList,MutationObserver */
+/*exported Arrive*/
+/*jshint latedef:false */
+
+/*
+ * arrive.js
+ * v2.4.1
+ * https://github.com/uzairfarooq/arrive
+ * MIT licensed
+ *
+ * Copyright (c) 2014-2017 Uzair Farooq
+ */
+var Arrive = (function(window, $, undefined) {
+
+  "use strict";
+
+  if(!window.MutationObserver || typeof HTMLElement === 'undefined'){
+    return; //for unsupported browsers
+  }
+
+  var arriveUniqueId = 0;
+
+  var utils = (function() {
+    var matches = HTMLElement.prototype.matches || HTMLElement.prototype.webkitMatchesSelector || HTMLElement.prototype.mozMatchesSelector
+                  || HTMLElement.prototype.msMatchesSelector;
+
+    return {
+      matchesSelector: function(elem, selector) {
+        return elem instanceof HTMLElement && matches.call(elem, selector);
+      },
+      // to enable function overloading - By John Resig (MIT Licensed)
+      addMethod: function (object, name, fn) {
+        var old = object[ name ];
+        object[ name ] = function(){
+          if ( fn.length == arguments.length ) {
+            return fn.apply( this, arguments );
+          }
+          else if ( typeof old == 'function' ) {
+            return old.apply( this, arguments );
+          }
+        };
+      },
+      callCallbacks: function(callbacksToBeCalled, registrationData) {
+        if (registrationData && registrationData.options.onceOnly && registrationData.firedElems.length == 1) {
+          // as onlyOnce param is true, make sure we fire the event for only one item
+          callbacksToBeCalled = [callbacksToBeCalled[0]];
+        }
+
+        for (var i = 0, cb; (cb = callbacksToBeCalled[i]); i++) {
+          if (cb && cb.callback) {
+            cb.callback.call(cb.elem, cb.elem);
+          }
+        }
+
+        if (registrationData && registrationData.options.onceOnly && registrationData.firedElems.length == 1) {
+          // unbind event after first callback as onceOnly is true.
+          registrationData.me.unbindEventWithSelectorAndCallback.call(
+            registrationData.target, registrationData.selector, registrationData.callback);
+        }
+      },
+      // traverse through all descendants of a node to check if event should be fired for any descendant
+      checkChildNodesRecursively: function(nodes, registrationData, matchFunc, callbacksToBeCalled) {
+        // check each new node if it matches the selector
+        for (var i=0, node; (node = nodes[i]); i++) {
+          if (matchFunc(node, registrationData, callbacksToBeCalled)) {
+            callbacksToBeCalled.push({ callback: registrationData.callback, elem: node });
+          }
+
+          if (node.childNodes.length > 0) {
+            utils.checkChildNodesRecursively(node.childNodes, registrationData, matchFunc, callbacksToBeCalled);
+          }
+        }
+      },
+      mergeArrays: function(firstArr, secondArr){
+        // Overwrites default options with user-defined options.
+        var options = {},
+            attrName;
+        for (attrName in firstArr) {
+          if (firstArr.hasOwnProperty(attrName)) {
+            options[attrName] = firstArr[attrName];
+          }
+        }
+        for (attrName in secondArr) {
+          if (secondArr.hasOwnProperty(attrName)) {
+            options[attrName] = secondArr[attrName];
+          }
+        }
+        return options;
+      },
+      toElementsArray: function (elements) {
+        // check if object is an array (or array like object)
+        // Note: window object has .length property but it's not array of elements so don't consider it an array
+        if (typeof elements !== "undefined" && (typeof elements.length !== "number" || elements === window)) {
+          elements = [elements];
+        }
+        return elements;
+      }
+    };
+  })();
+
+
+  // Class to maintain state of all registered events of a single type
+  var EventsBucket = (function() {
+    var EventsBucket = function() {
+      // holds all the events
+
+      this._eventsBucket    = [];
+      // function to be called while adding an event, the function should do the event initialization/registration
+      this._beforeAdding    = null;
+      // function to be called while removing an event, the function should do the event destruction
+      this._beforeRemoving  = null;
+    };
+
+    EventsBucket.prototype.addEvent = function(target, selector, options, callback) {
+      var newEvent = {
+        target:             target,
+        selector:           selector,
+        options:            options,
+        callback:           callback,
+        firedElems:         []
+      };
+
+      if (this._beforeAdding) {
+        this._beforeAdding(newEvent);
+      }
+
+      this._eventsBucket.push(newEvent);
+      return newEvent;
+    };
+
+    EventsBucket.prototype.removeEvent = function(compareFunction) {
+      for (var i=this._eventsBucket.length - 1, registeredEvent; (registeredEvent = this._eventsBucket[i]); i--) {
+        if (compareFunction(registeredEvent)) {
+          if (this._beforeRemoving) {
+              this._beforeRemoving(registeredEvent);
+          }
+
+          // mark callback as null so that even if an event mutation was already triggered it does not call callback
+          var removedEvents = this._eventsBucket.splice(i, 1);
+          if (removedEvents && removedEvents.length) {
+            removedEvents[0].callback = null;
+          }
+        }
+      }
+    };
+
+    EventsBucket.prototype.beforeAdding = function(beforeAdding) {
+      this._beforeAdding = beforeAdding;
+    };
+
+    EventsBucket.prototype.beforeRemoving = function(beforeRemoving) {
+      this._beforeRemoving = beforeRemoving;
+    };
+
+    return EventsBucket;
+  })();
+
+
+  /**
+   * @constructor
+   * General class for binding/unbinding arrive and leave events
+   */
+  var MutationEvents = function(getObserverConfig, onMutation) {
+    var eventsBucket    = new EventsBucket(),
+        me              = this;
+
+    var defaultOptions = {
+      fireOnAttributesModification: false
+    };
+
+    // actual event registration before adding it to bucket
+    eventsBucket.beforeAdding(function(registrationData) {
+      var
+        target    = registrationData.target,
+        observer;
+
+      // mutation observer does not work on window or document
+      if (target === window.document || target === window) {
+        target = document.getElementsByTagName("html")[0];
+      }
+
+      // Create an observer instance
+      observer = new MutationObserver(function(e) {
+        onMutation.call(this, e, registrationData);
+      });
+
+      var config = getObserverConfig(registrationData.options);
+
+      observer.observe(target, config);
+
+      registrationData.observer = observer;
+      registrationData.me = me;
+    });
+
+    // cleanup/unregister before removing an event
+    eventsBucket.beforeRemoving(function (eventData) {
+      eventData.observer.disconnect();
+    });
+
+    this.bindEvent = function(selector, options, callback) {
+      options = utils.mergeArrays(defaultOptions, options);
+
+      var elements = utils.toElementsArray(this);
+
+      for (var i = 0; i < elements.length; i++) {
+        eventsBucket.addEvent(elements[i], selector, options, callback);
+      }
+    };
+
+    this.unbindEvent = function() {
+      var elements = utils.toElementsArray(this);
+      eventsBucket.removeEvent(function(eventObj) {
+        for (var i = 0; i < elements.length; i++) {
+          if (this === undefined || eventObj.target === elements[i]) {
+            return true;
+          }
+        }
+        return false;
+      });
+    };
+
+    this.unbindEventWithSelectorOrCallback = function(selector) {
+      var elements = utils.toElementsArray(this),
+          callback = selector,
+          compareFunction;
+
+      if (typeof selector === "function") {
+        compareFunction = function(eventObj) {
+          for (var i = 0; i < elements.length; i++) {
+            if ((this === undefined || eventObj.target === elements[i]) && eventObj.callback === callback) {
+              return true;
+            }
+          }
+          return false;
+        };
+      }
+      else {
+        compareFunction = function(eventObj) {
+          for (var i = 0; i < elements.length; i++) {
+            if ((this === undefined || eventObj.target === elements[i]) && eventObj.selector === selector) {
+              return true;
+            }
+          }
+          return false;
+        };
+      }
+      eventsBucket.removeEvent(compareFunction);
+    };
+
+    this.unbindEventWithSelectorAndCallback = function(selector, callback) {
+      var elements = utils.toElementsArray(this);
+      eventsBucket.removeEvent(function(eventObj) {
+          for (var i = 0; i < elements.length; i++) {
+            if ((this === undefined || eventObj.target === elements[i]) && eventObj.selector === selector && eventObj.callback === callback) {
+              return true;
+            }
+          }
+          return false;
+      });
+    };
+
+    return this;
+  };
+
+
+  /**
+   * @constructor
+   * Processes 'arrive' events
+   */
+  var ArriveEvents = function() {
+    // Default options for 'arrive' event
+    var arriveDefaultOptions = {
+      fireOnAttributesModification: false,
+      onceOnly: false,
+      existing: false
+    };
+
+    function getArriveObserverConfig(options) {
+      var config = {
+        attributes: false,
+        childList: true,
+        subtree: true
+      };
+
+      if (options.fireOnAttributesModification) {
+        config.attributes = true;
+      }
+
+      return config;
+    }
+
+    function onArriveMutation(mutations, registrationData) {
+      mutations.forEach(function( mutation ) {
+        var newNodes    = mutation.addedNodes,
+            targetNode = mutation.target,
+            callbacksToBeCalled = [],
+            node;
+
+        // If new nodes are added
+        if( newNodes !== null && newNodes.length > 0 ) {
+          utils.checkChildNodesRecursively(newNodes, registrationData, nodeMatchFunc, callbacksToBeCalled);
+        }
+        else if (mutation.type === "attributes") {
+          if (nodeMatchFunc(targetNode, registrationData, callbacksToBeCalled)) {
+            callbacksToBeCalled.push({ callback: registrationData.callback, elem: targetNode });
+          }
+        }
+
+        utils.callCallbacks(callbacksToBeCalled, registrationData);
+      });
+    }
+
+    function nodeMatchFunc(node, registrationData, callbacksToBeCalled) {
+      // check a single node to see if it matches the selector
+      if (utils.matchesSelector(node, registrationData.selector)) {
+        if(node._id === undefined) {
+          node._id = arriveUniqueId++;
+        }
+        // make sure the arrive event is not already fired for the element
+        if (registrationData.firedElems.indexOf(node._id) == -1) {
+          registrationData.firedElems.push(node._id);
+
+          return true;
+        }
+      }
+
+      return false;
+    }
+
+    arriveEvents = new MutationEvents(getArriveObserverConfig, onArriveMutation);
+
+    var mutationBindEvent = arriveEvents.bindEvent;
+
+    // override bindEvent function
+    arriveEvents.bindEvent = function(selector, options, callback) {
+
+      if (typeof callback === "undefined") {
+        callback = options;
+        options = arriveDefaultOptions;
+      } else {
+        options = utils.mergeArrays(arriveDefaultOptions, options);
+      }
+
+      var elements = utils.toElementsArray(this);
+
+      if (options.existing) {
+        var existing = [];
+
+        for (var i = 0; i < elements.length; i++) {
+          var nodes = elements[i].querySelectorAll(selector);
+          for (var j = 0; j < nodes.length; j++) {
+            existing.push({ callback: callback, elem: nodes[j] });
+          }
+        }
+
+        // no need to bind event if the callback has to be fired only once and we have already found the element
+        if (options.onceOnly && existing.length) {
+          return callback.call(existing[0].elem, existing[0].elem);
+        }
+
+        setTimeout(utils.callCallbacks, 1, existing);
+      }
+
+      mutationBindEvent.call(this, selector, options, callback);
+    };
+
+    return arriveEvents;
+  };
+
+
+  /**
+   * @constructor
+   * Processes 'leave' events
+   */
+  var LeaveEvents = function() {
+    // Default options for 'leave' event
+    var leaveDefaultOptions = {};
+
+    function getLeaveObserverConfig() {
+      var config = {
+        childList: true,
+        subtree: true
+      };
+
+      return config;
+    }
+
+    function onLeaveMutation(mutations, registrationData) {
+      mutations.forEach(function( mutation ) {
+        var removedNodes  = mutation.removedNodes,
+            callbacksToBeCalled = [];
+
+        if( removedNodes !== null && removedNodes.length > 0 ) {
+          utils.checkChildNodesRecursively(removedNodes, registrationData, nodeMatchFunc, callbacksToBeCalled);
+        }
+
+        utils.callCallbacks(callbacksToBeCalled, registrationData);
+      });
+    }
+
+    function nodeMatchFunc(node, registrationData) {
+      return utils.matchesSelector(node, registrationData.selector);
+    }
+
+    leaveEvents = new MutationEvents(getLeaveObserverConfig, onLeaveMutation);
+
+    var mutationBindEvent = leaveEvents.bindEvent;
+
+    // override bindEvent function
+    leaveEvents.bindEvent = function(selector, options, callback) {
+
+      if (typeof callback === "undefined") {
+        callback = options;
+        options = leaveDefaultOptions;
+      } else {
+        options = utils.mergeArrays(leaveDefaultOptions, options);
+      }
+
+      mutationBindEvent.call(this, selector, options, callback);
+    };
+
+    return leaveEvents;
+  };
+
+
+  var arriveEvents = new ArriveEvents(),
+      leaveEvents  = new LeaveEvents();
+
+  function exposeUnbindApi(eventObj, exposeTo, funcName) {
+    // expose unbind function with function overriding
+    utils.addMethod(exposeTo, funcName, eventObj.unbindEvent);
+    utils.addMethod(exposeTo, funcName, eventObj.unbindEventWithSelectorOrCallback);
+    utils.addMethod(exposeTo, funcName, eventObj.unbindEventWithSelectorAndCallback);
+  }
+
+  /*** expose APIs ***/
+  function exposeApi(exposeTo) {
+    exposeTo.arrive = arriveEvents.bindEvent;
+    exposeUnbindApi(arriveEvents, exposeTo, "unbindArrive");
+
+    exposeTo.leave = leaveEvents.bindEvent;
+    exposeUnbindApi(leaveEvents, exposeTo, "unbindLeave");
+  }
+
+  if ($) {
+    exposeApi($.fn);
+  }
+  exposeApi(HTMLElement.prototype);
+  exposeApi(NodeList.prototype);
+  exposeApi(HTMLCollection.prototype);
+  exposeApi(HTMLDocument.prototype);
+  exposeApi(Window.prototype);
+
+  var Arrive = {};
+  // expose functions to unbind all arrive/leave events
+  exposeUnbindApi(arriveEvents, Arrive, "unbindAllArrive");
+  exposeUnbindApi(leaveEvents, Arrive, "unbindAllLeave");
+
+  return Arrive;
+
+})(window, typeof jQuery === 'undefined' ? null : jQuery, undefined);

Diff do ficheiro suprimidas por serem muito extensas
+ 9 - 0
js/arrive.min.js


+ 30 - 25
js/custom.js

@@ -584,6 +584,7 @@ $(document).on("click", ".addNewUser", function () {
 		message('User Created',response.message,activeInfo.settings.notifications.position,"#FFF","success","5000");
 		if(callbacks){ callbacks.fire(); }
 		clearForm('#new-user-form');
+		$('#jsGrid-Users').jsGrid('render');
 		$.magnificPopup.close();
 	}).fail(function(xhr) {
 		OrganizrApiError(xhr, 'API Error');
@@ -1668,30 +1669,6 @@ Mousetrap.bind('ctrl+shift+down', function(e) {
     nextTab.trigger("click");
     return false;
 });
-$(document).on('change', "#new-tab-form-chooseImage", function (e) {
-    var newIcon = $('#new-tab-form-chooseImage').val();
-    if(newIcon !== 'Select or type Icon'){
-        $('#new-tab-form-inputImageNew').val(newIcon);
-    }
-});
-$(document).on('change', "#edit-tab-form-chooseImage", function (e) {
-    var newIcon = $('#edit-tab-form-chooseImage').val();
-    if(newIcon !== 'Select or type Icon'){
-        $('#edit-tab-form-inputImage').val(newIcon);
-    }
-});
-$(document).on('change', "#new-tab-form-chooseIcon", function (e) {
-    var newIcon = $('#new-tab-form-chooseIcon').val();
-    if(newIcon !== 'Select or type Icon'){
-        $('#new-tab-form-inputImageNew').val(newIcon);
-    }
-});
-$(document).on('change', "#edit-tab-form-chooseIcon", function (e) {
-    var newIcon = $('#edit-tab-form-chooseIcon').val();
-    if(newIcon !== 'Select or type Icon'){
-        $('#edit-tab-form-inputImage').val(newIcon);
-    }
-});
 $(document).on('change', "#choose-calender-filter, #choose-calender-filter-status", function (e) {
     filter = $('#choose-calender-filter').val();
     filterDownload = $('#choose-calender-filter-status').val();
@@ -1895,4 +1872,32 @@ function checkMetadataDiv(target,type,classList){
 			});
 		}
 	});
-}
+}
+
+// Plugins settings bind
+$(document).on('click', '[id$=-settings-button]', function() {
+	let el = $(this)[0];
+	let bind = $(el).attr('data-bind');
+	let api = $(el).attr('data-api');
+	let prefix = $(el).attr('data-config-prefix');
+	if(bind == 'true' && api !== 'false' && prefix !== 'false'){
+		ajaxloader(".content-wrap","in");
+		organizrAPI2('GET',api).success(function(data) {
+			var response = data.response;
+			$('#'+prefix+'-settings-items').html(buildFormGroup(response.data));
+		}).fail(function(xhr) {
+			OrganizrApiError(xhr);
+		});
+		ajaxloader();
+	}
+});
+$(document).on('change', '[id*=-form-chooseI]', function (e) {
+	let el = $(this)[0];
+	let id = $(el).attr('id');
+	let newForm = (id.includes('new')) ? 'New' : '';
+	let pasteId = id.match(/(?:[a-z]*-){1,5}/) + 'inputImage' + newForm;
+	let newValue = $('#'+id).val();
+	if(newValue !== 'Select or type Icon'){
+		$('#'+pasteId).val(newValue);
+	}
+});

Diff do ficheiro suprimidas por serem muito extensas
+ 0 - 0
js/custom.min.js


+ 23 - 14
js/functions.js

@@ -498,16 +498,18 @@ function cleanClass(string){
 // What the hell is this?  I don't remember this lol
 function noTabs(arrayItems){
 	if (arrayItems.data.user.loggedin === true) {
-		organizrConnect('api/?v1/no_tabs').success(function(data) {
-            try {
-                var response = JSON.parse(data);
-            }catch(e) {
-	            organizrCatchError(e,data);
-            }
-			console.log("Organizr Function: No Tabs Available");
-			$(response.data).appendTo($('.organizr-area'));
+		organizrAPI2('GET','api/v2/page/tabs').success(function(data) {
+			try {
+				var json = data.response;
+				organizrConsole('Organizr Function','No tabs available');
+				$(json.data).appendTo($('.organizr-area'));
+				$('.organizr-area').removeClass('hidden');
+				$("#preloader").fadeOut();
+			}catch(e) {
+				organizrCatchError(e,data);
+			}
 		}).fail(function(xhr) {
-			OrganizrApiError(xhr);
+			OrganizrApiError(xhr, 'Error');
 		});
 	}else {
 		$('.show-login').trigger('click');
@@ -1154,7 +1156,7 @@ function buildPluginsItem(array){
 		var href = (v.settings == true) ? '#'+v.idPrefix+'-settings-page' : 'javascript:void(0);';
 		if(v.enabled == true){
 			var activeToggle = `<li><a class="btn default btn-outline disablePlugin" href="javascript:void(0);" data-plugin-name="`+v.name+`" data-config-prefix="`+v.configPrefix+`" data-config-name="`+v.configPrefix+`-enabled"><i class="ti-power-off fa-2x"></i></a></li>`;
-			var settings = `<li><a class="btn default btn-outline popup-with-form" href="`+href+`" data-effect="mfp-3d-unfold"data-plugin-name="`+v.name+`" id="`+v.idPrefix+`-settings-button" data-config-prefix="`+v.configPrefix+`"><i class="ti-panel fa-2x"></i></a></li>`;
+			var settings = `<li><a class="btn default btn-outline popup-with-form" href="`+href+`" data-effect="mfp-3d-unfold"data-plugin-name="`+v.name+`" id="`+v.idPrefix+`-settings-button" data-config-prefix="`+v.configPrefix+`" data-api="${v.api}" data-settings="${v.settings}" data-bind="${v.bind}"><i class="ti-panel fa-2x"></i></a></li>`;
 		}else{
 			var activeToggle = `<li><a class="btn default btn-outline enablePlugin" href="javascript:void(0);" data-plugin-name="`+v.name+`" data-config-prefix="`+v.configPrefix+`" data-config-name="`+v.configPrefix+`-enabled"><i class="ti-plug fa-2x"></i></a></li>`;
 			var settings = '';
@@ -3876,7 +3878,6 @@ function countdown(remaining) {
 function dockerUpdate(){
     if(activeInfo.settings.misc.docker){
 	    showUpdateBar();
-        //$(updateBar()).appendTo('.organizr-area');
         updateUpdateBar('Starting Download','20%');
         messageSingle(window.lang.translate('[DO NOT CLOSE WINDOW]'),window.lang.translate('Starting Update Process'),activeInfo.settings.notifications.position,'#FFF','success','60000');
         organizrAPI2('GET','api/v2/update/docker').success(function(data) {
@@ -3890,7 +3891,6 @@ function dockerUpdate(){
 function windowsUpdate(){
     if(activeInfo.serverOS == 'win'){
 	    showUpdateBar();
-    	//$(updateBar()).appendTo('.organizr-area');
         updateUpdateBar('Starting Download','20%');
         messageSingle(window.lang.translate('[DO NOT CLOSE WINDOW]'),window.lang.translate('Starting Update Process'),activeInfo.settings.notifications.position,'#FFF','success','60000');
         organizrAPI2('GET','api/v2/update/windows').success(function(data) {
@@ -3913,7 +3913,6 @@ function updateNow(){
     }
 	organizrConsole('Update Function','Starting Update Process');
 	showUpdateBar();
-	//$(updateBar()).appendTo('.organizr-area');
 	updateUpdateBar('Starting Download','5%');
 	messageSingle(window.lang.translate('[DO NOT CLOSE WINDOW]'),window.lang.translate('Starting Update Process'),activeInfo.settings.notifications.position,'#FFF','success','60000');
 	organizrAPI2('GET','api/v2/update/download/'+ activeInfo.branch).success(function(data) {
@@ -4183,6 +4182,7 @@ function buildWizard(){
         }
 		organizrConsole('Organizr Function','Starting Install Wizard');
 		$(json.data).appendTo($('.organizr-area'));
+		$('.organizr-area').removeClass('hidden');
 	}).fail(function(xhr) {
 		OrganizrApiError(xhr, 'Wiizard Error');
 	});
@@ -4197,6 +4197,7 @@ function buildDependencyCheck(orgdata){
         }
 		organizrConsole('Organizr Function','Starting Dependencies Check');
 		$(json.data).appendTo($('.organizr-area'));
+		$('.organizr-area').removeClass('hidden');
 		$(buildBrowserInfo()).appendTo($('#browser-info'));
 		$('#web-folder').html(buildWebFolder(orgdata));
 		$('#php-version-check').html(buildPHPCheck(orgdata));
@@ -4212,7 +4213,7 @@ function buildDependencyInfo(arrayItems){
 			listing += '<li class="depenency-item" data-name="'+v+'"><a href="javascript:void(0)"><i class="fa fa-check text-success"></i> '+v+'</a></li>';
 		});
 	$.each(arrayItems.data.status.dependenciesInactive, function(i,v) {
-		listing += '<li class="depenency-item" data-name="'+v+'"><a href="javascript:void(0)"><i class="fa fa-close text-danger"><div class="notify"><span class="heartbit"></span></div></i> '+v+'</a></li>';
+		listing += '<li class="depenency-item" data-name="'+v+'"><a href="javascript:void(0)"><i class="fa fa-close text-danger"><div class="notify"><span class="heartbit depend-heartbit"></span></div></i> '+v+'</a></li>';
 	});
 	return listing;
 }
@@ -4343,6 +4344,12 @@ function logIcon(type){
 		case "success":
 			return '<i class="fa fa-check text-success"></i><span class="hidden">Success</span>';
 			break;
+		case "info":
+			return '<i class="fa fa-info text-info"></i><span class="hidden">Info</span>';
+			break;
+		case "debug":
+			return '<i class="fa fa-code text-primary"></i><span class="hidden">Debug</span>';
+			break;
 		case "warning":
 			return '<i class="fa fa-exclamation-triangle text-warning"></i><span class="hidden">Warning</span>';
 			break;
@@ -10555,6 +10562,8 @@ function launch(){
 		        style:json.data.style,
 		        version:json.data.version
 	        };
+	        // Add element to signal activeInfo Ready
+	        $('#wrapper').after('<div id="activeInfo"></div>');
 	        console.info("%c Organizr %c ".concat(currentVersion, " "), "color: white; background: #66D9EF; font-weight: 700; font-size: 24px; font-family: Monospace;", "color: #66D9EF; background: white; font-weight: 700; font-size: 24px; font-family: Monospace;");
 	        console.info("%c Status %c ".concat("Starting Up...", " "), "color: white; background: #F92671; font-weight: 700;", "color: #F92671; background: white; font-weight: 700;");
 	        local('set','initial',true);

+ 7 - 0
js/version.json

@@ -334,5 +334,12 @@
     "new": "",
     "fixed": "Missing variable",
     "notes": "This is a quick hotfix"
+  },
+  "2.1.195": {
+    "date": "2021-02-12 20:00",
+    "title": "Weekly Update",
+    "new": "ignore cert to transmission (#1177)|ignore cert to qbit (#1177)|no tabs api function|log functions",
+    "fixed": "Jellyfin SSO fails behind reverse proxy (#1585)|Unable to use \"@\" in username during initial account setup (#1584)|Re-add JWT claims removed from token (#1577)|heart on dependency|refresh to add new user|organizr-area css",
+    "notes": "remove spinner css classes"
   }
 }

BIN
plugins/images/tabs/bookstack.png


BIN
plugins/images/tabs/petio.png


BIN
plugins/images/tabs/prometheus.png


Alguns ficheiros não foram mostrados porque muitos ficheiros mudaram neste diff