Automatic WordPress security: local firewall, malware and file-integrity scanning, vulnerability protection, quarantine, scheduled backups, an optional page cache, and automatic global threat intelligence. See README.md for installation, update, and uninstall instructions.
199 lines
7.8 KiB
PHP
199 lines
7.8 KiB
PHP
<?php
|
|
|
|
if ( ! defined( 'ABSPATH' ) ) {
|
|
exit;
|
|
}
|
|
|
|
$argus_active_page = 'threats';
|
|
include ARGUS_WPD_DIR . 'admin/views/partials/header.php';
|
|
|
|
$category_labels = array(
|
|
'waf' => __( 'WAF (SQLi/XSS/RCE/etc.)', 'argus-wordpress-defence' ),
|
|
'integrity' => __( 'File Integrity', 'argus-wordpress-defence' ),
|
|
'malware' => __( 'Malware', 'argus-wordpress-defence' ),
|
|
'vulnerability' => __( 'Vulnerability', 'argus-wordpress-defence' ),
|
|
'account' => __( 'Account / Login', 'argus-wordpress-defence' ),
|
|
'correlation' => __( 'Correlated Campaign', 'argus-wordpress-defence' ),
|
|
'rest_inventory' => __( 'REST API Inventory', 'argus-wordpress-defence' ),
|
|
'ajax_inventory' => __( 'AJAX Inventory', 'argus-wordpress-defence' ),
|
|
);
|
|
|
|
$category_display = array();
|
|
foreach ( $category_counts as $category => $count ) {
|
|
$category_display[ $category_labels[ $category ] ?? ucfirst( $category ) ] = $count;
|
|
}
|
|
|
|
$action_labels = array(
|
|
'block' => __( 'Blocked', 'argus-wordpress-defence' ),
|
|
'challenge' => __( 'Challenged', 'argus-wordpress-defence' ),
|
|
'rate_limit' => __( 'Rate Limited', 'argus-wordpress-defence' ),
|
|
);
|
|
|
|
?>
|
|
|
|
<h1 class="argus-wpd-page-title"><?php esc_html_e( 'Threats', 'argus-wordpress-defence' ); ?></h1>
|
|
<p style="color:var(--muted);font-size:13px;margin:0 0 24px;">
|
|
<?php esc_html_e( 'Recent malicious activity detected and blocked by ARGUS.', 'argus-wordpress-defence' ); ?>
|
|
<a href="<?php echo esc_url( admin_url( 'admin.php?page=argus-wpd-findings' ) ); ?>" style="color:var(--cyan);font-weight:600;text-decoration:none"><?php esc_html_e( 'View all findings', 'argus-wordpress-defence' ); ?></a>
|
|
</p>
|
|
|
|
<div class="argus-wpd-panel argus-wpd-live-chart-panel">
|
|
<h2><?php esc_html_e( 'Live Threat Activity · Blocked / Min', 'argus-wordpress-defence' ); ?></h2>
|
|
<?php
|
|
$blocked_total = array_sum( wp_list_pluck( $blocked_per_minute, 'value' ) );
|
|
if ( $blocked_total <= 0 ) :
|
|
?>
|
|
<p class="argus-wpd-empty"><?php esc_html_e( 'No blocked activity in the last 30 minutes.', 'argus-wordpress-defence' ); ?></p>
|
|
<?php else : ?>
|
|
<div class="argus-wpd-chart-wrap">
|
|
<?php echo Argus_Charts::area_chart( $blocked_per_minute, 900, 170 ); // phpcs:ignore WordPress.Security.EscapeOutput ?>
|
|
</div>
|
|
<?php endif; ?>
|
|
</div>
|
|
|
|
<div id="argus-wpd-live-feed-rows" class="argus-wpd-live-feed-rows"
|
|
data-ajax-url="<?php echo esc_url( admin_url( 'admin-ajax.php' ) ); ?>"
|
|
data-nonce="<?php echo esc_attr( $live_feed_nonce ); ?>">
|
|
<?php if ( empty( $latest_blocked ) ) : ?>
|
|
<p class="argus-wpd-empty" data-empty-placeholder="1"><?php esc_html_e( 'No blocked events recorded yet.', 'argus-wordpress-defence' ); ?></p>
|
|
<?php else : ?>
|
|
<?php foreach ( $latest_blocked as $row ) : ?>
|
|
<div class="argus-wpd-live-feed-row" data-key="<?php echo esc_attr( md5( $row['ip'] . '|' . $row['created_at'] . '|' . $row['type'] ) ); ?>">
|
|
<span class="argus-wpd-live-feed-time"><?php echo esc_html( Argus_Console_Stats::live_feed_time_label( $row['created_at'] ) ); ?></span>
|
|
<span class="argus-wpd-live-feed-desc"><?php echo esc_html( $row['desc'] ); ?></span>
|
|
<span class="argus-wpd-live-feed-action">BLOCKED</span>
|
|
</div>
|
|
<?php endforeach; ?>
|
|
<?php endif; ?>
|
|
</div>
|
|
|
|
<div class="argus-wpd-panel">
|
|
<h2><?php esc_html_e( 'Attack Types', 'argus-wordpress-defence' ); ?></h2>
|
|
<?php if ( empty( $category_display ) ) : ?>
|
|
<p class="argus-wpd-empty"><?php esc_html_e( 'No findings recorded yet.', 'argus-wordpress-defence' ); ?></p>
|
|
<?php else : ?>
|
|
<div class="argus-wpd-donut-row">
|
|
<?php echo Argus_Charts::donut( $category_display ); // phpcs:ignore WordPress.Security.EscapeOutput ?>
|
|
<?php echo Argus_Charts::legend( $category_display ); // phpcs:ignore WordPress.Security.EscapeOutput ?>
|
|
</div>
|
|
<?php endif; ?>
|
|
</div>
|
|
|
|
<div class="argus-wpd-panel">
|
|
<h2><?php esc_html_e( 'Recent Activity', 'argus-wordpress-defence' ); ?></h2>
|
|
<?php if ( empty( $recent_activity ) ) : ?>
|
|
<p class="argus-wpd-empty"><?php esc_html_e( 'No enforcement-relevant activity recorded yet.', 'argus-wordpress-defence' ); ?></p>
|
|
<?php else : ?>
|
|
<table class="widefat striped">
|
|
<thead>
|
|
<tr>
|
|
<th><?php esc_html_e( 'Time', 'argus-wordpress-defence' ); ?></th>
|
|
<th><?php esc_html_e( 'Source IP', 'argus-wordpress-defence' ); ?></th>
|
|
<th><?php esc_html_e( 'Type', 'argus-wordpress-defence' ); ?></th>
|
|
<th><?php esc_html_e( 'Action', 'argus-wordpress-defence' ); ?></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<?php foreach ( $recent_activity as $row ) : ?>
|
|
<tr>
|
|
<td><?php echo esc_html( human_time_diff( strtotime( $row->created_at . ' UTC' ) ) ); ?> <?php esc_html_e( 'ago', 'argus-wordpress-defence' ); ?></td>
|
|
<td><span class="argus-wpd-flag" title="<?php echo esc_attr( Argus_GeoIP::label( $row->ip ) ); ?>"><?php echo esc_html( Argus_GeoIP::icon( $row->ip ) ); ?></span> <code><?php echo esc_html( $row->ip ); ?></code></td>
|
|
<td><?php echo esc_html( $row->label ); ?></td>
|
|
<td><?php echo esc_html( $action_labels[ $row->action ] ?? ucfirst( $row->action ) ); ?></td>
|
|
</tr>
|
|
<?php endforeach; ?>
|
|
</tbody>
|
|
</table>
|
|
<?php
|
|
$page = $activity_page;
|
|
$total_pages = $activity_total_pages;
|
|
$base_url = remove_query_arg( 'activity_paged' );
|
|
$argus_page_param = 'activity_paged';
|
|
include ARGUS_WPD_DIR . 'admin/views/partials/pagination.php';
|
|
unset( $argus_page_param );
|
|
?>
|
|
<?php endif; ?>
|
|
</div>
|
|
|
|
<script>
|
|
(function () {
|
|
// Vanilla JS only, no external library (matches this plugin's
|
|
// zero-JS-dependency baseline elsewhere) -- polls the live feed and
|
|
// re-renders only when the newest row actually changed, so a quiet
|
|
// site never flickers/re-animates for no reason.
|
|
var container = document.getElementById( 'argus-wpd-live-feed-rows' );
|
|
if ( ! container ) {
|
|
return;
|
|
}
|
|
var ajaxUrl = container.getAttribute( 'data-ajax-url' );
|
|
var nonce = container.getAttribute( 'data-nonce' );
|
|
var lastTopKey = null;
|
|
var reduceMotion = window.matchMedia && window.matchMedia( '(prefers-reduced-motion: reduce)' ).matches;
|
|
|
|
function rowHtml( row ) {
|
|
var el = document.createElement( 'div' );
|
|
el.className = 'argus-wpd-live-feed-row';
|
|
el.setAttribute( 'data-key', row.key );
|
|
if ( ! reduceMotion ) {
|
|
el.classList.add( 'is-entering' );
|
|
}
|
|
el.innerHTML =
|
|
'<span class="argus-wpd-live-feed-time">' + row.time + '</span>' +
|
|
'<span class="argus-wpd-live-feed-desc">' + row.desc + '</span>' +
|
|
'<span class="argus-wpd-live-feed-action">BLOCKED</span>';
|
|
return el;
|
|
}
|
|
|
|
function poll() {
|
|
var body = 'action=argus_wpd_live_feed&nonce=' + encodeURIComponent( nonce );
|
|
fetch( ajaxUrl, {
|
|
method: 'POST',
|
|
credentials: 'same-origin',
|
|
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
body: body,
|
|
} )
|
|
.then( function ( r ) { return r.json(); } )
|
|
.then( function ( json ) {
|
|
if ( ! json || ! json.success || ! Array.isArray( json.data ) || 0 === json.data.length ) {
|
|
return;
|
|
}
|
|
var rows = json.data;
|
|
if ( rows[0].key === lastTopKey ) {
|
|
return; // Nothing new -- no reload, no re-animation.
|
|
}
|
|
lastTopKey = rows[0].key;
|
|
|
|
var placeholder = container.querySelector( '[data-empty-placeholder]' );
|
|
if ( placeholder ) {
|
|
placeholder.remove();
|
|
}
|
|
|
|
var existingKeys = {};
|
|
Array.prototype.forEach.call( container.children, function ( el ) {
|
|
existingKeys[ el.getAttribute( 'data-key' ) ] = true;
|
|
} );
|
|
|
|
rows.forEach( function ( row ) {
|
|
if ( existingKeys[ row.key ] ) {
|
|
return;
|
|
}
|
|
container.insertBefore( rowHtml( row ), container.firstChild );
|
|
} );
|
|
|
|
while ( container.children.length > 10 ) {
|
|
container.removeChild( container.lastChild );
|
|
}
|
|
} )
|
|
.catch( function () {
|
|
// Silent -- a transient polling failure must never surface
|
|
// as a visible error to the customer; the next tick just
|
|
// tries again.
|
|
} );
|
|
}
|
|
|
|
setInterval( poll, 5000 );
|
|
})();
|
|
</script>
|
|
|
|
<?php include ARGUS_WPD_DIR . 'admin/views/partials/footer.php'; ?>
|