commit
1c5c53452a
4
.gitignore
vendored
4
.gitignore
vendored
@ -1,3 +1,3 @@
|
||||
|
||||
.idea
|
||||
config.php
|
||||
/config.php
|
||||
!php-fpm/src/config.php
|
262
LookingGlass.php
262
LookingGlass.php
@ -1,4 +1,4 @@
|
||||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
/**
|
||||
* Hybula Looking Glass
|
||||
*
|
||||
@ -10,15 +10,28 @@
|
||||
* @since File available since release 0.1
|
||||
* @link https://github.com/hybula/lookingglass
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Hybula;
|
||||
|
||||
class LookingGlass
|
||||
{
|
||||
public static $targetHost;
|
||||
public static $targetType;
|
||||
public const IPV4 = 'ipv4';
|
||||
public const IPV6 = 'ipv6';
|
||||
|
||||
public const SESSION_TARGET_HOST = 'target_host';
|
||||
public const SESSION_TARGET_METHOD = 'target_method';
|
||||
public const SESSION_TOS_CHECKED = 'tos_checked';
|
||||
public const SESSION_CALL_BACKEND = 'call_backend';
|
||||
public const SESSION_ERROR_MESSAGE = 'error_message';
|
||||
public const SESSION_CSRF = 'CSRF';
|
||||
|
||||
public const METHOD_PING = 'ping';
|
||||
public const METHOD_PING6 = 'ping6';
|
||||
public const METHOD_MTR = 'mtr';
|
||||
public const METHOD_MTR6 = 'mtr6';
|
||||
public const METHOD_TRACEROUTE = 'traceroute';
|
||||
public const METHOD_TRACEROUTE6 = 'traceroute6';
|
||||
|
||||
private const MTR_COUNT = 10;
|
||||
|
||||
/**
|
||||
* Validates the config.php file for required constants.
|
||||
@ -27,6 +40,7 @@ class LookingGlass
|
||||
*/
|
||||
public static function validateConfig(): void
|
||||
{
|
||||
//@formatter:off
|
||||
if (!defined('LG_TITLE')) die('LG_TITLE not found in config.php');
|
||||
if (!defined('LG_LOGO')) die('LG_LOGO not found in config.php');
|
||||
if (!defined('LG_LOGO_URL')) die('LG_LOGO_URL not found in config.php');
|
||||
@ -38,6 +52,7 @@ class LookingGlass
|
||||
if (!defined('LG_CUSTOM_HTML')) die('LG_CUSTOM_HTML not found in config.php');
|
||||
if (!defined('LG_CUSTOM_PHP')) die('LG_CUSTOM_PHP not found in config.php');
|
||||
if (!defined('LG_LOCATION')) die('LG_LOCATION not found in config.php');
|
||||
if (!defined('LG_MAPS_QUERY')) die('LG_MAPS_QUERY not found in config.php');
|
||||
if (!defined('LG_FACILITY')) die('LG_FACILITY not found in config.php');
|
||||
if (!defined('LG_FACILITY_URL')) die('LG_FACILITY_URL not found in config.php');
|
||||
if (!defined('LG_IPV4')) die('LG_IPV4 not found in config.php');
|
||||
@ -51,6 +66,7 @@ class LookingGlass
|
||||
if (!defined('LG_SPEEDTEST_CMD_OUTGOING')) die('LG_SPEEDTEST_CMD_OUTGOING not found in config.php');
|
||||
if (!defined('LG_SPEEDTEST_FILES')) die('LG_SPEEDTEST_FILES not found in config.php');
|
||||
if (!defined('LG_TERMS')) die('LG_TERMS not found in config.php');
|
||||
//@formatter:on
|
||||
}
|
||||
|
||||
/**
|
||||
@ -105,17 +121,20 @@ class LookingGlass
|
||||
if (!substr_count($host, '.')) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (filter_var('https://' . $host, FILTER_VALIDATE_URL)) {
|
||||
if ($host = parse_url('https://' . $host, PHP_URL_HOST)) {
|
||||
if ($type == 'ipv4' && isset(dns_get_record($host, DNS_A)[0]['ip'])) {
|
||||
if ($type === self::IPV4 && isset(dns_get_record($host, DNS_A)[0]['ip'])) {
|
||||
return $host;
|
||||
}
|
||||
if ($type == 'ipv6' && isset(dns_get_record($host, DNS_AAAA)[0]['ipv6'])) {
|
||||
if ($type === self::IPV6 && isset(dns_get_record($host, DNS_AAAA)[0]['ipv6'])) {
|
||||
return $host;
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
@ -128,6 +147,10 @@ class LookingGlass
|
||||
*/
|
||||
public static function detectIpAddress(): string
|
||||
{
|
||||
if (php_sapi_name() === 'cli') {
|
||||
return '127.0.0.1';
|
||||
}
|
||||
|
||||
if (isset($_SERVER['HTTP_X_FORWARDED_FOR']) && filter_var($_SERVER['HTTP_X_FORWARDED_FOR'], FILTER_VALIDATE_IP)) {
|
||||
return $_SERVER['HTTP_X_FORWARDED_FOR'];
|
||||
} else {
|
||||
@ -167,7 +190,7 @@ class LookingGlass
|
||||
*/
|
||||
public static function mtr(string $host): bool
|
||||
{
|
||||
return self::procExecute('mtr -4 --report --report-wide', $host);
|
||||
return self::procExecute('mtr --raw -n -4 -c ' . self::MTR_COUNT, $host);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -178,7 +201,7 @@ class LookingGlass
|
||||
*/
|
||||
public static function mtr6(string $host): bool
|
||||
{
|
||||
return self::procExecute('mtr -6 --report --report-wide', $host);
|
||||
return self::procExecute('mtr --raw -n -6 -c ' . self::MTR_COUNT, $host);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -219,11 +242,11 @@ class LookingGlass
|
||||
private static function procExecute(string $cmd, string $host, int $failCount = 2): bool
|
||||
{
|
||||
// define output pipes
|
||||
$spec = array(
|
||||
0 => array("pipe", "r"),
|
||||
1 => array("pipe", "w"),
|
||||
2 => array("pipe", "w")
|
||||
);
|
||||
$spec = [
|
||||
0 => ['pipe', 'r'],
|
||||
1 => ['pipe', 'w'],
|
||||
2 => ['pipe', 'w']
|
||||
];
|
||||
|
||||
// sanitize + remove single quotes
|
||||
$host = str_replace('\'', '', filter_var($host, FILTER_SANITIZE_URL));
|
||||
@ -238,16 +261,17 @@ class LookingGlass
|
||||
// check for mtr/traceroute
|
||||
if (strpos($cmd, 'mtr') !== false) {
|
||||
$type = 'mtr';
|
||||
$parser = new Parser();
|
||||
} elseif (strpos($cmd, 'traceroute') !== false) {
|
||||
$type = 'traceroute';
|
||||
} else {
|
||||
$type = '';
|
||||
}
|
||||
|
||||
$fail = 0;
|
||||
$match = 0;
|
||||
$fail = 0;
|
||||
$match = 0;
|
||||
$traceCount = 0;
|
||||
$lastFail = 'start';
|
||||
$lastFail = 'start';
|
||||
// iterate stdout
|
||||
while (($str = fgets($pipes[1], 4096)) != null) {
|
||||
// check for output buffer
|
||||
@ -260,12 +284,14 @@ class LookingGlass
|
||||
|
||||
// correct output for mtr
|
||||
if ($type === 'mtr') {
|
||||
if ($match < 10 && preg_match('/^[0-9]\. /', $str, $string)) {
|
||||
$str = preg_replace('/^[0-9]\. /', ' ' . $string[0], $str);
|
||||
$match++;
|
||||
} else {
|
||||
$str = preg_replace('/^[0-9]{2}\. /', ' ' . substr($str, 0, 4), $str);
|
||||
}
|
||||
// correct output for mtr
|
||||
$parser->update($str);
|
||||
echo '---' . PHP_EOL . $parser->__toString() . PHP_EOL . str_pad('', 4096) . PHP_EOL;
|
||||
|
||||
// flush output buffering
|
||||
@ob_flush();
|
||||
flush();
|
||||
continue;
|
||||
}
|
||||
// correct output for traceroute
|
||||
elseif ($type === 'traceroute') {
|
||||
@ -306,7 +332,7 @@ class LookingGlass
|
||||
}
|
||||
|
||||
$status = proc_get_status($process);
|
||||
if ($status['running'] == true) {
|
||||
if ($status['running']) {
|
||||
// close pipes that are still open
|
||||
foreach ($pipes as $pipe) {
|
||||
fclose($pipe);
|
||||
@ -328,3 +354,189 @@ class LookingGlass
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
class Hop
|
||||
{
|
||||
/** @var int */
|
||||
public $idx;
|
||||
/** @var string */
|
||||
public $asn = '';
|
||||
/** @var float */
|
||||
public $avg = 0.0;
|
||||
/** @var int */
|
||||
public $loss = 0;
|
||||
/** @var float */
|
||||
public $stdev = 0.0;
|
||||
/** @var int */
|
||||
public $sent = 0;
|
||||
/** @var int */
|
||||
public $recieved = 0;
|
||||
/** @var float */
|
||||
public $last = 0.0;
|
||||
/** @var float */
|
||||
public $best = 0.0;
|
||||
/** @var float */
|
||||
public $worst = 0.0;
|
||||
|
||||
/** @var string[] */
|
||||
public $ips = [];
|
||||
/** @var string[] */
|
||||
public $hosts = [];
|
||||
/** @var float[] */
|
||||
public $timings = [];
|
||||
|
||||
}
|
||||
|
||||
class RawHop
|
||||
{
|
||||
/** @var string */
|
||||
public $dataType;
|
||||
/** @var int */
|
||||
public $idx;
|
||||
/** @var string */
|
||||
public $value;
|
||||
}
|
||||
|
||||
class Parser
|
||||
{
|
||||
/** @var Hop[] */
|
||||
protected $hopsCollection = [];
|
||||
/** @var int */
|
||||
private $hopCount = 0;
|
||||
/** @var int */
|
||||
private $outputWidth = 38;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
putenv('RES_OPTIONS=retrans:1 retry:1 timeout:1 attempts:1');
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
$str = '';
|
||||
foreach ($this->hopsCollection as $index => $hop) {
|
||||
$host = $hop->hosts[0] ?? $hop->ips[0] ?? '???';
|
||||
|
||||
if (strlen($host) > $this->outputWidth) {
|
||||
$this->outputWidth = strlen($host);
|
||||
}
|
||||
|
||||
$hop->recieved = count($hop->timings);
|
||||
if (count($hop->timings)) {
|
||||
$hop->last = $hop->timings[count($hop->timings) - 1];
|
||||
$hop->best = $hop->timings[0];
|
||||
$hop->worst = $hop->timings[0];
|
||||
$hop->avg = array_sum($hop->timings) / count($hop->timings);
|
||||
}
|
||||
|
||||
if (count($hop->timings) > 1) {
|
||||
$hop->stdev = $this->stDev($hop->timings);
|
||||
}
|
||||
|
||||
foreach ($hop->timings as $time) {
|
||||
|
||||
if ($hop->best > $time) {
|
||||
$hop->best = $time;
|
||||
}
|
||||
|
||||
if ($hop->worst < $time) {
|
||||
$hop->worst = $time;
|
||||
}
|
||||
}
|
||||
|
||||
$hop->loss = $hop->sent ? (100 * ($hop->sent - $hop->recieved)) / $hop->sent : 100;
|
||||
|
||||
$str = sprintf(
|
||||
"%s%2d.|-- %s%3d.0%% %3d %5.1f %5.1f %5.1f %5.1f %5.1f\n",
|
||||
$str,
|
||||
$index,
|
||||
str_pad($host, $this->outputWidth + 3, ' ', STR_PAD_RIGHT),
|
||||
$hop->loss,
|
||||
$hop->sent,
|
||||
$hop->last,
|
||||
$hop->avg,
|
||||
$hop->best,
|
||||
$hop->worst,
|
||||
$hop->stdev
|
||||
);
|
||||
}
|
||||
|
||||
return sprintf(" Host%sLoss%% Snt Last Avg Best Wrst StDev\n%s", str_pad('', $this->outputWidth + 7, ' ', STR_PAD_RIGHT), $str);
|
||||
}
|
||||
|
||||
private function stDev(array $array): float
|
||||
{
|
||||
$sdSquare = function ($x, $mean) {
|
||||
return pow($x - $mean, 2);
|
||||
};
|
||||
|
||||
// square root of sum of squares devided by N-1
|
||||
return sqrt(array_sum(array_map($sdSquare, $array, array_fill(0, count($array), (array_sum($array) / count($array))))) / (count($array) - 1));
|
||||
}
|
||||
|
||||
public function update($rawMtrInput)
|
||||
{
|
||||
//Store each line of output in rawhop structure
|
||||
$things = explode(' ', $rawMtrInput);
|
||||
|
||||
if (count($things) !== 3 && (count($things) !== 4 && $things[0] === 'p')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$rawHop = new RawHop();
|
||||
$rawHop->dataType = $things[0];
|
||||
$rawHop->idx = (int)$things[1];
|
||||
$rawHop->value = $things[2];
|
||||
|
||||
if ($this->hopCount < $rawHop->idx + 1) {
|
||||
$this->hopCount = $rawHop->idx + 1;
|
||||
}
|
||||
|
||||
if (!isset($this->hopsCollection[$rawHop->idx])) {
|
||||
$this->hopsCollection[$rawHop->idx] = new Hop();
|
||||
}
|
||||
|
||||
$hop = $this->hopsCollection[$rawHop->idx];
|
||||
$hop->idx = $rawHop->idx;
|
||||
switch ($rawHop->dataType) {
|
||||
case 'h':
|
||||
$hop->ips[] = $rawHop->value;
|
||||
$hop->hosts[] = gethostbyaddr($rawHop->value) ? : null;
|
||||
break;
|
||||
case 'd':
|
||||
//Not entirely sure if multiple IPs. Better use -n in mtr and resolve later in summarize.
|
||||
//out.Hops[data.idx].Host = append(out.Hops[data.idx].Host, data.value)
|
||||
break;
|
||||
case 'p':
|
||||
$hop->sent++;
|
||||
$hop->timings[] = (float)$rawHop->value / 1000;
|
||||
break;
|
||||
}
|
||||
|
||||
$this->hopsCollection[$rawHop->idx] = $hop;
|
||||
|
||||
$this->filterLastDupeHop();
|
||||
}
|
||||
|
||||
// Function to calculate standard deviation (uses sd_square)
|
||||
|
||||
private function filterLastDupeHop()
|
||||
{
|
||||
// filter dupe last hop
|
||||
$finalIdx = 0;
|
||||
$previousIp = '';
|
||||
|
||||
foreach ($this->hopsCollection as $key => $hop) {
|
||||
if (count($hop->ips) && $hop->ips[0] !== $previousIp) {
|
||||
$previousIp = $hop->ips[0];
|
||||
$finalIdx = $key + 1;
|
||||
}
|
||||
}
|
||||
|
||||
unset($this->hopsCollection[$finalIdx]);
|
||||
|
||||
usort($this->hopsCollection, function ($a, $b) {
|
||||
return $a->idx - $b->idx;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
41
backend.php
41
backend.php
@ -1,4 +1,4 @@
|
||||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
/**
|
||||
* Hybula Looking Glass
|
||||
*
|
||||
@ -11,36 +11,39 @@
|
||||
* @link https://github.com/hybula/lookingglass
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
require __DIR__.'/config.php';
|
||||
require __DIR__.'/LookingGlass.php';
|
||||
require __DIR__.'/config.php';
|
||||
|
||||
use Hybula\LookingGlass;
|
||||
|
||||
LookingGlass::validateConfig();
|
||||
LookingGlass::startSession();
|
||||
|
||||
if ($_SESSION['TARGET'] && $_SESSION['METHOD'] && isset($_SESSION['BACKEND'])) {
|
||||
unset($_SESSION['BACKEND']);
|
||||
switch ($_SESSION['METHOD']) {
|
||||
case 'ping':
|
||||
LookingGlass::ping($_SESSION['TARGET']);
|
||||
if (isset($_SESSION[LookingGlass::SESSION_TARGET_HOST]) &&
|
||||
isset($_SESSION[LookingGlass::SESSION_TARGET_METHOD]) &&
|
||||
isset($_SESSION[LookingGlass::SESSION_CALL_BACKEND])
|
||||
) {
|
||||
unset($_SESSION[LookingGlass::SESSION_CALL_BACKEND]);
|
||||
|
||||
|
||||
switch ($_SESSION[LookingGlass::SESSION_TARGET_METHOD]) {
|
||||
case LookingGlass::METHOD_PING:
|
||||
LookingGlass::ping($_SESSION[LookingGlass::SESSION_TARGET_HOST]);
|
||||
break;
|
||||
case 'ping6':
|
||||
LookingGlass::ping6($_SESSION['TARGET']);
|
||||
case LookingGlass::METHOD_PING6:
|
||||
LookingGlass::ping6($_SESSION[LookingGlass::SESSION_TARGET_HOST]);
|
||||
break;
|
||||
case 'mtr':
|
||||
LookingGlass::mtr($_SESSION['TARGET']);
|
||||
case LookingGlass::METHOD_MTR:
|
||||
LookingGlass::mtr($_SESSION[LookingGlass::SESSION_TARGET_HOST]);
|
||||
break;
|
||||
case 'mtr6':
|
||||
LookingGlass::mtr6($_SESSION['TARGET']);
|
||||
case LookingGlass::METHOD_MTR6:
|
||||
LookingGlass::mtr6($_SESSION[LookingGlass::SESSION_TARGET_HOST]);
|
||||
break;
|
||||
case 'traceroute':
|
||||
LookingGlass::traceroute($_SESSION['TARGET']);
|
||||
case LookingGlass::METHOD_TRACEROUTE:
|
||||
LookingGlass::traceroute($_SESSION[LookingGlass::SESSION_TARGET_HOST]);
|
||||
break;
|
||||
case 'traceroute6':
|
||||
LookingGlass::traceroute6($_SESSION['TARGET']);
|
||||
case LookingGlass::METHOD_TRACEROUTE6:
|
||||
LookingGlass::traceroute6($_SESSION[LookingGlass::SESSION_TARGET_HOST]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
69
bootstrap.php
Normal file
69
bootstrap.php
Normal file
@ -0,0 +1,69 @@
|
||||
<?php declare(strict_types=1);
|
||||
/**
|
||||
* Hybula Looking Glass
|
||||
*
|
||||
* Provides UI and input for the looking glass backend.
|
||||
*
|
||||
* @copyright 2022 Hybula B.V.
|
||||
* @license Mozilla Public License 2.0
|
||||
* @version 1.1.0
|
||||
* @since File available since release 1.1.0
|
||||
* @link https://github.com/hybula/lookingglass
|
||||
*/
|
||||
use Hybula\LookingGlass;
|
||||
|
||||
if (!file_exists(__DIR__ . '/config.php')) {
|
||||
die('config.php is not found, but is required for application to work!');
|
||||
}
|
||||
|
||||
require __DIR__ . '/LookingGlass.php';
|
||||
require __DIR__ . '/config.php';
|
||||
|
||||
LookingGlass::validateConfig();
|
||||
LookingGlass::startSession();
|
||||
|
||||
function exitErrorMessage(string $message): void
|
||||
{
|
||||
unset($_SESSION[LookingGlass::SESSION_CALL_BACKEND]);
|
||||
$_SESSION[LookingGlass::SESSION_ERROR_MESSAGE] = $message;
|
||||
exitNormal();
|
||||
}
|
||||
|
||||
function exitNormal(): void
|
||||
{
|
||||
header('Location: /');
|
||||
exit;
|
||||
}
|
||||
|
||||
$templateData = [
|
||||
'title' => LG_TITLE,
|
||||
'custom_css' => LG_CSS_OVERRIDES,
|
||||
'logo_url' => LG_LOGO_URL,
|
||||
'logo_data' => LG_LOGO,
|
||||
//
|
||||
'block_network' => LG_BLOCK_NETWORK,
|
||||
'block_lookingglas' => LG_BLOCK_LOOKINGGLAS,
|
||||
'block_speedtest' => LG_BLOCK_SPEEDTEST,
|
||||
'block_custom' => LG_BLOCK_CUSTOM,
|
||||
'custom_html' => '',
|
||||
//
|
||||
'locations' => LG_LOCATIONS,
|
||||
'current_location' => LG_LOCATION,
|
||||
'maps_query' => LG_MAPS_QUERY,
|
||||
'facility' => LG_FACILITY,
|
||||
'facility_url' => LG_FACILITY_URL,
|
||||
'ipv4' => LG_IPV4,
|
||||
'ipv6' => LG_IPV6,
|
||||
'methods' => LG_METHODS,
|
||||
'user_ip' => LookingGlass::detectIpAddress(),
|
||||
//
|
||||
'speedtest_iperf' => LG_SPEEDTEST_IPERF,
|
||||
'speedtest_incoming_label' => LG_SPEEDTEST_LABEL_INCOMING,
|
||||
'speedtest_incoming_cmd' => LG_SPEEDTEST_CMD_INCOMING,
|
||||
'speedtest_outgoing_label' => LG_SPEEDTEST_LABEL_OUTGOING,
|
||||
'speedtest_outgoing_cmd' => LG_SPEEDTEST_CMD_OUTGOING,
|
||||
'speedtest_files' => LG_SPEEDTEST_FILES,
|
||||
//
|
||||
'tos' => LG_TERMS,
|
||||
'error_message' => false,
|
||||
];
|
@ -1,5 +1,5 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
<?php declare(strict_types=1);
|
||||
use Hybula\LookingGlass;
|
||||
|
||||
// Define the HTML title;
|
||||
const LG_TITLE = 'Looking Glass';
|
||||
@ -26,6 +26,8 @@ const LG_CUSTOM_PHP = __DIR__.'/custom.post.php';
|
||||
|
||||
// Define the location of this network, usually a city and a country;
|
||||
const LG_LOCATION = 'Amsterdam, Netherlands';
|
||||
// Define a query location for the link to openstreetmap (eg: Amsterdam, Netherlands will be https://www.openstreetmap.org/search?query=Amsterdam, Netherlands)
|
||||
const LG_MAPS_QUERY = 'Amsterdam, Netherlands';
|
||||
// Define the facility where the network is located, usually a data center;
|
||||
const LG_FACILITY = 'Nikhef';
|
||||
// Define a direct link to more information about the facility, this should be a link to PeeringDB;
|
||||
@ -36,7 +38,14 @@ const LG_IPV4 = '127.0.0.1';
|
||||
const LG_IPV6 = '::1';
|
||||
|
||||
// Define the methods that can be used by visitors to test it out;
|
||||
const LG_METHODS = ['ping', 'ping6', 'mtr', 'mtr6', 'traceroute', 'traceroute6'];
|
||||
const LG_METHODS = [
|
||||
LookingGlass::METHOD_PING,
|
||||
LookingGlass::METHOD_PING6,
|
||||
LookingGlass::METHOD_MTR,
|
||||
LookingGlass::METHOD_MTR6,
|
||||
LookingGlass::METHOD_TRACEROUTE,
|
||||
LookingGlass::METHOD_TRACEROUTE6,
|
||||
];
|
||||
|
||||
// Define other looking glasses, this is useful if you have multiple networks and looking glasses;
|
||||
const LG_LOCATIONS = [
|
||||
|
@ -1,8 +1,10 @@
|
||||
FROM php:8.1-fpm-bullseye
|
||||
|
||||
RUN apt update && apt install iputils-ping mtr traceroute -y
|
||||
RUN apt-get update && \
|
||||
apt-get --no-install-recommends -y install iputils-ping mtr traceroute && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
WORKDIR /var/www/html
|
||||
|
||||
COPY . .
|
||||
COPY --chown=www-data:www-data . .
|
||||
COPY docker/php-fpm/src/config.php config.php
|
||||
|
75
docker/php-fpm/src/config.php
Normal file
75
docker/php-fpm/src/config.php
Normal file
@ -0,0 +1,75 @@
|
||||
<?php declare(strict_types=1);
|
||||
use Hybula\LookingGlass;
|
||||
|
||||
// Define the HTML title;
|
||||
const LG_TITLE = 'Looking Glass';
|
||||
|
||||
// Define a logo, this can be HTML too, see the other example for an image;
|
||||
const LG_LOGO = '<h2>Company Looking Glass</h2>';
|
||||
// Define the URL where the logo points to;
|
||||
const LG_LOGO_URL = 'https://github.com/hybula/lookingglass/';
|
||||
|
||||
// Define a custom CSS file which can be used to style the LG, set false to disable, else point to the CSS file;
|
||||
const LG_CSS_OVERRIDES = false;
|
||||
|
||||
// Enable or disable blocks/parts of the LG, set false to hide a part;
|
||||
const LG_BLOCK_NETWORK = true;
|
||||
const LG_BLOCK_LOOKINGGLAS = true;
|
||||
const LG_BLOCK_SPEEDTEST = true;
|
||||
// This enables the custom block, which you can use to add something custom to the LG;
|
||||
define('LG_BLOCK_CUSTOM', getenv('ENABLE_CUSTOM_BLOCK') !== false);
|
||||
|
||||
// Define a file here which will be used to display the custom block, can be PHP too which outputs HTML;
|
||||
const LG_CUSTOM_HTML = __DIR__.'/custom.html.php';
|
||||
// Define a file here which will be loaded on top of the index file, this can be used to do some post logic;
|
||||
const LG_CUSTOM_PHP = __DIR__.'/custom.post.php';
|
||||
|
||||
// Define the location of this network, usually a city and a country;
|
||||
define('LG_LOCATION', getenv('LOCATION'));
|
||||
// Define a query location for the link to openstreetmap (eg: Amsterdam, Netherlands will be https://www.openstreetmap.org/search?query=Amsterdam, Netherlands)
|
||||
define('LG_MAPS_QUERY', getenv('MAPS_QUERY'));
|
||||
// Define the facility where the network is located, usually a data center;
|
||||
define('LG_FACILITY', getenv('FACILITY'));
|
||||
// Define a direct link to more information about the facility, this should be a link to PeeringDB;
|
||||
define('LG_FACILITY_URL', getenv('FACILITY_URL'));
|
||||
// Define an IPv4 for testing;
|
||||
define('LG_IPV4', getenv('IPV4_ADDRESS'));
|
||||
// Define an IPv6 for testing;
|
||||
define('LG_IPV6', getenv('IPV6_ADDRESS'));
|
||||
|
||||
// Define the methods that can be used by visitors to test it out;
|
||||
const LG_METHODS = [
|
||||
LookingGlass::METHOD_PING,
|
||||
LookingGlass::METHOD_PING6,
|
||||
LookingGlass::METHOD_MTR,
|
||||
LookingGlass::METHOD_MTR6,
|
||||
LookingGlass::METHOD_TRACEROUTE,
|
||||
LookingGlass::METHOD_TRACEROUTE6,
|
||||
];
|
||||
|
||||
// Define other looking glasses, this is useful if you have multiple networks and looking glasses;
|
||||
const LG_LOCATIONS = [
|
||||
'Location A' => 'https://github.com/hybula/lookingglass/',
|
||||
'Location B' => 'https://github.com/hybula/lookingglass/',
|
||||
'Location C' => 'https://github.com/hybula/lookingglass/',
|
||||
];
|
||||
|
||||
// Enable the iPerf info inside the speedtest block, set too false to disable;
|
||||
const LG_SPEEDTEST_IPERF = true;
|
||||
// Define the label of an incoming iPerf test;
|
||||
const LG_SPEEDTEST_LABEL_INCOMING = 'iPerf3 Incoming';
|
||||
// Define the command to use to test incoming speed using iPerf, preferable iPerf3;
|
||||
const LG_SPEEDTEST_CMD_INCOMING = 'iperf3 -4 -c hostname -p 5201 -P 4';
|
||||
// Define the label of an outgoing iPerf test;
|
||||
const LG_SPEEDTEST_LABEL_OUTGOING = 'iPerf3 Outgoing';
|
||||
// Define the command to use to test outgoing speed using iPerf, preferable iPerf3;
|
||||
const LG_SPEEDTEST_CMD_OUTGOING = 'iperf3 -4 -c hostname -p 5201 -P 4 -R';
|
||||
// Define speedtest files with URLs to the actual files;
|
||||
const LG_SPEEDTEST_FILES = [
|
||||
'100M' => 'https://github.com/hybula/lookingglass/',
|
||||
'1G' => 'https://github.com/hybula/lookingglass/',
|
||||
'10G' => 'https://github.com/hybula/lookingglass/'
|
||||
];
|
||||
|
||||
// Define if you require visitors to agree with the Terms, set false to disable;
|
||||
define('LG_TERMS', getenv('LG_TERMS') ?: 'https://github.com/hybula/lookingglass/');
|
294
index.php
294
index.php
@ -1,4 +1,4 @@
|
||||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
/**
|
||||
* Hybula Looking Glass
|
||||
*
|
||||
@ -11,69 +11,73 @@
|
||||
* @link https://github.com/hybula/lookingglass
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
require __DIR__.'/config.php';
|
||||
require __DIR__.'/LookingGlass.php';
|
||||
require __DIR__.'/bootstrap.php';
|
||||
|
||||
use Hybula\LookingGlass;
|
||||
|
||||
LookingGlass::validateConfig();
|
||||
LookingGlass::startSession();
|
||||
$detectIpAddress = LookingGlass::detectIpAddress();
|
||||
|
||||
$errorMessage = null;
|
||||
if (!empty($_POST)) {
|
||||
do {
|
||||
if (!isset($_POST['csrfToken']) || !isset($_SESSION['CSRF']) || ($_POST['csrfToken'] != $_SESSION['CSRF'])) {
|
||||
$errorMessage = 'Missing or incorrect CSRF token.';
|
||||
break;
|
||||
}
|
||||
if (isset($_POST['submitForm'])) {
|
||||
if (!in_array($_POST['backendMethod'], LG_METHODS)) {
|
||||
$errorMessage = 'Unsupported backend method.';
|
||||
break;
|
||||
}
|
||||
$_SESSION['METHOD'] = $_POST['backendMethod'];
|
||||
$_SESSION['TARGET'] = $_POST['targetHost'];
|
||||
if (!isset($_POST['checkTerms']) && LG_TERMS) {
|
||||
$errorMessage = 'You must agree with the Terms of Service.';
|
||||
break;
|
||||
}
|
||||
if (!isset($_POST['csrfToken']) || !isset($_SESSION[LookingGlass::SESSION_CSRF]) || ($_POST['csrfToken'] !== $_SESSION[LookingGlass::SESSION_CSRF])) {
|
||||
exitErrorMessage('Missing or incorrect CSRF token.');
|
||||
}
|
||||
|
||||
if (in_array($_POST['backendMethod'], ['ping', 'mtr', 'traceroute'])) {
|
||||
if (!LookingGlass::isValidIpv4($_POST['targetHost'])) {
|
||||
$targetHost = LookingGlass::isValidHost($_POST['targetHost'], 'ipv4');
|
||||
if (!$targetHost) {
|
||||
$errorMessage = 'No valid IPv4 provided.';
|
||||
break;
|
||||
}
|
||||
$_SESSION['TARGET'] = $targetHost;
|
||||
}
|
||||
}
|
||||
if (in_array($_POST['backendMethod'], ['ping6', 'mtr6', 'traceroute6'])) {
|
||||
if (!LookingGlass::isValidIpv6($_POST['targetHost'])) {
|
||||
$targetHost = LookingGlass::isValidHost($_POST['targetHost'], 'ipv6');
|
||||
if (!$targetHost) {
|
||||
$errorMessage = 'No valid IPv6 provided.';
|
||||
break;
|
||||
}
|
||||
$_SESSION['TARGET'] = $targetHost;
|
||||
}
|
||||
}
|
||||
$_SESSION['TERMS'] = true;
|
||||
$_SESSION['BACKEND'] = true;
|
||||
break;
|
||||
if (!isset($_POST['submitForm']) || !isset($_POST['backendMethod']) || !isset($_POST['targetHost'])) {
|
||||
exitErrorMessage('Unsupported POST received.');
|
||||
}
|
||||
|
||||
if (!in_array($_POST['backendMethod'], LG_METHODS)) {
|
||||
exitErrorMessage('Unsupported backend method.');
|
||||
}
|
||||
|
||||
$_SESSION[LookingGlass::SESSION_TARGET_METHOD] = $_POST['backendMethod'];
|
||||
$_SESSION[LookingGlass::SESSION_TARGET_HOST] = $_POST['targetHost'];
|
||||
if (!isset($_POST['checkTerms']) && LG_TERMS) {
|
||||
exitErrorMessage('You must agree with the Terms of Service.');
|
||||
}
|
||||
|
||||
$targetHost = $_POST['targetHost'];
|
||||
if (in_array($_POST['backendMethod'], ['ping', 'mtr', 'traceroute'])) {
|
||||
if (!LookingGlass::isValidIpv4($_POST['targetHost']) &&
|
||||
!$targetHost = LookingGlass::isValidHost($_POST['targetHost'], LookingGlass::IPV4)
|
||||
) {
|
||||
exitErrorMessage('No valid IPv4 provided.');
|
||||
}
|
||||
$errorMessage = 'Unsupported POST received.';
|
||||
break;
|
||||
} while (true);
|
||||
}
|
||||
|
||||
if (in_array($_POST['backendMethod'], ['ping6', 'mtr6', 'traceroute6'])) {
|
||||
if (!LookingGlass::isValidIpv6($_POST['targetHost']) ||
|
||||
!$targetHost = LookingGlass::isValidHost($_POST['targetHost'],LookingGlass::IPV6)
|
||||
) {
|
||||
exitErrorMessage('No valid IPv6 provided.');
|
||||
}
|
||||
}
|
||||
|
||||
$_SESSION[LookingGlass::SESSION_TARGET_HOST] = $targetHost;
|
||||
$_SESSION[LookingGlass::SESSION_TOS_CHECKED] = true;
|
||||
$_SESSION[LookingGlass::SESSION_CALL_BACKEND] = true;
|
||||
exitNormal();
|
||||
}
|
||||
|
||||
$_SESSION['CSRF'] = bin2hex(random_bytes(12));
|
||||
$templateData['session_target'] = $_SESSION[LookingGlass::SESSION_TARGET_HOST] ?? '';
|
||||
$templateData['session_method'] = $_SESSION[LookingGlass::SESSION_TARGET_METHOD] ?? '';
|
||||
$templateData['session_call_backend'] = $_SESSION[LookingGlass::SESSION_CALL_BACKEND] ?? false;
|
||||
$templateData['session_tos_checked'] = isset($_SESSION[LookingGlass::SESSION_TOS_CHECKED]) ? ' checked' : '';
|
||||
|
||||
if (isset($_SESSION[LookingGlass::SESSION_ERROR_MESSAGE])) {
|
||||
$templateData['error_message'] = $_SESSION[LookingGlass::SESSION_ERROR_MESSAGE];
|
||||
unset($_SESSION[LookingGlass::SESSION_ERROR_MESSAGE]);
|
||||
}
|
||||
|
||||
if (LG_BLOCK_CUSTOM) {
|
||||
include LG_CUSTOM_PHP;
|
||||
|
||||
ob_start();
|
||||
include LG_CUSTOM_HTML;
|
||||
$templateData['custom_html'] = ob_get_clean();
|
||||
}
|
||||
|
||||
$templateData['csrfToken'] = $_SESSION[LookingGlass::SESSION_CSRF] = bin2hex(random_bytes(12));
|
||||
?>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
@ -82,9 +86,9 @@ if (LG_BLOCK_CUSTOM) {
|
||||
<meta content="width=device-width, initial-scale=1" name="viewport">
|
||||
<meta content="" name="description">
|
||||
<meta content="Hybula" name="author">
|
||||
<title><?php echo LG_TITLE; ?></title>
|
||||
<title><?php echo $templateData['title'] ?></title>
|
||||
<link crossorigin="anonymous" href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" rel="stylesheet">
|
||||
<?php if (LG_CSS_OVERRIDES) { echo '<link href="'.LG_CSS_OVERRIDES.'" rel="stylesheet">'; } ?>
|
||||
<?php if ($templateData['custom_css']) { echo '<link href="'.$templateData['custom_css'].'" rel="stylesheet">'; } ?>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@ -92,23 +96,23 @@ if (LG_BLOCK_CUSTOM) {
|
||||
|
||||
<header class="d-flex align-items-center pb-3 mb-5 border-bottom">
|
||||
<div class="col-8">
|
||||
<a class="d-flex align-items-center text-dark text-decoration-none" href="<?php echo LG_LOGO_URL; ?>" target="_blank">
|
||||
<?php echo LG_LOGO; ?>
|
||||
<a class="d-flex align-items-center text-dark text-decoration-none" href="<?php echo $templateData['logo_url'] ?>" target="_blank">
|
||||
<?php echo $templateData['logo_data'] ?>
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-4 float-end">
|
||||
<select class="form-select" onchange="window.location = this.options[this.selectedIndex].value">
|
||||
<option selected><?php echo LG_LOCATION; ?></option>
|
||||
<?php foreach (LG_LOCATIONS as $location => $link) { ?>
|
||||
<option value="<?php echo $link; ?>"><?php echo $location; ?></option>
|
||||
<?php } ?>
|
||||
<option selected><?php echo $templateData['current_location'] ?></option>
|
||||
<?php foreach ($templateData['locations'] as $location => $link): ?>
|
||||
<option value="<?php echo $link ?>"><?php echo $location ?></option>
|
||||
<?php endforeach ?>
|
||||
</select>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
|
||||
<?php if (LG_BLOCK_NETWORK) { ?>
|
||||
<?php if (LG_BLOCK_NETWORK): ?>
|
||||
<div class="row mb-5">
|
||||
<div class="card shadow-lg">
|
||||
<div class="card-body p-3">
|
||||
@ -118,23 +122,23 @@ if (LG_BLOCK_CUSTOM) {
|
||||
<div class="col-md-7">
|
||||
<label class="mb-2 text-muted">Location</label>
|
||||
<div class="input-group mb-3">
|
||||
<input type="text" class="form-control" value="<?php echo LG_LOCATION; ?>" onfocus="this.select()" readonly="">
|
||||
<a class="btn btn-outline-secondary" href="https://www.openstreetmap.org/search?query=<?php echo urlencode(LG_LOCATION); ?>" target="_blank">Map</a>
|
||||
<?php if (!empty(LG_LOCATIONS)) { ?>
|
||||
<input type="text" class="form-control" value="<?php echo $templateData['current_location'] ?>" onfocus="this.select()" readonly="">
|
||||
<a class="btn btn-outline-secondary" href="https://www.openstreetmap.org/search?query=<?php echo urlencode($templateData['maps_query']); ?>" target="_blank">Map</a>
|
||||
<?php if (!empty($templateData['locations'])): ?>
|
||||
<button class="btn btn-outline-secondary dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false">Locations</button>
|
||||
<ul class="dropdown-menu dropdown-menu-end">
|
||||
<?php foreach (LG_LOCATIONS as $location => $link) { ?>
|
||||
<li><a class="dropdown-item" href="<?php echo $link; ?>"><?php echo $location; ?></a></li>
|
||||
<?php } ?>
|
||||
<?php foreach ($templateData['locations'] as $location => $link): ?>
|
||||
<li><a class="dropdown-item" href="<?php echo $link ?>"><?php echo $location ?></a></li>
|
||||
<?php endforeach ?>
|
||||
</ul>
|
||||
<?php } ?>
|
||||
<?php endif ?>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-5">
|
||||
<label class="mb-2 text-muted">Facility</label>
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" value="<?php echo LG_FACILITY; ?>" onfocus="this.select()" readonly="">
|
||||
<a href="<?php echo LG_FACILITY_URL; ?>" class="btn btn-outline-secondary" target="_blank">PeeringDB</a>
|
||||
<input type="text" class="form-control" value="<?php echo $templateData['facility'] ?>" onfocus="this.select()" readonly="">
|
||||
<a href="<?php echo $templateData['facility_url'] ?>" class="btn btn-outline-secondary" target="_blank">PeeringDB</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -143,22 +147,22 @@ if (LG_BLOCK_CUSTOM) {
|
||||
<div class="col-md-3">
|
||||
<label class="mb-2 text-muted">Test IPv4</label>
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" value="<?php echo LG_IPV4; ?>" onfocus="this.select()" readonly="">
|
||||
<button class="btn btn-outline-secondary" onclick="copyToClipboard('<?php echo LG_IPV4; ?>', this)">Copy</button>
|
||||
<input type="text" class="form-control" value="<?php echo $templateData['ipv4'] ?>" onfocus="this.select()" readonly="">
|
||||
<button class="btn btn-outline-secondary" onclick="copyToClipboard('<?php echo $templateData['ipv4'] ?>', this)">Copy</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-5">
|
||||
<label class="mb-2 text-muted">Test IPv6</label>
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" value="<?php echo LG_IPV6; ?>" onfocus="this.select()" readonly="">
|
||||
<button class="btn btn-outline-secondary" onclick="copyToClipboard('<?php echo LG_IPV6; ?>', this)">Copy</button>
|
||||
<input type="text" class="form-control" value="<?php echo $templateData['ipv6'] ?>" onfocus="this.select()" readonly="">
|
||||
<button class="btn btn-outline-secondary" onclick="copyToClipboard('<?php echo $templateData['ipv6'] ?>', this)">Copy</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="mb-2 text-muted">Your IP</label>
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" value="<?php echo $detectIpAddress; ?>" onfocus="this.select()" readonly="">
|
||||
<button class="btn btn-outline-secondary" onclick="copyToClipboard('<?php echo $detectIpAddress; ?>', this)">Copy</button>
|
||||
<input type="text" class="form-control" value="<?php echo $templateData['user_ip'] ?>" onfocus="this.select()" readonly="">
|
||||
<button class="btn btn-outline-secondary" onclick="copyToClipboard('<?php echo $templateData['user_ip'] ?>', this)">Copy</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -166,48 +170,50 @@ if (LG_BLOCK_CUSTOM) {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php } ?>
|
||||
<?php endif ?>
|
||||
|
||||
<?php if (LG_BLOCK_LOOKINGGLAS) { ?>
|
||||
<?php if (LG_BLOCK_LOOKINGGLAS): ?>
|
||||
<div class="row pb-5">
|
||||
<div class="card shadow-lg">
|
||||
<div class="card-body p-3">
|
||||
<h1 class="fs-4 card-title mb-4">Looking Glass</h1>
|
||||
<form method="POST" action="/" autocomplete="off">
|
||||
<input type="hidden" name="csrfToken" value="<?php echo $_SESSION['CSRF']; ?>">
|
||||
<input type="hidden" name="csrfToken" value="<?php echo $templateData['csrfToken'] ?>">
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-7 mb-3">
|
||||
<div class="input-group">
|
||||
<span class="input-group-text" id="basic-addon1">Target</span>
|
||||
<input type="text" class="form-control" placeholder="IP address or host..." name="targetHost" value="<?php if (isset($_SESSION['TARGET'])) { echo $_SESSION['TARGET']; } ?>" required="">
|
||||
<input type="text" class="form-control" placeholder="IP address or host..." name="targetHost" value="<?php echo $templateData['session_target'] ?>" required="">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-5 mb-3">
|
||||
<div class="input-group">
|
||||
<label class="input-group-text">Method</label>
|
||||
<select class="form-select" name="backendMethod" id="backendMethod">
|
||||
<?php foreach (LG_METHODS as $method) { ?>
|
||||
<option value="<?php echo $method; ?>"<?php if (isset($_SESSION['METHOD']) && $_SESSION['METHOD'] == $method) { echo 'selected'; } ?>><?php echo $method; ?></option>
|
||||
<?php } ?>
|
||||
<?php foreach ($templateData['methods'] as $method): ?>
|
||||
<option value="<?php echo $method ?>"<?php if($templateData['session_method'] === $method): ?> selected<?php endif ?>><?php echo $method ?></option>
|
||||
<?php endforeach ?>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="d-flex align-items-center">
|
||||
<?php if (LG_TERMS) { ?>
|
||||
<?php if ($templateData['tos']): ?>
|
||||
<div class="form-check">
|
||||
<input type="checkbox" id="checkTerms" name="checkTerms" class="form-check-input"<?php if (isset($_SESSION['TERMS'])) { echo 'checked'; } ?>>
|
||||
<label for="checkTerms" class="form-check-label">I agree with the <a href="<?php echo LG_TERMS; ?>" target="_blank">Terms of Use</a></label>
|
||||
<input type="checkbox" id="checkTerms" name="checkTerms" class="form-check-input"<?php echo $templateData['session_tos_checked'] ?>>
|
||||
<label for="checkTerms" class="form-check-label">I agree with the <a href="<?php echo $templateData['tos'] ?>" target="_blank">Terms of Use</a></label>
|
||||
</div>
|
||||
<?php } ?>
|
||||
<?php endif ?>
|
||||
<button type="submit" class="btn btn-primary ms-auto" id="executeButton" name="submitForm">
|
||||
Execute
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<?php if (isset($errorMessage)) echo '<div class="alert alert-danger mt-3" role="alert">'.$errorMessage.'</div>'; ?>
|
||||
<?php if ($templateData['error_message']): ?>
|
||||
<div class="alert alert-danger mt-3" role="alert"><?php echo $templateData['error_message'] ?></div>
|
||||
<?php endif ?>
|
||||
|
||||
<div class="card card-body bg-light mt-4" style="display: none;" id="outputCard">
|
||||
<pre id="outputContent" style="overflow: hidden; white-space: pre; word-wrap: normal;"></pre>
|
||||
@ -217,45 +223,44 @@ if (LG_BLOCK_CUSTOM) {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php } ?>
|
||||
<?php endif ?>
|
||||
|
||||
<?php if (LG_BLOCK_SPEEDTEST) { ?>
|
||||
<?php if (LG_BLOCK_SPEEDTEST): ?>
|
||||
<div class="row pb-5">
|
||||
<div class="card shadow-lg">
|
||||
<div class="card-body p-3">
|
||||
<h1 class="fs-4 card-title mb-4">Speedtest</h1>
|
||||
|
||||
<?php if (LG_SPEEDTEST_IPERF) { ?>
|
||||
<?php if ($templateData['speedtest_iperf']): ?>
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-6">
|
||||
<label class="mb-2 text-muted"><?php echo LG_SPEEDTEST_LABEL_INCOMING; ?></label>
|
||||
<p><code><?php echo LG_SPEEDTEST_CMD_INCOMING; ?></code></p>
|
||||
<button class="btn btn-sm btn-outline-secondary" onclick="copyToClipboard('<?php echo LG_SPEEDTEST_CMD_INCOMING; ?>', this)">Copy</button>
|
||||
<label class="mb-2 text-muted"><?php echo $templateData['speedtest_incoming_label'] ?></label>
|
||||
<p><code><?php echo $templateData['speedtest_incoming_cmd']; ?></code></p>
|
||||
<button class="btn btn-sm btn-outline-secondary" onclick="copyToClipboard('<?php echo $templateData['speedtest_incoming_cmd'] ?>', this)">Copy</button>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="mb-2 text-muted"><?php echo LG_SPEEDTEST_LABEL_OUTGOING; ?></label>
|
||||
<p><code><?php echo LG_SPEEDTEST_CMD_OUTGOING; ?></code></p>
|
||||
<button class="btn btn-sm btn-outline-secondary" onclick="copyToClipboard('<?php echo LG_SPEEDTEST_CMD_OUTGOING; ?>', this)">Copy</button>
|
||||
<label class="mb-2 text-muted"><?php echo $templateData['speedtest_outgoing_label'] ?></label>
|
||||
<p><code><?php echo $templateData['speedtest_outgoing_cmd'] ?></code></p>
|
||||
<button class="btn btn-sm btn-outline-secondary" onclick="copyToClipboard('<?php echo $templateData['speedtest_outgoing_cmd'] ?>', this)">Copy</button>
|
||||
</div>
|
||||
</div>
|
||||
<?php } ?>
|
||||
<?php endif ?>
|
||||
|
||||
<div class="row">
|
||||
<label class="mb-2 text-muted">Test Files</label>
|
||||
<div class="btn-group input-group mb-3">
|
||||
<?php foreach (LG_SPEEDTEST_FILES as $file => $link) { ?>
|
||||
<a href="<?php echo $link; ?>" class="btn btn-outline-secondary"><?php echo $file; ?></a>
|
||||
<?php } ?>
|
||||
<?php foreach ($templateData['speedtest_files'] as $file => $link): ?>
|
||||
<a href="<?php echo $link ?>" class="btn btn-outline-secondary"><?php echo $file ?></a>
|
||||
<?php endforeach ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php } ?>
|
||||
|
||||
<?php if (LG_BLOCK_CUSTOM) { include LG_CUSTOM_HTML; } ?>
|
||||
<?php endif ?>
|
||||
|
||||
<?php echo $templateData['custom_html'] ?>
|
||||
|
||||
</main>
|
||||
<footer class="pt-3 mt-5 my-5 text-muted border-top">
|
||||
@ -264,38 +269,69 @@ if (LG_BLOCK_CUSTOM) {
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
<?php if ($templateData['session_call_backend']): ?>
|
||||
<script type="text/javascript">
|
||||
<?php if (isset($_SESSION['BACKEND'])) { echo 'callBackend();'; } ?>
|
||||
function callBackend() {
|
||||
const executeButton = document.getElementById('executeButton');
|
||||
executeButton.innerText = 'Executing...';
|
||||
executeButton.disabled = true;
|
||||
document.getElementById('outputCard').style.display = 'inherit';
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.onreadystatechange = function () {
|
||||
document.getElementById('outputContent').innerHTML = this.responseText.replace(/<br \/> +/g, '<br />');
|
||||
if (this.readyState === XMLHttpRequest.DONE) {
|
||||
executeButton.innerText = 'Execute';
|
||||
executeButton.disabled = false;
|
||||
console.log('Backend ready!');
|
||||
}
|
||||
};
|
||||
xhr.open('GET', 'backend.php', true);
|
||||
xhr.send();
|
||||
(function () {
|
||||
const outputContent = document.getElementById('outputContent')
|
||||
const executeButton = document.getElementById('executeButton')
|
||||
const outputCard = document.getElementById('outputCard')
|
||||
|
||||
executeButton.innerText = 'Executing...'
|
||||
executeButton.disabled = true
|
||||
|
||||
outputCard.style.display = 'inherit'
|
||||
|
||||
fetch('/backend.php')
|
||||
.then(async (response) => {
|
||||
// response.body is a ReadableStream
|
||||
const reader = response.body.getReader()
|
||||
const decoder = new TextDecoder()
|
||||
|
||||
for await (const chunk of readChunks(reader)) {
|
||||
const text = decoder.decode(chunk)
|
||||
<?php if(in_array($_SESSION[LookingGlass::SESSION_TARGET_METHOD], [LookingGlass::METHOD_MTR, LookingGlass::METHOD_MTR6])): ?>
|
||||
let splittedText = text.split('---')
|
||||
if (!splittedText[1]) {
|
||||
continue
|
||||
}
|
||||
outputContent.innerHTML = splittedText[1].trim()
|
||||
<?php else: ?>
|
||||
outputContent.innerHTML = outputContent.innerHTML + text.trim().replace(/<br \/> +/g, '<br />')
|
||||
<?php endif ?>
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
executeButton.innerText = 'Execute'
|
||||
executeButton.disabled = false
|
||||
console.log('Backend ready!')
|
||||
})
|
||||
})()
|
||||
|
||||
// readChunks() reads from the provided reader and yields the results into an async iterable
|
||||
function readChunks(reader) {
|
||||
return {
|
||||
async* [Symbol.asyncIterator]() {
|
||||
let readResult = await reader.read()
|
||||
while (!readResult.done) {
|
||||
yield readResult.value
|
||||
readResult = await reader.read()
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<?php endif ?>
|
||||
|
||||
<script type="text/javascript">
|
||||
async function copyToClipboard(text, button) {
|
||||
button.innerHTML = 'Copied!';
|
||||
const textAreaObject = document.createElement('textarea');
|
||||
textAreaObject.value = text;
|
||||
document.body.appendChild(textAreaObject);
|
||||
textAreaObject.select();
|
||||
document.execCommand('copy');
|
||||
document.body.removeChild(textAreaObject);
|
||||
await new Promise(r => setTimeout(r, 2000));
|
||||
button.innerHTML = 'Copy';
|
||||
if (!navigator || !navigator.clipboard || !navigator.clipboard.writeText) {
|
||||
return Promise.reject('The Clipboard API is not available.')
|
||||
}
|
||||
|
||||
button.innerHTML = 'Copied!'
|
||||
await navigator.clipboard.writeText(text)
|
||||
await new Promise(r => setTimeout(r, 2000))
|
||||
button.innerHTML = 'Copy'
|
||||
}
|
||||
</script>
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user