Browse Source

Merge pull request #1933 from henrywhitaker3/uptime-kuma-homepage-item

Added uptime kuma homepage item
causefx 2 years ago
parent
commit
4bef331e33

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

@@ -64,6 +64,7 @@ class Organizr
 	use UnifiHomepageItem;
 	use WeatherHomepageItem;
 	use uTorrentHomepageItem;
+	use UptimeKumaHomepageItem;
 
 	// ===================================
 	// Organizr Version
@@ -4693,6 +4694,13 @@ class Organizr
 						$class .= ' faded';
 					}
 					break;
+				case 'homepageOrderUptimeKuma':
+					$class = 'bg-info';
+					$image = 'plugins/images/tabs/kuma.png';
+					if (!$this->config['homepageUptimeKumaEnabled']) {
+						$class .= ' faded';
+					}
+					break;
 				case 'homepageOrderWeatherAndAir':
 					$class = 'bg-success';
 					$image = 'plugins/images/tabs/wind.png';

+ 11 - 1
api/config/default.php

@@ -376,6 +376,7 @@ return [
 	'homepageOrderDonate' => '41',
 	'homepageOrderAdguard' => '42',
 	'homepageOrderProwlarr' => '43',
+	'homepageOrderUptimeKuma' => '44',
 	'homepageShowStreamNames' => false,
 	'homepageShowStreamNamesAuth' => '1',
 	'homepageShowStreamNamesWithoutIp' => false,
@@ -695,5 +696,14 @@ return [
 	'slackLogWebHookChannel' => '',
 	'matchUserAgents' => false,
 	'matchUserIP' => false,
-	'disableHomepageModals' => false
+	'disableHomepageModals' => false,
+	'homepageUptimeKumaEnabled' => false,
+	'homepageUptimeKumaAuth' => '1',
+	'uptimeKumaURL' => '',
+	'uptimeKumaToken' => '',
+	'homepageUptimeKumaRefresh' => '60000',
+	'homepageUptimeKumaHeader' => 'Uptime Kuma',
+	'homepageUptimeKumaHeaderToggle' => true,
+	'homepageUptimeKumaCompact' => true,
+	'homepageUptimeKumaShowLatency' => true,
 ];

+ 88 - 0
api/functions/UptimeKumaMetrics.php

@@ -0,0 +1,88 @@
+<?php
+
+class UptimeKumaMetrics
+{
+    protected string $raw;
+    private array $monitors = [];
+
+    public function __construct(string $raw)
+    {
+        $this->raw = $raw;
+    }
+
+    public function process(): self
+    {
+        $processed = explode(PHP_EOL, $this->raw);
+
+        $monitors = array_filter($processed, function (string $item) {
+            return str_starts_with($item, 'monitor_status');
+        });
+        // TODO: parse the latencies and add them on to the info card
+        $latencies = array_filter($processed, function (string $item) {
+            return str_starts_with($item, 'monitor_response_time');
+        });
+        
+        $monitors = array_map(function (string $item) {
+            return $this->parseMonitorStatus($item);
+        }, $monitors);
+        $this->addLatencyToMonitors($monitors, $latencies);
+        $this->monitors = array_values(array_filter($monitors));
+
+        return $this;
+    }
+
+    public function getMonitors(): array
+    {
+        return $this->monitors;
+    }
+
+    private function parseMonitorStatus(string $status): ?array
+    {  
+        if (substr($status, -1) === '2') {
+            return null;
+		}
+
+		$up = (substr($status, -1)) == '0' ? false : true;
+		$status = substr($status, 15);
+		$status = substr($status, 0, -4);
+		$status = explode(',', $status);
+		$data = [
+			'name' => $this->getStringBetweenQuotes($status[0]),
+			'url' => $this->getStringBetweenQuotes($status[2]),
+			'type' => $this->getStringBetweenQuotes($status[1]),
+			'status' => $up,
+		];
+
+		return $data;
+    }
+
+    private function addLatencyToMonitors(array &$monitors, array $latencies)
+    {
+        $latencies = $this->getLatenciesByName($latencies);
+        foreach ($monitors as &$monitor) {
+            $monitor['latency'] = $latencies[$monitor['name']] ?? null;
+        }
+    }
+
+    private function getLatenciesByName(array $latencies): array
+    {
+        $l = [];
+
+        foreach ($latencies as $latency) {
+            if (preg_match('/monitor_name="(.*)",monitor_type.* ([0-9]{1,})$/', $latency, $match)) {
+                $l[$match[1]] = (int) $match[2];
+            }
+            continue;
+        }
+
+        return $l;
+    }
+
+    private function getStringBetweenQuotes(string $input): string
+	{
+		if (preg_match('/"(.*?)"/', $input, $match) == 1) {
+			return $match[1];
+		}
+		return '';
+	} 
+}

+ 127 - 0
api/homepage/uptime_kuma.php

@@ -0,0 +1,127 @@
+<?php
+
+use GuzzleHttp\Client;
+use GuzzleHttp\Exception\GuzzleException;
+
+trait UptimeKumaHomepageItem
+{
+	private static Client $kumaClient;
+
+	public function uptimeKumaSettingsArray($infoOnly = false)
+	{
+		$homepageInformation = [
+			'name' => 'UptimeKuma',
+			'enabled' => true,
+			'image' => 'plugins/images/tabs/kuma.png',
+			'category' => 'Monitor',
+			'settingsArray' => __FUNCTION__
+		];
+		if ($infoOnly) {
+			return $homepageInformation;
+		}
+		$homepageSettings = [
+			'debug' => true,
+			'settings' => [
+				'Enable' => [
+					$this->settingsOption('enable', 'homepageUptimeKumaEnabled'),
+					$this->settingsOption('auth', 'homepageUptimeKumaAuth'),
+				],
+				'Connection' => [
+					$this->settingsOption('url', 'uptimeKumaURL', ['help' => 'URL for Uptime Kuma e.g. http://kuma:3001 (no trailing slash)', 'placeholder' => 'http://kuma:3001']),
+					$this->settingsOption('token', 'uptimeKumaToken'),
+				],
+				'Options' => [
+					$this->settingsOption('refresh', 'homepageUptimeKumaRefresh'),
+					$this->settingsOption('title', 'homepageUptimeKumaHeader'),
+					$this->settingsOption('toggle-title', 'homepageUptimeKumaHeaderToggle'),
+					$this->settingsOption('switch', 'homepageUptimeKumaCompact', ['label' => 'Compact view', 'help' => 'Toggles the compact view of this homepage module']),
+					$this->settingsOption('switch', 'homepageUptimeKumaShowLatency', ['label' => 'Show monitor latency']),
+				],
+			]
+		];
+		return array_merge($homepageInformation, $homepageSettings);
+	}
+
+	public function uptimeKumaHomepagePermissions($key = null)
+	{
+		$permissions = [
+			'main' => [
+				'enabled' => [
+					'homepageUptimeKumaEnabled'
+				],
+				'auth' => [
+					'homepageUptimeKumaAuth'
+				],
+				'not_empty' => [
+					'uptimeKumaURL',
+					'uptimeKumaToken',
+				]
+			]
+		];
+		return $this->homepageCheckKeyPermissions($key, $permissions);
+	}
+
+	public function homepageOrderUptimeKuma()
+	{
+		if ($this->homepageItemPermissions($this->uptimeKumaHomepagePermissions('main'))) {
+			return '
+				<div id="' . __FUNCTION__ . '">
+					<div class="white-box homepage-loading-box"><h2 class="text-center" lang="en">Loading Uptime Kuma...</h2></div>
+					<script>
+						// Uptime Kuma
+						homepageUptimeKuma("' . $this->config['homepageUptimeKumaRefresh'] . '");
+						// End Uptime Kuma
+					</script>
+				</div>
+				';
+		}
+	}
+
+	public function getUptimeKumaHomepageData()
+	{
+		if (!$this->homepageItemPermissions($this->uptimeKumaHomepagePermissions('main'), true)) {
+			return false;
+		}
+		$api = [];
+		$url = $this->qualifyURL($this->config['uptimeKumaURL']);
+		try {
+			$metrics = (new UptimeKumaMetrics(
+				$this->getKumaClient($url, $this->config['uptimeKumaToken'])
+					->get('/metrics')
+					->getBody()
+					->getContents()
+			))->process();
+
+			$api = [
+				'data' => $metrics->getMonitors(),
+				'options' => [
+					'title' => $this->config['homepageUptimeKumaHeader'],
+					'titleToggle' => $this->config['homepageUptimeKumaHeaderToggle'],
+					'compact' => $this->config['homepageUptimeKumaCompact'],
+					'showLatency' => $this->config['homepageUptimeKumaShowLatency'],
+				]
+			];
+		} catch (GuzzleException $e) {
+			$this->setLoggerChannel('UptimeKuma')->error($e);
+			$this->setAPIResponse('error', $e->getMessage(), 401);
+			return false;
+		};
+		$api = isset($api) ? $api : false;
+		$this->setAPIResponse('success', null, 200, $api);
+		return $api;
+	}
+
+	private function getKumaClient(string $url, string $token): Client
+	{
+		if (!isset(static::$kumaClient)) {
+			static::$kumaClient = new Client([
+				'base_uri' => $url,
+				'headers' => [
+					'Authorization' => sprintf("Basic %s", base64_encode(':'.$token)),
+				],
+			]);
+		}
+
+		return static::$kumaClient;
+	}
+}

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

@@ -308,6 +308,14 @@ $app->get('/homepage/monitorr/data', function ($request, $response, $args) {
 		->withHeader('Content-Type', 'application/json;charset=UTF-8')
 		->withStatus($GLOBALS['responseCode']);
 });
+$app->get('/homepage/kuma/data', function ($request, $response, $args) {
+	$Organizr = ($request->getAttribute('Organizr')) ?? new Organizr();
+	$Organizr->getUptimeKumaHomepageData();
+	$response->getBody()->write(jsonE($GLOBALS['api']));
+	return $response
+		->withHeader('Content-Type', 'application/json;charset=UTF-8')
+		->withStatus($GLOBALS['responseCode']);
+});
 $app->get('/homepage/speedtest/data', function ($request, $response, $args) {
 	$Organizr = ($request->getAttribute('Organizr')) ?? new Organizr();
 	$Organizr->getSpeedtestHomepageData();

+ 116 - 0
js/functions.js

@@ -9087,6 +9087,122 @@ function homepageMonitorr(timeout){
     timeouts[timeoutTitle] = setTimeout(function(){ homepageMonitorr(timeout); }, timeout);
     delete timeout;
 }
+function buildUptimeKumaItem(array){
+    var cards = '';
+    var options = array['options'];
+    var services = array['data'];
+    var tabName = '';
+
+    var buildCard = function(name, data) {
+        if(data.status == true) {
+            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'
+        }
+        tabName = data.name;
+        kumaLink = '<a href="javascript:void(0)" onclick="tabActions(event,\''+tabName+'\',1)">';
+        if(options['compact']) {
+            var card = `
+            <div class="col-xl-2 col-lg-3 col-md-4 col-sm-6 col-xs-12">
+                <div class="card bg-inverse text-white mb-3 monitorr-card">
+                    <div class="card-body bg-org-alt pt-1 pb-1">
+                        <div class="d-flex no-block align-items-center">
+                            <div class="left-health bg-`+statusColor+`"></div>
+                            <div class="ml-1 w-100">
+                                <i class="`+imageText+` font-20 pull-right mt-3 mb-2"></i>
+                                `; if (typeof data.url !== 'undefined') { card += kumaLink; }
+                                card += `<h3 class="d-flex no-block align-items-center mt-2 mb-2"><img class="lazyload loginTitle">&nbsp;`+data.name;
+                                if (data.latency != null && options.showLatency) {
+                                    card += `<span class="ml-3 font-12 align-self-center text-dark">`+data.latency+`ms</span></h3>`
+                                }
+                                card += `</h3>`
+                                if (typeof data.url !== 'undefined') { card +=`</a>`; }
+                                card += `<div class="clearfix"></div>
+                            </div>
+                        </div>
+                    </div>
+                </div>
+            </div>`;
+        } else {
+            var card = `
+            <div class="col-lg-2 col-md-3 col-sm-4 col-xs-6">
+                <div class="card bg-inverse text-white mb-3 monitorr-card">
+                    <div class="card-body bg-org-alt text-center">
+                        `; if (typeof data.url !== 'undefined') { card +=`<a href="`+data.url+`" target="_blank">`; }
+                        card += `<div class="d-block">
+                            <h3 class="mt-0 mb-2">`+data.name+`</h3>`
+                            
+                        if (data.latency != null && options.showLatency) {
+                            card += `<p class="text-dark mb-0">`+data.latency+`ms</p>`
+                        }
+
+                        card += `</div>
+                        <div class="d-inline-block mt-4 py-2 px-4 badge indicator bg-`+statusColor+`">
+                            <p class="mb-0">`; if(data.status == true) { card += 'ONLINE' } else { card += 'OFFLINE' } card+=`</p>
+                        </div>
+                        `; if (typeof data.url !== 'undefined') { card +=`</a>`; }
+                        card += `</div>
+                </div>
+            </div>
+            `;
+        }
+        return card;
+    }
+    for(var key in services) {
+        cards += buildCard(key, services[key]);
+    };
+    return cards;
+}
+function buildUptimeKuma(array){
+    if(array === false){ return ''; }
+    if(array.error != undefined) {
+	    organizrConsole('Uptime Kuma Function',array.error, 'error');
+    } else {
+        var html = `
+        <div id="allUptimeKuma">
+            <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>
+                    <hr class="hidden-xs ml-2">
+                </div>
+                <div class="clearfix"></div>
+            `;
+        }
+        html += `
+                <div class="uptimeKumaCards">
+                    `+buildUptimeKumaItem(array)+`
+                </div>
+            </div>
+        </div>
+        <div class="clearfix"></div>
+        `;
+    }
+    return (array) ? html : '';
+}
+function homepageUptimeKuma(timeout){
+    var timeout = (typeof timeout !== 'undefined') ? timeout : activeInfo.settings.homepage.refresh.homepageUptimeKumaRefresh;
+    organizrAPI2('GET','api/v2/homepage/kuma/data').success(function(data) {
+        try {
+            let response = data.response;
+	        document.getElementById('homepageOrderUptimeKuma').innerHTML = '';
+	        if(response.data !== null){
+		        buildUptimeKuma(response.data)
+		        $('#homepageOrderUptimeKuma').html(buildUptimeKuma(response.data));
+	        }
+        }catch(e) {
+            console.log(e)
+	        organizrCatchError(e,data);
+        }
+    }).fail(function(xhr) {
+	    OrganizrApiError(xhr);
+    });
+    let timeoutTitle = 'UptimeKuma-Homepage';
+    if(typeof timeouts[timeoutTitle] !== 'undefined'){ clearTimeout(timeouts[timeoutTitle]); }
+    timeouts[timeoutTitle] = setTimeout(function(){ homepageUptimeKuma(timeout); }, timeout);
+    delete timeout;
+}
 function homepageSpeedtest(timeout){
     var timeout = (typeof timeout !== 'undefined') ? timeout : activeInfo.settings.homepage.refresh.homepageSpeedtestRefresh;
     organizrAPI2('GET','api/v2/homepage/speedtest/data').success(function(data) {

BIN
plugins/images/tabs/kuma.png