Files
argus-wp-defence/admin/views/threats.php
T
root df0f2fccb8 ARGUS WordPress Defence 1.0.0 — first production release
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.
2026-08-09 13:40:16 +00:00

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'; ?>