Changeset 3373724
- Timestamp:
- 10/06/2025 01:17:03 PM (5 months ago)
- Location:
- secuseek/trunk
- Files:
-
- 14 edited
-
README.md (modified) (3 diffs)
-
assets/css/findings-filters.css (modified) (1 diff)
-
assets/js/alpine-init.js (modified) (4 diffs)
-
assets/js/findings-alpine-component.js (modified) (14 diffs)
-
assets/js/findings-initialization.js (modified) (4 diffs)
-
assets/js/no-findings-inline.js (modified) (1 diff)
-
assets/js/scan-control.js (modified) (5 diffs)
-
includes/auth-handler.php (modified) (1 diff)
-
includes/control.php (modified) (1 diff)
-
includes/fetcher.php (modified) (19 diffs)
-
readme.txt (modified) (4 diffs)
-
secuseek.php (modified) (1 diff)
-
templates/findings/findings-filters.php (modified) (4 diffs)
-
templates/login-form.php (modified) (2 diffs)
Legend:
- Unmodified
- Added
- Removed
-
secuseek/trunk/README.md
r3373197 r3373724 61 61 **Note:** All minified files are third-party libraries. The plugin's own JavaScript and CSS files are not minified and are included in their readable form in the assets directory. 62 62 63 ### Source and Human-Readable Code 64 This plugin ships with human-readable source code. All custom JavaScript and CSS are provided in unminified form under `assets/js/` and `assets/css/`. 65 66 The plugin also bundles a small number of third-party, minified assets. Their human-readable sources are publicly available here: 67 68 - `assets/js/alpinejs.min.js` — Source: `https://github.com/alpinejs/alpine` (Docs/Homepage: `https://alpinejs.dev`) 69 - `assets/css/fontawesome.min.css` — Source: `https://github.com/FortAwesome/Font-Awesome` 70 71 Build tools are not required to work on this plugin. If build tooling is added in the future, instructions will be documented here. 72 73 74 ### Direct downloads (exact versions used) 75 - Alpine.js 3.13.10 (minified): `https://cdn.jsdelivr.net/npm/alpinejs@3.13.10/dist/cdn.min.js` 76 - Font Awesome Free 6.5.0 (fontawesome.min.css): `https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@6.5.0/css/fontawesome.min.css` 77 78 63 79 ### Build Process 64 80 … … 82 98 - API keys are encrypted using AES-256-CBC encryption 83 99 100 ### Used technologies 101 - Alpine.js 3.13.10 — Docs: `https://alpinejs.dev` — Source: `https://github.com/alpinejs/alpine` — Minified CDN: `https://cdn.jsdelivr.net/npm/alpinejs@3.13.10/dist/cdn.min.js` 102 - Font Awesome Free 6.5.0 — Site: `https://fontawesome.com` — Source: `https://github.com/FortAwesome/Font-Awesome` — CSS CDN: `https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@6.5.0/css/fontawesome.min.css` 103 - WordPress HTTP API (remote requests) — Docs: `https://developer.wordpress.org/plugins/http-api/` 104 - WordPress Cron API (scheduling) — Docs: `https://developer.wordpress.org/plugins/cron/` 105 - WordPress AJAX (admin-ajax) — Docs: `https://developer.wordpress.org/plugins/javascript/ajax/` 106 84 107 ### API Integration 85 108 … … 93 116 - `GET /api/v1/external/issues/{id}` - Get scan results 94 117 - `DELETE /api/v1/external/schedule-scan/{id}` - Delete scan 118 119 95 120 96 121 ### Frontend Architecture -
secuseek/trunk/assets/css/findings-filters.css
r3373197 r3373724 130 130 .secuseek-multiselect .dropdown-item span:last-child { 131 131 margin-left: auto; 132 color: # 22ad5a;133 font-size: 1 3px;132 color: #1e9e4a; 133 font-size: 11px; 134 134 } -
secuseek/trunk/assets/js/alpine-init.js
r3373197 r3373724 15 15 } 16 16 17 console.log('SecuSeek: Registering secuSeekFindings component');18 17 19 18 Alpine.data('secuSeekFindings', function() { 20 console.log('SecuSeek: Component factory function called');21 19 return { 22 20 // Initialize with default values to prevent undefined errors … … 52 50 53 51 init() { 54 console.log('SecuSeek: Alpine component init() called');55 52 56 53 // Try to get data immediately … … 148 145 149 146 toggleFilter(type, value) { 150 console.log('SecuSeek: toggleFilter called', type, value);151 147 if (this.filters[type].includes(value)) { 152 148 this.filters[type] = this.filters[type].filter(function(x) { … … 155 151 } else { 156 152 this.filters[type].push(value); 153 } 154 }, 155 156 removeFilterItem(type, value) { 157 // Alpine.js reactivity: create new array to trigger reactivity 158 const newFilters = [...this.filters[type]]; 159 const index = newFilters.indexOf(value); 160 if (index > -1) { 161 newFilters.splice(index, 1); 162 this.filters[type] = newFilters; 157 163 } 158 164 }, -
secuseek/trunk/assets/js/findings-alpine-component.js
r3373197 r3373724 11 11 // Prevent Alpine.js from auto-starting until components are registered 12 12 if (typeof Alpine !== 'undefined') { 13 console.log('SecuSeek: Deferring Alpine.js start until component registration');14 13 Alpine.store('__secuseek_component_ready', false); 15 14 } … … 17 16 // Ensure Alpine.js is available and register component immediately 18 17 if (typeof Alpine !== 'undefined') { 19 console.log('SecuSeek: Alpine.js already available, registering component immediately');20 18 registerSecuSeekComponent(); 21 19 } else { 22 20 // Wait for Alpine.js if not yet loaded 23 21 document.addEventListener('alpine:init', function() { 24 console.log('SecuSeek: Alpine.js init event received, registering component');25 22 registerSecuSeekComponent(); 26 23 }); … … 29 26 document.addEventListener('DOMContentLoaded', function() { 30 27 if (typeof Alpine !== 'undefined' && !Alpine.store('__secuseek_component_ready')) { 31 console.log('SecuSeek: DOM ready, attempting component registration');32 28 registerSecuSeekComponent(); 33 29 } … … 36 32 37 33 function registerSecuSeekComponent() { 34 // Component already registered in alpine-init.js, skip this registration 35 return; 36 38 37 Alpine.data('secuSeekFindings', function() { 39 console.log('SecuSeek: Component factory function called');40 38 return { 41 39 // Initialize with default values to prevent undefined errors … … 64 62 65 63 init() { 66 console.log('SecuSeek: Alpine component init() called');67 64 68 65 // Initialize component when data is ready … … 71 68 // Listen for data ready event 72 69 document.addEventListener('secuseek:dataReady', function(event) { 73 console.log('SecuSeek: Received dataReady event', event.detail);74 70 const data = event.detail[0]; 75 71 if (data && data.findings) { … … 81 77 if (window.SecuSeekData && window.SecuSeekData.getFindingsData) { 82 78 const findingsData = window.SecuSeekData.getFindingsData(); 83 console.log('SecuSeek: Data already available, initializing', findingsData);84 79 this.initializeWithData(findingsData); 85 80 } … … 87 82 // Fallback: Try to get data from localized variables 88 83 if (!this.dataInitialized && typeof secuseek_findings_data !== 'undefined') { 89 console.log('SecuSeek: Using localized data directly', secuseek_findings_data);90 84 this.initializeWithData(secuseek_findings_data); 91 85 } … … 93 87 94 88 initializeWithData(data) { 95 console.log('SecuSeek: Initializing component with data', data);96 89 97 90 if (!data || typeof data !== 'object') { … … 115 108 }, { deep: true }); 116 109 117 console.log('SecuSeek: Component initialized successfully', {118 110 findings: this.allFindings.length, 119 111 categories: this.categories.length, … … 124 116 125 117 toggleSelectCard(idx) { 126 console.log('SecuSeek: toggleSelectCard called with idx:', idx);127 118 this.selected = idx; 128 119 }, 129 120 130 121 toggleFilter(type, value) { 131 console.log('SecuSeek: toggleFilter called', type, value);132 122 if (this.filters[type].includes(value)) { 133 123 this.filters[type] = this.filters[type].filter(function(x) { … … 137 127 this.filters[type].push(value); 138 128 } 129 }, 130 131 removeFilterItem(type, value) { 132 this.filters[type] = this.filters[type].filter(function(x) { 133 return x !== value; 134 }); 139 135 }, 140 136 … … 158 154 } 159 155 160 console.log('SecuSeek: Filtered findings updated', this.filteredFindings.length, 'of', this.allFindings.length);161 156 } 162 157 }; 163 158 }); 164 console.log('SecuSeek: Component registered successfully');165 159 166 160 // Mark component as ready … … 169 163 Alpine.store('__secuseek_component_ready', true); 170 164 } catch (e) { 171 console.log('SecuSeek: Could not set Alpine store, continuing anyway');172 165 } 173 166 } -
secuseek/trunk/assets/js/findings-initialization.js
r3373197 r3373724 16 16 const container = document.getElementById('secuseek-findings-container'); 17 17 18 console.log('SecuSeek: DOM ready, loader should be visible');19 18 20 19 // Wait for component registration … … 30 29 31 30 waitForComponent(function() { 32 console.log('SecuSeek: Initializing Alpine.js component manually');33 31 const element = document.getElementById('secuseek-findings-container'); 34 32 if (element && typeof Alpine !== 'undefined') { … … 41 39 // Listen for component ready event for precise timing 42 40 document.addEventListener('secuseek:componentReady', function(event) { 43 console.log('SecuSeek: Component fully ready, hiding loader');44 41 // Hide loader with smooth fade out 45 42 if (loader) { … … 55 52 setTimeout(function() { 56 53 if (loader && loader.style.display !== 'none') { 57 console.log('SecuSeek: Fallback loader hide after timeout');58 54 loader.style.transition = 'opacity 0.3s ease'; 59 55 loader.style.opacity = '0'; -
secuseek/trunk/assets/js/no-findings-inline.js
r3373197 r3373724 19 19 button.disabled = true; 20 20 21 // First clear cache, then start scan 21 22 jQuery.post(sekuseek_manual_scan.ajaxurl, { 22 action: 'secuseek_ start_scan',23 action: 'secuseek_clear_cache', 23 24 nonce: sekuseek_manual_scan.nonce 24 }, function(response) { 25 if (response.success) { 26 alert('Scan started successfully! The page will refresh in a moment...'); 27 setTimeout(function() { 28 window.location.reload(); 29 }, 2000); 30 } else { 31 alert('An error occurred! ' + (response.data && response.data.message ? response.data.message : 'Please try again.')); 25 }, function(cacheResponse, status, xhr) { 26 // Now start the scan 27 startNewScan(); 28 }).fail(function(xhr, status, error) { 29 // Proceed with scan even if cache clear fails 30 startNewScan(); 31 }); 32 33 function startNewScan() { 34 jQuery.post(sekuseek_manual_scan.ajaxurl, { 35 action: 'secuseek_start_scan', 36 nonce: sekuseek_manual_scan.nonce 37 }, function(response, status, xhr) { 38 39 if (response.success) { 40 if (response.data.countdown) { 41 // Clear any existing notices first 42 var oldNotice = document.querySelector('.secuseek-inline-notice'); 43 if (oldNotice) oldNotice.remove(); 44 45 // Show countdown message and reload after 5 seconds 46 var countdownMessage = 'Scan started successfully! Refreshing in <span id="countdown">5</span> seconds...'; 47 renderNoticeInline(countdownMessage, '', false, true); 48 49 // Start countdown 50 let timeLeft = 5; 51 const countdownElement = document.getElementById("countdown"); 52 if (countdownElement) { 53 const countdownInterval = setInterval(function() { 54 timeLeft--; 55 if (countdownElement) { 56 countdownElement.textContent = timeLeft; 57 } 58 if (timeLeft <= 0) { 59 clearInterval(countdownInterval); 60 window.location.reload(); 61 } 62 }, 1000); 63 } else { 64 // Fallback: reload after 5 seconds 65 setTimeout(function() { 66 window.location.reload(); 67 }, 5000); 68 } 69 } else { 70 // Success notice (optional) 71 setTimeout(function() { window.location.reload(); }, 1200); 72 } 73 } else { 74 var apiMessage = (response && response.data && response.data.message) ? response.data.message : (response && response.data) ? response.data : 'Please try again.'; 75 var apiDetails = (response && response.data && response.data.details) ? response.data.details : (response && response.data && response.data.debug) ? response.data.debug : ''; 76 var isNoScansFound = (response && response.data && response.data.error_type === 'no_scans_found'); 77 78 // If data is a string (wp_send_json_error format), use it directly 79 if (typeof response?.data === 'string') { 80 apiMessage = response.data; 81 apiDetails = ''; 82 } 83 84 85 renderNoticeInline(apiMessage, apiDetails, isNoScansFound); 86 // Restore button 87 button.innerHTML = originalText; 88 button.disabled = false; 89 } 90 }).fail(function(xhr, status, error) { 91 renderNoticeInline('Network error. Please check your connection and try again.', '', false); 32 92 // Restore button 33 93 button.innerHTML = originalText; 34 94 button.disabled = false; 35 } 36 }).fail(function(xhr, status, error) { 37 console.error('SecuSeek AJAX Error:', error); 38 alert('Network error. Please check your connection and try again.'); 39 // Restore button 40 button.innerHTML = originalText; 41 button.disabled = false; 42 }); 95 }); 96 } 97 } 98 99 function renderNoticeInline(message, details, isNoScansFound, isSuccess) { 100 var old = document.querySelector('.secuseek-inline-notice'); 101 if (old) old.remove(); 102 103 // Determine colors and icons based on type 104 var color, bgColor, borderColor, icon, title, noticeClass; 105 if (isSuccess) { 106 color = '#00a32a'; 107 bgColor = '#c3e6cb'; 108 borderColor = '#00a32a'; 109 icon = '✅'; 110 title = 'Scan Started Successfully'; 111 noticeClass = 'notice-success'; 112 } else if (isNoScansFound) { 113 color = '#0073aa'; 114 bgColor = '#c5d9ed'; 115 borderColor = '#0073aa'; 116 icon = 'ℹ️'; 117 title = 'No Scans Found'; 118 noticeClass = 'notice-info'; 119 } else { 120 color = '#d63638'; 121 bgColor = '#f5c2c7'; 122 borderColor = '#d63638'; 123 icon = '❌'; 124 title = 'Scan Failed'; 125 noticeClass = 'notice-error'; 126 } 127 128 // Show notice above the scan button 129 var scanButton = document.querySelector('#start-scan') || document.querySelector('button[onclick*="myCustomScanFunction"]'); 130 var targetContainer = scanButton ? scanButton.parentElement : (document.querySelector('#secuseek-findings-container') || document.querySelector('.wrap') || document.body); 131 var div = document.createElement('div'); 132 div.className = 'secuseek-inline-notice notice ' + noticeClass + ' is-dismissible'; 133 div.style.cssText = 'margin:20px auto;padding:12px 15px;border-left:4px solid ' + borderColor + ';background:#fff;border:1px solid ' + bgColor + ';border-radius:4px;box-shadow:0 1px 1px rgba(0,0,0,.04);max-width:600px;width:100%;display:block;'; 134 div.innerHTML = 135 '<div style="display:flex;align-items:flex-start;">' + 136 '<div style="margin-right:8px;font-size:16px;color:' + color + ';">' + icon + '</div>' + 137 '<div style="flex:1;">' + 138 '<strong style="color:' + color + ';font-size:14px;display:block;margin-bottom:4px;">' + title + '</strong>' + 139 '<div style="color:#2c3338;font-size:13px;line-height:1.4;">' + (message || 'Unknown error') + '</div>' + 140 (details ? '<div style="margin-top:8px;padding:8px 12px;background:#f6f7f7;border-radius:3px;font-size:12px;color:#50575e;font-family:monospace;white-space:pre-wrap;border:1px solid #c3c4c7;">' + details + '</div>' : '') + 141 '</div>' + 142 '</div>'; 143 144 // Insert above the scan button 145 if (scanButton) { 146 scanButton.parentNode.insertBefore(div, scanButton); 147 } else { 148 targetContainer.insertBefore(div, targetContainer.firstChild); 149 } 150 151 // Auto-dismiss after 10 seconds for errors and info (not for success) 152 if (!isSuccess) { 153 setTimeout(function() { 154 div.style.transition = 'opacity 0.3s'; 155 div.style.opacity = '0'; 156 setTimeout(function() { if (div.parentNode) div.parentNode.removeChild(div); }, 300); 157 }, 10000); 158 } 43 159 } 44 160 -
secuseek/trunk/assets/js/scan-control.js
r3373197 r3373724 4 4 console.log('Nonce:', secuseek_ajax.nonce); 5 5 6 // Render notice function with better styling and positioning 7 function renderNotice($container, type, message, details) { 8 const existing = $('.secuseek-inline-notice'); 9 if (existing.length) existing.remove(); 10 11 // Determine colors and icons based on type 12 let color, bgColor, borderColor, icon, title; 13 if (type === 'success') { 14 color = '#00a32a'; 15 bgColor = '#c3e6cb'; 16 borderColor = '#00a32a'; 17 icon = '✅'; 18 title = 'Scan Started Successfully'; 19 } else if (type === 'info') { 20 color = '#0073aa'; 21 bgColor = '#c5d9ed'; 22 borderColor = '#0073aa'; 23 icon = 'ℹ️'; 24 title = 'No Scans Found'; 25 } else { 26 color = '#d63638'; 27 bgColor = '#f5c2c7'; 28 borderColor = '#d63638'; 29 icon = '❌'; 30 title = 'Scan Failed'; 31 } 32 33 // Show notice above the scan button 34 const $scanButton = $('#start-scan'); 35 const $targetContainer = $scanButton.length ? $scanButton.parent() : ($('#secuseek-findings-container').length ? $('#secuseek-findings-container') : $('.wrap')); 36 const $notice = $( 37 '<div class="secuseek-inline-notice notice notice-' + (type || 'error') + ' is-dismissible" style="margin:20px auto;padding:12px 15px;border-left:4px solid ' + borderColor + ';background:#fff;border:1px solid ' + bgColor + ';border-radius:4px;box-shadow:0 1px 1px rgba(0,0,0,.04);max-width:600px;width:100%;display:block;">' + 38 '<div style="display:flex;align-items:flex-start;">' + 39 '<div style="margin-right:8px;font-size:16px;color:' + color + ';">' + icon + '</div>' + 40 '<div style="flex:1;">' + 41 '<strong style="color:' + color + ';font-size:14px;display:block;margin-bottom:4px;">' + title + '</strong>' + 42 '<div style="color:#2c3338;font-size:13px;line-height:1.4;">' + $('<div/>').text(message || 'Unknown error').html() + '</div>' + 43 (details ? '<div style="margin-top:8px;padding:8px 12px;background:#f6f7f7;border-radius:3px;font-size:12px;color:#50575e;font-family:monospace;white-space:pre-wrap;border:1px solid #c3c4c7;">' + $('<div/>').text(details).html() + '</div>' : '') + 44 '</div>' + 45 '</div>' + 46 '</div>' 47 ); 48 49 // Insert above the scan button 50 if ($scanButton.length) { 51 $scanButton.before($notice); 52 } else { 53 $targetContainer.prepend($notice); 54 } 55 56 // Auto-dismiss after 10 seconds for errors and info 57 if (type !== 'success') { 58 setTimeout(function() { 59 $notice.fadeOut(300, function() { $(this).remove(); }); 60 }, 10000); 61 } 62 } 63 6 64 $('#start-scan').on('click', function(e) { 7 65 e.preventDefault(); … … 19 77 $message.text('Starting scan...'); 20 78 21 // Start new scan with POST request79 // First clear cache, then start new scan 22 80 $.ajax({ 23 81 url: secuseek_ajax.ajax_url, … … 26 84 dataType: 'json', 27 85 data: { 28 action: 'secuseek_ start_scan',86 action: 'secuseek_clear_cache', 29 87 nonce: secuseek_ajax.nonce 30 88 }, 31 beforeSend: function(xhr) { 32 console.log('Sending AJAX request to:', secuseek_ajax.ajax_url); 33 console.log('Request data:', { 89 success: function(cacheResponse, status, xhr) { 90 // Now start the scan 91 startScan(); 92 }, 93 error: function(xhr, status, error) { 94 // Proceed with scan even if cache clear fails 95 startScan(); 96 } 97 }); 98 99 function startScan() { 100 // Start new scan with POST request 101 $.ajax({ 102 url: secuseek_ajax.ajax_url, 103 type: 'POST', 104 method: 'POST', 105 dataType: 'json', 106 data: { 34 107 action: 'secuseek_start_scan', 35 108 nonce: secuseek_ajax.nonce 36 }); 109 }, 110 beforeSend: function(xhr) { 111 // Request starting 37 112 }, 38 success: function(response) { 39 console.log('Start scan response:', response); 113 success: function(response, status, xhr) { 40 114 41 115 if (response.success && response.data.schedule_id) { 42 $message.text('Scan started successfully. Checking status...'); 43 startStatusCheck(response.data.schedule_id); 116 if (response.data.countdown) { 117 // Clear any existing notices first 118 $('.secuseek-inline-notice').remove(); 119 120 // Show countdown message and reload after 5 seconds 121 $message.html('✅ Scan started successfully! Refreshing in <span id="countdown">5</span> seconds...'); 122 123 // Start countdown 124 let timeLeft = 5; 125 const countdownElement = document.getElementById("countdown"); 126 if (countdownElement) { 127 const countdownInterval = setInterval(function() { 128 timeLeft--; 129 if (countdownElement) { 130 countdownElement.textContent = timeLeft; 131 } 132 if (timeLeft <= 0) { 133 clearInterval(countdownInterval); 134 location.reload(); 135 } 136 }, 1000); 137 } else { 138 // Fallback: reload after 5 seconds 139 setTimeout(function() { 140 location.reload(); 141 }, 5000); 142 } 143 } else { 144 $message.text('Scan started successfully. Checking status...'); 145 startStatusCheck(response.data.schedule_id); 146 } 44 147 } else { 45 $message.text('Failed to start scan: ' + (response.data?.message || 'Unknown error')); 148 // Handle wp_send_json_error response format 149 let apiMessage = response?.data?.message || response?.data || 'Unknown error'; 150 let apiDetails = response?.data?.details || response?.data?.debug || undefined; 151 let isNoScansFound = response?.data?.error_type === 'no_scans_found'; 152 153 // If data is a string (wp_send_json_error format), use it directly 154 if (typeof response?.data === 'string') { 155 apiMessage = response.data; 156 apiDetails = undefined; 157 } 158 159 160 if (isNoScansFound) { 161 // Show as info notice for no scans found 162 renderNotice($status, 'info', apiMessage, apiDetails); 163 } else { 164 renderNotice($status, 'error', apiMessage, apiDetails); 165 } 166 $message.text(''); 46 167 $button.prop('disabled', false); 47 168 } 48 },169 }, 49 170 error: function(xhr, status, error) { 50 console.error('Start scan error:', error); 51 console.error('Status:', status); 52 console.error('Response:', xhr.responseText); 53 $message.text('Error starting scan: ' + error); 54 $button.prop('disabled', false); 55 } 56 }); 171 renderNotice($status, 'error', 'Network/Server error while starting scan', error); 172 $message.text(''); 173 $button.prop('disabled', false); 174 } 175 }); 176 } 57 177 }); 58 178 … … 77 197 }, 78 198 beforeSend: function(xhr) { 79 console.log('Sending status check request'); 80 }, 81 success: function(response) { 82 console.log('Status check response:', response); 199 // Status check starting 200 }, 201 success: function(response, status, xhr) { 83 202 84 203 if (response.status?.isCompleted) { … … 102 221 }, 103 222 error: function(xhr, status, error) { 104 console.error('Status check error:', error);105 console.error('Status:', status);106 console.error('Response:', xhr.responseText);107 223 $message.text('Error checking status: ' + error); 108 224 $button.prop('disabled', false); -
secuseek/trunk/includes/auth-handler.php
r3373197 r3373724 517 517 error_log('secuseek_run_first_scan_if_needed: POST failed: ' . $scan_result['error']); 518 518 } 519 // Store error message for display on page refresh 520 set_transient('secuseek_scan_error_' . get_current_user_id(), $scan_result['error'], 300); // 5 minutes 519 521 } 520 522 } -
secuseek/trunk/includes/control.php
r3373197 r3373724 299 299 } 300 300 301 // Start a new scan 301 // Create a new scan (POST request) 302 $create_result = secuseek_create_schedule_scan(); 303 if (isset($create_result['error'])) { 304 return array('error' => $create_result['error']); 305 } 306 307 // Get the schedule information (GET request) 302 308 $result = secuseek_fetch_schedule_scan(); 303 309 if (isset($result['error'])) { -
secuseek/trunk/includes/fetcher.php
r3373197 r3373724 40 40 */ 41 41 function secuseek_create_schedule_scan() { 42 if (defined('WP_DEBUG') && WP_DEBUG) {43 // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log44 error_log('secuseek_create_schedule_scan: called');45 }46 42 $headers = secuseek_get_api_headers(true); 47 43 if (!$headers) { 48 if (defined('WP_DEBUG') && WP_DEBUG) {49 // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log50 error_log('secuseek_create_schedule_scan: API key not found');51 }52 44 return array('error' => 'API key not found', 'debug' => 'API key not found'); 53 45 } … … 56 48 $site_url = preg_replace('#^https?://#', '', $site_url); 57 49 $body = array('url' => $site_url, 'scanFrequency' => 1); 58 if (defined('WP_DEBUG') && WP_DEBUG) {59 // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log60 error_log('API REQUEST [create_schedule_scan] URL: ' . $api_url);61 }62 if (defined('WP_DEBUG') && WP_DEBUG) {63 // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log,WordPress.PHP.DevelopmentFunctions.error_log_print_r64 error_log('API REQUEST [create_schedule_scan] HEADERS: ' . print_r($headers, true));65 }66 if (defined('WP_DEBUG') && WP_DEBUG) {67 // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log68 error_log('API REQUEST [create_schedule_scan] BODY: ' . json_encode($body));69 }70 50 $result = Secuseek_Fetcher_Service::post($api_url, json_encode($body), $headers); 71 if (!$result['success']) { 72 if (defined('WP_DEBUG') && WP_DEBUG) { 73 // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log 74 error_log('secuseek_create_schedule_scan: Secuseek_Fetcher_Service::post error: ' . $result['error']); 75 } 51 // Handle network/connection errors only (not HTTP status errors like 400, 403) 52 if (!$result['success'] && $result['http_code'] === 0) { 76 53 return array('error' => $result['error'], 'debug' => $result['error']); 77 54 } 78 55 $status_code = $result['http_code']; 79 56 $body_resp = $result['response']; 80 if (defined('WP_DEBUG') && WP_DEBUG) {81 // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log,WordPress.PHP.DevelopmentFunctions.error_log_print_r82 error_log('API RESPONSE [create_schedule_scan]: ' . print_r($result, true));83 }84 if (defined('WP_DEBUG') && WP_DEBUG) {85 // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log86 error_log('API RESPONSE BODY [create_schedule_scan]: ' . $body_resp);87 }88 57 if ($status_code === 200) { 89 58 $data = json_decode($body_resp, true); 59 90 60 if (is_string($data)) { 91 61 // API returns id string directly 92 62 secuseek_save_user_job_schedule_id($data); 63 // Clear old scan data and save new scan ID 64 secuseek_delete_last_scan_id(); 65 secuseek_save_last_scan_id($data); 93 66 // Record scan creation timestamp for timing-aware deletion detection 94 67 if (function_exists('secuseek_record_scan_creation')) { … … 99 72 // If the API returns as an object 100 73 secuseek_save_user_job_schedule_id($data['id']); 74 // Clear old scan data and save new scan ID 75 secuseek_delete_last_scan_id(); 76 secuseek_save_last_scan_id($data['id']); 101 77 // Record scan creation timestamp for timing-aware deletion detection 102 78 if (function_exists('secuseek_record_scan_creation')) { … … 104 80 } 105 81 return array('success' => true, 'id' => $data['id'], 'debug' => 'Scan created successfully'); 106 } 107 return array('error' => 'No id returned from API', 'debug' => $body_resp); 82 } elseif (isset($data['scanId'])) { 83 // Alternative field name 84 secuseek_save_user_job_schedule_id($data['scanId']); 85 secuseek_delete_last_scan_id(); 86 secuseek_save_last_scan_id($data['scanId']); 87 if (function_exists('secuseek_record_scan_creation')) { 88 secuseek_record_scan_creation(); 89 } 90 return array('success' => true, 'id' => $data['scanId'], 'debug' => 'Scan created successfully with scanId field'); 91 } elseif (isset($data['result']['id'])) { 92 // Nested result object 93 secuseek_save_user_job_schedule_id($data['result']['id']); 94 secuseek_delete_last_scan_id(); 95 secuseek_save_last_scan_id($data['result']['id']); 96 if (function_exists('secuseek_record_scan_creation')) { 97 secuseek_record_scan_creation(); 98 } 99 return array('success' => true, 'id' => $data['result']['id'], 'debug' => 'Scan created successfully with nested result'); 100 } 101 // Enhanced debug info for troubleshooting 102 $debug_info = array( 103 'status_code' => $status_code, 104 'response_body' => $body_resp, 105 'decoded_data' => $data, 106 'data_type' => gettype($data), 107 'available_keys' => is_array($data) ? array_keys($data) : 'not_array' 108 ); 109 return array( 110 'error' => 'No id returned from API', 111 'debug' => json_encode($debug_info), 112 'debug_raw' => $body_resp 113 ); 114 } elseif ($status_code === 400) { 115 // Handle 400 Bad Request - return error immediately 116 $error_data = json_decode($body_resp, true); 117 if ($error_data && isset($error_data['errors'])) { 118 $error_messages = array(); 119 foreach ($error_data['errors'] as $field => $messages) { 120 if (is_array($messages)) { 121 foreach ($messages as $message) { 122 if ($field === 'url') { 123 if (strpos($message, 'User already has a scan') !== false) { 124 $error_messages[] = 'This website already has an active scan. Please wait for it to complete or try again later.'; 125 } else { 126 $error_messages[] = 'Scan Limit: ' . $message; 127 } 128 } elseif ($field === '') { 129 if (strpos($message, 'Maximum user domain count') !== false) { 130 $error_messages[] = 'You have reached the maximum number of websites you can scan. Please delete some existing scans or contact support to increase your limit.'; 131 } else { 132 $error_messages[] = 'Domain Limit: ' . $message; 133 } 134 } else { 135 $error_messages[] = ucfirst($field) . ': ' . $message; 136 } 137 } 138 } else { 139 $error_messages[] = $messages; 140 } 141 } 142 return array('error' => implode(' | ', $error_messages), 'debug' => $body_resp); 143 } 144 return array('error' => $error_data['message'] ?? 'Bad request', 'debug' => $body_resp); 108 145 } elseif ($status_code === 403) { 109 146 return array('error' => 'Invalid API key or access denied', 'debug' => '403 Forbidden'); … … 116 153 */ 117 154 function secuseek_fetch_schedule_scan($user_job_schedule_id = null) { 118 if (defined('WP_DEBUG') && WP_DEBUG) {119 // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log120 error_log('secuseek_fetch_schedule_scan: called');121 }122 155 $headers = secuseek_get_api_headers(); 123 156 if (!$headers) { 124 if (defined('WP_DEBUG') && WP_DEBUG) {125 // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log126 error_log('secuseek_fetch_schedule_scan: API key not found');127 }128 157 return array('error' => 'API key not found', 'debug' => 'API key not found'); 129 158 } … … 133 162 } 134 163 if (!$user_job_schedule_id) { 135 return array('error' => 'No UserJobScheduleId available', 'debug' => 'No UserJobScheduleId available');164 return array('error' => 'No scans found. It appears scans have been deleted or no scan has been started yet. Please start a new scan.', 'debug' => 'No UserJobScheduleId available'); 136 165 } 137 166 $api_url = secuseek_get_api_base_url() . '/api/v1/external/schedule-scan/' . urlencode($user_job_schedule_id); 138 if (defined('WP_DEBUG') && WP_DEBUG) {139 // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log140 error_log('API REQUEST [fetch_schedule_scan] URL: ' . $api_url);141 }142 if (defined('WP_DEBUG') && WP_DEBUG) {143 // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log,WordPress.PHP.DevelopmentFunctions.error_log_print_r144 error_log('API REQUEST [fetch_schedule_scan] HEADERS: ' . print_r($headers, true));145 }146 167 $result = Secuseek_Fetcher_Service::get($api_url, $headers); 147 168 if (!$result['success']) { 148 if (defined('WP_DEBUG') && WP_DEBUG) {149 // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log150 error_log('secuseek_fetch_schedule_scan: Secuseek_Fetcher_Service::get error: ' . $result['error']);151 }152 169 return array('error' => $result['error'], 'debug' => $result['error']); 153 170 } … … 202 219 203 220 /** 221 * Get all schedule scan IDs for the user (GetScheduleScanIds endpoint) 222 */ 223 function secuseek_fetch_schedule_scan_ids() { 224 if (defined('WP_DEBUG') && WP_DEBUG) { 225 // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log 226 error_log('secuseek_fetch_schedule_scan_ids: called'); 227 } 228 $headers = secuseek_get_api_headers(); 229 if (!$headers) { 230 if (defined('WP_DEBUG') && WP_DEBUG) { 231 // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log 232 error_log('secuseek_fetch_schedule_scan_ids: API key not found'); 233 } 234 return array('error' => 'API key not found', 'debug' => 'API key not found'); 235 } 236 $api_url = secuseek_get_api_base_url() . '/api/v1/external/schedule-scan-ids'; 237 if (defined('WP_DEBUG') && WP_DEBUG) { 238 // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log 239 error_log('API REQUEST [fetch_schedule_scan_ids] URL: ' . $api_url); 240 } 241 if (defined('WP_DEBUG') && WP_DEBUG) { 242 // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log,WordPress.PHP.DevelopmentFunctions.error_log_print_r 243 error_log('API REQUEST [fetch_schedule_scan_ids] HEADERS: ' . print_r($headers, true)); 244 } 245 $result = Secuseek_Fetcher_Service::get($api_url, $headers); 246 if (!$result['success']) { 247 if (defined('WP_DEBUG') && WP_DEBUG) { 248 // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log 249 error_log('secuseek_fetch_schedule_scan_ids: Secuseek_Fetcher_Service::get error: ' . $result['error']); 250 } 251 return array('error' => $result['error'], 'debug' => $result['error']); 252 } 253 $status_code = $result['http_code']; 254 $body_resp = $result['response']; 255 if (defined('WP_DEBUG') && WP_DEBUG) { 256 // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log,WordPress.PHP.DevelopmentFunctions.error_log_print_r 257 error_log('API RESPONSE [fetch_schedule_scan_ids]: ' . print_r($result, true)); 258 } 259 if (defined('WP_DEBUG') && WP_DEBUG) { 260 // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log 261 error_log('API RESPONSE BODY [fetch_schedule_scan_ids]: ' . $body_resp); 262 } 263 if ($status_code === 200) { 264 $data = json_decode($body_resp, true); 265 if (defined('WP_DEBUG') && WP_DEBUG) { 266 // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log 267 error_log('secuseek_fetch_schedule_scan_ids: data=' . substr($body_resp,0,300)); 268 } 269 if (is_array($data)) { 270 return $data + array('debug' => 'Schedule scan IDs fetched successfully'); 271 } 272 } elseif ($status_code === 403) { 273 return array('error' => 'Invalid API key or access denied', 'debug' => '403 Forbidden'); 274 } 275 return array('error' => 'Failed to get schedule scan IDs. Status: ' . $status_code, 'debug' => 'Status: ' . $status_code); 276 } 277 278 /** 204 279 * Get scheduled scan issues (GetScheduleScanIssues endpoint) 205 280 */ … … 319 394 add_action('wp_ajax_secuseek_get_scan_status', 'secuseek_ajax_get_scan_status'); 320 395 add_action('wp_ajax_secuseek_get_scan_results', 'secuseek_ajax_get_scan_results'); 396 add_action('wp_ajax_secuseek_clear_cache', 'secuseek_ajax_clear_cache'); 397 398 /** 399 * AJAX endpoint for clearing cache and old scan data 400 */ 401 function secuseek_ajax_clear_cache() { 402 // Capability check 403 if (!current_user_can('secuseek_manage_scans') && !current_user_can('manage_options')) { 404 wp_send_json_error('Insufficient permissions', 403); 405 wp_die(); 406 } 407 408 // Nonce verification 409 if (!check_ajax_referer('secuseek_ajax_nonce', 'nonce', false)) { 410 wp_send_json_error('Security check failed', 403); 411 wp_die(); 412 } 413 414 // Clear all scan-related data 415 secuseek_delete_last_scan_id(); 416 delete_option('SecuseekUserJobScheduleId'); 417 418 // Clear any transients 419 $user_id = get_current_user_id(); 420 delete_transient('secuseek_scan_rate_limit_' . $user_id); 421 delete_transient('secuseek_scan_error_' . $user_id); 422 423 wp_send_json_success('Cache cleared successfully'); 424 wp_die(); 425 } 321 426 322 427 /** … … 327 432 if (!current_user_can('secuseek_manage_scans') && !current_user_can('manage_options')) { 328 433 wp_send_json_error('Insufficient permissions', 403); 434 wp_die(); 329 435 } 330 436 … … 332 438 if (!check_ajax_referer('secuseek_ajax_nonce', 'nonce', false)) { 333 439 wp_send_json_error('Security check failed', 403); 440 wp_die(); 334 441 } 335 442 … … 339 446 if (get_transient($transient_key)) { 340 447 wp_send_json_error('Rate limit exceeded. Please wait before trying again.', 429); 448 wp_die(); 341 449 } 342 450 set_transient($transient_key, true, 60); // 1 minute wait … … 345 453 $create_result = secuseek_create_schedule_scan(); 346 454 if (isset($create_result['error'])) { 347 wp_send_json_error(array( 348 'message' => esc_html($create_result['error']) 349 )); 350 return; 351 455 // Stop immediately if create_scan fails - show the actual error 456 wp_send_json_error(esc_html($create_result['error'])); 457 wp_die(); 458 } 459 460 // Only continue if create_scan was successful 461 if (!isset($create_result['id'])) { 462 // Debug: log the actual response 463 wp_send_json_error('Failed to create scan - no ID returned. Debug: ' . (isset($create_result['debug']) ? $create_result['debug'] : 'No debug info')); 464 wp_die(); 352 465 } 353 466 … … 357 470 } 358 471 359 // Then get the schedule information (GET request) 360 $result = secuseek_fetch_schedule_scan(); 361 362 if (isset($result['error'])) { 363 wp_send_json_error(array( 364 'message' => esc_html($result['error']) 365 )); 366 return; 367 } 368 369 if (isset($result['id'])) { 472 // Use the new scan ID from creation 473 $new_scan_id = $create_result['id']; 474 // Save the new scan ID 475 secuseek_save_last_scan_id($new_scan_id); 476 370 477 wp_send_json_success(array( 371 478 'message' => 'Scan started successfully', 372 'schedule_id' => sanitize_text_field($result['id']) 479 'schedule_id' => sanitize_text_field($new_scan_id), 480 'countdown' => true 373 481 )); 374 } else { 375 wp_send_json_error(array( 376 'message' => 'No schedule ID received' 377 )); 378 } 482 wp_die(); 379 483 } 380 484 … … 386 490 if (!current_user_can('secuseek_view_results') && !current_user_can('manage_options')) { 387 491 wp_send_json_error('Insufficient permissions', 403); 492 wp_die(); 388 493 } 389 494 … … 391 496 if (!check_ajax_referer('secuseek_ajax_nonce', 'nonce', false)) { 392 497 wp_send_json_error('Security check failed', 403); 498 wp_die(); 393 499 } 394 500 … … 397 503 if (!$schedule_id) { 398 504 wp_send_json_error('No schedule ID available'); 399 return;505 wp_die(); 400 506 } 401 507 … … 411 517 if (!current_user_can('secuseek_view_results') && !current_user_can('manage_options')) { 412 518 wp_send_json_error('Insufficient permissions', 403); 519 wp_die(); 413 520 } 414 521 … … 416 523 if (!check_ajax_referer('secuseek_ajax_nonce', 'nonce', false)) { 417 524 wp_send_json_error('Security check failed', 403); 525 wp_die(); 418 526 } 419 527 … … 422 530 if (!$schedule_id) { 423 531 wp_send_json_error('No schedule ID available'); 424 return;532 wp_die(); 425 533 } 426 534 -
secuseek/trunk/readme.txt
r3373197 r3373724 1 === SecuSeek ===1 === SecuSeek - Web Security Scanner & Vulnerability Assessment === 2 2 Contributors: secuseek 3 Tags: security, scan, findings, vulnerability 3 Tags: security, scan, findings, vulnerability, web security, threat detection, vulnerability assessment, security analysis 4 4 Requires at least: 5.0 5 5 Tested up to: 6.8 6 Stable tag: 1.0 6 Stable tag: 1.0.1 7 7 License: GPLv2 or later 8 8 License URI: https://www.gnu.org/licenses/gpl-2.0.html 9 9 10 View and manage security scan results from SecuSeek in your WordPress dashboard.10 Professional web security scanning and vulnerability assessment plugin. Comprehensive security analysis, real-time threat detection, and detailed security reports for WordPress websites. 11 11 12 12 == Description == 13 13 14 **SecuSeek ** brings comprehensive security scanning and vulnerability management directly into your WordPress admin dashboard. Monitor, analyze, and manage security findings from multiple scans in a centralized, user-friendly interface.14 **SecuSeek - Web Security Scanner & Vulnerability Assessment** brings comprehensive security scanning and vulnerability management directly into your WordPress admin dashboard. Monitor, analyze, and manage security findings from multiple scans in a centralized, user-friendly interface. 15 15 16 16 = 🔍 Comprehensive Security Scanning = … … 55 55 * **Anyone using SecuSeek** - Seamlessly integrate scan results into WordPress admin 56 56 57 = 🔗 Privacy &External Services =57 = 🔗 External Services = 58 58 59 This plugin connects to SecuSeek's external API to fetch and display security scan results. For complete transparency: 60 61 * **Service**: SecuSeek API (https://api.secuseek.com) 62 * **Data Sent**: Your API key, site URL, and scan configuration 63 * **Data Received**: Security findings, vulnerability details, and scan status 64 * **When**: On schedule or manual refresh 65 66 Please review our [Terms of Service](https://secuseek.com/terms-and-conditions/) and [Privacy Policy](https://secuseek.com/privacy-policy/) for complete information. 59 This plugin connects to SecuSeek's external API to fetch and display security scan results - for complete details about data handling, privacy practices, and service transparency, visit [https://secuseek.com/](https://secuseek.com/). 67 60 68 61 = 💻 Modern Technology Stack = … … 76 69 All source code is human-readable and available in the plugin directory. 77 70 78 == External services ==79 This plugin connects to SecuSeek's external API to create scheduled scans, fetch scan status and results, validate API keys, and clean up schedules during uninstall.80 71 81 - **Service**: SecuSeek API (Base URL: `https://api.secuseek.com`) 82 - **What it’s used for**: 83 - Start a scan and create a schedule: `POST /api/v1/external/schedule-scan` 84 - Get schedule info: `GET /api/v1/external/schedule-scan/{UserJobScheduleId}` 85 - Get scan issues: `GET /api/v1/external/issues/{ScheduleId}` 86 - Validate API key: `GET /api/v1/external/validate` 87 - Remove schedule on uninstall: `DELETE /api/v1/external/schedule-scan/{UserJobScheduleId}` 88 - **What data is sent and when**: 89 - When starting a scan: site URL (domain only) and scan frequency in request body. 90 - On authenticated API calls: your API key in the `x-api-key` header. 91 - When polling status and fetching results: the schedule/scan identifier in the request path. 92 - During uninstall cleanup: the schedule identifier; WordPress may include a standard `User-Agent` header (e.g., `WordPress/{version}; {site_url}`). 93 - **Terms and Privacy**: 72 == Terms and Privacy == 94 73 - Terms of Service: `https://secuseek.com/terms-and-conditions/` 95 74 - Privacy Policy: `https://secuseek.com/privacy-policy/` 96 97 == Used technologies ==98 - Alpine.js 3.13.10 — Docs: `https://alpinejs.dev` — Source: `https://github.com/alpinejs/alpine` — Minified CDN: `https://cdn.jsdelivr.net/npm/alpinejs@3.13.10/dist/cdn.min.js`99 - Font Awesome Free 6.5.0 — Site: `https://fontawesome.com` — Source: `https://github.com/FortAwesome/Font-Awesome` — CSS CDN: `https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@6.5.0/css/fontawesome.min.css`100 - WordPress HTTP API (remote requests) — Docs: `https://developer.wordpress.org/plugins/http-api/`101 - WordPress Cron API (scheduling) — Docs: `https://developer.wordpress.org/plugins/cron/`102 - WordPress AJAX (admin-ajax) — Docs: `https://developer.wordpress.org/plugins/javascript/ajax/`103 104 == Source and Human-Readable Code ==105 This plugin ships with human-readable source code. All custom JavaScript and CSS are provided in unminified form under `assets/js/` and `assets/css/`.106 107 The plugin also bundles a small number of third-party, minified assets. Their human-readable sources are publicly available here:108 109 - `assets/js/alpinejs.min.js` — Source: `https://github.com/alpinejs/alpine` (Docs/Homepage: `https://alpinejs.dev`)110 - `assets/css/fontawesome.min.css` — Source: `https://github.com/FortAwesome/Font-Awesome`111 112 Build tools are not required to work on this plugin. If build tooling is added in the future, instructions will be documented here.113 114 === Direct downloads (exact versions used) ===115 - Alpine.js 3.13.10 (minified): `https://cdn.jsdelivr.net/npm/alpinejs@3.13.10/dist/cdn.min.js`116 - Font Awesome Free 6.5.0 (fontawesome.min.css): `https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@6.5.0/css/fontawesome.min.css`117 75 118 76 == Installation == … … 130 88 == Screenshots == 131 89 1. Findings dashboard 132 2. API key entry screen90 2. Findings dashboard - 2 133 91 134 92 == Changelog == 93 = 1.0.1 = 94 * Improved filter interface with simplified chip display 95 * Enhanced user experience with better filter management 96 * Fixed filter removal functionality 97 * Updated plugin branding and SEO optimization 98 135 99 = 1.0 = 136 100 * Initial release. 137 101 138 102 == Upgrade Notice == 103 = 1.0.1 = 104 Improved filter interface and enhanced user experience. 105 139 106 = 1.0 = 140 107 First public release. -
secuseek/trunk/secuseek.php
r3373197 r3373724 1 1 <?php 2 2 /** 3 * Plugin Name: SecuSeek 4 * Description: Admin panel integration for SecuSeek's external scan services.5 * Version: 1.0 3 * Plugin Name: SecuSeek - Web Security Scanner & Vulnerability Assessment 4 * Description: Professional web security scanning and vulnerability assessment plugin. Comprehensive security analysis, real-time threat detection, and detailed security reports for WordPress websites. 5 * Version: 1.0.1 6 6 * Author: SecuSeek 7 7 * License: GPL2 -
secuseek/trunk/templates/findings/findings-filters.php
r3373197 r3373724 11 11 <div class="input-area compact" @click="filtersOpen.category = !filtersOpen.category"> 12 12 <span class="input-label" :class="{'floating': filters.category.length > 0}">Category</span> 13 <template x-for="item in filters.category" :key="item"> 14 <span class="chip"> 15 <span x-text="item"></span> 16 <button @click.stop="filters.category = filters.category.filter(function(x) { return x !== item; })">×</button> 17 </span> 18 </template> 13 <span x-show="filters.category.length > 0" class="filter-chips"> 14 <span x-text="filters.category.join(', ')" style="color:#22ad5a;font-size:13px;margin-right:8px;"></span> 15 <button @click.stop="filters.category = []" style="background:none;border:none;color:#22ad5a;cursor:pointer;font-size:13px;font-weight:bold;">×</button> 16 </span> 19 17 <span class="arrow" style="margin-left:auto;"> 20 18 <svg width="12" height="12" viewBox="0 0 20 20" fill="none"><path d="M5 8l5 5 5-5" stroke="#374151" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg> … … 34 32 <div class="input-area compact" @click="filtersOpen.severity = !filtersOpen.severity"> 35 33 <span class="input-label" :class="{'floating': filters.severity.length > 0}">Severity</span> 36 <template x-for="item in filters.severity" :key="item"> 37 <span class="chip"> 38 <span x-text="item"></span> 39 <button @click.stop="filters.severity = filters.severity.filter(function(x) { return x !== item; })">×</button> 40 </span> 41 </template> 34 <span x-show="filters.severity.length > 0" class="filter-chips"> 35 <span x-text="filters.severity.join(', ')" style="color:#22ad5a;font-size:13px;margin-right:8px;"></span> 36 <button @click.stop="filters.severity = []" style="background:none;border:none;color:#22ad5a;cursor:pointer;font-size:13px;font-weight:bold;">×</button> 37 </span> 42 38 <span class="arrow" style="margin-left:auto;"> 43 39 <svg width="12" height="12" viewBox="0 0 20 20" fill="none"><path d="M5 8l5 5 5-5" stroke="#374151" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg> … … 57 53 <div class="input-area compact" @click="filtersOpen.confidence = !filtersOpen.confidence"> 58 54 <span class="input-label" :class="{'floating': filters.confidence.length > 0}">Confidence</span> 59 <template x-for="item in filters.confidence" :key="item"> 60 <span class="chip"> 61 <span x-text="item"></span> 62 <button @click.stop="filters.confidence = filters.confidence.filter(function(x) { return x !== item; })">×</button> 63 </span> 64 </template> 55 <span x-show="filters.confidence.length > 0" class="filter-chips"> 56 <span x-text="filters.confidence.join(', ')" style="color:#22ad5a;font-size:13px;margin-right:8px;"></span> 57 <button @click.stop="filters.confidence = []" style="background:none;border:none;color:#22ad5a;cursor:pointer;font-size:13px;font-weight:bold;">×</button> 58 </span> 65 59 <span class="arrow" style="margin-left:auto;"> 66 60 <svg width="12" height="12" viewBox="0 0 20 20" fill="none"><path d="M5 8l5 5 5-5" stroke="#374151" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg> … … 80 74 <div class="input-area compact" @click="filtersOpen.status = !filtersOpen.status"> 81 75 <span class="input-label" :class="{'floating': filters.status.length > 0}">Status</span> 82 <template x-for="item in filters.status" :key="item"> 83 <span class="chip"> 84 <span x-text="item"></span> 85 <button @click.stop="filters.status = filters.status.filter(function(x) { return x !== item; })">×</button> 86 </span> 87 </template> 76 <span x-show="filters.status.length > 0" class="filter-chips"> 77 <span x-text="filters.status.join(', ')" style="color:#22ad5a;font-size:13px;margin-right:8px;"></span> 78 <button @click.stop="filters.status = []" style="background:none;border:none;color:#22ad5a;cursor:pointer;font-size:13px;font-weight:bold;">×</button> 79 </span> 88 80 <span class="arrow" style="margin-left:auto;"> 89 81 <svg width="12" height="12" viewBox="0 0 20 20" fill="none"><path d="M5 8l5 5 5-5" stroke="#374151" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg> -
secuseek/trunk/templates/login-form.php
r3373197 r3373724 5 5 <?php 6 6 $error_message = ''; // Assign an empty value for the error message. 7 8 // Check for scan errors from automatic scan start 9 $scan_error = get_transient('secuseek_scan_error_' . get_current_user_id()); 10 if ($scan_error) { 11 delete_transient('secuseek_scan_error_' . get_current_user_id()); 12 $error_message = $scan_error; 13 } 7 14 8 15 if (!empty($_POST['secuseek_scan_api_key'])) { … … 28 35 } 29 36 // Show info message to the user 30 echo '<div class="notice notice-success"><p>✅ API Key saved successfully! Refreshing...</p></div>'; 31 echo '<meta http-equiv="refresh" content="2">'; 37 echo '<div class="notice notice-success"><p>✅ API Key saved successfully! Refreshing in <span id="countdown">5</span> seconds...</p></div>'; 38 echo '<meta http-equiv="refresh" content="5">'; 39 // Add countdown script inline for immediate execution 40 wp_enqueue_script('secuseek-login-countdown', SECUSEEK_PLUGIN_URL . 'assets/js/login-countdown.js', array(), false, true); 32 41 } 33 42 }
Note: See TracChangeset
for help on using the changeset viewer.