Browse Source

Start ping work
Added ping variables
Added ping db value updates
Added custom ping times for admin vs everyone

causefx 8 năm trước cách đây
mục cha
commit
3ebb94ee35

+ 4 - 1
api/config/default.php

@@ -149,5 +149,8 @@ return array(
 	'mediaSearchAuth' => '1',
 	'registrationPassword' => '',
 	'hideRegistration' => false,
-	'favIcon' => ''
+	'favIcon' => '',
+	'pingAuth' => '1',
+	'adminPingRefresh' => '60000',
+	'otherPingRefresh' => '600000'
 );

+ 19 - 0
api/functions/api-functions.php

@@ -637,6 +637,23 @@ function editTabs($array)
 				return false;
 			}
 			break;
+		case 'changePing':
+			try {
+				$connect = new Dibi\Connection([
+					'driver' => 'sqlite3',
+					'database' => $GLOBALS['dbLocation'] . $GLOBALS['dbName'],
+				]);
+				$connect->query('
+                        UPDATE tabs SET', [
+					'ping' => $array['data']['tabPing'],
+				], '
+                        WHERE id=?', $array['data']['id']);
+				writeLog('success', 'Tab Editor Function - Tab: ' . $array['data']['tab'] . '\'s ping status was changed to [' . $array['data']['tabPingWord'] . ']', $GLOBALS['organizrUser']['username']);
+				return true;
+			} catch (Dibi\Exception $e) {
+				return false;
+			}
+			break;
 		case 'changeDefault':
 			try {
 				$connect = new Dibi\Connection([
@@ -678,6 +695,7 @@ function editTabs($array)
                     UPDATE tabs SET', [
 					'name' => $array['data']['tabName'],
 					'url' => $array['data']['tabURL'],
+					'ping_url' => $array['data']['pingURL'],
 					'image' => $array['data']['tabImage'],
 				], '
                     WHERE id=?', $array['data']['id']);
@@ -720,6 +738,7 @@ function editTabs($array)
 					'category_id' => $default,
 					'name' => $array['data']['tabName'],
 					'url' => $array['data']['tabURL'],
+					'ping_url' => $array['data']['pingURL'],
 					'default' => $array['data']['tabDefault'],
 					'enabled' => 1,
 					'group_id' => $array['data']['tabGroupID'],

+ 15 - 53
api/functions/homepage-functions.php

@@ -218,44 +218,6 @@ function getHomepageList()
 			'value' => 'emby'
 		)
 	);
-	$time = array(
-		array(
-			'name' => '5',
-			'value' => '5000'
-		),
-		array(
-			'name' => '10',
-			'value' => '10000'
-		),
-		array(
-			'name' => '15',
-			'value' => '15000'
-		),
-		array(
-			'name' => '30',
-			'value' => '30000'
-		),
-		array(
-			'name' => '60 [1 Minute]',
-			'value' => '60000'
-		),
-		array(
-			'name' => '300 [5 Minutes]',
-			'value' => '300000'
-		),
-		array(
-			'name' => '900 [15 Minutes]',
-			'value' => '900000'
-		),
-		array(
-			'name' => '1800 [30 Minutes]',
-			'value' => '1800000'
-		),
-		array(
-			'name' => '3600 [1 Hour]',
-			'value' => '3600000'
-		),
-	);
 	$limit = array(
 		array(
 			'name' => '1 Item',
@@ -502,7 +464,7 @@ function getHomepageList()
 					'name' => 'calendarRefresh',
 					'label' => 'Refresh Seconds',
 					'value' => $GLOBALS['calendarRefresh'],
-					'options' => $time
+					'options' => optionTime()
 				)
 			),
 		)
@@ -582,7 +544,7 @@ function getHomepageList()
 						'name' => 'homepageStreamRefresh',
 						'label' => 'Refresh Seconds',
 						'value' => $GLOBALS['homepageStreamRefresh'],
-						'options' => $time
+						'options' => optionTime()
 					),
 				),
 				'Recent Items' => array(
@@ -610,7 +572,7 @@ function getHomepageList()
 						'name' => 'homepageRecentRefresh',
 						'label' => 'Refresh Seconds',
 						'value' => $GLOBALS['homepageRecentRefresh'],
-						'options' => $time
+						'options' => optionTime()
 					),
 				),
 				'Media Search' => array(
@@ -750,7 +712,7 @@ function getHomepageList()
 						'name' => 'homepageStreamRefresh',
 						'label' => 'Refresh Seconds',
 						'value' => $GLOBALS['homepageStreamRefresh'],
-						'options' => $time
+						'options' => optionTime()
 					),
 				),
 				'Recent Items' => array(
@@ -778,7 +740,7 @@ function getHomepageList()
 						'name' => 'homepageRecentRefresh',
 						'label' => 'Refresh Seconds',
 						'value' => $GLOBALS['homepageRecentRefresh'],
-						'options' => $time
+						'options' => optionTime()
 					),
 				),
 				'Misc Options' => array(
@@ -841,7 +803,7 @@ function getHomepageList()
 						'name' => 'homepageDownloadRefresh',
 						'label' => 'Refresh Seconds',
 						'value' => $GLOBALS['homepageDownloadRefresh'],
-						'options' => $time
+						'options' => optionTime()
 					)
 				),
 				'Test Connection' => array(
@@ -908,7 +870,7 @@ function getHomepageList()
 						'name' => 'homepageDownloadRefresh',
 						'label' => 'Refresh Seconds',
 						'value' => $GLOBALS['homepageDownloadRefresh'],
-						'options' => $time
+						'options' => optionTime()
 					)
 				),
 				'Test Connection' => array(
@@ -986,7 +948,7 @@ function getHomepageList()
 						'name' => 'homepageDownloadRefresh',
 						'label' => 'Refresh Seconds',
 						'value' => $GLOBALS['homepageDownloadRefresh'],
-						'options' => $time
+						'options' => optionTime()
 					)
 				)
 			)
@@ -1062,7 +1024,7 @@ function getHomepageList()
 						'name' => 'homepageDownloadRefresh',
 						'label' => 'Refresh Seconds',
 						'value' => $GLOBALS['homepageDownloadRefresh'],
-						'options' => $time
+						'options' => optionTime()
 					)
 				)
 			)
@@ -1140,7 +1102,7 @@ function getHomepageList()
 						'name' => 'homepageDownloadRefresh',
 						'label' => 'Refresh Seconds',
 						'value' => $GLOBALS['homepageDownloadRefresh'],
-						'options' => $time
+						'options' => optionTime()
 					)
 				),
 				'Test Connection' => array(
@@ -1243,7 +1205,7 @@ function getHomepageList()
 						'name' => 'calendarRefresh',
 						'label' => 'Refresh Seconds',
 						'value' => $GLOBALS['calendarRefresh'],
-						'options' => $time
+						'options' => optionTime()
 					),
 					array(
 						'type' => 'switch',
@@ -1352,7 +1314,7 @@ function getHomepageList()
 						'name' => 'calendarRefresh',
 						'label' => 'Refresh Seconds',
 						'value' => $GLOBALS['calendarRefresh'],
-						'options' => $time
+						'options' => optionTime()
 					)
 				),
 				'Test Connection' => array(
@@ -1441,7 +1403,7 @@ function getHomepageList()
 						'name' => 'calendarRefresh',
 						'label' => 'Refresh Seconds',
 						'value' => $GLOBALS['calendarRefresh'],
-						'options' => $time
+						'options' => optionTime()
 					)
 				)
 			)
@@ -1516,7 +1478,7 @@ function getHomepageList()
 						'name' => 'calendarRefresh',
 						'label' => 'Refresh Seconds',
 						'value' => $GLOBALS['calendarRefresh'],
-						'options' => $time
+						'options' => optionTime()
 					)
 				)
 			)
@@ -1576,7 +1538,7 @@ function getHomepageList()
 						'name' => 'ombiRefresh',
 						'label' => 'Refresh Seconds',
 						'value' => $GLOBALS['ombiRefresh'],
-						'options' => $time
+						'options' => optionTime()
 					)
 				)
 			)

+ 85 - 0
api/functions/option-functions.php

@@ -0,0 +1,85 @@
+<?php
+function optionLimit()
+{
+	return array(
+		array(
+			'name' => '1 Item',
+			'value' => '1'
+		),
+		array(
+			'name' => '2 Items',
+			'value' => '2'
+		),
+		array(
+			'name' => '3 Items',
+			'value' => '3'
+		),
+		array(
+			'name' => '4 Items',
+			'value' => '4'
+		),
+		array(
+			'name' => '5 Items',
+			'value' => '5'
+		),
+		array(
+			'name' => '6 Items',
+			'value' => '6'
+		),
+		array(
+			'name' => '7 Items',
+			'value' => '7'
+		),
+		array(
+			'name' => '8 Items',
+			'value' => '8'
+		),
+		array(
+			'name' => 'Unlimited',
+			'value' => '1000'
+		),
+	);
+}
+
+function optionTime()
+{
+	return array(
+		array(
+			'name' => '5',
+			'value' => '5000'
+		),
+		array(
+			'name' => '10',
+			'value' => '10000'
+		),
+		array(
+			'name' => '15',
+			'value' => '15000'
+		),
+		array(
+			'name' => '30',
+			'value' => '30000'
+		),
+		array(
+			'name' => '60 [1 Minute]',
+			'value' => '60000'
+		),
+		array(
+			'name' => '300 [5 Minutes]',
+			'value' => '300000'
+		),
+		array(
+			'name' => '900 [15 Minutes]',
+			'value' => '900000'
+		),
+		array(
+			'name' => '1800 [30 Minutes]',
+			'value' => '1800000'
+		),
+		array(
+			'name' => '3600 [1 Hour]',
+			'value' => '3600000'
+		),
+	);
+	
+}

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

@@ -439,6 +439,29 @@ function getSettingsMain()
 				'label' => 'Hide Registration',
 				'value' => $GLOBALS['hideRegistration']
 			)
+		),
+		'Ping' => array(
+			array(
+				'type' => 'select',
+				'name' => 'pingAuth',
+				'label' => 'Minimum Authentication',
+				'value' => $GLOBALS['pingAuth'],
+				'options' => groupSelect()
+			),
+			array(
+				'type' => 'select',
+				'name' => 'adminPingRefresh',
+				'label' => 'Admin Refresh Seconds',
+				'value' => $GLOBALS['adminPingRefresh'],
+				'options' => optionTime()
+			),
+			array(
+				'type' => 'select',
+				'name' => 'otherPingRefresh',
+				'label' => 'Everyone Refresh Seconds',
+				'value' => $GLOBALS['otherPingRefresh'],
+				'options' => optionTime()
+			),
 		)
 	);
 }
@@ -1417,4 +1440,55 @@ function frameTest($url)
 	} else {
 		return false;
 	}
+}
+
+function ping($pings)
+{
+	if (qualifyRequest($GLOBALS['pingAuth'])) {
+		$type = gettype($pings);
+		$ping = new Ping("");
+		$ping->setTtl(128);
+		$ping->setTimeout(2);
+		switch ($type) {
+			case "array":
+				$results = [];
+				foreach ($pings as $k => $v) {
+					if (strpos($v, ':') !== false) {
+						$domain = explode(':', $v)[0];
+						$port = explode(':', $v)[1];
+						$ping->setHost($domain);
+						$ping->setPort($port);
+						$latency = $ping->ping('fsockopen');
+					} else {
+						$ping->setHost($v);
+						$latency = $ping->ping();
+					}
+					if ($latency || $latency === 0) {
+						$results[$v] = $latency;
+					} else {
+						$results[$v] = false;
+					}
+				}
+				break;
+			case "string":
+				if (strpos($pings, ':') !== false) {
+					$domain = explode(':', $pings)[0];
+					$port = explode(':', $pings)[1];
+					$ping->setHost($domain);
+					$ping->setPort($port);
+					$latency = $ping->ping('fsockopen');
+				} else {
+					$ping->setHost($pings);
+					$latency = $ping->ping();
+				}
+				if ($latency || $latency === 0) {
+					$results = $latency;
+				} else {
+					$results = false;
+				}
+				break;
+		}
+		return $results;
+	}
+	return false;
 }

+ 328 - 0
api/functions/ping.class.php

@@ -0,0 +1,328 @@
+<?php
+
+class Ping
+{
+	
+	private $host;
+	private $ttl;
+	private $timeout;
+	private $port = 80;
+	private $data = 'Ping';
+	private $commandOutput;
+	
+	/**
+	 * Called when the Ping object is created.
+	 *
+	 * @param string $host
+	 *   The host to be pinged.
+	 * @param int $ttl
+	 *   Time-to-live (TTL) (You may get a 'Time to live exceeded' error if this
+	 *   value is set too low. The TTL value indicates the scope or range in which
+	 *   a packet may be forwarded. By convention:
+	 *     - 0 = same host
+	 *     - 1 = same subnet
+	 *     - 32 = same site
+	 *     - 64 = same region
+	 *     - 128 = same continent
+	 *     - 255 = unrestricted
+	 * @param int $timeout
+	 *   Timeout (in seconds) used for ping and fsockopen().
+	 * @throws \Exception if the host is not set.
+	 */
+	public function __construct($host, $ttl = 255, $timeout = 10)
+	{
+		if (!isset($host)) {
+			throw new \Exception("Error: Host name not supplied.");
+		}
+		$this->host = $host;
+		$this->ttl = $ttl;
+		$this->timeout = $timeout;
+	}
+	
+	/**
+	 * Set the ttl (in hops).
+	 *
+	 * @param int $ttl
+	 *   TTL in hops.
+	 */
+	public function setTtl($ttl)
+	{
+		$this->ttl = $ttl;
+	}
+	
+	/**
+	 * Get the ttl.
+	 *
+	 * @return int
+	 *   The current ttl for Ping.
+	 */
+	public function getTtl()
+	{
+		return $this->ttl;
+	}
+	
+	/**
+	 * Set the timeout.
+	 *
+	 * @param int $timeout
+	 *   Time to wait in seconds.
+	 */
+	public function setTimeout($timeout)
+	{
+		$this->timeout = $timeout;
+	}
+	
+	/**
+	 * Get the timeout.
+	 *
+	 * @return int
+	 *   Current timeout for Ping.
+	 */
+	public function getTimeout()
+	{
+		return $this->timeout;
+	}
+	
+	/**
+	 * Set the host.
+	 *
+	 * @param string $host
+	 *   Host name or IP address.
+	 */
+	public function setHost($host)
+	{
+		$this->host = $host;
+	}
+	
+	/**
+	 * Get the host.
+	 *
+	 * @return string
+	 *   The current hostname for Ping.
+	 */
+	public function getHost()
+	{
+		return $this->host;
+	}
+	
+	/**
+	 * Set the port (only used for fsockopen method).
+	 *
+	 * Since regular pings use ICMP and don't need to worry about the concept of
+	 * 'ports', this is only used for the fsockopen method, which pings servers by
+	 * checking port 80 (by default).
+	 *
+	 * @param int $port
+	 *   Port to use for fsockopen ping (defaults to 80 if not set).
+	 */
+	public function setPort($port)
+	{
+		$this->port = $port;
+	}
+	
+	/**
+	 * Get the port (only used for fsockopen method).
+	 *
+	 * @return int
+	 *   The port used by fsockopen pings.
+	 */
+	public function getPort()
+	{
+		return $this->port;
+	}
+	
+	/**
+	 * Return the command output when method=exec.
+	 * @return string
+	 */
+	public function getCommandOutput()
+	{
+		return $this->commandOutput;
+	}
+	
+	/**
+	 * Matches an IP on command output and returns.
+	 * @return string
+	 */
+	public function getIpAddress()
+	{
+		$out = array();
+		if (preg_match('/\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/', $this->commandOutput, $out)) {
+			return $out[0];
+		}
+		return null;
+	}
+	
+	/**
+	 * Ping a host.
+	 *
+	 * @param string $method
+	 *   Method to use when pinging:
+	 *     - exec (default): Pings through the system ping command. Fast and
+	 *       robust, but a security risk if you pass through user-submitted data.
+	 *     - fsockopen: Pings a server on port 80.
+	 *     - socket: Creates a RAW network socket. Only usable in some
+	 *       environments, as creating a SOCK_RAW socket requires root privileges.
+	 *
+	 * @throws InvalidArgumentException if $method is not supported.
+	 *
+	 * @return mixed
+	 *   Latency as integer, in ms, if host is reachable or FALSE if host is down.
+	 */
+	public function ping($method = 'exec')
+	{
+		$latency = false;
+		switch ($method) {
+			case 'exec':
+				$latency = $this->pingExec();
+				break;
+			case 'fsockopen':
+				$latency = $this->pingFsockopen();
+				break;
+			case 'socket':
+				$latency = $this->pingSocket();
+				break;
+			default:
+				throw new \InvalidArgumentException('Unsupported ping method.');
+		}
+		// Return the latency.
+		return $latency;
+	}
+	
+	/**
+	 * The exec method uses the possibly insecure exec() function, which passes
+	 * the input to the system. This is potentially VERY dangerous if you pass in
+	 * any user-submitted data. Be SURE you sanitize your inputs!
+	 *
+	 * @return int
+	 *   Latency, in ms.
+	 */
+	private function pingExec()
+	{
+		$latency = false;
+		$ttl = escapeshellcmd($this->ttl);
+		$timeout = escapeshellcmd($this->timeout);
+		$host = escapeshellcmd($this->host);
+		// Exec string for Windows-based systems.
+		if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
+			// -n = number of pings; -i = ttl; -w = timeout (in milliseconds).
+			$exec_string = 'ping -n 1 -i ' . $ttl . ' -w ' . ($timeout * 1000) . ' ' . $host;
+		} // Exec string for Darwin based systems (OS X).
+		else if (strtoupper(PHP_OS) === 'DARWIN') {
+			// -n = numeric output; -c = number of pings; -m = ttl; -t = timeout.
+			$exec_string = 'ping -n -c 1 -m ' . $ttl . ' -t ' . $timeout . ' ' . $host;
+		} // Exec string for other UNIX-based systems (Linux).
+		else {
+			// -n = numeric output; -c = number of pings; -t = ttl; -W = timeout
+			$exec_string = 'ping -n -c 1 -t ' . $ttl . ' -W ' . $timeout . ' ' . $host . ' 2>&1';
+		}
+		exec($exec_string, $output, $return);
+		// Strip empty lines and reorder the indexes from 0 (to make results more
+		// uniform across OS versions).
+		$this->commandOutput = implode($output, '');
+		$output = array_values(array_filter($output));
+		// If the result line in the output is not empty, parse it.
+		if (!empty($output[1])) {
+			// Search for a 'time' value in the result line.
+			$response = preg_match("/time(?:=|<)(?<time>[\.0-9]+)(?:|\s)ms/", $output[1], $matches);
+			// If there's a result and it's greater than 0, return the latency.
+			if ($response > 0 && isset($matches['time'])) {
+				$latency = round($matches['time'], 2);
+			}
+		}
+		return $latency;
+	}
+	
+	/**
+	 * The fsockopen method simply tries to reach the host on a port. This method
+	 * is often the fastest, but not necessarily the most reliable. Even if a host
+	 * doesn't respond, fsockopen may still make a connection.
+	 *
+	 * @return int
+	 *   Latency, in ms.
+	 */
+	private function pingFsockopen()
+	{
+		$start = microtime(true);
+		// fsockopen prints a bunch of errors if a host is unreachable. Hide those
+		// irrelevant errors and deal with the results instead.
+		$fp = @fsockopen($this->host, $this->port, $errno, $errstr, $this->timeout);
+		if (!$fp) {
+			$latency = false;
+		} else {
+			$latency = microtime(true) - $start;
+			$latency = round($latency * 1000, 2);
+		}
+		return $latency;
+	}
+	
+	/**
+	 * The socket method uses raw network packet data to try sending an ICMP ping
+	 * packet to a server, then measures the response time. Using this method
+	 * requires the script to be run with root privileges, though, so this method
+	 * only works reliably on Windows systems and on Linux servers where the
+	 * script is not being run as a web user.
+	 *
+	 * @return int
+	 *   Latency, in ms.
+	 */
+	private function pingSocket()
+	{
+		// Create a package.
+		$type = "\x08";
+		$code = "\x00";
+		$checksum = "\x00\x00";
+		$identifier = "\x00\x00";
+		$seq_number = "\x00\x00";
+		$package = $type . $code . $checksum . $identifier . $seq_number . $this->data;
+		// Calculate the checksum.
+		$checksum = $this->calculateChecksum($package);
+		// Finalize the package.
+		$package = $type . $code . $checksum . $identifier . $seq_number . $this->data;
+		// Create a socket, connect to server, then read socket and calculate.
+		if ($socket = socket_create(AF_INET, SOCK_RAW, 1)) {
+			socket_set_option($socket, SOL_SOCKET, SO_RCVTIMEO, array(
+				'sec' => 10,
+				'usec' => 0,
+			));
+			// Prevent errors from being printed when host is unreachable.
+			@socket_connect($socket, $this->host, null);
+			$start = microtime(true);
+			// Send the package.
+			@socket_send($socket, $package, strlen($package), 0);
+			if (socket_read($socket, 255) !== false) {
+				$latency = microtime(true) - $start;
+				$latency = round($latency * 1000, 2);
+			} else {
+				$latency = false;
+			}
+		} else {
+			$latency = false;
+		}
+		// Close the socket.
+		socket_close($socket);
+		return $latency;
+	}
+	
+	/**
+	 * Calculate a checksum.
+	 *
+	 * @param string $data
+	 *   Data for which checksum will be calculated.
+	 *
+	 * @return string
+	 *   Binary string checksum of $data.
+	 */
+	private function calculateChecksum($data)
+	{
+		if (strlen($data) % 2) {
+			$data .= "\x00";
+		}
+		$bit = unpack('n*', $data);
+		$sum = array_sum($bit);
+		while ($sum >> 16) {
+			$sum = ($sum >> 16) + ($sum & 0xffff);
+		}
+		return pack('n*', ~$sum);
+	}
+}

+ 13 - 0
api/index.php

@@ -343,6 +343,19 @@ switch ($function) {
 				break;
 		}
 		break;
+	case 'v1_ping':
+		switch ($method) {
+			case 'POST':
+				$result['status'] = 'success';
+				$result['statusText'] = 'success';
+				$result['data'] = ping($_POST['data']['pingList']);
+				break;
+			default:
+				$result['status'] = 'error';
+				$result['statusText'] = 'The function requested is not defined for method: ' . $method;
+				break;
+		}
+		break;
 	case 'v1_test_api_connection':
 		switch ($method) {
 			case 'POST':

+ 9 - 0
api/pages/settings-tab-editor-tabs.php

@@ -31,6 +31,7 @@ $( \'#tabEditorTable\' ).sortable({
                         <th lang="en style="text-align:center"">DEFAULT</th>
                         <th lang="en" style="text-align:center">ACTIVE</th>
                         <th lang="en" style="text-align:center">SPLASH</th>
+                        <th lang="en" style="text-align:center">PING</th>
                         <th lang="en" style="text-align:center">EDIT</th>
                         <th lang="en" style="text-align:center">DELETE</th>
                     </tr>
@@ -59,6 +60,10 @@ $( \'#tabEditorTable\' ).sortable({
             <label class="control-label" for="new-tab-form-inputURLNew" lang="en">Tab URL</label>
             <input type="text" class="form-control" id="new-tab-form-inputURLNew" name="tabURL"  required="">
         </div>
+        <div class="form-group">
+            <label class="control-label" for="new-tab-form-inputPingURLNew" lang="en">Ping URL</label>
+            <input type="text" class="form-control" id="new-tab-form-inputPingURLNew" name="pingURL"  placeholde="host/ip:port" rrequired="">
+        </div>
         <div class="form-group">
             <label class="control-label" for="new-tab-form-inputImageNew" lang="en">Tab Image</label>
             <input type="text" class="form-control" id="new-tab-form-inputImageNew" name="tabImage"  required="">
@@ -88,6 +93,10 @@ $( \'#tabEditorTable\' ).sortable({
             <label class="control-label" for="edit-tab-form-inputURL" lang="en">Tab URL</label>
             <input type="text" class="form-control" id="edit-tab-form-inputURL" name="tabURL"  required="">
         </div>
+        <div class="form-group">
+            <label class="control-label" for="edit-tab-form-pingURL" lang="en">Ping URL</label>
+            <input type="text" class="form-control" id="edit-tab-form-pingURL" name="pingURL" placeholde="host/ip:port" required="">
+        </div>
         <div class="form-group">
             <label class="control-label" for="edit-tab-form-inputImage" lang="en">Tab Image</label>
             <input type="text" class="form-control" id="edit-tab-form-inputImage" name="tabImage"  required="">

+ 32 - 0
css/organizr.css

@@ -889,4 +889,36 @@ ul.nav.customtab.nav-tabs.nav-low-margin {
 }
 i.fa.fa-life-ring.fa-fw {
     color: #C62828;
+}
+.ping {
+    position: relative;
+    margin-top: 0;
+}
+.ping .heartbit {
+    position: absolute;
+    top: -15px;
+    left: 15px;
+    height: 25px;
+    width: 25px;
+    z-index: 10;
+    border: 5px solid #ff7676;
+    border-radius: 70px;
+    -moz-animation: heartbit 1s ease-out;
+    -moz-animation-iteration-count: infinite;
+    -o-animation: heartbit 1s ease-out;
+    -o-animation-iteration-count: infinite;
+    -webkit-animation: heartbit 1s ease-out;
+    -webkit-animation-iteration-count: infinite;
+    animation-iteration-count: infinite;
+}
+.ping .point {
+    width: 6px;
+    height: 6px;
+    -webkit-border-radius: 30px;
+    -moz-border-radius: 30px;
+    border-radius: 30px;
+    background-color: #ff7676;
+    position: absolute;
+    left: 25px;
+    top: -5px;
 }

Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 0 - 0
css/organizr.min.css


+ 21 - 0
js/custom.js

@@ -717,6 +717,24 @@ $(document).on("change", ".splashSwitch", function () {
     callbacks.add( buildTabEditor );
     settingsAPI(post,callbacks);
 });
+// CHANGE SPLASH TAB
+$(document).on("change", ".pingSwitch", function () {
+    //Create POST Array
+    var post = {
+        action:'changePing',
+        api:'api/?v1/settings/tab/editor/tabs',
+        id:$(this).parent().parent().attr("data-id"),
+        tab:$(this).parent().parent().attr("data-name"),
+        tabPing:$(this).prop("checked") ? 1 : 0,
+        tabPingWord:$(this).prop("checked") ? "On" : "Off",
+        messageTitle:'',
+        messageBody:'Tab Info updated for '+$(this).parent().parent().attr("data-name"),
+        error:'Organizr Function: Tab API Connection Failed'
+    };
+    var callbacks = $.Callbacks();
+    callbacks.add( buildTabEditor );
+    settingsAPI(post,callbacks);
+});
 // CHANGE DEFAULT TAB
 $(document).on("change", ".defaultSwitch", function () {
     //Create POST Array
@@ -767,6 +785,7 @@ $(document).on("click", ".deleteTab", function () {
 $(document).on("click", ".editTabButton", function () {
     $('#edit-tab-form [name=tabName]').val($(this).parent().parent().attr("data-name"));
     $('#edit-tab-form [name=tabURL]').val($(this).parent().parent().attr("data-url"));
+    $('#edit-tab-form [name=pingURL]').val($(this).parent().parent().attr("data-ping-url"));
     $('#edit-tab-form [name=tabImage]').val($(this).parent().parent().attr("data-image"));
     $('#edit-tab-form [name=id]').val($(this).parent().parent().attr("data-id"));
     if( $(this).parent().parent().attr("data-url").indexOf('/?v') > 0){
@@ -785,6 +804,7 @@ $(document).on("click", ".editTab", function () {
         tabName:$('#edit-tab-form [name=tabName]').val(),
         tabImage:$('#edit-tab-form [name=tabImage]').val(),
         tabURL:$('#edit-tab-form [name=tabURL]').val(),
+        pingURL:$('#edit-tab-form [name=pingURL]').val(),
         messageTitle:'',
         messageBody:'Edited Tab '+$('#edit-tab-form [name=tabName]').val(),
         error:'Organizr Function: Tab Editor API Connection Failed'
@@ -819,6 +839,7 @@ $(document).on("click", ".addNewTab", function () {
         tabName:$('#new-tab-form [name=tabName]').val(),
         tabImage:$('#new-tab-form [name=tabImage]').val(),
         tabURL:$('#new-tab-form [name=tabURL]').val(),
+        pingURL:$('#new-tab-form [name=pingURL]').val(),
         tabGroupID:1,
         tabEnabled:0,
         tabDefault:0,

Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 0 - 0
js/custom.min.js


+ 41 - 4
js/functions.js

@@ -1220,8 +1220,9 @@ function buildFrameContainer(name,url,type){
 function buildInternalContainer(name,url,type){
 	return `<div id="internal-`+cleanClass(name)+`" data-type="`+type+`" class="internal-container frame-`+cleanClass(name)+` hidden" data-url="`+url+`" data-name="`+cleanClass(name)+`"></div>`;
 }
-function buildMenuList(name,url,type,icon){
-	return `<li id="menu-`+cleanClass(name)+`" type="`+type+`" data-url="`+url+`"><a class="waves-effect" onclick="tabActions(event,'`+cleanClass(name)+`',`+type+`);">`+iconPrefix(icon)+`<span class="hide-menu">`+name+`</span></a></li>`;
+function buildMenuList(name,url,type,icon,ping=null){
+    var ping = (ping !== null) ? `<div class="menu-`+cleanClass(ping)+`-ping"></div>` : '';
+	return `<li id="menu-`+cleanClass(name)+`" type="`+type+`" data-url="`+url+`"><a class="waves-effect" onclick="tabActions(event,'`+cleanClass(name)+`',`+type+`);">`+iconPrefix(icon)+`<span class="hide-menu">`+name+`</span>`+ping+`</a></li>`;
 }
 function splashMenu(arrayItems){
 
@@ -1261,7 +1262,7 @@ function tabProcess(arrayItems) {
 					default:
 						console.error('Tab Process: Action not set');
 				}
-				menuList = buildMenuList(v.name,v.url,v.type,v.image);
+				menuList = buildMenuList(v.name,v.url,v.type,v.image,v.ping_url);
 				if(v.category_id === 0){
 					$(menuList).prependTo($('#side-menu'));
 				}else{
@@ -1478,13 +1479,14 @@ function buildTabEditorItem(array){
 		var deleteDisabled = v.url.indexOf('/settings/') > 0 ? 'disabled' : 'deleteTab';
 		var buttonDisabled = v.url.indexOf('/settings/') > 0 ? 'disabled' : '';
 		tabList += `
-		<tr class="tabEditor" data-order="`+v.order+`" data-id="`+v.id+`" data-group-id="`+v.group_id+`" data-category-id="`+v.category_id+`" data-name="`+v.name+`" data-url="`+v.url+`" data-image="`+v.image+`">
+		<tr class="tabEditor" data-order="`+v.order+`" data-id="`+v.id+`" data-group-id="`+v.group_id+`" data-category-id="`+v.category_id+`" data-name="`+v.name+`" data-url="`+v.url+`" data-ping-url="`+v.ping_url+`" data-image="`+v.image+`">
 			<input type="hidden" class="form-control" name="tab[`+v.id+`].id" value="`+v.id+`">
 			<input type="hidden" class="form-control order" name="tab[`+v.id+`].order" value="`+v.order+`">
 			<input type="hidden" class="form-control" name="tab[`+v.id+`].originalOrder" value="`+v.order+`">
 			<input type="hidden" class="form-control" name="tab[`+v.id+`].url_local" value="`+v.url_local+`">
 			<input type="hidden" class="form-control" name="tab[`+v.id+`].name" value="`+v.name+`">
 			<input type="hidden" class="form-control" name="tab[`+v.id+`].url" value="`+v.url+`">
+			<input type="hidden" class="form-control" name="tab[`+v.id+`].ping_url" value="`+v.ping_url+`">
 			<input type="hidden" class="form-control" name="tab[`+v.id+`].image" value="`+v.image+`">
 			<td style="text-align:center" class="text-center el-element-overlay">
 				<div class="el-card-item p-0">
@@ -1506,6 +1508,7 @@ function buildTabEditorItem(array){
 
 			<td style="text-align:center"><input `+buttonDisabled+` type="checkbox" class="js-switch enabledSwitch `+buttonDisabled+`" data-size="small" data-color="#99d683" data-secondary-color="#f96262" name="tab[`+v.id+`].enabled" value="true" `+tof(v.enabled,'c')+`/><input type="hidden" class="form-control" name="tab[`+v.id+`].enabled" value="false"></td>
 			<td style="text-align:center"><input type="checkbox" class="js-switch splashSwitch" data-size="small" data-color="#99d683" data-secondary-color="#f96262" name="tab[`+v.id+`].splash" value="true" `+tof(v.splash,'c')+`/><input type="hidden" class="form-control" name="tab[`+v.id+`].splash" value="false"></td>
+			<td style="text-align:center"><input type="checkbox" class="js-switch pingSwitch" data-size="small" data-color="#99d683" data-secondary-color="#f96262" name="tab[`+v.id+`].ping" value="true" `+tof(v.ping,'c')+`/><input type="hidden" class="form-control" name="tab[`+v.id+`].ping" value="false"></td>
 			<td style="text-align:center"><button type="button" class="btn btn-info btn-outline btn-circle btn-lg m-r-5 editTabButton popup-with-form" href="#edit-tab-form" data-effect="mfp-3d-unfold"><i class="ti-pencil-alt"></i></button></td>
 			<td style="text-align:center"><button type="button" class="btn btn-danger btn-outline btn-circle btn-lg m-r-5 `+deleteDisabled+`"><i class="ti-trash"></i></button></td>
 		</tr>
@@ -3916,6 +3919,39 @@ function buildMediaResults(array,source,term){
     `;
     return buttons+results;
 }
+function getPingList(arrayItems){
+    var pingList = [];
+    var timeout = (activeInfo.user.groupID < 1) ? activeInfo.settings.homepage.refresh.adminPingRefresh : activeInfo.settings.homepage.refresh.otherPingRefresh;
+    if (Array.isArray(arrayItems['data']['tabs']) && arrayItems['data']['tabs'].length > 0) {
+        $.each(arrayItems['data']['tabs'], function(i,v) {
+            if(v.ping && v.ping_url !== null){
+                pingList.push(v.ping_url);
+            }
+        });
+    }
+    return (pingList.length > 0) ? pingUpdate(pingList,timeout): false;
+}
+function pingUpdate(pingList,timeout){
+    console.log('starting checks');
+    organizrAPI('POST','api/?v1/ping',{pingList:pingList}).success(function(data) {
+        var response = JSON.parse(data);
+        if (response.data !== false || response.data !== null) {
+            console.log('checks completed');
+            $.each(response.data, function(i,v) {
+                if(v == false){
+                    var elm = $('.menu-'+cleanClass(i)+'-ping');
+                    elm.html('<div class="ping"><span class="heartbit"></span><span class="point"></span></div>');
+                    elm.parent().find('img').addClass('grayscale');
+                }
+            });
+        }
+    }).fail(function(xhr) {
+        console.error("Organizr Function: API Connection Failed");
+    });
+    var timeoutTitle = 'ping';
+    if(typeof timeouts[timeoutTitle] !== 'undefined'){ clearTimeout(timeouts[timeoutTitle]); }
+    timeouts[timeoutTitle] = setTimeout(function(){ pingUpdate(pingList,timeout); }, timeout);
+}
 function launch(){
 	organizrConnect('api/?v1/launch_organizr').success(function (data) {
         try {
@@ -3971,6 +4007,7 @@ function launch(){
 				tabProcess(json);
 				accountManager(json);
 				organizrSpecialSettings(json);
+                getPingList(json);
 				break;
 			default:
 				console.error('Organizr Function: Action not set or defined');

Một số tệp đã không được hiển thị bởi vì quá nhiều tập tin thay đổi trong này khác