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"> + +> + + + + + + + + +
+
+
+
+
+ +
+ +
+
+ +
+
+
Limit
+ + + +
+
+
+
+
From
+ + +
+
+
+
+
To
+ + + +
+ +
+
+ + +
+ + +
+
+
+ +> + 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();