Jelajahi Sumber

Added weather homepage item - WIP - air and pollen not complete yet

CauseFX 6 tahun lalu
induk
melakukan
46159a1f62

+ 12 - 0
api/config/default.php

@@ -139,6 +139,17 @@ return array(
 	'ombiLimitUser' => false,
 	'ombiRefresh' => '600000',
 	'ombiTvDefault' => 'all',
+	'homepageWeatherAndAirEnabled' => false,
+	'homepageWeatherAndAirAuth' => '1',
+	'homepageWeatherAndAirRefresh' => '3600000',
+	'homepageWeatherAndAirLatitude' => '',
+	'homepageWeatherAndAirLongitude' => '',
+	'homepageWeatherAndAirUnits' => 'imperial',
+	'homepageWeatherAndAirPollenEnabled' => false,
+	'homepageWeatherAndAirAirQualityEnabled' => false,
+	'homepageWeatherAndAirWeatherEnabled' => false,
+	'homepageWeatherAndAirWeatherHeader' => '',
+	'homepageWeatherAndAirWeatherHeaderToggle' => false,
 	'homepageHealthChecksEnabled' => false,
 	'healthChecksToken' => '',
 	'healthChecksTags' => '',
@@ -165,6 +176,7 @@ return array(
 	'homepageOrderPihole' => '20',
 	'homepageOrdertautulli' => '21',
 	'homepageOrderMonitorr' => '22',
+	'homepageOrderWeatherAndAir' => '23',
 	'homepageShowStreamNames' => false,
 	'homepageShowStreamNamesAuth' => '1',
 	'homepageStreamRefresh' => '60000',

+ 69 - 30
api/functions/homepage-connect-functions.php

@@ -68,6 +68,9 @@ function homepageConnect($array)
 		case 'getMonitorr':
 			return getMonitorr();
 			break;
+		case 'getWeatherAndAir':
+			return getWeatherAndAir();
+			break;
 		default:
 			# code...
 			break;
@@ -92,6 +95,55 @@ function healthChecksTags($tags)
 	}
 }
 
+function getWeatherAndAir()
+{
+	if ($GLOBALS['homepageWeatherAndAirEnabled'] && !empty($GLOBALS['homepageWeatherAndAirLatitude']) && !empty($GLOBALS['homepageWeatherAndAirLongitude']) && qualifyRequest($GLOBALS['homepageWeatherAndAirAuth'])) {
+		$api['content'] = array(
+			'weather' => false,
+			'air' => false,
+			'pollen' => false
+		);
+		$apiURL = qualifyURL('https://api.breezometer.com/');
+		$info = '&lat=' . $GLOBALS['homepageWeatherAndAirLatitude'] . '&lon=' . $GLOBALS['homepageWeatherAndAirLongitude'] . '&units=' . $GLOBALS['homepageWeatherAndAirUnits'] . '&key=b7401295888443538a7ebe04719c8394';
+		try {
+			$headers = array();
+			$options = array();
+			if ($GLOBALS['homepageWeatherAndAirWeatherEnabled']) {
+				$endpoint = '/weather/v1/forecast/hourly?hours=120&metadata=true';
+				$response = Requests::get($apiURL . $endpoint . $info, $headers, $options);
+				if ($response->success) {
+					$apiData = json_decode($response->body, true);
+					$api['content']['weather'] = ($apiData['error'] === null) ? $apiData : false;
+					unset($apiData);
+				}
+			}
+			if ($GLOBALS['homepageWeatherAndAirAirQualityEnabled']) {
+				$endpoint = '/air-quality/v2/current-conditions?features=breezometer_aqi,local_aqi,health_recommendations,sources_and_effects,pollutants_concentrations,pollutants_aqi_information&metadata=true';
+				$response = Requests::get($apiURL . $endpoint . $info, $headers, $options);
+				if ($response->success) {
+					$apiData = json_decode($response->body, true);
+					$api['content']['air'] = ($apiData['error'] === null) ? $apiData : false;
+					unset($apiData);
+				}
+			}
+			if ($GLOBALS['homepageWeatherAndAirPollenEnabled']) {
+				$endpoint = '/pollen/v2/forecast/daily/?features=plants_information,types_information&days=1&metadata=true';
+				$response = Requests::get($apiURL . $endpoint . $info, $headers, $options);
+				if ($response->success) {
+					$apiData = json_decode($response->body, true);
+					$api['content']['pollen'] = ($apiData['error'] === null) ? $apiData : false;
+					unset($apiData);
+				}
+			}
+		} catch (Requests_Exception $e) {
+			writeLog('error', 'Weather And Air Connect Function - Error: ' . $e->getMessage(), 'SYSTEM');
+		};
+		$api['content'] = isset($api['content']) ? $api['content'] : false;
+		return $api;
+	}
+	return false;
+}
+
 function getHealthChecks($tags = null)
 {
 	if ($GLOBALS['homepageHealthChecksEnabled'] && !empty($GLOBALS['healthChecksToken']) && !empty($GLOBALS['healthChecksURL']) && qualifyRequest($GLOBALS['homepageHealthChecksAuth'])) {
@@ -2500,7 +2552,7 @@ function getTautulli()
 			}
 			$ids = array_merge(['top_music', 'popular_music', 'last_watched', 'most_concurrent'], $ids);
 			foreach ($ids as $id) {
-				if($key = array_search($id, array_column($api['homestats']['data'], 'stat_id'))) {
+				if ($key = array_search($id, array_column($api['homestats']['data'], 'stat_id'))) {
 					unset($api['homestats']['data'][$key]);
 					$api['homestats']['data'] = array_values($api['homestats']['data']);
 				}
@@ -2521,10 +2573,9 @@ function getMonitorr()
 		$url = qualifyURL($GLOBALS['monitorrURL']);
 		$dataUrl = $url . '/assets/php/loop.php';
 		try {
-			$response = Requests::get($dataUrl, [ 'Token' => $GLOBALS['organizrAPI'] ], []);
+			$response = Requests::get($dataUrl, ['Token' => $GLOBALS['organizrAPI']], []);
 			if ($response->success) {
 				$html = html_entity_decode($response->body);
-
 				// This section grabs the names of all services by regex
 				$services = [];
 				$servicesMatch = [];
@@ -2532,76 +2583,64 @@ function getMonitorr()
 				preg_match_all($servicePattern, $html, $servicesMatch);
 				unset($servicesMatch[0]);
 				$servicesMatch = array_values($servicesMatch);
-				foreach($servicesMatch as $group) {
-					foreach($group as $service) {
-						if($service !== '') {
+				foreach ($servicesMatch as $group) {
+					foreach ($group as $service) {
+						if ($service !== '') {
 							array_push($services, $service);
 						}
 					}
 				}
-
 				// This section then grabs the status and image of that service with regex
 				$statuses = [];
-				foreach($services as $service) {
+				foreach ($services as $service) {
 					$statusPattern = '/' . $service . '<\/div><\/div><div class="btnonline">(Online)<\/div>|' . $service . '<\/div><\/div><div class="btnoffline".*>(Offline)<\/div><\/div><\/div>/';
 					$status = [];
 					preg_match($statusPattern, $html, $status);
 					$statuses[$service] = $status;
-					foreach($status as $match) {
-						if($match == 'Online') {
+					foreach ($status as $match) {
+						if ($match == 'Online') {
 							$statuses[$service] = [
 								'status' => true
 							];
-						} else if($match == 'Offline') {
+						} else if ($match == 'Offline') {
 							$statuses[$service] = [
 								'status' => false
 							];
 						}
 					}
-
 					$imageMatch = [];
-
-					$imgPattern = '/assets\/img\/\.\.(.*)" class="serviceimg" alt=.*><\/div><\/div><div id="servicetitle"><div>'.$service.'|assets\/img\/\.\.(.*)" class="serviceimg imgoffline" alt=.*><\/div><\/div><div id="servicetitleoffline".*><div>'.$service.'|assets\/img\/\.\.(.*)" class="serviceimg" alt=.*><\/div><\/div><div id="servicetitlenolink".*><div>'.$service.'/';
-
+					$imgPattern = '/assets\/img\/\.\.(.*)" class="serviceimg" alt=.*><\/div><\/div><div id="servicetitle"><div>' . $service . '|assets\/img\/\.\.(.*)" class="serviceimg imgoffline" alt=.*><\/div><\/div><div id="servicetitleoffline".*><div>' . $service . '|assets\/img\/\.\.(.*)" class="serviceimg" alt=.*><\/div><\/div><div id="servicetitlenolink".*><div>' . $service . '/';
 					preg_match($imgPattern, $html, $imageMatch);
 					unset($imageMatch[0]);
 					$imageMatch = array_values($imageMatch);
 					// array_push($api['imagematches'][$service], $imageMatch);
-					foreach($imageMatch as $match) {
-						if($match!== '') {
+					foreach ($imageMatch as $match) {
+						if ($match !== '') {
 							$image = $match;
 						}
 					}
 					$ext = explode('.', $image);
 					$ext = $ext[key(array_slice($ext, -1, 1, true))];
-
 					$imageUrl = $url . '/assets' . $image;
-
 					$cacheDirectory = dirname(__DIR__, 2) . DIRECTORY_SEPARATOR . 'plugins' . DIRECTORY_SEPARATOR . 'images' . DIRECTORY_SEPARATOR . 'cache' . DIRECTORY_SEPARATOR;
-
-					$img = Requests::get($imageUrl, [ 'Token' => $GLOBALS['organizrAPI'] ], []);
-					if($img->success) {
+					$img = Requests::get($imageUrl, ['Token' => $GLOBALS['organizrAPI']], []);
+					if ($img->success) {
 						$base64 = 'data:image/' . $ext . ';base64,' . base64_encode($img->body);
 						$statuses[$service]['image'] = $base64;
 					} else {
 						$statuses[$service]['image'] = $cacheDirectory . 'no-list.png';
 					}
-
 					$linkMatch = [];
-
-					$linkPattern = '/<a class="servicetile" href="(.*)" target="_blank" style="display: block"><div id="serviceimg"><div><img id="'.strtolower($service).'-service-img/';
-
+					$linkPattern = '/<a class="servicetile" href="(.*)" target="_blank" style="display: block"><div id="serviceimg"><div><img id="' . strtolower($service) . '-service-img/';
 					preg_match($linkPattern, $html, $linkMatch);
-
 					$linkMatch = array_values($linkMatch);
 					unset($linkMatch[0]);
-					foreach($linkMatch as $link) {
-						if($link!== '') {
+					foreach ($linkMatch as $link) {
+						if ($link !== '') {
 							$statuses[$service]['link'] = $link;
 						}
 					}
 				}
-
 				ksort($statuses);
 				$api['services'] = $statuses;
 				$api['options'] = [

+ 201 - 81
api/functions/homepage-functions.php

@@ -25,6 +25,7 @@ function homepageOrder()
 		"homepageOrdertautulli" => $GLOBALS['homepageOrdertautulli'],
 		"homepageOrderPihole" => $GLOBALS['homepageOrderPihole'],
 		"homepageOrderMonitorr" => $GLOBALS['homepageOrderMonitorr'],
+		"homepageOrderWeatherAndAir" => $GLOBALS['homepageOrderWeatherAndAir']
 	);
 	asort($homepageOrder);
 	return $homepageOrder;
@@ -370,6 +371,18 @@ function buildHomepageItem($homepageItem)
 				';
 			}
 			break;
+		case 'homepageOrderWeatherAndAir':
+			if ($GLOBALS['homepageWeatherAndAirEnabled']) {
+				$item .= '<div class="white-box"><h2 class="text-center" lang="en">Loading Weather And Air...</h2></div>';
+				$item .= '
+				<script>
+				// Weather And Air
+				homepageWeatherAndAir("' . $GLOBALS['homepageWeatherAndAirRefresh'] . '");
+				// End Weather And Air
+				</script>
+				';
+			}
+			break;
 		default:
 			# code...
 			break;
@@ -639,87 +652,88 @@ function getHomepageList()
 		)
 	);
 	$xmlStatus = (extension_loaded('xmlrpc')) ? 'Installed' : 'Not Installed';
-	return array(array(
-		'name' => 'Calendar',
-		'enabled' => (strpos('personal', $GLOBALS['license']) !== false) ? true : false,
-		'image' => 'plugins/images/tabs/calendar.png',
-		'category' => 'HOMEPAGE',
-		'settings' => array(
-			'Enable' => array(
-				array(
-					'type' => 'switch',
-					'name' => 'homepageCalendarEnabled',
-					'label' => 'Enable iCal',
-					'value' => $GLOBALS['homepageCalendarEnabled']
-				),
-				array(
-					'type' => 'select',
-					'name' => 'homepageCalendarAuth',
-					'label' => 'Minimum Authentication',
-					'value' => $GLOBALS['homepageCalendarAuth'],
-					'options' => $groups
-				),
-				array(
-					'type' => 'input',
-					'name' => 'calendariCal',
-					'label' => 'iCal URL\'s',
-					'value' => $GLOBALS['calendariCal'],
-					'placeholder' => 'separate by comma\'s'
-				),
-			),
-			'Misc Options' => array(
-				array(
-					'type' => 'number',
-					'name' => 'calendarStart',
-					'label' => '# of Days Before',
-					'value' => $GLOBALS['calendarStart'],
-					'placeholder' => ''
-				),
-				array(
-					'type' => 'number',
-					'name' => 'calendarEnd',
-					'label' => '# of Days After',
-					'value' => $GLOBALS['calendarEnd'],
-					'placeholder' => ''
-				),
-				array(
-					'type' => 'select',
-					'name' => 'calendarFirstDay',
-					'label' => 'Start Day',
-					'value' => $GLOBALS['calendarFirstDay'],
-					'options' => $day
-				),
-				array(
-					'type' => 'select',
-					'name' => 'calendarDefault',
-					'label' => 'Default View',
-					'value' => $GLOBALS['calendarDefault'],
-					'options' => $calendarDefault
-				),
-				array(
-					'type' => 'select',
-					'name' => 'calendarTimeFormat',
-					'label' => 'Time Format',
-					'value' => $GLOBALS['calendarTimeFormat'],
-					'options' => $timeFormat
-				),
-				array(
-					'type' => 'select',
-					'name' => 'calendarLimit',
-					'label' => 'Items Per Day',
-					'value' => $GLOBALS['calendarLimit'],
-					'options' => $limit
-				),
-				array(
-					'type' => 'select',
-					'name' => 'calendarRefresh',
-					'label' => 'Refresh Seconds',
-					'value' => $GLOBALS['calendarRefresh'],
-					'options' => optionTime()
-				)
-			),
-		)
-	),
+	return array(
+		array(
+			'name' => 'Calendar',
+			'enabled' => (strpos('personal', $GLOBALS['license']) !== false) ? true : false,
+			'image' => 'plugins/images/tabs/calendar.png',
+			'category' => 'HOMEPAGE',
+			'settings' => array(
+				'Enable' => array(
+					array(
+						'type' => 'switch',
+						'name' => 'homepageCalendarEnabled',
+						'label' => 'Enable iCal',
+						'value' => $GLOBALS['homepageCalendarEnabled']
+					),
+					array(
+						'type' => 'select',
+						'name' => 'homepageCalendarAuth',
+						'label' => 'Minimum Authentication',
+						'value' => $GLOBALS['homepageCalendarAuth'],
+						'options' => $groups
+					),
+					array(
+						'type' => 'input',
+						'name' => 'calendariCal',
+						'label' => 'iCal URL\'s',
+						'value' => $GLOBALS['calendariCal'],
+						'placeholder' => 'separate by comma\'s'
+					),
+				),
+				'Misc Options' => array(
+					array(
+						'type' => 'number',
+						'name' => 'calendarStart',
+						'label' => '# of Days Before',
+						'value' => $GLOBALS['calendarStart'],
+						'placeholder' => ''
+					),
+					array(
+						'type' => 'number',
+						'name' => 'calendarEnd',
+						'label' => '# of Days After',
+						'value' => $GLOBALS['calendarEnd'],
+						'placeholder' => ''
+					),
+					array(
+						'type' => 'select',
+						'name' => 'calendarFirstDay',
+						'label' => 'Start Day',
+						'value' => $GLOBALS['calendarFirstDay'],
+						'options' => $day
+					),
+					array(
+						'type' => 'select',
+						'name' => 'calendarDefault',
+						'label' => 'Default View',
+						'value' => $GLOBALS['calendarDefault'],
+						'options' => $calendarDefault
+					),
+					array(
+						'type' => 'select',
+						'name' => 'calendarTimeFormat',
+						'label' => 'Time Format',
+						'value' => $GLOBALS['calendarTimeFormat'],
+						'options' => $timeFormat
+					),
+					array(
+						'type' => 'select',
+						'name' => 'calendarLimit',
+						'label' => 'Items Per Day',
+						'value' => $GLOBALS['calendarLimit'],
+						'options' => $limit
+					),
+					array(
+						'type' => 'select',
+						'name' => 'calendarRefresh',
+						'label' => 'Refresh Seconds',
+						'value' => $GLOBALS['calendarRefresh'],
+						'options' => optionTime()
+					)
+				),
+			)
+		),
 		array(
 			'name' => 'Plex',
 			'enabled' => (strpos('personal', $GLOBALS['license']) !== false) ? true : false,
@@ -2778,6 +2792,105 @@ function getHomepageList()
 				),
 			)
 		),
+		array(
+			'name' => 'Weather-Air',
+			'enabled' => true,
+			'image' => 'plugins/images/tabs/wind.png',
+			'category' => 'Monitor',
+			'settings' => array(
+				'Enable' => array(
+					array(
+						'type' => 'switch',
+						'name' => 'homepageWeatherAndAirEnabled',
+						'label' => 'Enable',
+						'value' => $GLOBALS['homepageWeatherAndAirEnabled']
+					),
+					array(
+						'type' => 'select',
+						'name' => 'homepageWeatherAndAirAuth',
+						'label' => 'Minimum Authentication',
+						'value' => $GLOBALS['homepageWeatherAndAirAuth'],
+						'options' => $groups
+					)
+				),
+				'Connection' => array(
+					array(
+						'type' => 'input',
+						'name' => 'homepageWeatherAndAirLatitude',
+						'label' => 'Latitude',
+						'value' => $GLOBALS['homepageWeatherAndAirLatitude'],
+						'help' => 'Please enter full latitude including minus if needed'
+					),
+					array(
+						'type' => 'input',
+						'name' => 'homepageWeatherAndAirLongitude',
+						'label' => 'Longitude',
+						'value' => $GLOBALS['homepageWeatherAndAirLongitude'],
+						'help' => 'Please enter full longitude including minus if needed'
+					)
+				),
+				'Options' => array(
+					array(
+						'type' => 'input',
+						'name' => 'homepageWeatherAndAirWeatherHeader',
+						'label' => 'Title',
+						'value' => $GLOBALS['homepageWeatherAndAirWeatherHeader'],
+						'help' => 'Sets the title of this homepage module',
+					),
+					array(
+						'type' => 'switch',
+						'name' => 'homepageWeatherAndAirWeatherHeaderToggle',
+						'label' => 'Toggle Title',
+						'value' => $GLOBALS['homepageWeatherAndAirWeatherHeaderToggle'],
+						'help' => 'Shows/hides the title of this homepage module'
+					),
+					array(
+						'type' => 'switch',
+						'name' => 'homepageWeatherAndAirWeatherEnabled',
+						'label' => 'Enable Weather',
+						'value' => $GLOBALS['homepageWeatherAndAirWeatherEnabled'],
+						'help' => 'Toggles the view module for Weather'
+					),
+					array(
+						'type' => 'switch',
+						'name' => 'homepageWeatherAndAirAirQualityEnabled',
+						'label' => 'Enable Air Quality',
+						'value' => $GLOBALS['homepageWeatherAndAirAirQualityEnabled'],
+						'help' => 'Toggles the view module for Air Quality'
+					),
+					array(
+						'type' => 'switch',
+						'name' => 'homepageWeatherAndAirPollenEnabled',
+						'label' => 'Enable Pollen',
+						'value' => $GLOBALS['homepageWeatherAndAirPollenEnabled'],
+						'help' => 'Toggles the view module for Pollen'
+					),
+					array(
+						'type' => 'select',
+						'name' => 'homepageWeatherAndAirUnits',
+						'label' => 'Unit of Measurement',
+						'value' => $GLOBALS['homepageWeatherAndAirUnits'],
+						'options' => array(
+							array(
+								'name' => 'Imperial',
+								'value' => 'imperial'
+							),
+							array(
+								'name' => 'Metric',
+								'value' => 'metric'
+							)
+						)
+					),
+					array(
+						'type' => 'select',
+						'name' => 'homepageWeatherAndAirRefresh',
+						'label' => 'Refresh Seconds',
+						'value' => $GLOBALS['homepageWeatherAndAirRefresh'],
+						'options' => optionTime()
+					),
+				),
+			)
+		),
 	);
 }
 
@@ -2924,6 +3037,13 @@ function buildHomepageSettings()
 					$class .= ' faded';
 				}
 				break;
+			case 'homepageOrderWeatherAndAir':
+				$class = 'bg-success';
+				$image = 'plugins/images/tabs/wind.png';
+				if (!$GLOBALS['homepageWeatherAndAirEnabled']) {
+					$class .= ' faded';
+				}
+				break;
 			default:
 				$class = 'blue-bg';
 				$image = '';

+ 157 - 0
js/functions.js

@@ -6874,6 +6874,163 @@ function homepageTautulli(timeout){
     if(typeof timeouts[timeoutTitle] !== 'undefined'){ clearTimeout(timeouts[timeoutTitle]); }
     timeouts[timeoutTitle] = setTimeout(function(){ homepageTautulli(timeout); }, timeout);
 }
+function weatherIcon(code, daytime = true){
+    switch (code) {
+        case 1:
+        case 2:
+            return (daytime) ? 'wi-day-sunny' : 'wi-night-clear';
+        case 3:
+        case 4:
+        case 5:
+        case 6:
+        case 22:
+            return (daytime) ? 'wi-day-sunny-overcast' : 'wi-night-alt-partly-cloudy';
+        case 7:
+        case 8:
+        case 9:
+            return (daytime) ? 'wi-day-cloudy-high' : 'wi-night-partly-cloudy';
+        case 10:
+        case 11:
+        case 12:
+            return (daytime) ? 'wi-day-thunderstorm' : 'wi-night-thunderstorm';
+        case 13:
+        case 14:
+        case 15:
+            return (daytime) ? 'wi-day-haze' : 'wi-night-cloudy-windy';
+        case 16:
+        case 17:
+        case 18:
+            return (daytime) ? 'wi-day-fog' : 'wi-night-fog';
+        case 19:
+        case 20:
+        case 21:
+            return (daytime) ? 'wi-day-cloudy-high' : 'wi-night-cloudy-high';
+        case 23:
+        case 25:
+            return (daytime) ? 'wi-day-rain' : 'wi-night-rain';
+        case 24:
+        case 26:
+            return (daytime) ? 'wi-day-snow' : 'wi-night-snow';
+        case 27:
+        case 28:
+        case 30:
+        case 31:
+        case 33:
+            return (daytime) ? 'wi-day-rain-mix' : 'wi-night-alt-rain-mix';
+        case 29:
+        case 32:
+        case 34:
+        case 35:
+            return (daytime) ? 'wi-day-snow-thunderstorm' : 'wi-night-alt-snow-thunderstorm';
+        default:
+            return (daytime) ? 'wi-day-sunny' : 'wi-night-clear';
+    }
+}
+function buildWeatherAndAir(array){
+    if (typeof array.content === 'undefined'){ return ''; }
+    if(array.content.weather !== false){
+        if(array.content.weather.error === null){
+            let dates = {};
+            $.each(array.content.weather.data, function(i,v) {
+                let date = moment(v.datetime).format('YYYY-MM-DD')
+                if( typeof dates[date] === 'undefined'){
+                    dates[date] = v;
+                    dates[date]['temps'] = {
+                        'high': v.temperature.value,
+                        'low': v.temperature.value
+                    }
+                }else{
+                    if(moment(v.datetime).format('hh:mm a') == '12:00 pm'){
+                        dates[date]['icon_code'] = v.icon_code;
+                        dates[date]['is_day_time'] = v.is_day_time;
+                    }
+                    if(v.temperature.value > dates[date]['temps']['high']){
+                        dates[date]['temps']['high'] = v.temperature.value;
+                    }
+                    if(v.temperature.value < dates[date]['temps']['low']){
+                        dates[date]['temps']['low'] = v.temperature.value;
+                    }
+                }
+            })
+            let weatherItems = '<div class="row">';
+            let weatherItemsCount = 0;
+            $.each(dates, function(i,v) {
+                if(weatherItemsCount === 0){
+                    weatherItems += `
+                    <div class="col-lg-4 col-sm-12 col-xs-12">
+                        <div class="white-box">
+                            <h3 class="box-title">`+moment(v.datetime).format('dddd')+`<small class="pull-right m-t-10">Feels Like `+Math.round(v.feels_like_temperature.value)+`°</small></h3>
+                            <ul class="list-inline two-part">
+                                <li><i class="wi `+weatherIcon(v.icon_code, v.is_day_time)+` text-info"></i></li>
+                                <li class="text-right"><span class="counter">`+Math.round(v.temperature.value)+`<small><sup>°`+v.temperature.units+`</sup></small></span></li>
+                            </ul>
+                            <ul class="list-inline m-b-0">
+                                <li class="pull-left"><h5 class="text-uppercase">`+v.weather_text+`</h5></li>
+                                <li class="pull-right"><h5><i class="wi wi-strong-wind m-r-5 text-primary tooltip-primary" data-toggle="tooltip" data-placement="top" title="" data-original-title="Wind"></i>`+Math.round(v.wind.speed.value)+` `+v.wind.speed.units+`</h5></li>
+                                <li class="pull-right"><h5><i class="wi wi-barometer m-r-5 text-primary tooltip-primary" data-toggle="tooltip" data-placement="top" title="" data-original-title="Pressure"></i>`+Math.round(v.pressure.value)+` `+v.pressure.units+`</h5></li>
+                                <li class="pull-right"><h5><i class="wi wi-humidity m-r-5 text-primary tooltip-primary" data-toggle="tooltip" data-placement="top" title="" data-original-title="Humidity"></i>`+Math.round(v.relative_humidity)+`</h5></li>
+                                <li class="pull-right"><h5><i class="wi wi-raindrop m-r-5 text-primary tooltip-primary" data-toggle="tooltip" data-placement="top" title="" data-original-title="Dew Point"></i>`+Math.round(v.dew_point.value)+`°</h5></li>
+                                <div class="clearfix"></div>
+                            </ul>
+                        </div>
+                    </div>
+                    `;
+                }else if(weatherItemsCount !== 5){
+                    weatherItems += `
+                    <div class="col-lg-2 col-sm-3 col-xs-12">
+                        <div class="white-box">
+                            <h3 class="box-title">`+moment(v.datetime).format('dddd')+`</h3>
+                            <ul class="list-inline two-part">
+                                <li><i class="wi `+weatherIcon(v.icon_code, v.is_day_time)+` text-info"></i></li>
+                                <li class="text-right"><span class="counter">`+Math.round(v.temps.high)+`<small><sup>°`+v.temperature.units+`</sup></small></span></li>
+                            </ul>
+                            <ul class="list-inline m-b-0">
+                                <li class="pull-left"><h5 class="text-uppercase">`+v.weather_text+`</h5></li>
+                                <div class="clearfix"></div>
+                            </ul>
+                        </div>
+                    </div>
+                    `;
+                }
+                weatherItemsCount ++;
+            })
+            weatherItems += '</div>';
+            return weatherItems;
+        }
+    }
+    if(array.content.air !== false){
+        if(array.content.air.error === null) {
+
+            console.log('load air')
+        }
+    }
+    if(array.content.pollen !== false){
+        if(array.content.pollen.error === null){
+            console.log('load pollen')
+        }
+    }
+}
+function homepageWeatherAndAir(array){
+    var timeout = (typeof timeout !== 'undefined') ? timeout : activeInfo.settings.homepage.refresh.homepageWeatherAndAirRefresh;
+    organizrAPI('POST','api/?v1/homepage/connect',{action:'getWeatherAndAir'}).success(function(data) {
+        try {
+            var response = JSON.parse(data);
+        }catch(e) {
+            console.log(e + ' error: ' + data);
+            orgErrorAlert('<h4>' + e + '</h4>' + formatDebug(data));
+            return false;
+        }
+        document.getElementById('homepageOrderWeatherAndAir').innerHTML = '';
+        if(response.data !== null){
+            $('#homepageOrderWeatherAndAir').html(buildWeatherAndAir(response.data));
+        }
+    }).fail(function(xhr) {
+        console.error("Organizr Function: API Connection Failed");
+    });
+    var timeoutTitle = 'Tautulli-Homepage';
+    if(typeof timeouts[timeoutTitle] !== 'undefined'){ clearTimeout(timeouts[timeoutTitle]); }
+    timeouts[timeoutTitle] = setTimeout(function(){ homepageTautulli(timeout); }, timeout);
+}
 function buildMonitorrItem(array){
     var cards = '';
     var options = array['options'];