Просмотр исходного кода

Merge pull request #1414 from causefx/v2-develop

V2 develop
causefx 6 лет назад
Родитель
Сommit
c5a7ba3369

+ 2 - 0
.gitignore

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

+ 42 - 1
api/config/default.php

@@ -8,6 +8,8 @@ return array(
 	'authBackendHostSuffix' => '',
 	'ldapBindUsername' => '',
 	'ldapBindPassword' => '',
+	'ldapSSL' => false,
+	'ldapTLS' => false,
 	'authBaseDN' => '',
 	'authBackendDomain' => '',
 	'ldapType' => '1',
@@ -316,37 +318,76 @@ return array(
 	'speedtestHeaderToggle' => true,
 	'speedtestHeader' => 'Speedtest',
 	'homepageNetdataEnabled' => false,
-	'homepageNetdataRefresh' => '2500',
+	'homepageNetdataRefresh' => '10000',
 	'homepageNetdataAuth' => '1',
 	'netdataURL' => '',
 	'netdata1Title' => '',
 	'netdata1Chart' => '',
 	'netdata1Data' => '',
 	'netdata1Colour' => '',
+	'netdata1Size' => '',
+	'netdata1sm' => true,
+	'netdata1md' => true,
+	'netdata1lg' => true,
 	'netdata2Title' => '',
 	'netdata2Chart' => '',
 	'netdata2Data' => '',
 	'netdata2Colour' => '',
+	'netdata2Size' => '',
+	'netdata2sm' => true,
+	'netdata2md' => true,
+	'netdata2lg' => true,
 	'netdata3Title' => '',
 	'netdata3Chart' => '',
 	'netdata3Data' => '',
 	'netdata3Colour' => '',
+	'netdata3Size' => '',
+	'netdata3sm' => true,
+	'netdata3md' => true,
+	'netdata3lg' => true,
 	'netdata4Title' => '',
 	'netdata4Chart' => '',
 	'netdata4Data' => '',
 	'netdata4Colour' => '',
+	'netdata4Size' => '',
+	'netdata4sm' => true,
+	'netdata4md' => true,
+	'netdata4lg' => true,
 	'netdata5Title' => '',
 	'netdata5Chart' => '',
 	'netdata5Data' => '',
 	'netdata5Colour' => '',
+	'netdata5Size' => '',
+	'netdata5sm' => true,
+	'netdata5md' => true,
+	'netdata5lg' => true,
 	'netdata6Title' => '',
 	'netdata6Chart' => '',
 	'netdata6Data' => '',
 	'netdata6Colour' => '',
+	'netdata6Size' => '',
+	'netdata6sm' => true,
+	'netdata6md' => true,
+	'netdata6lg' => true,
+	'netdata7Title' => '',
+	'netdata7Chart' => '',
+	'netdata7Data' => '',
+	'netdata7Colour' => '',
+	'netdata7Size' => '',
+	'netdata7sm' => true,
+	'netdata7md' => true,
+	'netdata7lg' => true,
 	'netdata1Enabled' => false,
 	'netdata2Enabled' => false,
 	'netdata3Enabled' => false,
 	'netdata4Enabled' => false,
 	'netdata5Enabled' => false,
 	'netdata6Enabled' => false,
+	'netdata7Enabled' => false,
+	'netdataCustom' => '{
+    
+	}',
+	'githubMenuLink' => true,
+	'organizrSupportMenuLink' => true,
+	'organizrDocsMenuLink' => true
 );

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

@@ -225,8 +225,8 @@ if (function_exists('ldap_connect')) {
 				'account_suffix' => (empty($GLOBALS['authBackendHostSuffix'])) ? null : $GLOBALS['authBackendHostSuffix'],
 				'port' => $ldapPort,
 				'follow_referrals' => false,
-				'use_ssl' => false,
-				'use_tls' => false,
+				'use_ssl' => $GLOBALS['ldapSSL'],
+				'use_tls' => $GLOBALS['ldapTLS'],
 				'version' => 3,
 				'timeout' => 5,
 				// Custom LDAP Options

+ 76 - 56
api/functions/homepage-connect-functions.php

@@ -1535,43 +1535,42 @@ function calendarDaysCheck($entryStart, $entryEnd)
 
 function calendarStandardizeTimezone($timezone)
 {
-    switch ($timezone) {
-        case('CST'):
-        case('Central Time'):
-        case('Central Standard Time'):
-            $timezone = 'America/Chicago';
-            break;
-        case('CET'):
-        case('Central European Time'):
-            $timezone = 'Europe/Berlin';
-            break;
-        case('EST'):
-        case('Eastern Time'):
-        case('Eastern Standard Time'):
-            $timezone = 'America/New_York';
-            break;
-        case('PST'):
-        case('Pacific Time'):
-        case('Pacific Standard Time'):
-            $timezone = 'America/Los_Angeles';
-            break;
-        case('China Time'):
-        case('China Standard Time'):
-            $timezone = 'Asia/Beijing';
-            break;
-        case('IST'):
-        case('India Time'):
-        case('India Standard Time'):
-            $timezone = 'Asia/New_Delhi';
-            break;
-        case('JST');
-        case('Japan Time'):
-        case('Japan Standard Time'):
-            $timezone = 'Asia/Tokyo';
-            break;
-    }
-
-    return $timezone;
+	switch ($timezone) {
+		case('CST'):
+		case('Central Time'):
+		case('Central Standard Time'):
+			$timezone = 'America/Chicago';
+			break;
+		case('CET'):
+		case('Central European Time'):
+			$timezone = 'Europe/Berlin';
+			break;
+		case('EST'):
+		case('Eastern Time'):
+		case('Eastern Standard Time'):
+			$timezone = 'America/New_York';
+			break;
+		case('PST'):
+		case('Pacific Time'):
+		case('Pacific Standard Time'):
+			$timezone = 'America/Los_Angeles';
+			break;
+		case('China Time'):
+		case('China Standard Time'):
+			$timezone = 'Asia/Beijing';
+			break;
+		case('IST'):
+		case('India Time'):
+		case('India Standard Time'):
+			$timezone = 'Asia/New_Delhi';
+			break;
+		case('JST');
+		case('Japan Time'):
+		case('Japan Standard Time'):
+			$timezone = 'Asia/Tokyo';
+			break;
+	}
+	return $timezone;
 }
 
 function getCalenderRepeat($value)
@@ -2636,7 +2635,7 @@ function getMonitorr()
 				// This section grabs the names of all services by regex
 				$services = [];
 				$servicesMatch = [];
-				$servicePattern = '/<div id="servicetitle"><div>(.*)<\/div><\/div><div class="btnonline">Online<\/div><\/a><\/div><\/div>|<div id="servicetitleoffline".*><div>(.*)<\/div><\/div><div class="btnoffline".*>Offline<\/div><\/div><\/div>|<div id="servicetitlenolink".*><div>(.*)<\/div><\/div><div class="btnonline".*>Online<\/div><\/div><\/div>/';
+				$servicePattern = '/<div id="servicetitle"><div>(.*)<\/div><\/div><div class="btnonline">Online<\/div><\/a><\/div><\/div>|<div id="servicetitleoffline".*><div>(.*)<\/div><\/div><div class="btnoffline".*>Offline<\/div><\/div><\/div>|<div id="servicetitlenolink".*><div>(.*)<\/div><\/div><div class="btnonline".*>Online<\/div><\/div><\/div>|<div id="servicetitle"><div>(.*)<\/div><\/div><div class="btnunknown">/';
 				preg_match_all($servicePattern, $html, $servicesMatch);
 				unset($servicesMatch[0]);
 				$servicesMatch = array_values($servicesMatch);
@@ -2650,7 +2649,7 @@ function getMonitorr()
 				// This section then grabs the status and image of that service with regex
 				$statuses = [];
 				foreach ($services as $service) {
-					$statusPattern = '/' . $service . '<\/div><\/div><div class="btnonline">(Online)<\/div>|' . $service . '<\/div><\/div><div class="btnoffline".*>(Offline)<\/div><\/div><\/div>/';
+					$statusPattern = '/' . $service . '<\/div><\/div><div class="btnonline">(Online)<\/div>|' . $service . '<\/div><\/div><div class="btnoffline".*>(Offline)<\/div><\/div><\/div>|' . $service . '<\/div><\/div><div class="btnunknown">(.*)<\/div><\/a>/';
 					$status = [];
 					preg_match($statusPattern, $html, $status);
 					$statuses[$service] = $status;
@@ -2663,6 +2662,10 @@ function getMonitorr()
 							$statuses[$service] = [
 								'status' => false
 							];
+						} else if ($match == 'Unresponsive') {
+							$statuses[$service] = [
+								'status' => 'unresponsive'
+							];
 						}
 					}
 					$imageMatch = [];
@@ -2708,6 +2711,7 @@ function getMonitorr()
 			}
 		} catch (Requests_Exception $e) {
 			writeLog('error', 'Monitorr Connect Function - Error: ' . $e->getMessage(), 'SYSTEM');
+			$api['error'] = $e->getMessage();
 		};
 		$api = isset($api) ? $api : false;
 		return $api;
@@ -2724,13 +2728,11 @@ function getSpeedtest()
 			$response = Requests::get($dataUrl);
 			if ($response->success) {
 				$json = json_decode($response->body, true);
-
 				$api['data'] = [
 					'current' => $json['data'],
 					'average' => $json['average'],
 					'max' => $json['max'],
 				];
-
 				$api['options'] = [
 					'title' => $GLOBALS['speedtestHeader'],
 					'titleToggle' => $GLOBALS['speedtestHeaderToggle'],
@@ -2751,12 +2753,10 @@ function getNetdata()
 		$api = [];
 		$api['data'] = [];
 		$api['url'] = $GLOBALS['netdataURL'];
-
 		$url = qualifyURL($GLOBALS['netdataURL']);
-
-		for($i = 1; $i < 7; $i++) {
-			if($GLOBALS['netdata'.($i).'Enabled']) {
-				switch($GLOBALS['netdata'.$i.'Data']) {
+		for ($i = 1; $i < 8; $i++) {
+			if ($GLOBALS['netdata' . ($i) . 'Enabled']) {
+				switch ($GLOBALS['netdata' . $i . 'Data']) {
 					case 'disk-read':
 						$data = disk('in', $url);
 						break;
@@ -2779,29 +2779,49 @@ function getNetdata()
 					case 'ram-used':
 						$data = ram($url);
 						break;
+					case 'swap-used':
+						$data = swap($url);
+						break;
+					case 'disk-avail':
+						$data = diskSpace('avail', $url);
+						break;
+					case 'disk-used':
+						$data = diskSpace('used', $url);
+						break;
 					case 'ipmi-temp-c':
 						$data = ipmiTemp($url, 'c');
 						break;
 					case 'ipmi-temp-f':
 						$data = ipmiTemp($url, 'f');
 						break;
+					case 'cpu-temp-c':
+						$data = cpuTemp($url, 'c');
+						break;
+					case 'cpu-temp-f':
+						$data = cpuTemp($url, 'f');
+						break;
+					case 'custom':
+						$data = customNetdata($url, $i);
+						break;
 					default:
 						$data = [
 							'title' => 'DNC',
 							'value' => 0,
 							'units' => 'N/A',
+							'max' => 100,
 						];
 						break;
 				}
-
-				$data['title'] = $GLOBALS['netdata'.$i.'Title'];
-				$data['colour'] = $GLOBALS['netdata'.$i.'Colour'];
-				$data['chart'] = $GLOBALS['netdata'.$i.'Chart'];
-
+				$data['title'] = $GLOBALS['netdata' . $i . 'Title'];
+				$data['colour'] = $GLOBALS['netdata' . $i . 'Colour'];
+				$data['chart'] = $GLOBALS['netdata' . $i . 'Chart'];
+				$data['size'] = $GLOBALS['netdata' . $i . 'Size'];
+				$data['lg'] = $GLOBALS['netdata' . ($i) . 'lg'];
+				$data['md'] = $GLOBALS['netdata' . ($i) . 'md'];
+				$data['sm'] = $GLOBALS['netdata' . ($i) . 'sm'];
 				array_push($api['data'], $data);
 			}
 		}
-
 		$api = isset($api) ? $api : false;
 		return $api;
 	}
@@ -3129,8 +3149,8 @@ function testAPIConnection($array)
 					'account_suffix' => (empty($GLOBALS['authBackendHostSuffix'])) ? null : $GLOBALS['authBackendHostSuffix'],
 					'port' => $ldapPort,
 					'follow_referrals' => false,
-					'use_ssl' => false,
-					'use_tls' => false,
+					'use_ssl' => $GLOBALS['ldapSSL'],
+					'use_tls' => $GLOBALS['ldapTLS'],
 					'version' => 3,
 					'timeout' => 5,
 					// Custom LDAP Options
@@ -3207,8 +3227,8 @@ function testAPIConnection($array)
 					'account_suffix' => '',
 					'port' => $ldapPort,
 					'follow_referrals' => false,
-					'use_ssl' => false,
-					'use_tls' => false,
+					'use_ssl' => $GLOBALS['ldapSSL'],
+					'use_tls' => $GLOBALS['ldapTLS'],
 					'version' => 3,
 					'timeout' => 5,
 					// Custom LDAP Options

+ 3 - 293
api/functions/homepage-functions.php

@@ -2619,7 +2619,7 @@ function getHomepageList()
 		),
 		array(
 			'name' => 'Tautulli',
-			'enabled' => true,
+			'enabled' => (strpos('personal', $GLOBALS['license']) !== false) ? true : false,
 			'image' => 'plugins/images/tabs/tautulli.png',
 			'category' => 'Monitor',
 			'settings' => array(
@@ -2940,7 +2940,7 @@ function getHomepageList()
 						'type' => 'html',
 						'override' => 6,
 						'label' => 'Info',
-						'html' => '<p>This homepage item requires <a href="https://github.com/henrywhitaker3/Speedtest-Tracker" target="_blank" rel="noreferrer noopener">Speedtest-Tracker</a> to be running on your network.</p>'
+						'html' => '<p>This homepage item requires <a href="https://github.com/henrywhitaker3/Speedtest-Tracker" target="_blank" rel="noreferrer noopener">Speedtest-Tracker <i class="fa fa-external-link" aria-hidden="true"></i></a> to be running on your network.</p>'
 					),
 					array(
 						'type' => 'switch',
@@ -2983,297 +2983,7 @@ function getHomepageList()
 				),
 			)
 		),
-		array(
-			'name' => 'Netdata',
-			'enabled' => true,
-			'image' => 'plugins/images/tabs/netdata.png',
-			'category' => 'Monitor',
-			'settings' => array(
-				'Enable' => array(
-					array(
-						'type' => 'switch',
-						'name' => 'homepageNetdataEnabled',
-						'label' => 'Enable',
-						'value' => $GLOBALS['homepageNetdataEnabled']
-					),
-					array(
-						'type' => 'select',
-						'name' => 'homepageNetdataAuth',
-						'label' => 'Minimum Authentication',
-						'value' => $GLOBALS['homepageNetdataAuth'],
-						'options' => $groups
-					)
-				),
-				'Connection' => array(
-					array(
-						'type' => 'html',
-						'override' => 12,
-						'label' => 'Info',
-						'html' => 'The URL needs to be on the same domain as your Organizr, and be proxied by subdomain. E.g. If Organizr is accessed at: https://domain.com, then your URL for netdata should be: https://netdata.domain.com'
-					),
-					array(
-						'type' => 'input',
-						'name' => 'netdataURL',
-						'label' => 'URL',
-						'value' => $GLOBALS['netdataURL'],
-						'help' => 'Please enter the local IP:PORT of your netdata instance'
-					),
-					array(
-						'type' => 'blank',
-						'label' => ''
-					),
-				),
-				'Chart 1' => array(
-					array(
-						'type' => 'switch',
-						'name' => 'netdata1Enabled',
-						'label' => 'Enable',
-						'value' => $GLOBALS['netdata1Enabled']
-					),
-					array(
-						'type' => 'blank',
-						'label' => ''
-					),
-					array(
-						'type' => 'input',
-						'name' => 'netdata1Title',
-						'label' => 'Title',
-						'value' => $GLOBALS['netdata1Title'],
-						'help' => 'Title for the netdata graph'
-					),
-					array(
-						'type' => 'select',
-						'name' => 'netdata1Data',
-						'label' => 'Data',
-						'value' => $GLOBALS['netdata1Data'],
-						'options' => netdataOptions(),
-					),
-					array(
-						'type' => 'select',
-						'name' => 'netdata1Chart',
-						'label' => 'Chart',
-						'value' => $GLOBALS['netdata1Chart'],
-						'options' => netdataChartOptions(),
-					),
-					array(
-						'type' => 'select',
-						'name' => 'netdata1Colour',
-						'label' => 'Colour',
-						'value' => $GLOBALS['netdata1Colour'],
-						'options' => netdataColourOptions(),
-					),
-				),
-				'Chart 2' => array(
-					array(
-						'type' => 'switch',
-						'name' => 'netdata2Enabled',
-						'label' => 'Enable',
-						'value' => $GLOBALS['netdata2Enabled']
-					),
-					array(
-						'type' => 'blank',
-						'label' => ''
-					),
-					array(
-						'type' => 'input',
-						'name' => 'netdata2Title',
-						'label' => 'Title',
-						'value' => $GLOBALS['netdata2Title'],
-						'help' => 'Title for the netdata graph'
-					),
-					array(
-						'type' => 'select',
-						'name' => 'netdata2Data',
-						'label' => 'Data',
-						'value' => $GLOBALS['netdata2Data'],
-						'options' => netdataOptions(),
-					),
-					array(
-						'type' => 'select',
-						'name' => 'netdata2Chart',
-						'label' => 'Chart',
-						'value' => $GLOBALS['netdata2Chart'],
-						'options' => netdataChartOptions(),
-					),
-					array(
-						'type' => 'select',
-						'name' => 'netdata2Colour',
-						'label' => 'Colour',
-						'value' => $GLOBALS['netdata2Colour'],
-						'options' => netdataColourOptions(),
-					),
-				),
-				'Chart 3' => array(
-					array(
-						'type' => 'switch',
-						'name' => 'netdata3Enabled',
-						'label' => 'Enable',
-						'value' => $GLOBALS['netdata3Enabled']
-					),
-					array(
-						'type' => 'blank',
-						'label' => ''
-					),
-					array(
-						'type' => 'input',
-						'name' => 'netdata3Title',
-						'label' => 'Title',
-						'value' => $GLOBALS['netdata3Title'],
-						'help' => 'Title for the netdata graph'
-					),
-					array(
-						'type' => 'select',
-						'name' => 'netdata3Data',
-						'label' => 'Data',
-						'value' => $GLOBALS['netdata3Data'],
-						'options' => netdataOptions(),
-					),
-					array(
-						'type' => 'select',
-						'name' => 'netdata3Chart',
-						'label' => 'Data',
-						'value' => $GLOBALS['netdata3Chart'],
-						'options' => netdataChartOptions(),
-					),
-					array(
-						'type' => 'select',
-						'name' => 'netdata3Colour',
-						'label' => 'Colour',
-						'value' => $GLOBALS['netdata3Colour'],
-						'options' => netdataColourOptions(),
-					),
-				),
-				'Chart 4' => array(
-					array(
-						'type' => 'switch',
-						'name' => 'netdata4Enabled',
-						'label' => 'Enable',
-						'value' => $GLOBALS['netdata4Enabled']
-					),
-					array(
-						'type' => 'blank',
-						'label' => ''
-					),
-					array(
-						'type' => 'input',
-						'name' => 'netdata4Title',
-						'label' => 'Title',
-						'value' => $GLOBALS['netdata4Title'],
-						'help' => 'Title for the netdata graph'
-					),
-					array(
-						'type' => 'select',
-						'name' => 'netdata4Data',
-						'label' => 'Data',
-						'value' => $GLOBALS['netdata4Data'],
-						'options' => netdataOptions(),
-					),
-					array(
-						'type' => 'select',
-						'name' => 'netdata4Chart',
-						'label' => 'Chart',
-						'value' => $GLOBALS['netdata4Chart'],
-						'options' => netdataChartOptions(),
-					),
-					array(
-						'type' => 'select',
-						'name' => 'netdata4Colour',
-						'label' => 'Colour',
-						'value' => $GLOBALS['netdata4Colour'],
-						'options' => netdataColourOptions(),
-					),
-				),
-				'Chart 5' => array(
-					array(
-						'type' => 'switch',
-						'name' => 'netdata5Enabled',
-						'label' => 'Enable',
-						'value' => $GLOBALS['netdata5Enabled']
-					),
-					array(
-						'type' => 'blank',
-						'label' => ''
-					),
-					array(
-						'type' => 'input',
-						'name' => 'netdata5Title',
-						'label' => 'Title',
-						'value' => $GLOBALS['netdata5Title'],
-						'help' => 'Title for the netdata graph'
-					),
-					array(
-						'type' => 'select',
-						'name' => 'netdata5Data',
-						'label' => 'Data',
-						'value' => $GLOBALS['netdata5Data'],
-						'options' => netdataOptions(),
-					),
-					array(
-						'type' => 'select',
-						'name' => 'netdata5Chart',
-						'label' => 'Chart',
-						'value' => $GLOBALS['netdata5Chart'],
-						'options' => netdataChartOptions(),
-					),
-					array(
-						'type' => 'select',
-						'name' => 'netdata5Colour',
-						'label' => 'Colour',
-						'value' => $GLOBALS['netdata5Colour'],
-						'options' => netdataColourOptions(),
-					),
-				),
-				'Chart 6' => array(
-					array(
-						'type' => 'switch',
-						'name' => 'netdata6Enabled',
-						'label' => 'Enable',
-						'value' => $GLOBALS['netdata6Enabled']
-					),
-					array(
-						'type' => 'blank',
-						'label' => ''
-					),
-					array(
-						'type' => 'input',
-						'name' => 'netdata6Title',
-						'label' => 'Title',
-						'value' => $GLOBALS['netdata6Title'],
-						'help' => 'Title for the netdata graph'
-					),
-					array(
-						'type' => 'select',
-						'name' => 'netdata6Data',
-						'label' => 'Data',
-						'value' => $GLOBALS['netdata6Data'],
-						'options' => netdataOptions(),
-					),
-					array(
-						'type' => 'select',
-						'name' => 'netdata6Chart',
-						'label' => 'Chart',
-						'value' => $GLOBALS['netdata6Chart'],
-						'options' => netdataChartOptions(),
-					),
-					array(
-						'type' => 'select',
-						'name' => 'netdata6Colour',
-						'label' => 'Colour',
-						'value' => $GLOBALS['netdata6Colour'],
-						'options' => netdataColourOptions(),
-					),
-				),
-				'Options' => array(
-					array(
-						'type' => 'select',
-						'name' => 'homepageNetdataRefresh',
-						'label' => 'Refresh Seconds',
-						'value' => $GLOBALS['homepageNetdataRefresh'],
-						'options' => optionTime()
-					),
-				),
-			)
-		),
+		netdataSettngsArray()
 	);
 }
 

+ 354 - 14
api/functions/netdata-functions.php

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

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

@@ -79,6 +79,7 @@ function parseDomain($value, $force = false)
 // Cookie Custom Function
 function coookie($type, $name, $value = '', $days = -1, $http = true)
 {
+	$days = ($days > 365) ? 365 : $days;
 	if (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] == "https") {
 		$Secure = true;
 		$HTTPOnly = true;

+ 31 - 5
api/functions/option-functions.php

@@ -176,13 +176,21 @@ function netdataOptions()
 			'value' => 'ram-used',
 		],
 		[
-			'name' => 'IPMI Temperature C',
-			'value' => 'ipmi-temp-c'
+			'name' => 'Used Swap',
+			'value' => 'swap-used',
 		],
 		[
-			'name' => 'IPMI Temperature F',
-			'value' => 'ipmi-temp-f'
+			'name' => 'Disk space used',
+			'value' => 'disk-used',
 		],
+		[
+			'name' => 'Disk space available',
+			'value' => 'disk-avail',
+		],
+		[
+			'name' => 'Custom',
+			'value' => 'custom',
+		]
 	];
 }
 
@@ -209,7 +217,7 @@ function netdataColourOptions()
 		],
 		[
 			'name' => 'Green',
-			'value' => 'DD4400',
+			'value' => '46e302',
 		],
 		[
 			'name' => 'Purple',
@@ -228,4 +236,22 @@ function netdataColourOptions()
 			'value' => 'd66300',
 		]
 	];
+}
+
+function netdataSizeOptions()
+{
+	return [
+		[
+			'name' => 'Large',
+			'value' => 'lg',
+		],
+		[
+			'name' => 'Medium',
+			'value' => 'md',
+		],
+		[
+			'name' => 'Small',
+			'value' => 'sm'
+		]
+	];
 }

+ 74 - 7
api/functions/organizr-functions.php

@@ -131,6 +131,11 @@ function organizrSpecialSettings()
 			'debugArea' => qualifyRequest($GLOBALS['debugAreaAuth']),
 			'debugErrors' => $GLOBALS['debugErrors'],
 			'sandbox' => $GLOBALS['sandbox'],
+		),
+		'menuLink' => array(
+			'githubMenuLink' => $GLOBALS['githubMenuLink'],
+			'organizrSupportMenuLink' => $GLOBALS['organizrSupportMenuLink'],
+			'organizrDocsMenuLink' => $GLOBALS['organizrDocsMenuLink']
 		)
 	);
 }
@@ -684,6 +689,22 @@ function getSettingsMain()
 				'label' => 'Account DN',
 				'html' => '<span id="accountDN" class="ldapAuth switchAuth">' . $GLOBALS['authBackendHostPrefix'] . 'TestAcct' . $GLOBALS['authBackendHostSuffix'] . '</span>'
 			),
+			array(
+				'type' => 'switch',
+				'name' => 'ldapSSL',
+				'class' => 'ldapAuth switchAuth',
+				'label' => 'Enable LDAP SSL',
+				'value' => $GLOBALS['ldapSSL'],
+				'help' => 'This will enable the use of SSL for LDAP connections'
+			),
+			array(
+				'type' => 'switch',
+				'name' => 'ldapSSL',
+				'class' => 'ldapAuth switchAuth',
+				'label' => 'Enable LDAP TLS',
+				'value' => $GLOBALS['ldapTLS'],
+				'help' => 'This will enable the use of TLS for LDAP connections'
+			),
 			array(
 				'type' => 'button',
 				'name' => 'test-button-ldap',
@@ -825,6 +846,10 @@ function getSettingsMain()
 						'name' => 'Allow Top Navigation',
 						'value' => 'allow-top-navigation'
 					),
+					array(
+						'name' => 'Allow Downloads',
+						'value' => 'allow-downloads'
+					),
 				)
 			),
 			array(
@@ -1236,6 +1261,24 @@ function getCustomizeAppearance()
 					'label' => 'Show Debug Errors',
 					'value' => $GLOBALS['debugErrors']
 				),
+				array(
+					'type' => 'switch',
+					'name' => 'githubMenuLink',
+					'label' => 'Show GitHub Repo Link',
+					'value' => $GLOBALS['githubMenuLink']
+				),
+				array(
+					'type' => 'switch',
+					'name' => 'organizrSupportMenuLink',
+					'label' => 'Show Organizr Support Link',
+					'value' => $GLOBALS['organizrSupportMenuLink']
+				),
+				array(
+					'type' => 'switch',
+					'name' => 'organizrDocsMenuLink',
+					'label' => 'Show Organizr Docs Link',
+					'value' => $GLOBALS['organizrDocsMenuLink']
+				),
 				array(
 					'type' => 'select',
 					'name' => 'unsortedTabs',
@@ -1787,16 +1830,35 @@ function showoAuth()
 
 function getImages()
 {
+	$allIconsPrep = array();
+	$allIcons = array();
+	$ignore = array(".", "..", "._.DS_Store", ".DS_Store", ".pydio_id", "index.html");
 	$dirname = dirname(__DIR__, 2) . DIRECTORY_SEPARATOR . 'plugins' . DIRECTORY_SEPARATOR . 'images' . DIRECTORY_SEPARATOR . 'tabs' . DIRECTORY_SEPARATOR;
 	$path = 'plugins/images/tabs/';
 	$images = scandir($dirname);
-	$ignore = array(".", "..", "._.DS_Store", ".DS_Store", ".pydio_id");
-	$allIcons = array();
 	foreach ($images as $image) {
 		if (!in_array($image, $ignore)) {
-			$allIcons[] = $path . $image;
+			$allIconsPrep[$image] = array(
+				'path' => $path,
+				'name' => $image
+			);
 		}
 	}
+	$dirname = dirname(__DIR__, 2) . DIRECTORY_SEPARATOR . 'plugins' . DIRECTORY_SEPARATOR . 'images' . DIRECTORY_SEPARATOR . 'userTabs' . DIRECTORY_SEPARATOR;
+	$path = 'plugins/images/userTabs/';
+	$images = scandir($dirname);
+	foreach ($images as $image) {
+		if (!in_array($image, $ignore)) {
+			$allIconsPrep[$image] = array(
+				'path' => $path,
+				'name' => $image
+			);
+		}
+	}
+	ksort($allIconsPrep);
+	foreach ($allIconsPrep as $item) {
+		$allIcons[] = $item['path'] . $item['name'];
+	}
 	return $allIcons;
 }
 
@@ -1817,7 +1879,7 @@ function editImages()
 	$array = array();
 	$postCheck = array_filter($_POST);
 	$filesCheck = array_filter($_FILES);
-	$approvedPath = 'plugins/images/tabs/';
+	$approvedPath = 'plugins/images/userTabs/';
 	if (!empty($postCheck)) {
 		$removeImage = $approvedPath . pathinfo($_POST['data']['imagePath'], PATHINFO_BASENAME);
 		if ($_POST['data']['action'] == 'deleteImage' && approvedFileExtension($removeImage)) {
@@ -1831,7 +1893,7 @@ function editImages()
 		ini_set('upload_max_filesize', '10M');
 		ini_set('post_max_size', '10M');
 		$tempFile = $_FILES['file']['tmp_name'];
-		$targetPath = dirname(__DIR__, 2) . DIRECTORY_SEPARATOR . 'plugins' . DIRECTORY_SEPARATOR . 'images' . DIRECTORY_SEPARATOR . 'tabs' . DIRECTORY_SEPARATOR;
+		$targetPath = dirname(__DIR__, 2) . DIRECTORY_SEPARATOR . 'plugins' . DIRECTORY_SEPARATOR . 'images' . DIRECTORY_SEPARATOR . 'userTabs' . DIRECTORY_SEPARATOR;
 		$targetFile = $targetPath . $_FILES['file']['name'];
 		return (move_uploaded_file($tempFile, $targetFile)) ? true : false;
 	}
@@ -2640,9 +2702,14 @@ function settingsPathChecks()
 
 function dockerUpdate()
 {
+	$dockerUpdate = null;
 	chdir('/etc/cont-init.d/');
-	$dockerUpdate = shell_exec('./30-install');
-	return $dockerUpdate;
+	if (file_exists('./30-install')) {
+		$dockerUpdate = shell_exec('./30-install');
+	} elseif (file_exists('./40-install')) {
+		$dockerUpdate = shell_exec('./40-install');
+	}
+	return $dockerUpdate ?? 'Update Failed';
 }
 
 function windowsUpdate()

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

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

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

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

+ 13 - 1
css/organizr.css

@@ -1070,9 +1070,15 @@ input.has-success {
 ul.nav.customtab.nav-tabs.nav-low-margin {
     margin: -25px -25px 0px -25px !important;
 }
-i.fa.fa-life-ring.fa-fw {
+#menu-Organizr-Support i {
     color: #C62828;
 }
+#menu-GitHub-Repo i {
+    color: #2cabe3;
+}
+#menu-Organizr-Docs i {
+    color: #707cd2;
+}
 .ping {
     position: relative;
     margin-top: 0;
@@ -4387,3 +4393,9 @@ html {
 .noty_layout [data-name~="mojs-shape"] {
     pointer-events:none
 }
+.bottom-close-splash {
+    position: absolute;
+    width: 130px;
+    right: 20px;
+    bottom: 20px;
+}

Разница между файлами не показана из-за своего большого размера
+ 0 - 0
css/organizr.min.css


+ 3 - 3
index.php

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

+ 9 - 5
js/cbpFWTabs.js

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

+ 19 - 0
js/custom.js

@@ -1987,4 +1987,23 @@ $(document).on('click', ".ipInfo", function(){
 $(document).on('click', '.allGroupsList', function() {
     console.log($(this));
     $(this).toggleClass('active');
+});
+// Control init of custom netdata JSON editor
+$(document).on('click', 'li a[aria-controls="Custom data"]', function() {
+    var resizeEditor = function(jsonEditor) {
+        const aceEditor = jsonEditor;
+        const newHeight = aceEditor.getSession().getScreenLength() * (aceEditor.renderer.lineHeight + aceEditor.renderer.scrollBar.getWidth());
+        aceEditor.container.style.height = newHeight + 'px';
+        aceEditor.resize();
+    }
+
+    jsonEditor = ace.edit("netdataCustomTextAce");
+    var JsonMode = ace.require("ace/mode/javascript").Mode;
+    jsonEditor.session.setMode(new JsonMode());
+    jsonEditor.setTheme("ace/theme/idle_fingers");
+    jsonEditor.setShowPrintMargin(false);
+    jsonEditor.session.on('change', function(delta) {
+        $('#netdataCustomText').val(jsonEditor.getValue());
+        $('#customize-appearance-form-save').removeClass('hidden');
+    });
 });

Разница между файлами не показана из-за своего большого размера
+ 0 - 0
js/custom.min.js


+ 263 - 83
js/functions.js

@@ -204,7 +204,7 @@ function isNumberKey(evt) {
     return true;
 }
 function setTabInfo(tab,action,value){
-    if(tab == 'Organizr-Support'){
+    if(tab == 'Organizr-Support' || tab == 'Organizr-Docs'){
         return false;
     }
     if(tab !== null && action !== null && value !== null){
@@ -2582,14 +2582,18 @@ function userMenu(user){
 }
 function menuExtras(active){
     var supportFrame = buildFrameContainer('Organizr Support','https://organizr.app/support',1);
-    var adminMenu = (activeInfo.user.groupID <= 1) ? buildMenuList('Organizr Support','https://organizr.app/support',1,'fontawesome::life-ring'): '';
+    var docsFrame = buildFrameContainer('Organizr Docs','https://docs.organizr.app',1);
+    var adminMenu = (activeInfo.user.groupID <= 1 && activeInfo.settings.menuLink.githubMenuLink) ? buildMenuList('GitHub Repo','https://github.com/causefx/organizr',2,'fontawesome::github') : '';
+    adminMenu += (activeInfo.user.groupID <= 1 && activeInfo.settings.menuLink.organizrSupportMenuLink) ? buildMenuList('Organizr Support','https://organizr.app/support',1,'fontawesome::life-ring') : '';
+    adminMenu += (activeInfo.user.groupID <= 1 && activeInfo.settings.menuLink.organizrDocsMenuLink) ? buildMenuList('Organizr Docs','https://docs.organizr.app',1,'simpleline::docs') : '';
+
     $(supportFrame).appendTo($('.iFrame-listing'));
+    $(docsFrame).appendTo($('.iFrame-listing'));
 	if(active === true){
 		return `
 			<li class="devider"></li>
 			<li id="sign-out"><a class="waves-effect" onclick="logout();"><i class="fa fa-sign-out fa-fw"></i> <span class="hide-menu" lang="en">Logout</span></a></li>
 			<li class="devider"></li>
-			<li id="github"><a href="https://github.com/causefx/organizr" target="_blank" class="waves-effect"><i class="fa fa-github fa-fw text-success"></i> <span class="hide-menu">GitHub</span></a></li>
 		`+adminMenu;
 	}else{
 		return `
@@ -2795,7 +2799,7 @@ function buildSplashScreen(json){
         <section id="splashScreen" class="lock-screen splash-screen fade in">
             <div class="row p-20 flexbox">`+items+`</div>
             <div class="row p-20 p-t-0 flexbox">
-                <div class="col-xs-12 col-sm-12 col-md-12 col-lg-12 col-xl-12 mouse hvr-wobble-bottom" onclick="$('.splash-screen').addClass('hidden').removeClass('in')">
+                <div class="col-xs-12 col-sm-12 col-md-12 col-lg-12 col-xl-12 mouse hvr-wobble-bottom bottom-close-splash" onclick="$('.splash-screen').addClass('hidden').removeClass('in')">
                     <div class="homepage-drag fc-event bg-danger lazyload"  data-src="">
                         <span class="homepage-text">&nbsp; Close Splash</span>
                     </div>
@@ -3302,7 +3306,10 @@ function newsLoad(){
         try {
             var response = JSON.parse(data);
             var items = [];
+            var limit = 5;
+            var count = 0;
             $.each(response, function(i,v) {
+                count++;
                 var newBody = `
                 <h5 class="pull-left">`+moment(v.date).format('LLL')+`</h5>
                 <h5 class="pull-right">`+v.author+`</h5>
@@ -3310,9 +3317,11 @@ function newsLoad(){
                 `+((v.subTitle) ? '<h5>' + v.subTitle + '</h5>' : '' )+`
                 <p>`+v.body+`</p>
                 `;
-                items[i] = {
-                    title:v.title,
-                    body:newBody
+                if(count <= limit){
+                    items[i] = {
+                        title:v.title,
+                        body:newBody
+                    }
                 }
             });
             var body = buildAccordion(items, true);
@@ -3731,7 +3740,7 @@ function marketplaceJSON(type) {
 }
 function allIcons() {
     return $.ajax({
-        url: "/js/icons.json",
+        url: "js/icons.json",
     });
 }
 function organizrConnect(path){
@@ -4816,7 +4825,7 @@ function buildRequest(array){
 			<div class="white-box m-b-0 search-div resultBox-outside">
 				<div class="form-group m-b-0">
 					<div id="request-input-div" class="input-group">
-						<input id="request-input" lang="en" placeholder="Request Show or Movie" type="text" class="form-control inline-focus">
+						<input id="request-input" lang="en" placeholder="Request a Show or Movie" type="text" class="form-control inline-focus">
                         <input id="request-page" type="hidden" class="form-control">
                         <div class="input-group-btn">
                             <button type="button" class="btn waves-effect waves-light btn-info dropdown-toggle" data-toggle="dropdown" aria-expanded="false"><span lang="en">Suggestions</span> <span class="caret"></span></button>
@@ -6094,9 +6103,12 @@ function buildPiholeItem(array){
             <div class="card text-white mb-3 pihole-stat bg-green">
                 <div class="card-body">
                     <div class="inline-block">
-                        <p>Total queries</p>`;
+                        <p class="d-inline mr-1">Total queries</p>`;
         for(var key in data) {
             var e = data[key];
+            if(length > 1 && !combine) {
+                card += `<p class="d-inline text-muted">(`+key+`)</p>`;
+            }
             card += `<h3 data-toggle="tooltip" data-placement="right" title="`+key+`">`+e['dns_queries_today'].toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",")+`</h3>`;
         };
         card += `
@@ -6114,9 +6126,12 @@ function buildPiholeItem(array){
             <div class="card bg-inverse text-white mb-3 pihole-stat bg-aqua">
                 <div class="card-body">
                     <div class="inline-block">
-                        <p>Queries Blocked</p>`;
+                        <p class="d-inline mr-1">Queries Blocked</p>`;
         for(var key in data) {
             var e = data[key];
+            if(length > 1 && !combine) {
+                card += `<p class="d-inline text-muted">(`+key+`)</p>`;
+            }
             card += `<h3 data-toggle="tooltip" data-placement="right" title="`+key+`">`+e['ads_blocked_today'].toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",")+`</h3>`;
         };
         card += `
@@ -6134,9 +6149,12 @@ function buildPiholeItem(array){
             <div class="card bg-inverse text-white mb-3 pihole-stat bg-yellow">
                 <div class="card-body">
                     <div class="inline-block">
-                        <p>Percent Blocked</p>`;
+                        <p class="d-inline mr-1">Percent Blocked</p>`;
         for(var key in data) {
             var e = data[key];
+            if(length > 1 && !combine) {
+                card += `<p class="d-inline text-muted">(`+key+`)</p>`;
+            }
             card += `<h3 data-toggle="tooltip" data-placement="right" title="`+key+`">`+e['ads_percentage_today'].toFixed(1)+`%</h3>`
         };
         card += `
@@ -6154,9 +6172,12 @@ function buildPiholeItem(array){
             <div class="card bg-inverse text-white mb-3 pihole-stat bg-red">
                 <div class="card-body">
                     <div class="inline-block">
-                        <p>Domains on Blocklist</p>`;
+                        <p class="d-inline mr-1">Domains on Blocklist</p>`;
         for(var key in data) {
             var e = data[key];
+            if(length > 1 && !combine) {
+                card += `<p class="d-inline text-muted">(`+key+`)</p>`;
+            }
             card += `<h3 data-toggle="tooltip" data-placement="right" title="`+key+`">`+e['domains_being_blocked'].toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",")+`</h3>`;
         };
         card += `
@@ -6177,15 +6198,6 @@ function buildPiholeItem(array){
         stats += '</div>';
     } else {
         for(var key in array['data']) {
-            if(length > 1) {
-                stats += `
-                <div class="row mb-2">
-                    <div class="col-sm-12">
-                        `+key+`
-                    </div>
-                </div>
-                `;
-            }
             var data = array['data'][key];
             obj = {};
             obj[key] = data;
@@ -6588,13 +6600,13 @@ function buildTautulliItem(array){
             var section_name = null;
             if(type == 'movie'){
                 extraField = 'Movies';
-                section_name = 'Movie Libaries';
+                section_name = 'Movie Libraries';
             }else if(type == 'show'){
                 extraField = 'Shows/Seasons/Episodes';
-                section_name = 'TV Show Libaries';
+                section_name = 'TV Show Libraries';
             }else if(type == 'artist'){
                 extraField = 'Artists/Albums/Tracks';
-                section_name = 'Music Libaries';
+                section_name = 'Music Libraries';
             }
             var cardTitle = '<th><span class="pull-left cardTitle">'+section_name.toUpperCase()+'</span><span class="pull-right cardCountType">'+extraField.toUpperCase()+'</th>';
             var card = `
@@ -7129,8 +7141,13 @@ function buildMonitorrItem(array){
     var services = array['services'];
 
     var buildCard = function(name, data) {
-        if(data.status) { var statusColor = 'success'; var imageText = 'fa fa-check-circle text-success' } 
-            else { var statusColor = 'danger animated-3 loop-animation flash'; var imageText = 'fa fa-times-circle text-danger'}
+        if(data.status == true) {
+            var statusColor = 'success'; var imageText = 'fa fa-check-circle text-success'
+        } else if (data.status == 'unresponsive') {
+            var statusColor = 'warning animated-3 loop-animation flash'; var imageText = 'fa fa-times-circle text-warning'
+        } else {
+            var statusColor = 'danger animated-3 loop-animation flash'; var imageText = 'fa fa-times-circle text-danger'
+        }
         if(options['compact']) {
             var card = `
             <div class="col-xl-2 col-lg-3 col-md-4 col-sm-6 col-xs-12">
@@ -7160,7 +7177,7 @@ function buildMonitorrItem(array){
                             <img class="monitorrImage" src="`+data.image+`" alt="service icon">
                         </div>
                         <div class="d-inline-block mt-4 py-2 px-4 badge indicator bg-`+statusColor+`">
-                            <p class="mb-0">`; if(data.status) { card += 'ONLINE' } else { card += 'OFFLINE' } card+=`</p>
+                            <p class="mb-0">`; if(data.status == true) { card += 'ONLINE' } else if(data.status == 'unresponsive') { card += 'UNRESPONSIVE' } else { card += 'OFFLINE' } card+=`</p>
                         </div>
                         `; if (typeof data.link !== 'undefined') { card +=`</a>`; }
                         card += `</div>
@@ -7177,27 +7194,31 @@ function buildMonitorrItem(array){
 }
 function buildMonitorr(array){
     if(array === false){ return ''; }
-    var services = (typeof array.services !== 'undefined') ? Object.keys(array.services).length : false;
-    var html = `
-    <div id="allMonitorr">
-		<div class="el-element-overlay row">`
-    if(array['options']['titleToggle']) {
+    if(array.error != undefined) {
+        console.log('Monitorr error: ' + array.error);
+    } else {
+        var services = (typeof array.services !== 'undefined') ? Object.keys(array.services).length : false;
+        var html = `
+        <div id="allMonitorr">
+            <div class="el-element-overlay row">`
+        if(array['options']['titleToggle']) {
+            html += `
+                <div class="col-md-12">
+                    <h4 class="pull-left homepage-element-title"><span lang="en">`+array['options']['title']+`</span> : </h4><h4 class="pull-left">&nbsp;<span class="label label-info m-l-20 checkbox-circle good-monitorr-services mouse" onclick="homepageMonitorr()">`+services+`</span></h4></h4>
+                    <hr class="hidden-xs ml-2">
+                </div>
+                <div class="clearfix"></div>
+            `;
+        }
         html += `
-            <div class="col-md-12">
-                <h4 class="pull-left homepage-element-title"><span lang="en">`+array['options']['title']+`</span> : </h4><h4 class="pull-left">&nbsp;<span class="label label-info m-l-20 checkbox-circle good-monitorr-services mouse" onclick="homepageMonitorr()">`+services+`</span></h4></h4>
-                <hr class="hidden-xs ml-2">
+                <div class="monitorrCards">
+                    `+buildMonitorrItem(array)+`
+                </div>
             </div>
-            <div class="clearfix"></div>
+        </div>
+        <div class="clearfix"></div>
         `;
     }
-    html += `
-            <div class="monitorrCards">
-                `+buildMonitorrItem(array)+`
-			</div>
-		</div>
-    </div>
-    <div class="clearfix"></div>
-    `;
     return (array) ? html : '';
 }
 function homepageMonitorr(timeout){
@@ -7376,10 +7397,6 @@ function buildSpeedtest(array){
 function buildNetdataItem(array){
     var html = `
     <style>
-    .all-netdata .chart {
-        width: 183px;
-        height:183px;
-    }
     .all-netdata .easyPieChart-value {
         position: absolute;
         top: 77px;
@@ -7391,11 +7408,9 @@ function buildNetdataItem(array){
     }
     .all-netdata .easyPieChart-title {
         position: absolute;
-        top: 37px;
         width: 100%;
         text-align: center;
         left: 0;
-        font-size: 15px;
         font-weight: bold;
     }
     .all-netdata .easyPieChart-units {
@@ -7409,7 +7424,7 @@ function buildNetdataItem(array){
     }
 
     .all-netdata .gauge-chart .gauge-value {
-        position: absolute;
+        position: relative;
         width: 100%;
         text-align: center;
         top: 30px;
@@ -7420,7 +7435,7 @@ function buildNetdataItem(array){
     }
 
     .all-netdata .gauge-chart .gauge-title {
-        position: absolute;
+        position: relative;
         width: 100%;
         text-align: center;
         top: -10px;
@@ -7429,45 +7444,173 @@ function buildNetdataItem(array){
         left: 0;
         font-size: 15px;
     }
+
+    .all-netdata .chart-lg .gauge-chart .gauge-value {
+        top: 70px;
+        font-size: 26px;
+    }
+
+    .all-netdata .chart-lg .gauge-chart .gauge-title {
+        top: 45px;
+        font-size: 15px;
+    }
+
+    .all-netdata .chart-md .gauge-chart .gauge-value {
+        top: 65px;
+        font-size: 26px;
+    }
+
+    .all-netdata .chart-md .gauge-chart .gauge-title {
+        top: 45px;
+        font-size: 15px;
+    }
+
+    .all-netdata .chart-sm .gauge-chart .gauge-value {
+        top: 65px;
+        font-size: 26px;
+    }
+
+    .all-netdata .chart-sm .gauge-chart .gauge-title {
+        top: 45px;
+        font-size: 15px;
+    }
+
+    .all-netdata .chart-lg,
+    .all-netdata .chart-md,
+    .all-netdata .chart-sm {
+        display: inline-block;
+        margin: 15px;
+    }
+
+    .all-netdata .chart-lg,
+    .all-netdata .chart-lg .chart {
+        height: 180px;
+        width: 180px;
+    }
+
+    .all-netdata .chart-md,
+    .all-netdata .chart-md .chart {
+        height: 160px;
+        width: 160px;
+    }
+
+    .all-netdata .chart-sm,
+    .all-netdata .chart-sm .chart {
+        height: 140px;
+        width: 140px;
+    }
+
+    .all-netdata .chart-lg .gauge-chart,
+    .all-netdata .gauge-cont.chart-lg {
+        //height: 300px;
+        width: 300px;
+    }
+
+    .all-netdata .chart-md .gauge-chart,
+    .all-netdata .gauge-cont.chart-md {
+        //height: 275px;
+        width: 275px;
+    }
+
+    .all-netdata .chart-sm .gauge-chart,
+    .all-netdata .gauge-cont.chart-sm {
+        //height: 250px;
+        width: 250px;
+    }
+
+    .all-netdata .chart-lg .easyPieChart-title {
+        top: 37px;
+        font-size: 15px;
+    }
+
+    .all-netdata .chart-md .easyPieChart-title {
+        top: 33px;
+        font-size: 13.5px;
+    }
+
+    .all-netdata .chart-sm .easyPieChart-title {
+        top: 30px;
+        font-size: 12px;
+    }
+
+    .all-netdata .chart-lg .easyPieChart-value {
+        top: 75px;
+        font-size: 24.4625px;
+    }
+
+    .all-netdata .chart-md .easyPieChart-value {
+        top: 65px;
+        font-size: 24.4625px;
+    }
+
+    .all-netdata .chart-sm .easyPieChart-value {
+        top: 55px;
+        font-size: 24.4625px;
+    }
+
+    .all-netdata .chart-lg .easyPieChart-units {
+        top: 130px;
+        font-size: 15px;
+    }
+
+    .all-netdata .chart-md .easyPieChart-units {
+        top: 108px;
+        font-size: 15px;
+    }
+
+    .all-netdata .chart-sm .easyPieChart-units {
+        top: 95px;
+        font-size: 15px;
+    }
     </style>
     `;
 
-    var buildEasyPieChart = function(e,i) {
+    var buildEasyPieChart = function(e,i,size,easySize,display) {
         return `
-        <div class="col-lg-2 col-md-3 col-sm-4 col-xs-12 my-3 text-center">
-            <div class="d-flex justify-content-center">
-                <div class="chart" id="easyPieChart`+(i+1)+`" data-percent="`+e.percent+`">
-                    <span class="easyPieChart-title">`+e.title+`</span>
-                    <span class="easyPieChart-value" id="easyPieChart`+(i+1)+`Value">`+parseFloat(e.value).toFixed(1)+`</span>
-                    <span class="easyPieChart-units" id="easyPieChart`+(i+1)+`Units">`+e.units+`</span>
-                </div>
+        <div class="chart-`+size+` my-3 text-center `+display+`">
+            <div class="chart" id="easyPieChart`+(i+1)+`" data-percent="`+e.percent+`">
+                <span class="easyPieChart-title">`+e.title+`</span>
+                <span class="easyPieChart-value" id="easyPieChart`+(i+1)+`Value">`+parseFloat(e.value).toFixed(1)+`</span>
+                <span class="easyPieChart-units" id="easyPieChart`+(i+1)+`Units">`+e.units+`</span>
             </div>
         </div>
         <script>
         $(function() {
-            $('#easyPieChart`+(i+1)+`').easyPieChart({
-                size: 183,
+            var opts = {
+                size: `+easySize+`,
                 lineWidth: 7,
-                //animate: false,
                 scaleColor: false,
                 barColor: '#`+e.colour+`',
-                trackColor: '#bababa',
-            });
+                trackColor: '#636363',
+            };
+            if(`+e.percent+` == 0) {
+                opts.lineCap = 'butt';
+            }
+            $('#easyPieChart`+(i+1)+`').easyPieChart(opts);
         });
         </script>
         `;
     }
 
-    var buildGaugeChart = function(e,i) {
+    var buildGaugeChart = function(e,i,size,easySize,display) {
+        switch(size) {
+            case 'lg':
+                easySize = 300;
+                break;
+            case 'sm':
+                easySize = 275;
+                break;
+            case 'md':
+            default:
+                easySize = 250;
+                break;
+        }
         return `
-        <div class="col-lg-2 col-md-3 col-sm-4 col-xs-12 my-3 text-center">
-            <div class="d-flex justify-content-center">
-                <div class="gauge-chart">
-                    <span class="gauge-title" id="gaugeChart`+(i+1)+`Title">`+e.title+`</span>
-                    <span class="gauge-value" id="gaugeChart`+(i+1)+`Value">`+parseFloat(e.value).toFixed(1)+`</span>
-                    <canvas id="gaugeChart`+(i+1)+`">
-                    </canvas>
-                </div>
+        <div class="mx-0 gauge-cont chart-`+size+` my-3 text-center `+display+`">
+            <div class="gauge-chart text-center">
+                <span class="gauge-title d-block" id="gaugeChart`+(i+1)+`Title">`+e.title+`</span>
+                <span class="gauge-value d-block" id="gaugeChart`+(i+1)+`Value">`+parseFloat(e.value).toFixed(1)+`</span>
+                <canvas id="gaugeChart`+(i+1)+`" style="width: 100%"></canvas>
             </div>
         </div>
         <script>
@@ -7494,7 +7637,7 @@ function buildNetdataItem(array){
             var gauge = new Gauge(target).setOptions(opts); // create sexy gauge!
             gauge.maxValue = `+e.max+`; // set max gauge value
             gauge.setMinValue(0);  // Prefer setter over gauge.minValue = 0
-            gauge.animationSpeed = 20; // set animation speed (32 is default value)
+            gauge.animationSpeed = 8; // set animation speed (32 is default value)
             gauge.set(`+e.percent+`); // set actual value
             window.netdata[`+(i+1)+`] = gauge
         });
@@ -7503,17 +7646,54 @@ function buildNetdataItem(array){
     }
 
     array.forEach((e, i) => {
-        if(e.chart == 'easypiechart') {
-            html += buildEasyPieChart(e,i);
+        var size = e.size;
+        var easySize;
+        if(size == '') {
+            size = 'md';
+        }
+        switch(size) {
+            case 'lg':
+                easySize = 180;
+                break;
+            case 'sm':
+                easySize = 140;
+                break;
+            case 'md':
+            default:
+                easySize = 160;
+                break;
+        }
+
+        var display = ' ';
+        if(e.lg) {
+            display += ' d-xl-inline-block d-lg-inline-block';
+        } else {
+            display += ' d-xl-none d-lg-none d-none';
+        }
+        if(e.md) {
+            display += ' d-md-inline-block';
+        } else {
+            display += ' d-md-none d-none';
+        }
+        if(e.sm) {
+            display += ' d-sm-inline-block d-xs-inline-block';
+        } else {
+            display += ' d-sm-none d-xs-none d-none';
+        }
+        display += ' ';
+
+        if(e.error) {
+            console.log('Netdata error (Chart ' + (i+1) + '): ' + e.error);
+        } else if(e.chart == 'easypiechart') {
+            html += buildEasyPieChart(e,i,size,easySize,display);
         } else if(e.chart == 'gauge') {
-            html += buildGaugeChart(e,i);
+            html += buildGaugeChart(e,i,size,easySize,display);
         }
     });
     
     return html;
 }
 function buildNetdata(array){
-    // console.log(array);
     var data = array.data;
     if(array === false){ return ''; }
     window.netdata = [];
@@ -7545,7 +7725,7 @@ function buildNetdata(array){
       
       .all-netdata .chart {
           float: left;
-          margin: 10px;
+          //margin: 10px;
       }
       
       .all-netdata .percentage,
@@ -7655,7 +7835,7 @@ function buildNetdata(array){
     html += `
     <div class="row">
         
-            <div class="d-lg-flex d-md-block d-sm-block d-xs-block align-items-center justify-content-center all-netdata">
+            <div class="d-block text-center all-netdata">
     `;
     html += buildNetdataItem(data);
     html += `
@@ -7702,7 +7882,7 @@ function tryUpdateNetdata(array){
             if(window.netdata) {
                 if(window.netdata[(i+1)]) {
                     window.netdata[(i+1)].set(e.percent); // set actual value
-                    $('#gaugeChart' + (i+1) + 'Value').html(parseFloat(e.percent).toFixed(1));
+                    $('#gaugeChart' + (i+1) + 'Value').html(parseFloat(e.value).toFixed(1));
                     existing = true;
                 }
             } else {

+ 7 - 0
js/news.json

@@ -1,4 +1,11 @@
 [
+  {
+    "title": "Plex OAuth Patch",
+    "subTitle": "You server hostname will now be listed on OAuth box",
+    "date": "2020-06-18 16:30",
+    "body": "Plex has now fixed the 3 open CVE's pertaining to the ability for an attacker to phish for an Plex Admins token.  Please follow this link to read more about it.  <a href=\"https:\/\/www.bleepingcomputer.com\/news\/security\/plex-fixes-media-server-bugs-allowing-full-system-takeover\" target=\"_blank\">Plex Vulnerabilities Patched<\/a>",
+    "author": "CauseFX"
+  },
   {
     "title": "Tab Redirect Loops and SameSite Cookie Issues",
     "subTitle": "New Browser Restrictions",

+ 7 - 0
js/version.json

@@ -250,5 +250,12 @@
     "new": "Weather Homepage Item|Pi-Hole Homepage Item|Tautulli Homepage Item|Monitorr Homepage Item|Netdata Homepage Item|SpeedTest Homepage Item|HealthChecks Plugin|Bandwidth info to Now Playing|Config files can now contain arrays|Scrape API endpoint|New API Docs WIP located at /api/docs",
     "fixed": "iOS icon issue|Language Bug|Cache Image timing|fix hardcoded plugins directory for root (#1341)|Ombi Posters|Plex Timeouts|Indent cleanup|Homepage Item shortcut in Tab Editor broken (#1368)|Empty login form submitting|Sanitize username going into log (#1359)|Ombi Div for tv items|edit sttabs for reflect more than just settings tabs|Emby HomePage Add-in - Item Details URL Needs updating (#1290)|Fix JQuery error on settings save (#1356)",
     "notes": "Added note about Samesite issues|New Radarr Logo|Please report bugs in GitHub issues page"
+  },
+  "2.0.631": {
+    "date": "2020-05-01 19:15",
+    "title": "Whoa - sorry for the wait",
+    "new": "Added css bottom-close-splash",
+    "fixed": "Netadata fixes|Docker update fixes|Tautulli Fixes|Hardcode fixes for days beyond one year|Typos|NZBHydra tab image|fix stray / in index.php and js/functions.php (#1408)|LDAP with STARTTLS support (#1411)",
+    "notes": "Please report bugs in GitHub issues page"
   }
 }

BIN
plugins/images/tabs/nzbhydra.png


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

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

Некоторые файлы не были показаны из-за большого количества измененных файлов