diff --git a/smarty/compile/12e7c104d0458c0f98059f5061a369703f954f4a_2.file_index.tpl.php b/smarty/compile/12e7c104d0458c0f98059f5061a369703f954f4a_2.file_index.tpl.php
new file mode 100644
index 0000000..633cb11
--- /dev/null
+++ b/smarty/compile/12e7c104d0458c0f98059f5061a369703f954f4a_2.file_index.tpl.php
@@ -0,0 +1,321 @@
+getCompiled()->isFresh($_smarty_tpl, array (
+ 'version' => '5.4.2',
+ 'unifunc' => 'content_67632c1ed9f985_55329758',
+ 'has_nocache_code' => false,
+ 'file_dependency' =>
+ array (
+ '12e7c104d0458c0f98059f5061a369703f954f4a' =>
+ array (
+ 0 => 'index.tpl',
+ 1 => 1734552581,
+ 2 => 'file',
+ ),
+ ),
+ 'includes' =>
+ array (
+ ),
+))) {
+function content_67632c1ed9f985_55329758 (\Smarty\Template $_smarty_tpl) {
+$_smarty_current_dir = '/home/upw/clients/kpopping/xbotcontrol/smarty/template';
+?>
+
+
+
+
+
+
+
+ XBotControl
+
+
+
+ src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"
+ integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous">
+
+>
+
+
+ src="https://code.jquery.com/jquery-3.7.1.min.js">
+>
+
+
+ src="https://cdn.jsdelivr.net/npm/bootstrap-table@1.23.5/dist/bootstrap-table.min.js">
+>
+
+
+ src="https://cdn.jsdelivr.net/npm/bootstrap-table@1.23.5/dist/locale/bootstrap-table-en-US.min.js">
+>
+
+
+
+ src="https://cdn.jsdelivr.net/npm/bootstrap-table@1.23.5/dist/extensions/filter-control/bootstrap-table-filter-control.js">
+
+>
+
+
+
+
+
+
+
+
+
+
+>
+ document.getElementById('date-from').addEventListener('change', refreshTable);
+
+ document.getElementById('limit').addEventListener('change', refreshTable);
+
+ function refreshTable() {
+ $('#table').bootstrapTable('refresh');
+ }
+ window.onload = function() {
+ const dateFrom = document.getElementById('date-from');
+ const dateTo = document.getElementById('date-to');
+
+ const today = new Date();
+ const yesterday = new Date(today);
+ yesterday.setDate(today.getDate() - 1);
+
+ const tomorrow = new Date(today);
+ tomorrow.setDate(today.getDate() + 1);
+
+ dateFrom.value = yesterday.toISOString().slice(0, 16);
+ dateTo.value = tomorrow.toISOString().slice(0, 16);
+ };
+ document.getElementById('date-to').addEventListener('change', refreshTable);
+
+
+ function initializeTable(latest_requests) {
+ var url = location.pathname + '/api/report/' + latest_requests;
+ var $table = $('#table');
+
+ if ($table.length) {
+ $table.bootstrapTable('destroy');
+ }
+
+ $.get(url, function(response) {
+ $table.bootstrapTable({
+ url: url,
+ sortable: true,
+ toolbar: '#toolbar',
+ showRefresh: true,
+ iconsPrefix: 'fa',
+ showColumns: true,
+ classes: ['table', 'table-borderless', 'table-hover', 'table-striped'],
+ filterControl: true,
+ searchable: true,
+ pagination: false,
+ sidePagination: "server",
+ serverSort: false,
+ columns: response.columns,
+ queryParams: queryParams,
+ loadingFontSize: '12px'
+ });
+ });
+ }
+
+ function queryParams(params) {
+ const limit = document.getElementById('limit').value;
+ const from = document.getElementById('date-from').value;
+ const to = document.getElementById('date-to').value;
+
+ params.limit = limit;
+ params.from = from;
+ params.to = to;
+
+ return params;
+ }
+
+
+
+
+
+ function listFormatter(value, row, index) {
+ var editBtn = ' ';
+
+ var showBtn = '';
+
+ return [showBtn, editBtn, value, ].join('')
+
+ return [showBtn, value, ].join('')
+
+
+ }
+
+>
+
+
+>
+ function ipFormatter(value) {
+ return `${value}`;
+ }
+
+ document.addEventListener('mouseover', async (event) => {
+ const target = event.target;
+ if (target.classList.contains('ip-address')) {
+ const ipAddress = target.getAttribute('data-ip');
+ const popupId = `popup-${ipAddress.replace(/\./g, '-')}`;
+ let popup = document.getElementById(popupId);
+
+ if (!popup) {
+ popup = document.createElement('div');
+ popup.id = popupId;
+ popup.style.position = 'absolute';
+ popup.style.background = '#f9f9f9';
+ popup.style.border = '1px solid #ccc';
+ popup.style.padding = '10px';
+ popup.style.borderRadius = '5px';
+ popup.style.boxShadow = '0 0 10px rgba(0, 0, 0, 0.2)';
+ popup.style.zIndex = '1000';
+ popup.style.whiteSpace = 'nowrap';
+ popup.style.display = 'none';
+ document.body.appendChild(popup);
+
+ fetch(location.pathname + `/api/ipinfo/${ipAddress}`)
+ .then((response) => response.json())
+ .then((data) => {
+ const location = data.geo.continent?.names?.en + ' > ' + data.geo.country
+ ?.names?.en + ' > ' + data.geo.city?.names?.en || 'Unknown';
+ const reverseDns = data.reverse_dns || 'N/A';
+ popup.innerHTML = `
+Location: ${location}
+Reverse DNS: ${reverseDns}
+ `;
+ })
+ .catch(() => {
+ popup.innerHTML = 'Error fetching data.';
+ });
+ }
+
+ popup.style.display = 'block';
+ popup.style.left = `${event.pageX + 10}px`;
+ popup.style.top = `${event.pageY + 10}px`;
+ }
+ });
+
+ document.addEventListener('mouseout', (event) => {
+ const target = event.target;
+ if (target.classList.contains('ip-address')) {
+ const ipAddress = target.getAttribute('data-ip');
+ const popupId = `popup-${ipAddress.replace(/\./g, '-')}`;
+ const popup = document.getElementById(popupId);
+ if (popup) {
+ popup.style.display = 'none';
+ }
+ }
+ });
+
+>
+
+
+getCompiled()->isFresh($_smarty_tpl, array (
+ 'version' => '5.4.2',
+ 'unifunc' => 'content_6763204e824e49_51557939',
+ 'has_nocache_code' => false,
+ 'file_dependency' =>
+ array (
+ 'b56b63fa35b4c8d6169eae7042db3ea0125ea5bf' =>
+ array (
+ 0 => 'login.tpl',
+ 1 => 1734549413,
+ 2 => 'file',
+ ),
+ ),
+ 'includes' =>
+ array (
+ ),
+))) {
+function content_6763204e824e49_51557939 (\Smarty\Template $_smarty_tpl) {
+$_smarty_current_dir = '/home/upw/clients/kpopping/xbotcontrol/smarty/template';
+?>
+
+
+
+
+
+
+
+ XBotControl
+
+
+
+ src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"
+ integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous">
+
+>
+
+
+ src="https://code.jquery.com/jquery-3.7.1.min.js">
+>
+
+
+ src="https://cdn.jsdelivr.net/npm/bootstrap-table@1.23.5/dist/bootstrap-table.min.js">
+>
+
+
+ src="https://cdn.jsdelivr.net/npm/bootstrap-table@1.23.5/dist/locale/bootstrap-table-zh-CN.min.js">
+>
+
+
+
+ src="https://cdn.jsdelivr.net/npm/bootstrap-table@1.23.5/dist/extensions/filter-control/bootstrap-table-filter-control.js">
+
+>
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Classes/Report.php b/src/Classes/Report.php
index 7052464..6f64bff 100644
--- a/src/Classes/Report.php
+++ b/src/Classes/Report.php
@@ -61,7 +61,7 @@ class Report
{
$columnsDefinition = self::generateColumns([
["title" => "id", "field" => "id", "visible" => false],
- ["title" => "ip", "field" => "ip", 'formatter'=> 'ipFormatter'],
+ ["title" => "ip", "field" => "ip", 'formatter' => 'ipFormatter'],
["title" => "domain", "field" => "domain", "visible" => false],
["title" => "path", "field" => "path"],
["title" => "useragent", "field" => "useragent"],
@@ -70,6 +70,12 @@ class Report
]);
$queryParams = self::parseQueryParams($request);
+ if (!isset($queryParams['limit'])) {
+ return [
+ "columns" => $columnsDefinition,
+ "rows" => [],
+ ];
+ }
$sql = "
SELECT
req.rowid AS id, ip.data AS ip, domain.data AS domain,
@@ -97,11 +103,17 @@ class Report
public static function count_requests_by_ip(ServerRequestInterface $request): PromiseInterface
{
$columnsDefinition = self::generateColumns([
- ["title" => "ip", "field" => "ip_address", 'formatter'=> 'ipFormatter'],
+ ["title" => "ip", "field" => "ip_address", 'formatter' => 'ipFormatter'],
["title" => "request_count", "field" => "request_count"],
]);
$queryParams = self::parseQueryParams($request);
+ if (!isset($queryParams['limit'])) {
+ return [
+ "columns" => $columnsDefinition,
+ "rows" => [],
+ ];
+ }
$sql = "
SELECT
ip.data AS ip_address,
@@ -110,16 +122,14 @@ class Report
request
INNER JOIN
ip ON request.id_ip = ip.rowid
- WHERE
- request.timestamp BETWEEN ? AND ?
- GROUP BY
- ip.data
- ORDER BY
- request_count DESC
- LIMIT ?;
+
+ WHERE 1=1
";
- $params = [$queryParams['from'], $queryParams['to'], $queryParams['limit']];
+ list($filterSQL, $filterParams) = self::prepareFilterClauses($queryParams['filter']);
+ $sql .= $filterSQL . " AND request.timestamp BETWEEN ? AND ? GROUP BY
+ ip.data ORDER BY request_count DESC LIMIT ?;";
+ $params = array_merge($filterParams, [$queryParams['from'], $queryParams['to'], $queryParams['limit']]);
return self::executeQuery($sql, $params, $columnsDefinition);
}
@@ -132,6 +142,12 @@ class Report
]);
$queryParams = self::parseQueryParams($request);
+ if (!isset($queryParams['limit'])) {
+ return [
+ "columns" => $columnsDefinition,
+ "rows" => [],
+ ];
+ }
$sql = "
SELECT
useragent.data AS id_useragent,
@@ -140,16 +156,12 @@ class Report
request
INNER JOIN
useragent ON request.id_useragent = useragent.rowid
- WHERE
- request.timestamp BETWEEN ? AND ?
- GROUP BY
- useragent.data
- ORDER BY
- request_count DESC
- LIMIT ?;
+ WHERE 1=1
";
- $params = [$queryParams['from'], $queryParams['to'], $queryParams['limit']];
+ list($filterSQL, $filterParams) = self::prepareFilterClauses($queryParams['filter']);
+ $sql .= $filterSQL . " AND req.timestamp BETWEEN ? AND ? GROUP BY useragent.data ORDER BY request_count DESC LIMIT ?;";
+ $params = array_merge($filterParams, [$queryParams['from'], $queryParams['to'], $queryParams['limit']]);
return self::executeQuery($sql, $params, $columnsDefinition);
}
@@ -157,13 +169,19 @@ class Report
public static function top_ip_ua_path(ServerRequestInterface $request): PromiseInterface
{
$columnsDefinition = self::generateColumns([
- ["title" => "ip", "field" => "ip", 'formatter'=> 'ipFormatter'],
+ ["title" => "ip", "field" => "ip", 'formatter' => 'ipFormatter'],
["title" => "useragent", "field" => "user_agent"],
["title" => "path", "field" => "path"],
["title" => "count", "field" => "count"],
]);
$queryParams = self::parseQueryParams($request);
+ if (!isset($queryParams['limit'])) {
+ return [
+ "columns" => $columnsDefinition,
+ "rows" => [],
+ ];
+ }
$sql = "
SELECT
ip.data AS ip,
@@ -175,16 +193,14 @@ class Report
JOIN ip ON request.id_ip = ip.rowid
JOIN useragent ON request.id_useragent = useragent.rowid
JOIN path ON request.id_path = path.rowid
- WHERE
- request.timestamp BETWEEN ? AND ?
- GROUP BY
- ip.data, useragent.data, path.data
- ORDER BY
- count DESC
- LIMIT ?;
+
+ WHERE 1=1
";
- $params = [$queryParams['from'], $queryParams['to'], $queryParams['limit']];
+ list($filterSQL, $filterParams) = self::prepareFilterClauses($queryParams['filter']);
+ $sql .= $filterSQL . " AND request.timestamp BETWEEN ? AND ? GROUP BY ip.data, useragent.data, path.data ORDER BY count DESC LIMIT ?;";
+
+ $params = array_merge($filterParams, [$queryParams['from'], $queryParams['to'], $queryParams['limit']]);
return self::executeQuery($sql, $params, $columnsDefinition);
}
@@ -192,12 +208,18 @@ class Report
public static function top_ip_by_load(ServerRequestInterface $request): PromiseInterface
{
$columnsDefinition = self::generateColumns([
- ["title" => "ip", "field" => "data", 'formatter'=> 'ipFormatter'],
+ ["title" => "ip", "field" => "data", 'formatter' => 'ipFormatter'],
["title" => "avg_load", "field" => "avg_load"],
["title" => "request_count", "field" => "request_count"],
]);
$queryParams = self::parseQueryParams($request);
+ if (!isset($queryParams['limit'])) {
+ return [
+ "columns" => $columnsDefinition,
+ "rows" => [],
+ ];
+ }
$sql = "
SELECT
ip.data,
@@ -211,17 +233,13 @@ class Report
FROM load AS load_sub
WHERE load_sub.rowid > request.timestamp
)
- WHERE
- load.load1 > 1
- AND request.timestamp BETWEEN ? AND ?
- GROUP BY
- ip.data
- ORDER BY
- avg_load DESC, request_count DESC
- LIMIT ?;
+ WHERE load.load1 > 1
";
- $params = [$queryParams['from'], $queryParams['to'], $queryParams['limit']];
+ list($filterSQL, $filterParams) = self::prepareFilterClauses($queryParams['filter']);
+ $sql .= $filterSQL . " AND request.timestamp BETWEEN ? AND ? GROUP BY ip.data ORDER BY avg_load DESC LIMIT ?;";
+
+ $params = array_merge($filterParams, [$queryParams['from'], $queryParams['to'], $queryParams['limit']]);
return self::executeQuery($sql, $params, $columnsDefinition);
}
@@ -229,11 +247,17 @@ class Report
public static function top_ip_by_rps(ServerRequestInterface $request): PromiseInterface
{
$columnsDefinition = self::generateColumns([
- ["title" => "ip", "field" => "ip_address", 'formatter'=> 'ipFormatter'],
+ ["title" => "ip", "field" => "ip_address", 'formatter' => 'ipFormatter'],
["title" => "avg_request_per_second", "field" => "avg_request_per_second"],
]);
$queryParams = self::parseQueryParams($request);
+ if (!isset($queryParams['limit'])) {
+ return [
+ "columns" => $columnsDefinition,
+ "rows" => [],
+ ];
+ }
$sql = "
WITH TimestampIPRequests AS (
SELECT
@@ -266,13 +290,13 @@ SELECT
FROM
IPRequestPerSecond
JOIN ip ON IPRequestPerSecond.id_ip = ip.rowid
-ORDER BY
- avg_request_per_second DESC
-LIMIT ?;
+ WHERE 1 = 1
";
- $params = [$queryParams['from'], $queryParams['to'], $queryParams['limit']];
+ list($filterSQL, $filterParams) = self::prepareFilterClauses($queryParams['filter']);
+ $sql .= $filterSQL . " GROUP BY ip.data ORDER BY avg_request_per_second DESC LIMIT ?;";
+ $params = array_merge($filterParams, [$queryParams['from'], $queryParams['to'], $queryParams['limit']]);
return self::executeQuery($sql, $params, $columnsDefinition);
}
@@ -285,6 +309,12 @@ LIMIT ?;
]);
$queryParams = self::parseQueryParams($request);
+ if (!isset($queryParams['limit'])) {
+ return [
+ "columns" => $columnsDefinition,
+ "rows" => [],
+ ];
+ }
$sql = "
CREATE FUNCTION cidr_to_network(cidr VARCHAR(30), prefix INT) RETURNS VARCHAR(30)
BEGIN
diff --git a/src/Config.php b/src/Config.php
index baf9f63..b618b48 100644
--- a/src/Config.php
+++ b/src/Config.php
@@ -4,8 +4,6 @@ declare(strict_types=1);
namespace XBotControl;
-use MaxMind\Db\Reader;
-
class Config
{
@@ -32,7 +30,7 @@ class Config
]);
$this->smarty->compile_check = 1;
if (isset($_ENV['GEOIP_DB_FILE_PATH'])) {
- $this->geoipreader = new Reader($_ENV['APP_DIR'].'/'.$_ENV['GEOIP_DB_FILE']);
+ $this->geoipreader = new \MaxMind\Db\Reader($_ENV['APP_DIR'].'/'.$_ENV['GEOIP_DB_FILE']);
}
$dnsConfig = \React\Dns\Config\Config::loadSystemConfigBlocking();
$dnsConfig->nameservers[] = '8.8.8.8';
diff --git a/src/Controllers/AuthController.php b/src/Controllers/AuthController.php
new file mode 100644
index 0000000..2b70ef6
--- /dev/null
+++ b/src/Controllers/AuthController.php
@@ -0,0 +1,30 @@
+ $_ENV['BASE_URI'] . '/login'
+ ]
+ );
+ }
+}
diff --git a/src/Controllers/LoginController.php b/src/Controllers/LoginController.php
new file mode 100644
index 0000000..531fa72
--- /dev/null
+++ b/src/Controllers/LoginController.php
@@ -0,0 +1,34 @@
+getParsedBody();
+ if ($data['api_key'] === $_ENV['API_KEY']) {
+ $_SESSION['API_KEY'] = $_ENV['API_KEY'];
+ $uri = $request->getUri();
+ var_dump($uri->getPath() );
+ return new Response(
+ Response::STATUS_FOUND,
+ [
+ 'Location' => $_ENV['BASE_URI'] . '/'
+ ]
+ );
+ }
+ return Response::html(
+ \XBotControl\Config::getInstance()->smarty->fetch('login.tpl')
+ );
+ }
+}
diff --git a/xbotcontrol.php b/xbotcontrol.php
index 3021755..16cfe2c 100644
--- a/xbotcontrol.php
+++ b/xbotcontrol.php
@@ -20,10 +20,10 @@ $app->any(
return XBotControl\Request::save($request);
}
);
+$app->any('/login', XBotControl\Controllers\LoginController::class);
+$app->any('/', XBotControl\Controllers\AuthController::class, XBotControl\Controllers\IndexController::class);
-$app->any('/', XBotControl\Controllers\IndexController::class);
-
-$app->any('/api/{action}/{resource}', XBotControl\Controllers\APIController::class);
+$app->any('/api/{action}/{resource}', XBotControl\Controllers\AuthController::class, XBotControl\Controllers\APIController::class);
XBotControl\Classes\Schedule::run();