/**
* AtomicEdge Adaptive Defense JavaScript
*
* Handles all AJAX interactions for the Adaptive Defense admin page.
*
* @package AtomicEdge
* @since 2.1.0
*/
/* global jQuery, atomicedge_admin */
(function($) {
'use strict';
var AtomicEdgeAD = {
/** Current page for each section */
pages: {
blocked: 1,
actors: 1,
detections: 1
},
/** Items per page */
perPage: 20,
/**
* Initialize the Adaptive Defense module
*/
init: function() {
if ($('#atomicedge-adaptive-defense-page').length === 0) {
return;
}
this.bindEvents();
this.loadCurrentTab();
},
/**
* Load content for the currently visible tab (server-side rendered)
*/
loadCurrentTab: function() {
// Detect which tab is visible by checking for tab-specific elements
if ($('#atomicedge-ad-status-card').length) {
this.loadStatusTab();
} else if ($('#atomicedge-ad-blocked-card').length) {
this.loadBlockedIps();
} else if ($('#atomicedge-ad-actors-card').length) {
this.loadActorProfiles();
} else if ($('#atomicedge-ad-detections-card').length) {
this.loadThreatDetections();
}
},
/**
* Bind event handlers
*/
bindEvents: function() {
var self = this;
// Tab navigation is handled server-side, no need to intercept clicks
// Just bind refresh buttons and other interactive elements
// Status tab refresh
$('#atomicedge-ad-status-refresh').on('click', function() {
self.loadStatusTab();
});
// Blocked IPs tab
$('#atomicedge-ad-blocked-refresh').on('click', function() {
self.loadBlockedIps();
});
// Block IP button click (not form submit)
$('#atomicedge-ad-block-btn').on('click', function(e) {
e.preventDefault();
self.blockIpFromForm();
});
$(document).on('click', '.atomicedge-ad-unblock-btn', function() {
var ip = $(this).data('ip');
self.unblockIp(ip);
});
// Actor Profiles tab
$('#atomicedge-ad-actors-refresh').on('click', function() {
self.loadActorProfiles();
});
$('#atomicedge-ad-actors-filter').on('change', function() {
self.pages.actors = 1;
self.loadActorProfiles();
});
$('#atomicedge-ad-actors-search').on('keypress', function(e) {
if (e.which === 13) {
self.pages.actors = 1;
self.loadActorProfiles();
}
});
// Search button click handler
$('#atomicedge-ad-actors-search-btn').on('click', function() {
self.pages.actors = 1;
self.loadActorProfiles();
});
$(document).on('click', '.atomicedge-ad-block-actor-btn', function() {
var ip = $(this).data('ip');
self.blockIp(ip, 'actor');
});
$(document).on('click', '.atomicedge-ad-delete-actor-btn', function() {
var id = $(this).data('id');
var ip = $(this).data('ip');
self.deleteActor(id, ip);
});
// Threat Detections tab
$('#atomicedge-ad-detections-refresh').on('click', function() {
self.loadThreatDetections();
});
$('#atomicedge-ad-detections-status').on('change', function() {
self.pages.detections = 1;
self.loadThreatDetections();
});
$(document).on('click', '.atomicedge-ad-view-detection-btn', function() {
var id = $(this).data('id');
var $row = $(this).closest('tr');
self.toggleDetectionDetail(id, $row);
});
$(document).on('click', '.atomicedge-ad-block-detection-btn', function() {
var ip = $(this).data('ip');
self.blockIp(ip, 'detection');
});
$(document).on('click', '.atomicedge-ad-dismiss-btn', function() {
var id = $(this).data('id');
self.dismissDetection(id);
});
$(document).on('click', '.atomicedge-ad-detail-close', function() {
$(this).closest('.atomicedge-ad-detail-row').remove();
});
// Pagination
$(document).on('click', '.atomicedge-ad-page-btn', function() {
var section = $(this).data('section');
var page = $(this).data('page');
self.pages[section] = page;
switch (section) {
case 'blocked':
self.loadBlockedIps();
break;
case 'actors':
self.loadActorProfiles();
break;
case 'detections':
self.loadThreatDetections();
break;
}
});
},
/**
* Switch to a tab
*
* @param {string} tab Tab identifier
*/
switchTab: function(tab) {
// Update nav tabs
$('.nav-tab').removeClass('nav-tab-active');
$('.nav-tab[data-tab="' + tab + '"]').addClass('nav-tab-active');
// Update tab panels
$('.atomicedge-ad-tab-panel').removeClass('active');
$('#atomicedge-ad-tab-' + tab).addClass('active');
// Load tab content
switch (tab) {
case 'status':
this.loadStatusTab();
break;
case 'blocked':
this.loadBlockedIps();
break;
case 'actors':
this.loadActorProfiles();
break;
case 'detections':
this.loadThreatDetections();
break;
}
},
/**
* Load Status tab content
*/
loadStatusTab: function() {
var self = this;
var $loading = $('#atomicedge-ad-status-loading');
var $content = $('#atomicedge-ad-status-content');
$loading.show();
$content.hide();
$.ajax({
url: atomicedge_admin.ajax_url,
type: 'POST',
data: {
action: 'atomicedge_get_adaptive_defense',
nonce: atomicedge_admin.nonce
},
success: function(response) {
$loading.hide();
if (response.success && response.data) {
self.renderStatusTab(response.data);
$content.show();
} else {
self.showError($content, response.data ? response.data.message : 'Failed to load status');
$content.show();
}
},
error: function(xhr, status, error) {
$loading.hide();
self.showError($content, 'Network error: ' + error);
$content.show();
}
});
},
/**
* Render Status tab content
*
* @param {Object} data API response data
*/
renderStatusTab: function(data) {
// Update status badge
var $status = $('#atomicedge-ad-status-badge');
var enabled = data.settings && data.settings.enabled;
$status.removeClass('atomicedge-ad-badge-enabled atomicedge-ad-badge-disabled');
if (enabled) {
$status.addClass('atomicedge-ad-badge-enabled').text('Enabled');
} else {
$status.addClass('atomicedge-ad-badge-disabled').text('Disabled');
}
// Update threat level
var threatLevel = data.threat_level || 'low';
var $threatLevel = $('#atomicedge-ad-threat-level');
$threatLevel.html(this.formatThreatLevel(threatLevel));
// Update stats - IDs must match those in adaptive-defense-status-tab.php
var stats = data.stats || {};
$('#atomicedge-ad-stat-actors').text(stats.total_actors || 0);
$('#atomicedge-ad-stat-blocked').text(stats.blocked_ips || stats.blocked_count || 0);
$('#atomicedge-ad-stat-pending').text(stats.pending_detections || stats.pending_reviews || 0);
$('#atomicedge-ad-stat-high-risk').text(stats.high_threat_count || 0);
// Update AI budget in settings section
$('#atomicedge-ad-budget-used').text(stats.budget_used || stats.ai_budget_used || 0);
$('#atomicedge-ad-budget-total').text(stats.ai_budget_total || 0);
// Update settings display
var settings = data.settings || {};
$('#atomicedge-ad-mode').text(this.formatMode(settings.mode || 'monitor'));
$('#atomicedge-ad-sensitivity').text(this.formatSensitivity(settings.sensitivity || 'balanced'));
// High-risk actors preview
this.renderHighRiskActors(data.high_risk_actors || []);
},
/**
* Render high-risk actors preview
*
* @param {Array} actors High-risk actors list
*/
renderHighRiskActors: function(actors) {
var $section = $('#atomicedge-ad-high-risk-section');
var $tbody = $('#atomicedge-ad-high-risk-body');
var $table = $('#atomicedge-ad-high-risk-table');
$tbody.empty();
if (!actors || actors.length === 0) {
$section.hide();
return;
}
// Show section and table when we have high-risk actors
$section.show();
$table.show();
var self = this;
actors.slice(0, 5).forEach(function(actor) {
var ipAddress = actor.ip_address || actor.ip || '';
var html = '
';
html += '| ' + self.escapeHtml(ipAddress) + ' | ';
html += '' + self.formatScore(actor.threat_score || actor.score || 0) + ' | ';
html += '' + (actor.requests || actor.total_requests || 0) + ' | ';
html += '' + (actor.waf_hits || actor.total_waf_hits || 0) + ' | ';
html += '';
html += '';
html += ' | ';
html += '
';
$tbody.append(html);
});
},
/**
* Load Blocked IPs
*/
loadBlockedIps: function() {
var self = this;
var $loading = $('#atomicedge-ad-blocked-loading');
var $wrapper = $('#atomicedge-ad-blocked-table-wrapper');
$loading.show();
$wrapper.hide();
$.ajax({
url: atomicedge_admin.ajax_url,
type: 'POST',
data: {
action: 'atomicedge_get_adaptive_defense',
nonce: atomicedge_admin.nonce
},
success: function(response) {
$loading.hide();
$wrapper.show();
if (response.success && response.data && response.data.blocked_ips) {
self.renderBlockedIps(response.data.blocked_ips);
} else {
self.renderBlockedIps([]);
}
},
error: function(xhr, status, error) {
$loading.hide();
$wrapper.show();
self.showTableError('#atomicedge-ad-blocked-body', 'Network error: ' + error);
}
});
},
/**
* Render Blocked IPs table
*
* @param {Array} blockedIps List of blocked IPs
*/
renderBlockedIps: function(blockedIps) {
var $tbody = $('#atomicedge-ad-blocked-body');
var $empty = $('#atomicedge-ad-blocked-empty');
var $table = $('#atomicedge-ad-blocked-table');
$tbody.empty();
if (!blockedIps || blockedIps.length === 0) {
$table.hide();
$empty.show();
return;
}
$table.show();
$empty.hide();
var self = this;
blockedIps.forEach(function(blocked) {
var ipAddress = blocked.ip_address || blocked.ip || '';
var html = '';
html += '| ' + self.escapeHtml(ipAddress) + ' | ';
html += '' + self.formatScore(blocked.score || 0) + ' | ';
html += '' + (blocked.waf_hits || 0) + ' | ';
// Expires column
if (blocked.is_permanent) {
html += 'Never (Permanent) | ';
} else if (blocked.expires) {
html += '' + self.formatDate(blocked.expires) + ' | ';
} else {
html += '— | ';
}
html += '';
html += '';
html += ' | ';
html += '
';
$tbody.append(html);
});
},
/**
* Block an IP address from the form
*/
blockIpFromForm: function() {
var self = this;
var ip = $('#atomicedge-ad-block-ip').val().trim();
var durationValue = $('#atomicedge-ad-block-duration').val();
if (!ip) {
alert('Please enter an IP address');
return;
}
var permanent = durationValue === 'permanent';
var durationHours = permanent ? 24 : parseInt(durationValue, 10);
var $btn = $('#atomicedge-ad-block-btn');
$btn.prop('disabled', true).text('Blocking...');
$.ajax({
url: atomicedge_admin.ajax_url,
type: 'POST',
data: {
action: 'atomicedge_block_ip',
nonce: atomicedge_admin.nonce,
ip: ip,
duration_hours: durationHours,
permanent: permanent ? 'true' : 'false'
},
success: function(response) {
$btn.prop('disabled', false).html(' Block IP');
if (response.success) {
$('#atomicedge-ad-block-ip').val('');
self.showNotice('IP address blocked successfully', 'success');
self.loadBlockedIps();
} else {
self.showNotice(response.data ? response.data.message : 'Failed to block IP', 'error');
}
},
error: function(xhr, status, error) {
$btn.prop('disabled', false).html(' Block IP');
self.showNotice('Network error: ' + error, 'error');
}
});
},
/**
* Block an IP address
*
* @param {string} ip Optional IP address (from form if not provided)
* @param {string} source Source context (form, actor, detection)
*/
blockIp: function(ip, source) {
var self = this;
source = source || 'form';
if (!ip && source === 'form') {
ip = $('#atomicedge-ad-block-ip').val().trim();
}
if (!ip) {
alert('Please enter an IP address');
return;
}
$.ajax({
url: atomicedge_admin.ajax_url,
type: 'POST',
data: {
action: 'atomicedge_block_ip',
nonce: atomicedge_admin.nonce,
ip: ip,
duration_hours: 24,
permanent: 'false'
},
beforeSend: function() {
if (source === 'form') {
$('#atomicedge-ad-block-ip-submit').prop('disabled', true).text('Blocking...');
}
},
success: function(response) {
if (source === 'form') {
$('#atomicedge-ad-block-ip-submit').prop('disabled', false).text('Block IP');
}
if (response.success) {
if (source === 'form') {
$('#atomicedge-ad-block-ip').val('');
$('#atomicedge-ad-block-reason').val('');
}
self.showNotice('IP address blocked successfully', 'success');
self.loadBlockedIps();
// Refresh other tabs if needed
if (source === 'actor') {
self.loadActorProfiles();
} else if (source === 'detection') {
self.loadThreatDetections();
}
} else {
self.showNotice(response.data ? response.data.message : 'Failed to block IP', 'error');
}
},
error: function(xhr, status, error) {
if (source === 'form') {
$('#atomicedge-ad-block-ip-submit').prop('disabled', false).text('Block IP');
}
self.showNotice('Network error: ' + error, 'error');
}
});
},
/**
* Unblock an IP address
*
* @param {string} ip IP address to unblock
*/
unblockIp: function(ip) {
var self = this;
if (!confirm('Are you sure you want to unblock ' + ip + '?')) {
return;
}
$.ajax({
url: atomicedge_admin.ajax_url,
type: 'POST',
data: {
action: 'atomicedge_unblock_ip',
nonce: atomicedge_admin.nonce,
ip: ip
},
success: function(response) {
if (response.success) {
self.showNotice('IP address unblocked successfully', 'success');
self.loadBlockedIps();
} else {
self.showNotice(response.data ? response.data.message : 'Failed to unblock IP', 'error');
}
},
error: function(xhr, status, error) {
self.showNotice('Network error: ' + error, 'error');
}
});
},
/**
* Load Actor Profiles
*/
loadActorProfiles: function() {
var self = this;
var $loading = $('#atomicedge-ad-actors-loading');
var $wrapper = $('#atomicedge-ad-actors-table-wrapper');
var filter = $('#atomicedge-ad-actors-filter').val();
var search = $('#atomicedge-ad-actors-search').val().trim();
$loading.show();
$wrapper.hide();
$.ajax({
url: atomicedge_admin.ajax_url,
type: 'POST',
data: {
action: 'atomicedge_get_actor_profiles',
nonce: atomicedge_admin.nonce,
filter: filter,
search: search,
page: self.pages.actors,
per_page: self.perPage
},
success: function(response) {
$loading.hide();
$wrapper.show();
if (response.success && response.data) {
self.renderActorProfiles(response.data.actors || response.data);
self.renderPagination('actors', response.data.pagination);
} else {
self.renderActorProfiles([]);
}
},
error: function(xhr, status, error) {
$loading.hide();
$wrapper.show();
self.showTableError('#atomicedge-ad-actors-body', 'Network error: ' + error);
}
});
},
/**
* Render Actor Profiles table
*
* @param {Array} actors List of actor profiles
*/
renderActorProfiles: function(actors) {
var $tbody = $('#atomicedge-ad-actors-body');
var $empty = $('#atomicedge-ad-actors-empty');
var $table = $('#atomicedge-ad-actors-table');
$tbody.empty();
if (!actors || actors.length === 0) {
$table.hide();
$empty.show();
return;
}
$table.show();
$empty.hide();
var self = this;
actors.forEach(function(actor) {
var score = actor.score || actor.threat_score || 0;
var ipAddress = actor.ip_address || actor.ip || '';
var html = '';
html += '| ' + self.escapeHtml(ipAddress) + ' | ';
html += '' + self.formatScore(score) + ' | ';
html += '' + (actor.total_requests || actor.requests || 0) + ' | ';
html += '' + (actor.waf_hits || 0) + ' | ';
html += '' + (actor.error_4xx || 0) + '/' + (actor.error_5xx || 0) + ' | ';
// Status column
html += '';
if (actor.is_blocked) {
html += 'Blocked';
} else if (score >= 70) {
html += 'High Risk';
} else {
html += '—';
}
html += ' | ';
html += '' + self.formatDate(actor.last_seen_at || actor.last_seen || actor.updated_at) + ' | ';
html += '';
if (!actor.is_blocked) {
html += ' ';
}
html += '';
html += ' | ';
html += '
';
$tbody.append(html);
});
},
/**
* Delete an actor profile
*
* @param {number} id Actor ID
* @param {string} ip Actor IP address
*/
deleteActor: function(id, ip) {
var self = this;
if (!confirm('Are you sure you want to delete the actor profile for ' + ip + '? This will also delete associated threat detections.')) {
return;
}
$.ajax({
url: atomicedge_admin.ajax_url,
type: 'POST',
data: {
action: 'atomicedge_delete_actor',
nonce: atomicedge_admin.nonce,
actor_id: id
},
success: function(response) {
if (response.success) {
self.showNotice('Actor profile deleted successfully', 'success');
self.loadActorProfiles();
} else {
self.showNotice(response.data ? response.data.message : 'Failed to delete actor profile', 'error');
}
},
error: function(xhr, status, error) {
self.showNotice('Network error: ' + error, 'error');
}
});
},
/**
* Load Threat Detections
*/
loadThreatDetections: function() {
var self = this;
var $loading = $('#atomicedge-ad-detections-loading');
var $wrapper = $('#atomicedge-ad-detections-table-wrapper');
var status = $('#atomicedge-ad-detections-status').val();
$loading.show();
$wrapper.hide();
$.ajax({
url: atomicedge_admin.ajax_url,
type: 'POST',
data: {
action: 'atomicedge_get_threat_detections',
nonce: atomicedge_admin.nonce,
status: status !== 'all' ? status : '',
page: self.pages.detections,
per_page: self.perPage
},
success: function(response) {
$loading.hide();
$wrapper.show();
if (response.success && response.data) {
self.renderThreatDetections(response.data.detections || response.data);
self.renderPagination('detections', response.data.pagination);
} else {
self.renderThreatDetections([]);
}
},
error: function(xhr, status, error) {
$loading.hide();
$wrapper.show();
self.showTableError('#atomicedge-ad-detections-body', 'Network error: ' + error);
}
});
},
/**
* Render Threat Detections table
*
* @param {Array} detections List of threat detections
*/
renderThreatDetections: function(detections) {
var $tbody = $('#atomicedge-ad-detections-body');
var $empty = $('#atomicedge-ad-detections-empty');
var $table = $('#atomicedge-ad-detections-table');
$tbody.empty();
if (!detections || detections.length === 0) {
$table.hide();
$empty.show();
return;
}
$table.show();
$empty.hide();
var self = this;
detections.forEach(function(detection) {
var ipAddress = detection.ip_address || detection.ip || (detection.actor && detection.actor.ip_address) || 'N/A';
var html = '';
html += '| ' + self.escapeHtml(ipAddress) + ' | ';
html += '' + self.formatScore(detection.score || 0) + ' | ';
html += '' + self.formatThreatLevel(detection.threat_level || 'low') + ' | ';
html += '' + self.formatIndicators(detection.reasons || detection.reasons_summary || detection.key_indicators || []) + ' | ';
html += '' + self.formatDetectionStatus(detection.status || 'pending') + ' | ';
html += '' + self.formatDate(detection.created_at || detection.detected_at) + ' | ';
html += '';
html += ' ';
if (detection.status !== 'blocked') {
html += ' ';
}
if (detection.status === 'pending' || detection.status === 'pending_review') {
html += '';
}
html += ' | ';
html += '
';
$tbody.append(html);
});
},
/**
* Toggle detection detail view (inline expandable row)
*
* @param {number} id Detection ID
* @param {jQuery} $row Table row element
*/
toggleDetectionDetail: function(id, $row) {
var self = this;
var $existingDetail = $row.next('.atomicedge-ad-detail-row');
// If detail row already exists, toggle it
if ($existingDetail.length) {
$existingDetail.remove();
return;
}
// Remove any other open detail rows
$('.atomicedge-ad-detail-row').remove();
// Clone the detail template and insert it
var $template = $('#atomicedge-ad-detection-detail-template').find('tr').clone();
$row.after($template);
// Load detail data
$.ajax({
url: atomicedge_admin.ajax_url,
type: 'POST',
data: {
action: 'atomicedge_get_threat_detection_detail',
nonce: atomicedge_admin.nonce,
detection_id: id
},
success: function(response) {
var $detailRow = $row.next('.atomicedge-ad-detail-row');
$detailRow.find('.atomicedge-ad-detail-loading').hide();
if (response.success && response.data) {
self.renderDetectionDetail($detailRow, response.data);
$detailRow.find('.atomicedge-ad-detail-content').show();
} else {
$detailRow.find('.atomicedge-ad-detail-content').html(
'Failed to load detection details.
'
).show();
}
},
error: function(xhr, status, error) {
var $detailRow = $row.next('.atomicedge-ad-detail-row');
$detailRow.find('.atomicedge-ad-detail-loading').hide();
$detailRow.find('.atomicedge-ad-detail-content').html(
'Network error: ' + self.escapeHtml(error) + '
'
).show();
}
});
},
/**
* Render detection detail in the expanded row
*
* @param {jQuery} $detailRow The detail row element
* @param {Object} data Detection detail data
*/
renderDetectionDetail: function($detailRow, data) {
var detection = data.detection || data;
var actor = detection.actor || data.actor || {};
// Detection details
$detailRow.find('.atomicedge-ad-detail-score').html(this.formatScore(detection.score || 0));
$detailRow.find('.atomicedge-ad-detail-confidence').text((detection.confidence || 0) + '%');
$detailRow.find('.atomicedge-ad-detail-status').html(this.formatDetectionStatus(detection.status || 'pending'));
$detailRow.find('.atomicedge-ad-detail-detected-at').text(this.formatDate(detection.created_at));
// Actor details
$detailRow.find('.atomicedge-ad-detail-ip').text(actor.ip_address || detection.ip_address || 'N/A');
$detailRow.find('.atomicedge-ad-detail-requests').text(actor.total_requests || 0);
$detailRow.find('.atomicedge-ad-detail-waf-hits').text(actor.waf_hits || 0);
$detailRow.find('.atomicedge-ad-detail-errors').text((actor.error_4xx || 0) + ' / ' + (actor.error_5xx || 0));
$detailRow.find('.atomicedge-ad-detail-first-seen').text(this.formatDate(actor.first_seen_at));
$detailRow.find('.atomicedge-ad-detail-last-seen').text(this.formatDate(actor.last_seen_at || actor.updated_at));
// Reasons
var $reasons = $detailRow.find('.atomicedge-ad-detail-reasons');
$reasons.empty();
var reasons = detection.reasons || detection.key_indicators || [];
if (reasons.length > 0) {
var self = this;
reasons.forEach(function(reason) {
if (typeof reason === 'string') {
$reasons.append('' + self.escapeHtml(reason) + '');
} else if (reason.indicator && reason.value) {
$reasons.append('' + self.escapeHtml(reason.indicator) + ': ' + self.escapeHtml(reason.value) + '');
}
});
} else {
$reasons.append('No specific indicators recorded');
}
// AI Analysis
var $aiSection = $detailRow.find('.atomicedge-ad-detail-ai-section');
if (detection.ai_analysis || detection.ai_response) {
$aiSection.find('.atomicedge-ad-detail-ai-content').text(detection.ai_analysis || detection.ai_response);
$aiSection.show();
} else {
$aiSection.hide();
}
},
/**
* Dismiss a threat detection
*
* @param {number} id Detection ID
*/
dismissDetection: function(id) {
var self = this;
if (!confirm('Are you sure you want to dismiss this detection?')) {
return;
}
$.ajax({
url: atomicedge_admin.ajax_url,
type: 'POST',
data: {
action: 'atomicedge_dismiss_detection',
nonce: atomicedge_admin.nonce,
detection_id: id
},
success: function(response) {
if (response.success) {
self.showNotice('Detection dismissed successfully', 'success');
self.loadThreatDetections();
} else {
self.showNotice(response.data ? response.data.message : 'Failed to dismiss detection', 'error');
}
},
error: function(xhr, status, error) {
self.showNotice('Network error: ' + error, 'error');
}
});
},
/**
* Render pagination controls
*
* @param {string} section Section identifier
* @param {Object} pagination Pagination data
*/
renderPagination: function(section, pagination) {
var $container = $('#atomicedge-ad-' + section + '-pagination');
$container.empty();
if (!pagination || pagination.last_page <= 1) {
return;
}
var html = '';
html += '' + pagination.total + ' items';
html += '
';
$container.html(html);
},
/* ============================
* Formatting Helpers
* ============================ */
/**
* Format score as colored badge
*
* @param {number} score Threat score
* @return {string} HTML string
*/
formatScore: function(score) {
var className = 'atomicedge-ad-score-low';
if (score >= 80) {
className = 'atomicedge-ad-score-critical';
} else if (score >= 60) {
className = 'atomicedge-ad-score-high';
} else if (score >= 40) {
className = 'atomicedge-ad-score-medium';
}
return '' + score + '';
},
/**
* Format threat level badge
*
* @param {string} level Threat level
* @return {string} HTML string
*/
formatThreatLevel: function(level) {
var labels = {
'critical': 'Critical',
'high': 'High',
'medium': 'Medium',
'low': 'Low',
'minimal': 'Minimal'
};
return '' +
(labels[level] || level) + '';
},
/**
* Format detection status badge
*
* @param {string} status Detection status
* @return {string} HTML string
*/
formatDetectionStatus: function(status) {
var labels = {
'pending': 'Pending',
'blocked': 'Blocked',
'dismissed': 'Dismissed'
};
return '' +
(labels[status] || status) + '';
},
/**
* Format key indicators (truncated list)
*
* @param {Array} indicators List of indicators
* @return {string} HTML string
*/
formatIndicators: function(indicators) {
if (!indicators || !Array.isArray(indicators) || indicators.length === 0) {
return '—';
}
var display = [];
var self = this;
// Filter out null/undefined values first
var validIndicators = indicators.filter(function(ind) {
return ind !== null && ind !== undefined;
});
if (validIndicators.length === 0) {
return '—';
}
validIndicators.slice(0, 2).forEach(function(ind) {
if (typeof ind === 'string') {
display.push(self.escapeHtml(ind));
} else if (ind && ind.indicator) {
display.push(self.escapeHtml(ind.indicator));
} else if (ind && ind.reason) {
display.push(self.escapeHtml(ind.reason));
}
});
var html = display.join(', ');
if (validIndicators.length > 2) {
html += ' +' + (validIndicators.length - 2) + ' more';
}
return html || '—';
},
/**
* Format operating mode
*
* @param {string} mode Mode value
* @return {string} Formatted mode
*/
formatMode: function(mode) {
var modes = {
'monitor': 'Monitor Only',
'auto_enforce': 'Auto Enforce'
};
return modes[mode] || mode;
},
/**
* Format sensitivity level
*
* @param {string} sensitivity Sensitivity value
* @return {string} Formatted sensitivity
*/
formatSensitivity: function(sensitivity) {
var levels = {
'low': 'Low',
'balanced': 'Balanced',
'high': 'High',
'aggressive': 'Aggressive'
};
return levels[sensitivity] || sensitivity;
},
/**
* Format duration in hours
*
* @param {number} hours Hours
* @return {string} Formatted duration
*/
formatDuration: function(hours) {
if (hours >= 24) {
var days = Math.floor(hours / 24);
return days + ' day' + (days > 1 ? 's' : '');
}
return hours + ' hour' + (hours > 1 ? 's' : '');
},
/**
* Format date string
*
* @param {string} dateString ISO date string
* @return {string} Formatted date
*/
formatDate: function(dateString) {
if (!dateString) {
return '—';
}
try {
var date = new Date(dateString);
return date.toLocaleDateString() + ' ' + date.toLocaleTimeString([], {hour: '2-digit', minute: '2-digit'});
} catch (e) {
return dateString;
}
},
/* ============================
* Utility Helpers
* ============================ */
/**
* Escape HTML to prevent XSS
*
* @param {string} str String to escape
* @return {string} Escaped string
*/
escapeHtml: function(str) {
if (!str) {
return '';
}
var div = document.createElement('div');
div.textContent = str;
return div.innerHTML;
},
/**
* Show an error message in a table body
*
* @param {string} selector Table body selector
* @param {string} message Error message
*/
showTableError: function(selector, message) {
$(selector).html(
'| ' +
' ' + this.escapeHtml(message) +
' |
'
);
},
/**
* Show an error in a content area
*
* @param {jQuery} $container Container element
* @param {string} message Error message
*/
showError: function($container, message) {
$container.html(
'' +
'
' + this.escapeHtml(message) + '
' +
'
'
);
},
/**
* Show a temporary admin notice
*
* @param {string} message Notice message
* @param {string} type Notice type (success, error, warning, info)
*/
showNotice: function(message, type) {
type = type || 'info';
var $notice = $(
'' +
'
' + this.escapeHtml(message) + '
' +
'
' +
'
'
);
// Insert at top of page
$('.wrap h1').first().after($notice);
// Bind dismiss handler
$notice.find('.notice-dismiss').on('click', function() {
$notice.fadeOut(200, function() { $(this).remove(); });
});
// Auto dismiss after 5 seconds
setTimeout(function() {
$notice.fadeOut(200, function() { $(this).remove(); });
}, 5000);
}
};
// Initialize on document ready
$(document).ready(function() {
AtomicEdgeAD.init();
});
})(jQuery);