diff --git a/inc/spbc-settings.php b/inc/spbc-settings.php index bec1a07b2..bfcb450a1 100644 --- a/inc/spbc-settings.php +++ b/inc/spbc-settings.php @@ -700,14 +700,18 @@ function spbc_field_security_logs__prepare_data(&$table) $time_offset = current_time('timestamp') - time(); foreach ($table->rows as $row) { - $ip = IP::reduceIPv6($row->auth_ip); + $ip = IP::validate($row->auth_ip) === 'v6' + ? (IP::normalizeIPv6($row->auth_ip) ?: $row->auth_ip) + : $row->auth_ip; + $ip_attr = Escape::escAttr($ip); + $ip_js = Escape::escJs($ip); $allow_layout = '' + . $ip_js + . '\')" class="spbcGreen tbl-row_action--allow" data-ip="' . $ip_attr . '">' . esc_html__('Allow', 'security-malware-firewall') . ''; $ban_layout = '' + . $ip_js + . '\')" class="spbc---red tbl-row_action--ban" data-ip="' . $ip_attr . '">' . esc_html__('Ban', 'security-malware-firewall') . ''; $user_part = $row->user_login; @@ -759,9 +763,9 @@ function spbc_field_security_logs__prepare_data(&$table) 'blacklists/%s' ); $ip_part = sprintf( - '%s
%s', - $row->auth_ip, - IP::reduceIPv6($row->auth_ip), + '%s
%s', + Escape::escUrl(sprintf($ip_blacklist_link, rawurlencode($ip))), + Escape::escHtml($ip), $country_part ); diff --git a/js/spbc-timeline-widget.min.js b/js/spbc-timeline-widget.min.js index 5ec0bd062..6d061a4da 100644 --- a/js/spbc-timeline-widget.min.js +++ b/js/spbc-timeline-widget.min.js @@ -1,4 +1,4 @@ -class SpbcTimelineWidget{static markerRadius=4;static hoursRangeMax=24;static axisStepsCount=6;static userActionsUpstep=24;static legendCircleX=-120;static legendTextX=-105;static userLegendMaxRows=2;static userLegendRowHeight=18;static userLegendItemGap=15;static userLegendAxisOffset=60;constructor(){this.now=new Date,this.currentTimestamp=Math.floor(this.now.getTime()/1e3),this.eventsData=null,this.timelineMarkersData=[],this.timelineKnownEventsData=[],this.timelineEventTooltip=null,this.timelineStartTimestamp=this.currentTimestamp-3600*SpbcTimelineWidget.hoursRangeMax,this.usersCount=0,this.summaryEventsCount=0,this.userLegendExpanded=!1,this.selectedUserValue="most_active__action"}render(){"undefined"!=typeof spbcTimelineWidgetData&&(this.timelineEventTooltip=document.getElementById("spbc-timeline-tooltip"),this.eventsData=spbcTimelineWidgetData.users_actions,spbcTimelineWidgetData.current_timestamp_local&&(this.currentTimestamp=Number(spbcTimelineWidgetData.current_timestamp_local)),this.timelineStartTimestamp=Number(spbcTimelineWidgetData.first_event_timestamp),this.timelineKnownEventsData=spbcTimelineWidgetData.known_events_data,this.timelineMarkersData=this.generateMarkersData(spbcTimelineWidgetData.found_types),window.addEventListener("spbctTabLoaded",e=>{"security_log"===e.detail.tab&&this.initTimeline()}),jQuery("#spbc-timeline-tab").on("click",e=>{e.hasOwnProperty("originalEvent")&&("1"!==localStorage.getItem("spbc_timeline_closed")?localStorage.setItem("spbc_timeline_closed","1"):localStorage.setItem("spbc_timeline_closed","0"))}),window.addEventListener("resize",()=>{this.selectedUserValue&&(this.userLegendExpanded=!1,"most_active__action"===this.selectedUserValue?this.drawUserTimelineMostActive():this.drawUserTimeline(this.selectedUserValue))}))}initTimeline(){let i=document.getElementById("spbc-timeline-user-select-wrapper");var e=document.getElementById("spbc-timeline-user-select-trigger");let s=document.getElementById("spbc-timeline-user-select-dropdown"),n=document.getElementById("spbc-timeline-user-select-value");var t,r,a,l=[];for(t in this.usersCount=0,this.summaryEventsCount=0,this.eventsData)this.eventsData.hasOwnProperty(t)&&(r=this.eventsData[t],0<(a=(Array.isArray(r)?r:r.events||[]).filter(e=>e.time>=this.timelineStartTimestamp&&e.time<=this.currentTimestamp)).length)&&(this.usersCount++,this.summaryEventsCount+=a.length,l.push({user:t,role:r.role||"unknown",eventsCount:a.length}));s.innerHTML="";let c=(e,t)=>{var i=document.createElement("div");return i.className="spbc-timeline-select-option",i.dataset.value=t,i.textContent=e,i};var o=c("Most Active Users","most_active__action"),o=(o.classList.add("selected"),s.appendChild(o),l.forEach(e=>{var t=e.user+` (${e.role}) (${e.eventsCount} events)`;s.appendChild(c(t,e.user))}),e.addEventListener("click",e=>{e.stopPropagation(),i.classList.toggle("open")}),document.addEventListener("click",e=>{i.contains(e.target)||i.classList.remove("open")}),s.addEventListener("click",e=>{var t,e=e.target.closest(".spbc-timeline-select-option");e&&(t=e.dataset.value,this.selectedUserValue=t,this.userLegendExpanded=!1,n.textContent=e.textContent,s.querySelectorAll(".spbc-timeline-select-option").forEach(e=>{e.classList.remove("selected")}),e.classList.add("selected"),i.classList.remove("open"),"most_active__action"===t?(this.drawUserTimelineMostActive(),(e=new URL(window.location)).searchParams.delete("users"),window.history.pushState({},"",e)):(this.drawUserTimeline(t),(e=new URL(window.location)).searchParams.set("users",t),window.history.pushState({},"",e)),updateSecurityLogTable())}),0{const t=()=>{const i=document.getElementById("spbc-timeline-accordion");if(!i)return!1;e.initTimeline();const s=jQuery(i);s.length&&!s.hasClass("ui-accordion")&&s.accordion({heightStyle:"content",collapsible:!0,active:!1,activate:()=>{e.redrawCurrentTimeline()}});return!0};if(!t()){const i=new MutationObserver(()=>{t()&&i.disconnect()});i.observe(document.body,{childList:!0,subtree:!0})}})(this),window.addEventListener("spbctTabLoaded",e=>{"security_log"===e.detail.tab&&this.initTimeline()}),jQuery("#spbc-timeline-tab").on("click",e=>{e.hasOwnProperty("originalEvent")&&("1"!==localStorage.getItem("spbc_timeline_closed")?localStorage.setItem("spbc_timeline_closed","1"):localStorage.setItem("spbc_timeline_closed","0"))}),window.addEventListener("resize",()=>{this.userLegendExpanded=!1,this.redrawCurrentTimeline()}))}redrawCurrentTimeline(){if(!this.timelineInited||!this.selectedUserValue||!document.getElementById("spbc-timeline-svg"))return;"most_active__action"===this.selectedUserValue?this.drawUserTimelineMostActive():this.drawUserTimeline(this.selectedUserValue)}initTimeline(){if(this.timelineInited)return;let i=document.getElementById("spbc-timeline-user-select-wrapper");var e=document.getElementById("spbc-timeline-user-select-trigger");let s=document.getElementById("spbc-timeline-user-select-dropdown"),n=document.getElementById("spbc-timeline-user-select-value");if(!i||!e||!s||!n)return;this.timelineInited=!0;var t,r,a,l=[];for(t in this.usersCount=0,this.summaryEventsCount=0,this.eventsData)this.eventsData.hasOwnProperty(t)&&(r=this.eventsData[t],0<(a=(Array.isArray(r)?r:r.events||[]).filter(e=>e.time>=this.timelineStartTimestamp&&e.time<=this.currentTimestamp)).length)&&(this.usersCount++,this.summaryEventsCount+=a.length,l.push({user:t,role:r.role||"unknown",eventsCount:a.length}));s.innerHTML="";let c=(e,t)=>{var i=document.createElement("div");return i.className="spbc-timeline-select-option",i.dataset.value=t,i.textContent=e,i};var o=c("Most Active Users","most_active__action"),o=(o.classList.add("selected"),s.appendChild(o),l.forEach(e=>{var t=e.user+` (${e.role}) (${e.eventsCount} events)`;s.appendChild(c(t,e.user))}),e.addEventListener("click",e=>{e.stopPropagation(),i.classList.toggle("open")}),document.addEventListener("click",e=>{i.contains(e.target)||i.classList.remove("open")}),s.addEventListener("click",e=>{var t,e=e.target.closest(".spbc-timeline-select-option");e&&(t=e.dataset.value,this.selectedUserValue=t,this.userLegendExpanded=!1,n.textContent=e.textContent,s.querySelectorAll(".spbc-timeline-select-option").forEach(e=>{e.classList.remove("selected")}),e.classList.add("selected"),i.classList.remove("open"),"most_active__action"===t?(this.drawUserTimelineMostActive(),(e=new URL(window.location)).searchParams.delete("users"),window.history.pushState({},"",e)):(this.drawUserTimeline(t),(e=new URL(window.location)).searchParams.set("users",t),window.history.pushState({},"",e)),updateSecurityLogTable())}),0{e.classList.remove("selected"),e.dataset.value===t&&(e.classList.add("selected"),i.textContent=e.textContent)})),this.drawUserTimeline(t);e=new URL(window.location);e.searchParams.set("users",t),window.history.pushState({},"",e),updateSecurityLogTable()}getMostActiveUsers(e){let t=this.currentTimestamp-86400;var i,s,n,r=[];for(i in e)e.hasOwnProperty(i)&&(s=e[i],0<(n=(Array.isArray(s)?s:s.events||[]).filter(e=>e.time>=t)).length)&&r.push({user:i,events:n,count:n.length,role:s.role||"unknown"});return r.sort((e,t)=>t.count-e.count),r.slice(0,15)}groupByHour(i,e){let s={};return e.forEach(e=>{var t=3600*Math.floor(e.time/3600);s[t]||(s[t]={timestamp:1800+t,user:i,count:0,byType:{}}),s[t].count++,s[t].byType[e.event]=(s[t].byType[e.event]||0)+1}),Object.values(s)}drawUserTimeline(n){var e=document.getElementById("spbc-timeline-svg"),t=document.getElementById("spbc-timeline-no-data");e.innerHTML="",t.style.display="none";let i=0;5e.time>=this.timelineStartTimestamp&&e.time<=this.currentTimestamp);if(0===r.length)t.style.display="block";else{let s=this.currentTimestamp-this.timelineStartTimestamp;var p=spbcTimelineWidgetData.found_types.length;for(let e=0;e{var t=(e.time-this.timelineStartTimestamp)/s*d,i=m+this.timelineMarkersData[e.event].upstep;u.appendChild(this.createEventElement(e,t,i,n))})}}measureTextWidth(e,t,i="spbc-timeline-legend-text"){var s=document.createElementNS("http://www.w3.org/2000/svg","text"),i=(s.setAttribute("class",i),s.textContent=t,s.style.visibility="hidden",e.appendChild(s),s.getComputedTextLength());return e.removeChild(s),i}calculateUserLegendRowsCount(t,i,s,n=null){var r=SpbcTimelineWidget.userLegendItemGap,a=SpbcTimelineWidget.markerRadius,l=this.measureTextWidth(t,"and 99 more")+r;let c=0,o=0;for(let e=0;em&&0=n)return n;c+=d}return o+1}drawUserTimelineMostActive(){var n=document.getElementById("spbc-timeline-svg"),r=document.getElementById("spbc-timeline-no-data"),a=(n.innerHTML="",r.style.display="none",this.getMostActiveUsers(this.eventsData));if(0===a.length)r.style.display="block";else{let e=0,i=(5{s[e.user]=i[(t+1)%i.length]});r=n.parentElement.clientWidth;const v=10+e,w=40,y=40,T=140;let t=r-T-w;var l=SpbcTimelineWidget.userLegendRowHeight,m=this.userLegendExpanded?null:SpbcTimelineWidget.userLegendMaxRows,m=this.calculateUserLegendRowsCount(n,a,t,m)*l,l=150+e+SpbcTimelineWidget.userLegendAxisOffset,m=l+m;let c=(l-v-y)/2,o=(n.setAttribute("width",r),n.setAttribute("height",m),document.createElementNS("http://www.w3.org/2000/svg","g")),d=(o.setAttribute("transform",`translate(${T}, ${v+30})`),n.appendChild(o),this.drawTimeAxis(o,t,10+c,!0),this.currentTimestamp-this.timelineStartTimestamp);var u=spbcTimelineWidgetData.found_types.length;for(let e=0;e{let l=s[a.user];this.groupByHour(a.user,a.events).forEach(r=>{if(0!==r.count){let n=Math.max(0,Math.min((r.timestamp-this.timelineStartTimestamp)/d*t,t));Object.entries(r.byType).forEach(([e,t])=>{var i=c+this.timelineMarkersData[e].upstep,s=SpbcTimelineWidget.markerRadius+Math.min(2*Math.sqrt(t-1),8),e={event:e,time:r.timestamp,count:t};o.appendChild(this.createEventElement(e,n,i,a.user,s,!0,l))})}})})}}drawUserLegend(t,i,s,n,r,a){var l=SpbcTimelineWidget.userLegendRowHeight,c=SpbcTimelineWidget.userLegendItemGap,o=SpbcTimelineWidget.markerRadius,d=this.userLegendExpanded?null:SpbcTimelineWidget.userLegendMaxRows;let m=0,u=0,p=0,h=0;var g=this.measureTextWidth(t,"and 99 more")+c;for(let e=0;ey&&0=d)return void(0<(y=s.length-p)&&(w=`and ${y} more`,y=r+(d-1)*l,(T=document.createElementNS("http://www.w3.org/2000/svg","text")).setAttribute("class","spbc-timeline-legend-text spbc-timeline-more-link"),T.setAttribute("x",h),T.setAttribute("y",y),T.setAttribute("text-anchor","start"),T.setAttribute("dominant-baseline","middle"),T.textContent=w,T.style.cursor="pointer",T.style.fill="#0073aa",T.style.textDecoration="underline",T.addEventListener("click",()=>{this.userLegendExpanded=!0,this.drawUserTimelineMostActive()}),i.appendChild(T)));null!==d&&u===d-1&&(h=m+v);var y=r+u*l,w=m+o,T=m+2*o+6,A=document.createElementNS("http://www.w3.org/2000/svg","circle"),w=(A.setAttribute("cx",w),A.setAttribute("cy",y),A.setAttribute("r",o),A.style.fill=n[b.user],i.appendChild(A),document.createElementNS("http://www.w3.org/2000/svg","text"));w.setAttribute("class","spbc-timeline-legend-text"),w.setAttribute("x",T),w.setAttribute("y",y),w.setAttribute("text-anchor","start"),w.setAttribute("dominant-baseline","middle"),w.textContent=b.user,i.appendChild(w),m+=v,p++}}generateMarkersData(e){let t={},i=0,s="default";return e.forEach(e=>{this.timelineKnownEventsData[e]&&this.timelineKnownEventsData[e].color_scheme&&(s=this.timelineKnownEventsData[e].color_scheme),t[e]={color:s,upstep:i},i-=SpbcTimelineWidget.userActionsUpstep}),t}}function spbcTimelineWidgetRender(){var e=new SpbcTimelineWidget;jQuery(function(){jQuery("#spbc-timeline-accordion").accordion({heightStyle:"content",collapsible:!0,active:!1})}),e.render()} diff --git a/js/src/spbc-timeline-widget.js b/js/src/spbc-timeline-widget.js index a8914066a..4bfad6162 100644 --- a/js/src/spbc-timeline-widget.js +++ b/js/src/spbc-timeline-widget.js @@ -32,6 +32,7 @@ class SpbcTimelineWidget { this.summaryEventsCount = 0; this.userLegendExpanded = false; this.selectedUserValue = 'most_active__action'; + this.timelineInited = false; } /** @@ -51,7 +52,37 @@ class SpbcTimelineWidget { this.timelineKnownEventsData = spbcTimelineWidgetData.known_events_data; this.timelineMarkersData = this.generateMarkersData(spbcTimelineWidgetData.found_types); - // Initialize timeline when tab is loaded + const tryInit = () => { + const accordion = document.getElementById('spbc-timeline-accordion'); + if (!accordion) { + return false; + } + // Draw first, while the panel is still visible. jQuery accordion + // with active:false sets display:none and clientWidth becomes 0. + this.initTimeline(); + const $acc = jQuery(accordion); + if ($acc.length && ! $acc.hasClass('ui-accordion')) { + $acc.accordion({ + heightStyle: 'content', + collapsible: true, + active: false, + activate: () => { + this.redrawCurrentTimeline(); + }, + }); + } + return true; + }; + + if (!tryInit()) { + const observer = new MutationObserver(() => { + if (tryInit()) { + observer.disconnect(); + } + }); + observer.observe(document.body, {childList: true, subtree: true}); + } + window.addEventListener('spbctTabLoaded', (event) => { if (event.detail.tab === 'security_log') { this.initTimeline(); @@ -70,18 +101,28 @@ class SpbcTimelineWidget { // Handle window resize window.addEventListener('resize', () => { - if (!this.selectedUserValue) { - return; - } this.userLegendExpanded = false; - if (this.selectedUserValue === 'most_active__action') { - this.drawUserTimelineMostActive(); - } else { - this.drawUserTimeline(this.selectedUserValue); - } + this.redrawCurrentTimeline(); }); } + /** + * Redraw the currently selected timeline using the live container width. + */ + redrawCurrentTimeline() { + if (!this.timelineInited || !this.selectedUserValue) { + return; + } + if (!document.getElementById('spbc-timeline-svg')) { + return; + } + if (this.selectedUserValue === 'most_active__action') { + this.drawUserTimelineMostActive(); + } else { + this.drawUserTimeline(this.selectedUserValue); + } + } + /** * Initialize timeline with user selection */ @@ -90,6 +131,10 @@ class SpbcTimelineWidget { const trigger = document.getElementById('spbc-timeline-user-select-trigger'); const dropdown = document.getElementById('spbc-timeline-user-select-dropdown'); const valueDisplay = document.getElementById('spbc-timeline-user-select-value'); + if (this.timelineInited || !wrapper || !trigger || !dropdown || !valueDisplay) { + return; + } + this.timelineInited = true; const usersWithRecentEvents = []; // Calculate summary statistics for the last 24 hours only