Browse Source

Merge branch 'v2-develop' into prowlarr-homepage

causefx 3 years ago
parent
commit
62d7575684

+ 8 - 0
api/classes/organizr.class.php

@@ -49,6 +49,7 @@ class Organizr
 	use OmbiHomepageItem;
 	use OverseerrHomepageItem;
 	use PiHoleHomepageItem;
+	use AdGuardHomepageItem;
 	use PlexHomepageItem;
 	use QBitTorrentHomepageItem;
 	use RadarrHomepageItem;
@@ -4595,6 +4596,13 @@ class Organizr
 						$class .= ' faded';
 					}
 					break;
+				case 'homepageOrderAdGuard':
+					$class = 'bg-info';
+					$image = 'plugins/images/tabs/AdGuardHomepageItem';
+					if (!$this->config['homepageAdGuardEnabled']) {
+						$class .= ' faded';
+					}
+					break;
 				case 'homepageOrderMonitorr':
 					$class = 'bg-info';
 					$image = 'plugins/images/tabs/monitorr.png';

+ 13 - 1
api/config/default.php

@@ -372,7 +372,8 @@ return [
 	'homepageOrderoverseerr' => '39',
 	'homepageOrderBookmarks' => '40',
 	'homepageOrderDonate' => '41',
-	'homepageOrderProwlarr' => '42',
+	'homepageOrderAdguard' => '42',
+  'homepageOrderProwlarr' => '43',
 	'homepageShowStreamNames' => false,
 	'homepageShowStreamNamesAuth' => '1',
 	'homepageShowStreamNamesWithoutIp' => false,
@@ -532,6 +533,17 @@ return [
 	'homepagePiholeCombine' => false,
 	'piholeHeaderToggle' => true,
 	'piholeURL' => '',
+	'homepageAdGuardEnabled' => false,
+	'homepageAdGuardAuth' => '1',
+	'homepageAdGuardRefresh' => '10000',
+	'homepageAdGuardCombine' => false,
+	'adguardQueriesToggle' => true,
+	'adguardQueriesBlockedToggle' => true,
+	'adguardPercentToggle' => true,
+	'adguardProcessingToggle' => true,
+	'adguardDomainListToggle' => false,
+	'adguardHeaderToggle' => true,
+	'adguardURL' => '',
 	'homepageMonitorrEnabled' => false,
 	'homepageMonitorrAuth' => '1',
 	'homepageMonitorrRefresh' => '60000',

+ 10 - 6
api/functions/normal-functions.php

@@ -439,22 +439,26 @@ trait NormalFunctions
 			$scheme = $digest['scheme'];
 		}
 		// Host
-		$host = (isset($digest['host']) ? $digest['host'] : '');
+		$host = ($digest['host'] ?? '');
 		// Port
 		$port = (isset($digest['port']) ? ':' . $digest['port'] : '');
 		// Path
-		$path = (isset($digest['path']) ? $digest['path'] : '');
+		$path = ($digest['path'] ?? '');
 		// Query
 		$query = (isset($digest['query']) ? '?' . $digest['query'] : '');
+		// Fragment
+		$fragment = (isset($digest['fragment']) ? '#' . $digest['fragment'] : '');
 		// Output
-		$array = array(
+		$array = [
 			'scheme' => $scheme,
 			'host' => $host,
 			'port' => $port,
 			'path' => $path,
-			'query' => $query
-		);
-		return ($return) ? $array : $scheme . '://' . $host . $port . $path . $query;
+			'query' => $query,
+			'fragment' => $fragment,
+			'digest' => $digest
+		];
+		return ($return) ? $array : $scheme . '://' . $host . $port . $path . $query . $fragment;
 	}
 
 	public function getServer($over = false)

+ 29 - 4
api/functions/update-functions.php

@@ -2,6 +2,25 @@
 
 trait UpdateFunctions
 {
+	public function testScriptFilePermissions($script, $retest = false)
+	{
+		if (file_exists($script)) {
+			if (is_executable($script)) {
+				return true;
+			} elseif ($retest == false) {
+				$this->setLoggerChannel('Update')->notice('Attempting to set correct permissions', ['file' => $script]);
+				$permissions = shell_exec('chmod 777 ' . $script);
+				return $this->testScriptFilePermissions($script, true);
+			} else {
+				$this->setLoggerChannel('Update')->warning('Update script doesn\'t have the correct permissions', ['file' => $script]);
+				return false;
+			}
+		} else {
+			$this->setLoggerChannel('Update')->warning('Update script doesn\'t exist', ['file' => $script]);
+			return false;
+		}
+	}
+
 	public function updateOrganizr()
 	{
 		if ($this->docker) {
@@ -81,8 +100,8 @@ trait UpdateFunctions
 		$branch = ($this->config['branch'] == 'v2-master') ? '-m' : '-d';
 		ini_set('max_execution_time', 0);
 		set_time_limit(0);
-		$logFile = dirname(__DIR__, 2) . DIRECTORY_SEPARATOR . 'scripts' . DIRECTORY_SEPARATOR . 'log.txt';
-		$windowsScript = dirname(__DIR__, 2) . DIRECTORY_SEPARATOR . 'scripts' . DIRECTORY_SEPARATOR . 'windows-update.bat ' . $branch . ' > ' . $logFile . ' 2>&1';
+		$logFile = $this->root . DIRECTORY_SEPARATOR . 'scripts' . DIRECTORY_SEPARATOR . 'log.txt';
+		$windowsScript = $this->root . DIRECTORY_SEPARATOR . 'scripts' . DIRECTORY_SEPARATOR . 'windows-update.bat ' . $branch . ' > ' . $logFile . ' 2>&1';
 		$windowsUpdate = shell_exec($windowsScript);
 		$this->removeUpdateStatusFile();
 		if ($windowsUpdate) {
@@ -109,8 +128,14 @@ trait UpdateFunctions
 		$branch = $this->config['branch'];
 		ini_set('max_execution_time', 0);
 		set_time_limit(0);
-		$logFile = dirname(__DIR__, 2) . DIRECTORY_SEPARATOR . 'scripts' . DIRECTORY_SEPARATOR . 'log.txt';
-		$script = dirname(__DIR__, 2) . DIRECTORY_SEPARATOR . 'scripts' . DIRECTORY_SEPARATOR . 'linux-update.sh ' . $branch . ' > ' . $logFile . ' 2>&1';
+		$logFile = $this->root . DIRECTORY_SEPARATOR . 'scripts' . DIRECTORY_SEPARATOR . 'log.txt';
+		$script = $this->root . DIRECTORY_SEPARATOR . 'scripts' . DIRECTORY_SEPARATOR . 'linux-update.sh ' . $branch . ' > ' . $logFile . ' 2>&1';
+		$checkScript = $this->testScriptFilePermissions($script);
+		if (!$checkScript) {
+			$this->setResponse(500, 'Update script permissions error');
+			$this->removeUpdateStatusFile();
+			return false;
+		}
 		$update = shell_exec($script);
 		$this->removeUpdateStatusFile();
 		if ($update) {

+ 176 - 0
api/homepage/adguard.php

@@ -0,0 +1,176 @@
+<?php
+
+trait AdGuardHomepageItem
+{
+	public function adguardSettingsArray($infoOnly = false)
+	{
+		$homepageInformation = [
+			'name' => 'AdGuardHome',
+			'enabled' => true,
+			'image' => 'plugins/images/tabs/AdGuardHome.png',
+			'category' => 'Monitor',
+			'settingsArray' => __FUNCTION__
+		];
+		if ($infoOnly) {
+			return $homepageInformation;
+		}
+		$homepageSettings = [
+			'debug' => true,
+			'settings' => [
+				'Enable' => [
+					$this->settingsOption('enable', 'homepageAdGuardEnabled'),
+					$this->settingsOption('auth', 'homepageAdGuardAuth'),
+				],
+				'Connection' => [
+					$this->settingsOption('url', 'adguardURL', ['help' => 'Please make sure to use local IP address and port. You can add multiple AdGuard Homes by comma separating the URLs.', 'placeholder' => 'http(s)://hostname:port']),
+          $this->settingsOption('username', 'adGuardUsername'),
+					$this->settingsOption('password', 'adGuardPassword'),
+        ],
+				'Misc' => [
+					$this->settingsOption('toggle-title', 'adguardToggle'),
+					$this->settingsOption('switch', 'homepageAdGuardCombine', ['label' => 'Combine stat cards', 'help' => 'This controls whether to combine the stats for multiple adguard instances into 1 card.']),
+				],
+				'Stats' => [
+					$this->settingsOption('switch', 'adguardQueriesToggle', ['label' => 'Total Queries']),
+					$this->settingsOption('switch', 'adguardQueriesBlockedToggle', ['label' => 'Queries Blocked']),
+					$this->settingsOption('switch', 'adguardPercentToggle', ['label' => 'Percent Blocked']),
+					$this->settingsOption('switch', 'adguardProcessingToggle', ['label' => 'Processing Time']),
+					$this->settingsOption('switch', 'adguardDomainListToggle', ['label' => 'Domains on Blocklist']),
+				],
+				'Test Connection' => [
+					$this->settingsOption('blank', null, ['label' => 'Please Save before Testing']),
+					$this->settingsOption('test', 'adguard'),
+				]
+			]
+		];
+		return array_merge($homepageInformation, $homepageSettings);
+	}
+
+	public function testConnectionAdGuard()
+	{
+		if (empty($this->config['adguardURL'])) {
+			$this->setAPIResponse('error', 'AdGuard URL is not defined', 422);
+			return false;
+		}
+		$api = array();
+		$failed = false;
+		$errors = '';
+		$urls = explode(',', $this->config['adguardURL']);
+		foreach ($urls as $url) {
+			$url = $url . '/control/stats';
+			try {
+				$options = array(
+					'auth' => array($this->config['adGuardUsername'], $this->decrypt($this->config['adGuardPassword']))
+				);
+				$response = Requests::get($url, [], $options);
+				if ($response->success) {
+					@$test = json_decode($response->body, true);
+					if (!is_array($test)) {
+						$ip = $this->qualifyURL($url, true)['host'];
+						$errors .= $ip . ': Response was not JSON';
+						$failed = true;
+					}
+				}
+				if (!$response->success) {
+					$ip = $this->qualifyURL($url, true)['host'];
+					$errors .= $ip . ": Unknown Failure";
+					$failed = true;
+				}
+			} catch (Requests_Exception $e) {
+				$failed = true;
+				$ip = $this->qualifyURL($url, true)['host'];
+				$errors .= $ip . ': ' . $e->getMessage();
+				$this->setLoggerChannel('AdGuard')->error($e);
+			};
+		}
+		if ($failed) {
+			$this->setAPIResponse('error', $errors, 500);
+			return false;
+		} else {
+			$this->setAPIResponse('success', null, 200);
+			return true;
+		}
+	}
+
+	public function adguardHomepagePermissions($key = null)
+	{
+		$permissions = [
+			'main' => [
+				'enabled' => [
+					'homepageAdGuardEnabled'
+				],
+				'auth' => [
+					'homepageAdGuardAuth'
+				],
+				'not_empty' => [
+					'adguardURL'
+				]
+			]
+		];
+		return $this->homepageCheckKeyPermissions($key, $permissions);
+	}
+
+	public function homepageOrderAdGuard()
+	{
+		if ($this->homepageItemPermissions($this->adguardHomepagePermissions('main'))) {
+			return '
+				<div id="' . __FUNCTION__ . '">
+					<div class="white-box homepage-loading-box"><h2 class="text-center" lang="en">Loading AdGuard...</h2></div>
+					<script>
+						// Pi-hole Stats
+						homepageAdGuard("' . $this->config['homepageAdGuardRefresh'] . '");
+						// End Pi-hole Stats
+					</script>
+				</div>
+				';
+		}
+	}
+
+	public function getAdGuardHomepageStats()
+	{
+		if (!$this->homepageItemPermissions($this->adguardHomepagePermissions('main'), true)) {
+			return false;
+		}
+		$stats = array();
+		$urls = explode(',', $this->config['adguardURL']);
+		foreach ($urls as $url) {
+			$stats_url = $url . '/control/stats?';
+			$filter_url = $url . '/control/filtering/status?';
+			try {
+				$options = array(
+					'auth' => array($this->config['adGuardUsername'], $this->decrypt($this->config['adGuardPassword']))
+				);
+				$response = Requests::get($stats_url, [], $options);
+				if ($response->success) {
+					@$adguardResults = json_decode($response->body, true);
+					if (is_array($adguardResults)) {
+						$ip = $this->qualifyURL($stats_url, true)['host'];
+						$stats['data'][$ip] = $adguardResults;
+					}
+				}
+				$response = Requests::get($filter_url, [], $options);
+				if ($response->success) {
+					@$adguardFilterResults = json_decode($response->body, true);
+					if (is_array($adguardFilterResults)) {
+						$ip = $this->qualifyURL($filter_url, true)['host'];
+						$stats['filters'][$ip] = $adguardFilterResults;
+					}
+				}
+			} catch (Requests_Exception $e) {
+				$this->setResponse(500, $e->getMessage());
+				$this->setLoggerChannel('AdGuard')->error($e);
+				return false;
+			};
+		}
+		$stats['options']['combine'] = $this->config['homepageAdGuardCombine'];
+		$stats['options']['title'] = $this->config['adguardHeaderToggle'];
+		$stats['options']['queries'] = $this->config['adguardQueriesToggle'];
+		$stats['options']['blocked_count'] = $this->config['adguardQueriesBlockedToggle'];
+		$stats['options']['blocked_percent'] = $this->config['adguardPercentToggle'];
+		$stats['options']['processing_time'] = $this->config['adguardProcessingToggle'];
+		$stats['options']['domain_count'] = $this->config['adguardDomainListToggle'];
+		$stats = isset($stats) ? $stats : null;
+		$this->setAPIResponse('success', null, 200, $stats);
+		return $stats;
+	}
+}

+ 10 - 2
api/plugins/php-mailer/plugin.php

@@ -197,6 +197,14 @@ class PhpMailer extends Organizr
 		$subject = isset($emailInfo['subject']) ? $emailInfo['subject'] : null;
 		$body = isset($emailInfo['body']) ? $emailInfo['body'] : null;
 		$username = isset($emailInfo['user']) ? $emailInfo['user'] : 'Organizr User';
+		$data = [
+			'to' => $to,
+			'cc' => $cc,
+			'bcc' => $bcc,
+			'subject' => $subject,
+			'body' => $body,
+			'username' => $username,
+		];
 		try {
 			$mail = new PHPMailer\PHPMailer\PHPMailer(true);
 			$mail->isSMTP();
@@ -242,8 +250,8 @@ class PhpMailer extends Organizr
 			$mail->send();
 			return true;
 		} catch (PHPMailer\PHPMailer\Exception $e) {
-			$this->setLoggerChannel('Email')->error($e);
-			return $e->errorMessage();
+			$this->setLoggerChannel('Email')->error($e, $data);
+			return false;
 		}
 	}
 

+ 22 - 0
api/v2/routes/connectionTester.php

@@ -200,6 +200,28 @@ $app->post('/test/pihole', function ($request, $response, $args) {
 		->withHeader('Content-Type', 'application/json;charset=UTF-8')
 		->withStatus($GLOBALS['responseCode']);
 });
+$app->post('/test/adguard', function ($request, $response, $args) {
+	/**
+ * @OA\Post(
+ *     security={{ "api_key":{} }},
+ *     tags={"test connection"},
+ *     path="/api/v2/test/adguard",
+ *     summary="Test connection to AdGuard",
+ *     @OA\Response(response="200",description="Success",@OA\JsonContent(ref="#/components/schemas/success-message")),
+ *     @OA\Response(response="401",description="Unauthorized",@OA\JsonContent(ref="#/components/schemas/unauthorized-message")),
+ *     @OA\Response(response="422",description="Error",@OA\JsonContent(ref="#/components/schemas/error-message")),
+ *     @OA\Response(response="500",description="Error",@OA\JsonContent(ref="#/components/schemas/error-message")),
+ * )
+ */
+$Organizr = ($request->getAttribute('Organizr')) ?? new Organizr();
+if ($Organizr->qualifyRequest(1, true)) {
+	$Organizr->testConnectionAdGuard($Organizr->apiData($request));
+}
+$response->getBody()->write(jsonE($GLOBALS['api']));
+return $response
+	->withHeader('Content-Type', 'application/json;charset=UTF-8')
+	->withStatus($GLOBALS['responseCode']);
+});
 $app->post('/test/rtorrent', function ($request, $response, $args) {
 	/**
 	 * @OA\Post(

+ 8 - 0
api/v2/routes/homepage.php

@@ -112,6 +112,14 @@ $app->get('/homepage/pihole/stats', function ($request, $response, $args) {
 		->withHeader('Content-Type', 'application/json;charset=UTF-8')
 		->withStatus($GLOBALS['responseCode']);
 });
+$app->get('/homepage/adguard/stats', function ($request, $response, $args) {
+	$Organizr = ($request->getAttribute('Organizr')) ?? new Organizr();
+	$Organizr->getAdGuardHomepageStats();
+	$response->getBody()->write(jsonE($GLOBALS['api']));
+	return $response
+		->withHeader('Content-Type', 'application/json;charset=UTF-8')
+		->withStatus($GLOBALS['responseCode']);
+});
 $app->get('/homepage/rtorrent/queue', function ($request, $response, $args) {
 	$Organizr = ($request->getAttribute('Organizr')) ?? new Organizr();
 	$Organizr->getRTorrentHomepageQueue();

+ 2 - 0
index.php

@@ -96,6 +96,8 @@ $Organizr = new Organizr(true);
                                 class="ti-reload"></i></a></li>
                 <li class=""><a class="dropdown-toggle waves-effect waves-light" onclick="closeCurrentTab(event);"> <i
                                 class="ti-close"></i></a></li>
+                <li class=""><a class="dropdown-toggle waves-effect waves-light" onclick="openInNewBrowserTab();"> <i
+                                class="ti-arrow-top-right"></i></a></li>
                 <li class=""><a class="dropdown-toggle waves-effect waves-light hidden" onclick="splashMenu();"> <i
                                 class="ti-layout-grid2"></i></a></li>
             </ul>

+ 291 - 0
js/functions.js

@@ -1062,6 +1062,16 @@ function closeCurrentTab(event){
 			organizrConsole('Tab Function','No Available Tab to open', 'error');
 	}
 }
+function openInNewBrowserTab(){
+    let id = $('body').attr('data-active-tab-id');
+    let tabInfo = findTab(id);
+    if(!tabInfo){
+        organizrConsole('Open In New Browser Tab Function', 'No Tab Info Found... Id: '+id, 'error');
+        return false;
+    }
+    let url = tabInfo.access_url;
+    window.open(url, '_blank');
+}
 function findTab(query, term = 'id'){
     let tabInfo = activeInfo.tabs.filter(tab => tab[term] == query );
     return tabInfo.length >= 1 ? tabInfo[0] : false;
@@ -7274,6 +7284,29 @@ function buildPihole(array){
     `;
     return (array) ? html : '';
 }
+function buildAdGuard(array){
+    if(array === false){ return ''; }
+    var html = `
+    <div id="allAdGuard">
+        <div class="el-element-overlay row">`;
+    if(array['options']['title']) {
+        html += `
+            <div class="col-md-12">
+                <h4 class="pull-left homepage-element-title"><span lang="en">AdGuard Home</span> : </h4><h4 class="pull-left">&nbsp;</h4>
+                <hr class="hidden-xs ml-2">
+            </div>
+            <div class="clearfix"></div>
+        `;
+    }
+    html += `
+		    <div class="adguardCards col-sm-12 my-3">
+			    `+buildAdGuardItem(array)+`
+			</div>
+		</div>
+	</div>
+    `;
+    return (array) ? html : '';
+}
 function buildUnifi(array){
     if(array === false){ return ''; }
     var items = (typeof array.content.unifi.data !== 'undefined') ? array.content.unifi.data.length : false;
@@ -7557,6 +7590,243 @@ function arrayRemove(arr, value) {
 		return ele != value;
 	});
 }
+function buildAdGuardItem(array){
+    var stats = `
+    <style>
+    .bg-green {
+        background-color: #00a65a !important;
+    }
+    
+    .bg-aqua {
+        background-color: #00c0ef!important;
+    }
+    
+    .bg-yellow {
+        background-color: #f39c12!important;
+    }
+    
+    .bg-red {
+        background-color: #dd4b39!important;
+    }
+    
+    .adguard-stat {
+        color: #fff !important;
+    }
+    
+    .adguard-stat .card-body h3 {
+        font-size: 38px;
+        font-weight: 700;
+    }
+
+    .adguard-stat .card-body i {
+        font-size: 5em;
+        float: right;
+        color: #ffffff6b;
+    }
+
+    .inline-block {
+        display: inline-block;
+    }
+    </style>
+    `;
+    var length = Object.keys(array['data']).length;
+    var combine = array['options']['combine'];
+    var totalQueries = function(data) {
+        var card = `
+        <div class="col-lg-3 col-md-6 col-sm-6 col-xs-12">
+            <div class="card text-white mb-3 adguard-stat bg-green">
+                <div class="card-body">
+                    <div class="inline-block">
+                        <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['num_dns_queries'].toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",")+`</h3>`;
+        };
+        card += `
+                    </div>
+                    <i class="fa fa-globe inline-block" aria-hidden="true"></i>
+                </div>
+            </div>
+        </div>
+        `
+        return card;
+    };
+    var totalBlocked = function(data) {
+        var card = `
+        <div class="col-lg-3 col-md-6 col-sm-6 col-xs-12">
+            <div class="card bg-inverse text-white mb-3 adguard-stat bg-aqua">
+                <div class="card-body">
+                    <div class="inline-block">
+                        <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['num_blocked_filtering'].toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",")}</h3>`;
+        };
+        card += `
+                    </div>
+                    <i class="fa fa-hand-paper-o inline-block" aria-hidden="true"></i>
+                </div>
+            </div>
+        </div>
+        `
+        return card;
+    };
+    var avgProcessingTime = function(data) {
+        var card = `
+        <div class="col-lg-3 col-md-6 col-sm-6 col-xs-12">
+            <div class="card bg-inverse text-white mb-3 adguard-stat bg-purple">
+                <div class="card-body">
+                    <div class="inline-block">
+                        <p class="d-inline mr-1">Avg Processing Time</p>`;
+        for(var key in data) {
+            var e = data[key];
+		        if (length > 1 && !combine) {
+			        card += `<p class="d-inline text-muted">(${key})</p>`;
+		        }
+                ms_time = parseFloat(e['avg_processing_time'])*1000
+		        card += `<h3 data-toggle="tooltip" data-placement="right" title="${key}">${ms_time.toFixed(2)} ms</h3>`;
+        };
+        card += `
+                    </div>
+                    <i class="fa fa-group inline-block" aria-hidden="true"></i>
+                </div>
+            </div>
+        </div>
+        `
+        return card;
+    };
+    var domainsBlocked = function(data) {
+        var card = `
+        <div class="col-lg-3 col-md-6 col-sm-6 col-xs-12">
+            <div class="card bg-inverse text-white mb-3 adguard-stat bg-red">
+                <div class="card-body">
+                    <div class="inline-block">
+                        <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>`;
+		        }
+                var total_domains_blocked = 0
+                for(var key in e['filters']){
+                    total_domains_blocked += parseFloat(e['filters'][key]['rules_count'])
+                }
+		        card += `<h3 data-toggle="tooltip" data-placement="right" title="${key}">${total_domains_blocked.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",")}</h3>`;
+        };
+        card += `
+                    </div>
+                    <i class="fa fa-list inline-block" aria-hidden="true"></i>
+                </div>
+            </div>
+        </div>
+        `
+        return card;
+    };
+    var domainsBlocked = function(data) {
+        var card = `
+        <div class="col-lg-3 col-md-6 col-sm-6 col-xs-12">
+            <div class="card bg-inverse text-white mb-3 adguard-stat bg-red">
+                <div class="card-body">
+                    <div class="inline-block">
+                        <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>`;
+		        }
+                var total_domains_blocked = 0
+                for(var key in e['filters']){
+                    total_domains_blocked += parseFloat(e['filters'][key]['rules_count'])
+                }
+                total_domains_blocked += Object.keys(e['user_rules']).length
+		        card += `<h3 data-toggle="tooltip" data-placement="right" title="${key}">${total_domains_blocked.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",")}</h3>`;
+        };
+        card += `
+                    </div>
+                    <i class="fa fa-list inline-block" aria-hidden="true"></i>
+                </div>
+            </div>
+        </div>
+        `
+        return card;
+    };
+    var percentBlocked = function(data) {
+        var card = `
+        <div class="col-lg-3 col-md-6 col-sm-6 col-xs-12">
+            <div class="card bg-inverse text-white mb-3 adguard-stat bg-yellow">
+                <div class="card-body">
+                    <div class="inline-block">
+                        <p class="d-inline mr-1">Percent Blocked</p>`;
+        for(var key in data) {
+            var e = data[key];
+	        if(typeof e['FTLnotrunning'] == 'undefined') {
+		        if (length > 1 && !combine) {
+			        card += `<p class="d-inline text-muted">(${key})</p>`;
+		        }
+                var percent = 100*(parseFloat(e['num_blocked_filtering'])/parseFloat(e['num_dns_queries']))
+		        card += `<h3 data-toggle="tooltip" data-placement="right" title="${key}">${percent.toFixed(2)}%</h3>`
+	        }
+        };
+        card += `
+                    </div>
+                    <i class="fa fa-pie-chart inline-block" aria-hidden="true"></i>
+                </div>
+            </div>
+        </div>
+        `
+        return card;
+    };
+        if(combine) {
+            stats += '<div class="row">'
+            if(array['options']['queries']){
+                stats += totalQueries(array['data']);
+            }
+            if(array['options']['blocked_count']){
+                stats += totalBlocked(array['data']);
+            }
+            if(array['options']['blocked_percent']){
+                stats += percentBlocked(array['data']);
+            }
+            if(array['options']['processing_time']){
+                stats += avgProcessingTime(array['data']);
+            }
+            if(array['options']['domain_count']){
+                stats += domainsBlocked(array['filters']);
+            }
+            stats += '</div>';
+        } else {
+            for(var key in array['data']) {
+                var data = array['data'][key];
+                obj = {};
+                obj[key] = data;
+                stats += '<div class="row">'
+                if(array['options']['queries']){
+                    stats += totalQueries(array['data']);
+                }
+                if(array['options']['blocked_count']){
+                    stats += totalBlocked(array['data']);
+                }
+                if(array['options']['blocked_percent']){
+                    stats += percentBlocked(array['data']);
+                }
+                if(array['options']['processing_time']){
+                    stats += avgProcessingTime(array['data']);
+                }
+                if(array['options']['domain_count']){
+                    stats += domainsBlocked(array['filters']);
+                }
+                stats += '</div>';
+            };
+        };
+        return stats
+}
+
 function buildPiholeItem(array){
     var stats = `
     <style>
@@ -7742,6 +8012,27 @@ function homepagePihole(timeout){
     timeouts[timeoutTitle] = setTimeout(function(){ homepagePihole(timeout); }, timeout);
     delete timeout;
 }
+function homepageAdGuard(timeout){
+    var timeout = (typeof timeout !== 'undefined') ? timeout : activeInfo.settings.homepage.refresh.homepageAdGuardRefresh;
+    organizrAPI2('GET','api/v2/homepage/adguard/stats').success(function(data) {
+        try {
+            let response = data.response;
+	        document.getElementById('homepageOrderAdGuard').innerHTML = '';
+	        if(response.data !== null){
+		        buildAdGuard(response.data)
+		        $('#homepageOrderAdGuard').html(buildAdGuard(response.data));
+	        }
+        }catch(e) {
+	        organizrCatchError(e,data);
+        }
+    }).fail(function(xhr) {
+	    OrganizrApiError(xhr);
+    });
+    let timeoutTitle = 'AdGuard-Homepage';
+    if(typeof timeouts[timeoutTitle] !== 'undefined'){ clearTimeout(timeouts[timeoutTitle]); }
+    timeouts[timeoutTitle] = setTimeout(function(){ homepageAdGuard(timeout); }, timeout);
+    delete timeout;
+}
 function homepageHealthChecks(tags, timeout){
     tags = (typeof tags !== 'undefined') ? tags : activeInfo.settings.homepage.options.healthChecksTags;
     if(tags == ''){

BIN
plugins/images/tabs/Duplicati.png


BIN
plugins/images/tabs/Kavita.png


BIN
plugins/images/tabs/zwavejs.png