feat: WAF log viewer with filters, one-click ban, and purge (#144)
Admin view at MokoWaaS > WAF Log: - Rule distribution cards showing block counts per shield - Filterable log table: rule, IP, search (URI/detail/UA), date range - Pagination (50 per page) - One-click IP ban from any log entry or top IPs sidebar - Top 10 blocked IPs sidebar with ban buttons - Purge old logs (configurable days) - Color-coded rule badges (sqli=red, xss=red, mua=yellow, etc.) WaflogModel: - getLogs with filters + pagination - getTotal for page count - getRuleCounts for distribution cards - getTopIps for sidebar - getRuleNames for filter dropdown - purgeLogs(days) with affected count - banIp adds to firewall plugin IP blocklist params ACL: core.admin (Super Users only) Submenu: MokoWaaS > WAF Log Authored-by: Moko Consulting Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -13,4 +13,5 @@ COM_MOKOWAAS_MENU_CHECKIN="Global Check-in"
|
||||
COM_MOKOWAAS_MENU_TICKETS="Helpdesk"
|
||||
COM_MOKOWAAS_MENU_HTACCESS=".htaccess Maker"
|
||||
COM_MOKOWAAS_MENU_PRIVACY="Privacy Guard"
|
||||
COM_MOKOWAAS_MENU_WAFLOG="WAF Log"
|
||||
COM_MOKOWAAS_MENU_CACHE="Cache Management"
|
||||
|
||||
@@ -30,6 +30,7 @@ class DisplayController extends BaseController
|
||||
'tickets' => 'mokowaas.tickets',
|
||||
'ticket' => 'mokowaas.tickets',
|
||||
'privacy' => 'core.admin',
|
||||
'waflog' => 'core.admin',
|
||||
];
|
||||
|
||||
public function display($cachable = false, $urlparams = [])
|
||||
@@ -269,6 +270,40 @@ class DisplayController extends BaseController
|
||||
}
|
||||
}
|
||||
|
||||
// ==================================================================
|
||||
// WAF Log
|
||||
// ==================================================================
|
||||
|
||||
public function purgeWafLog()
|
||||
{
|
||||
Session::checkToken() or die(Text::_('JINVALID_TOKEN'));
|
||||
|
||||
if (!$this->checkAcl('core.admin'))
|
||||
{
|
||||
$this->jsonForbidden();
|
||||
}
|
||||
|
||||
$days = Factory::getApplication()->getInput()->getInt('days', 30);
|
||||
$model = new \Moko\Component\MokoWaaS\Administrator\Model\WaflogModel();
|
||||
|
||||
$this->jsonResponse($model->purgeLogs($days));
|
||||
}
|
||||
|
||||
public function banIpFromLog()
|
||||
{
|
||||
Session::checkToken() or die(Text::_('JINVALID_TOKEN'));
|
||||
|
||||
if (!$this->checkAcl('core.admin'))
|
||||
{
|
||||
$this->jsonForbidden();
|
||||
}
|
||||
|
||||
$ip = Factory::getApplication()->getInput()->getString('ip', '');
|
||||
$model = new \Moko\Component\MokoWaaS\Administrator\Model\WaflogModel();
|
||||
|
||||
$this->jsonResponse($model->banIp($ip));
|
||||
}
|
||||
|
||||
// ==================================================================
|
||||
// Privacy Guard
|
||||
// ==================================================================
|
||||
|
||||
@@ -0,0 +1,215 @@
|
||||
<?php
|
||||
/**
|
||||
* @package MokoWaaS
|
||||
* @subpackage com_mokowaas
|
||||
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
|
||||
* @license GNU General Public License version 3 or later; see LICENSE
|
||||
*/
|
||||
|
||||
namespace Moko\Component\MokoWaaS\Administrator\Model;
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\MVC\Model\BaseDatabaseModel;
|
||||
|
||||
class WaflogModel extends BaseDatabaseModel
|
||||
{
|
||||
/**
|
||||
* Get WAF log entries with filters and pagination.
|
||||
*/
|
||||
public function getLogs(array $filters = [], int $limit = 50, int $offset = 0): array
|
||||
{
|
||||
$db = $this->getDatabase();
|
||||
$query = $db->getQuery(true)
|
||||
->select('*')
|
||||
->from($db->quoteName('#__mokowaas_waf_log'));
|
||||
|
||||
if (!empty($filters['rule']))
|
||||
{
|
||||
$query->where($db->quoteName('rule') . ' = ' . $db->quote($filters['rule']));
|
||||
}
|
||||
|
||||
if (!empty($filters['ip']))
|
||||
{
|
||||
$query->where($db->quoteName('ip') . ' LIKE ' . $db->quote('%' . $db->escape($filters['ip'], true) . '%'));
|
||||
}
|
||||
|
||||
if (!empty($filters['search']))
|
||||
{
|
||||
$search = $db->quote('%' . $db->escape($filters['search'], true) . '%');
|
||||
$query->where('(' . $db->quoteName('uri') . ' LIKE ' . $search
|
||||
. ' OR ' . $db->quoteName('detail') . ' LIKE ' . $search
|
||||
. ' OR ' . $db->quoteName('user_agent') . ' LIKE ' . $search . ')');
|
||||
}
|
||||
|
||||
if (!empty($filters['date_from']))
|
||||
{
|
||||
$query->where($db->quoteName('created') . ' >= ' . $db->quote($filters['date_from'] . ' 00:00:00'));
|
||||
}
|
||||
|
||||
if (!empty($filters['date_to']))
|
||||
{
|
||||
$query->where($db->quoteName('created') . ' <= ' . $db->quote($filters['date_to'] . ' 23:59:59'));
|
||||
}
|
||||
|
||||
$query->order($db->quoteName('created') . ' DESC');
|
||||
$query->setLimit($limit, $offset);
|
||||
|
||||
$db->setQuery($query);
|
||||
|
||||
return $db->loadObjectList() ?: [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get total count for pagination.
|
||||
*/
|
||||
public function getTotal(array $filters = []): int
|
||||
{
|
||||
$db = $this->getDatabase();
|
||||
$query = $db->getQuery(true)
|
||||
->select('COUNT(*)')
|
||||
->from($db->quoteName('#__mokowaas_waf_log'));
|
||||
|
||||
if (!empty($filters['rule']))
|
||||
{
|
||||
$query->where($db->quoteName('rule') . ' = ' . $db->quote($filters['rule']));
|
||||
}
|
||||
|
||||
if (!empty($filters['ip']))
|
||||
{
|
||||
$query->where($db->quoteName('ip') . ' LIKE ' . $db->quote('%' . $db->escape($filters['ip'], true) . '%'));
|
||||
}
|
||||
|
||||
$db->setQuery($query);
|
||||
|
||||
return (int) $db->loadResult();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get block counts grouped by rule for the summary bar.
|
||||
*/
|
||||
public function getRuleCounts(): array
|
||||
{
|
||||
$db = $this->getDatabase();
|
||||
$db->setQuery(
|
||||
$db->getQuery(true)
|
||||
->select([$db->quoteName('rule'), 'COUNT(*) AS ' . $db->quoteName('cnt')])
|
||||
->from($db->quoteName('#__mokowaas_waf_log'))
|
||||
->group($db->quoteName('rule'))
|
||||
->order($db->quoteName('cnt') . ' DESC')
|
||||
);
|
||||
|
||||
return $db->loadObjectList() ?: [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get top blocked IPs.
|
||||
*/
|
||||
public function getTopIps(int $limit = 10): array
|
||||
{
|
||||
$db = $this->getDatabase();
|
||||
$db->setQuery(
|
||||
$db->getQuery(true)
|
||||
->select([$db->quoteName('ip'), 'COUNT(*) AS ' . $db->quoteName('cnt'),
|
||||
'MAX(' . $db->quoteName('created') . ') AS ' . $db->quoteName('last_seen')])
|
||||
->from($db->quoteName('#__mokowaas_waf_log'))
|
||||
->group($db->quoteName('ip'))
|
||||
->order($db->quoteName('cnt') . ' DESC')
|
||||
->setLimit($limit)
|
||||
);
|
||||
|
||||
return $db->loadObjectList() ?: [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get distinct rule names for the filter dropdown.
|
||||
*/
|
||||
public function getRuleNames(): array
|
||||
{
|
||||
$db = $this->getDatabase();
|
||||
$db->setQuery(
|
||||
$db->getQuery(true)
|
||||
->select('DISTINCT ' . $db->quoteName('rule'))
|
||||
->from($db->quoteName('#__mokowaas_waf_log'))
|
||||
->order($db->quoteName('rule') . ' ASC')
|
||||
);
|
||||
|
||||
return $db->loadColumn() ?: [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete logs older than N days.
|
||||
*/
|
||||
public function purgeLogs(int $days): array
|
||||
{
|
||||
try
|
||||
{
|
||||
$db = $this->getDatabase();
|
||||
$cutoff = Factory::getDate('-' . $days . ' days')->toSql();
|
||||
|
||||
$db->setQuery(
|
||||
$db->getQuery(true)
|
||||
->delete($db->quoteName('#__mokowaas_waf_log'))
|
||||
->where($db->quoteName('created') . ' < ' . $db->quote($cutoff))
|
||||
)->execute();
|
||||
|
||||
$count = $db->getAffectedRows();
|
||||
|
||||
return ['success' => true, 'message' => "Purged {$count} log entries older than {$days} days."];
|
||||
}
|
||||
catch (\Throwable $e)
|
||||
{
|
||||
return ['success' => false, 'message' => 'Purge failed: ' . $e->getMessage()];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an IP to the firewall blocklist.
|
||||
*/
|
||||
public function banIp(string $ip, string $reason = 'Banned from WAF log'): array
|
||||
{
|
||||
try
|
||||
{
|
||||
$db = $this->getDatabase();
|
||||
|
||||
$query = $db->getQuery(true)
|
||||
->select($db->quoteName('params'))
|
||||
->from($db->quoteName('#__extensions'))
|
||||
->where($db->quoteName('element') . ' = ' . $db->quote('mokowaas_firewall'))
|
||||
->where($db->quoteName('type') . ' = ' . $db->quote('plugin'))
|
||||
->where($db->quoteName('folder') . ' = ' . $db->quote('system'));
|
||||
$db->setQuery($query);
|
||||
|
||||
$params = new \Joomla\Registry\Registry($db->loadResult() ?? '{}');
|
||||
$blocklist = json_decode($params->get('ip_blocklist', '[]'), true) ?: [];
|
||||
|
||||
// Check if already blocked
|
||||
foreach ($blocklist as $entry)
|
||||
{
|
||||
if (($entry['ip'] ?? '') === $ip)
|
||||
{
|
||||
return ['success' => false, 'message' => $ip . ' is already blocked.'];
|
||||
}
|
||||
}
|
||||
|
||||
$blocklist[] = ['ip' => $ip, 'enabled' => '1', 'label' => $reason];
|
||||
$params->set('ip_blocklist', json_encode($blocklist));
|
||||
|
||||
$db->setQuery(
|
||||
$db->getQuery(true)
|
||||
->update($db->quoteName('#__extensions'))
|
||||
->set($db->quoteName('params') . ' = ' . $db->quote($params->toString()))
|
||||
->where($db->quoteName('element') . ' = ' . $db->quote('mokowaas_firewall'))
|
||||
->where($db->quoteName('type') . ' = ' . $db->quote('plugin'))
|
||||
->where($db->quoteName('folder') . ' = ' . $db->quote('system'))
|
||||
)->execute();
|
||||
|
||||
return ['success' => true, 'message' => $ip . ' has been added to the IP blocklist.'];
|
||||
}
|
||||
catch (\Throwable $e)
|
||||
{
|
||||
return ['success' => false, 'message' => 'Ban failed: ' . $e->getMessage()];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
namespace Moko\Component\MokoWaaS\Administrator\View\Waflog;
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView;
|
||||
use Joomla\CMS\Toolbar\ToolbarHelper;
|
||||
|
||||
class HtmlView extends BaseHtmlView
|
||||
{
|
||||
protected $logs = [];
|
||||
protected $ruleCounts = [];
|
||||
protected $topIps = [];
|
||||
protected $ruleNames = [];
|
||||
protected $total = 0;
|
||||
protected $filters = [];
|
||||
|
||||
public function display($tpl = null)
|
||||
{
|
||||
$model = new \Moko\Component\MokoWaaS\Administrator\Model\WaflogModel();
|
||||
$input = Factory::getApplication()->getInput();
|
||||
|
||||
$this->filters = [
|
||||
'rule' => $input->getString('filter_rule', ''),
|
||||
'ip' => $input->getString('filter_ip', ''),
|
||||
'search' => $input->getString('filter_search', ''),
|
||||
'date_from' => $input->getString('filter_date_from', ''),
|
||||
'date_to' => $input->getString('filter_date_to', ''),
|
||||
];
|
||||
|
||||
$page = max(1, $input->getInt('page', 1));
|
||||
$limit = 50;
|
||||
$offset = ($page - 1) * $limit;
|
||||
|
||||
$this->logs = $model->getLogs($this->filters, $limit, $offset);
|
||||
$this->total = $model->getTotal($this->filters);
|
||||
$this->ruleCounts = $model->getRuleCounts();
|
||||
$this->topIps = $model->getTopIps(10);
|
||||
$this->ruleNames = $model->getRuleNames();
|
||||
|
||||
$this->addToolbar();
|
||||
|
||||
$wa = Factory::getApplication()->getDocument()->getWebAssetManager();
|
||||
$wa->registerAndUseStyle('com_mokowaas.dashboard', 'com_mokowaas/dashboard.css');
|
||||
|
||||
parent::display($tpl);
|
||||
}
|
||||
|
||||
protected function addToolbar(): void
|
||||
{
|
||||
ToolbarHelper::title('WAF Log Viewer', 'shield-alt');
|
||||
ToolbarHelper::back('JTOOLBAR_BACK', 'index.php?option=com_mokowaas');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,212 @@
|
||||
<?php
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\HTML\HTMLHelper;
|
||||
use Joomla\CMS\Router\Route;
|
||||
use Joomla\CMS\Session\Session;
|
||||
|
||||
$logs = $this->logs;
|
||||
$ruleCounts = $this->ruleCounts;
|
||||
$topIps = $this->topIps;
|
||||
$ruleNames = $this->ruleNames;
|
||||
$total = $this->total;
|
||||
$filters = $this->filters;
|
||||
$token = Session::getFormToken();
|
||||
$input = Factory::getApplication()->getInput();
|
||||
$page = max(1, $input->getInt('page', 1));
|
||||
$totalPages = max(1, ceil($total / 50));
|
||||
|
||||
$ruleBadge = [
|
||||
'sqli' => 'bg-danger', 'xss' => 'bg-danger', 'mua' => 'bg-warning text-dark',
|
||||
'rfi' => 'bg-danger', 'dfi' => 'bg-danger', 'blocked_file' => 'bg-info',
|
||||
'blocked_php' => 'bg-info', 'tmpl_switch' => 'bg-secondary',
|
||||
'ip_blocklist' => 'bg-dark', 'admin_secret' => 'bg-dark',
|
||||
];
|
||||
?>
|
||||
|
||||
<div id="mokowaas-waflog">
|
||||
<!-- Rule distribution cards -->
|
||||
<div class="d-flex flex-wrap gap-2 mb-4">
|
||||
<?php foreach ($ruleCounts as $rc): ?>
|
||||
<div class="card p-2 text-center" style="min-width:100px">
|
||||
<span class="badge <?php echo $ruleBadge[$rc->rule] ?? 'bg-secondary'; ?> mb-1"><?php echo htmlspecialchars($rc->rule); ?></span>
|
||||
<span class="fw-bold"><?php echo number_format($rc->cnt); ?></span>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
<div class="card p-2 text-center" style="min-width:100px">
|
||||
<span class="badge bg-primary mb-1">Total</span>
|
||||
<span class="fw-bold"><?php echo number_format($total); ?></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<!-- Main: Log table -->
|
||||
<div class="col-12 col-xl-9">
|
||||
<!-- Filters -->
|
||||
<form method="get" class="card mb-3">
|
||||
<div class="card-body">
|
||||
<input type="hidden" name="option" value="com_mokowaas">
|
||||
<input type="hidden" name="view" value="waflog">
|
||||
<div class="row g-2">
|
||||
<div class="col-md-2">
|
||||
<select name="filter_rule" class="form-select form-select-sm">
|
||||
<option value="">All Rules</option>
|
||||
<?php foreach ($ruleNames as $r): ?>
|
||||
<option value="<?php echo htmlspecialchars($r); ?>" <?php echo $filters['rule'] === $r ? 'selected' : ''; ?>><?php echo htmlspecialchars($r); ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<input type="text" name="filter_ip" class="form-control form-control-sm" placeholder="IP address" value="<?php echo htmlspecialchars($filters['ip']); ?>">
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<input type="text" name="filter_search" class="form-control form-control-sm" placeholder="Search URI/detail" value="<?php echo htmlspecialchars($filters['search']); ?>">
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<input type="date" name="filter_date_from" class="form-control form-control-sm" value="<?php echo htmlspecialchars($filters['date_from']); ?>">
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<input type="date" name="filter_date_to" class="form-control form-control-sm" value="<?php echo htmlspecialchars($filters['date_to']); ?>">
|
||||
</div>
|
||||
<div class="col-md-2 d-flex gap-1">
|
||||
<button type="submit" class="btn btn-sm btn-primary"><span class="icon-search"></span> Filter</button>
|
||||
<a href="<?php echo Route::_('index.php?option=com_mokowaas&view=waflog'); ?>" class="btn btn-sm btn-outline-secondary">Reset</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<!-- Log table -->
|
||||
<div class="card">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<strong><?php echo number_format($total); ?> blocked requests</strong>
|
||||
<button type="button" class="btn btn-sm btn-outline-danger" id="btn-purge"
|
||||
data-url="<?php echo Route::_('index.php?option=com_mokowaas&task=display.purgeWafLog&format=json'); ?>"
|
||||
data-token="<?php echo $token; ?>">
|
||||
<span class="icon-trash"></span> Purge Old Logs
|
||||
</button>
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped table-hover table-sm mb-0">
|
||||
<thead>
|
||||
<tr><th>Time</th><th>IP</th><th>Rule</th><th>URI</th><th>Detail</th><th>User Agent</th><th></th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php if (empty($logs)): ?>
|
||||
<tr><td colspan="7" class="text-center text-muted py-4">No blocked requests found.</td></tr>
|
||||
<?php else: ?>
|
||||
<?php foreach ($logs as $log): ?>
|
||||
<tr>
|
||||
<td class="text-nowrap small"><?php echo HTMLHelper::_('date', $log->created, 'M d H:i:s'); ?></td>
|
||||
<td><code><?php echo htmlspecialchars($log->ip); ?></code></td>
|
||||
<td><span class="badge <?php echo $ruleBadge[$log->rule] ?? 'bg-secondary'; ?>"><?php echo htmlspecialchars($log->rule); ?></span></td>
|
||||
<td class="small" style="max-width:250px;overflow:hidden;text-overflow:ellipsis" title="<?php echo htmlspecialchars($log->uri); ?>"><?php echo htmlspecialchars(mb_substr($log->uri, 0, 60)); ?></td>
|
||||
<td class="small" style="max-width:200px;overflow:hidden;text-overflow:ellipsis"><?php echo htmlspecialchars(mb_substr($log->detail, 0, 50)); ?></td>
|
||||
<td class="small" style="max-width:200px;overflow:hidden;text-overflow:ellipsis"><?php echo htmlspecialchars(mb_substr($log->user_agent, 0, 40)); ?></td>
|
||||
<td>
|
||||
<button type="button" class="btn btn-sm btn-outline-danger btn-ban-ip" data-ip="<?php echo htmlspecialchars($log->ip); ?>"
|
||||
data-url="<?php echo Route::_('index.php?option=com_mokowaas&task=display.banIpFromLog&format=json'); ?>"
|
||||
data-token="<?php echo $token; ?>" title="Ban this IP">
|
||||
<span class="icon-ban"></span>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<?php if ($totalPages > 1): ?>
|
||||
<div class="card-footer d-flex justify-content-between align-items-center">
|
||||
<small class="text-muted">Page <?php echo $page; ?> of <?php echo $totalPages; ?></small>
|
||||
<nav>
|
||||
<ul class="pagination pagination-sm mb-0">
|
||||
<?php if ($page > 1): ?>
|
||||
<li class="page-item"><a class="page-link" href="<?php echo Route::_('index.php?option=com_mokowaas&view=waflog&page=' . ($page - 1)); ?>">Prev</a></li>
|
||||
<?php endif; ?>
|
||||
<?php if ($page < $totalPages): ?>
|
||||
<li class="page-item"><a class="page-link" href="<?php echo Route::_('index.php?option=com_mokowaas&view=waflog&page=' . ($page + 1)); ?>">Next</a></li>
|
||||
<?php endif; ?>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Sidebar: Top IPs -->
|
||||
<div class="col-12 col-xl-3">
|
||||
<div class="card">
|
||||
<div class="card-header"><strong>Top Blocked IPs</strong></div>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm mb-0">
|
||||
<thead><tr><th>IP</th><th>Blocks</th><th>Last</th><th></th></tr></thead>
|
||||
<tbody>
|
||||
<?php foreach ($topIps as $tip): ?>
|
||||
<tr>
|
||||
<td><code class="small"><?php echo htmlspecialchars($tip->ip); ?></code></td>
|
||||
<td class="fw-bold"><?php echo $tip->cnt; ?></td>
|
||||
<td class="small text-nowrap"><?php echo HTMLHelper::_('date', $tip->last_seen, 'M d'); ?></td>
|
||||
<td>
|
||||
<button type="button" class="btn btn-sm btn-outline-danger btn-ban-ip" data-ip="<?php echo htmlspecialchars($tip->ip); ?>"
|
||||
data-url="<?php echo Route::_('index.php?option=com_mokowaas&task=display.banIpFromLog&format=json'); ?>"
|
||||
data-token="<?php echo $token; ?>" title="Ban">
|
||||
<span class="icon-ban"></span>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
var token = '<?php echo $token; ?>';
|
||||
|
||||
// Ban IP buttons
|
||||
document.querySelectorAll('.btn-ban-ip').forEach(function(btn) {
|
||||
btn.addEventListener('click', function() {
|
||||
var el = this;
|
||||
var ip = el.dataset.ip;
|
||||
if (!confirm('Add ' + ip + ' to the firewall IP blocklist?')) return;
|
||||
el.disabled = true;
|
||||
var fd = new FormData();
|
||||
fd.append('ip', ip);
|
||||
fd.append(el.dataset.token, '1');
|
||||
fetch(el.dataset.url, {method:'POST', body:fd, headers:{'X-Requested-With':'XMLHttpRequest'}})
|
||||
.then(function(r){return r.json()})
|
||||
.then(function(d){
|
||||
if (d.success) { Joomla.renderMessages({message:[d.message]}); el.textContent = 'Banned'; }
|
||||
else { Joomla.renderMessages({error:[d.message]}); el.disabled = false; }
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Purge button
|
||||
var purgeBtn = document.getElementById('btn-purge');
|
||||
if (purgeBtn) {
|
||||
purgeBtn.addEventListener('click', function() {
|
||||
var days = prompt('Delete WAF logs older than how many days?', '30');
|
||||
if (!days || isNaN(days)) return;
|
||||
this.disabled = true;
|
||||
var fd = new FormData();
|
||||
fd.append('days', days);
|
||||
fd.append(this.dataset.token, '1');
|
||||
fetch(this.dataset.url, {method:'POST', body:fd, headers:{'X-Requested-With':'XMLHttpRequest'}})
|
||||
.then(function(r){return r.json()})
|
||||
.then(function(d){
|
||||
if (d.success) { Joomla.renderMessages({message:[d.message]}); location.reload(); }
|
||||
else { Joomla.renderMessages({error:[d.message]}); }
|
||||
})
|
||||
.finally(function(){ purgeBtn.disabled = false; });
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@@ -33,6 +33,7 @@
|
||||
<menu link="option=com_mokowaas&view=tickets" img="class:headphones">COM_MOKOWAAS_MENU_TICKETS</menu>
|
||||
<menu link="option=com_mokowaas&view=htaccess" img="class:file-code">COM_MOKOWAAS_MENU_HTACCESS</menu>
|
||||
<menu link="option=com_mokowaas&view=privacy" img="class:lock">COM_MOKOWAAS_MENU_PRIVACY</menu>
|
||||
<menu link="option=com_mokowaas&view=waflog" img="class:shield-alt">COM_MOKOWAAS_MENU_WAFLOG</menu>
|
||||
<menu link="option=com_plugins&filter[folder]=system&filter[search]=mokowaas" img="class:power-off">COM_MOKOWAAS_MENU_PLUGINS</menu>
|
||||
<menu link="option=com_installer&view=update" img="class:refresh">COM_MOKOWAAS_MENU_UPDATES</menu>
|
||||
<menu link="option=com_checkin" img="class:check-square">COM_MOKOWAAS_MENU_CHECKIN</menu>
|
||||
|
||||
Reference in New Issue
Block a user