Plugin Directory

Changeset 3383675


Ignore:
Timestamp:
10/24/2025 12:21:31 AM (5 months ago)
Author:
nilbug
Message:

User-friendly focus: Settings page, block cronjobs, block emails from SMTP or send email, real-time tracking frontend interface, load time per page, plugin space usage improved etc.

Location:
sitepulse
Files:
66 added
16 edited

Legend:

Unmodified
Added
Removed
  • sitepulse/trunk/assets/css/backend.css

    r3374045 r3383675  
    1414        flex-direction: column;
    1515    }
     16}
     17
     18body.sitepulse_css_class .notice,
     19body.sitepulse_css_class .notice * {
     20    color: white;
     21    background-color: #283746;
    1622}
    1723
     
    248254#pulseLine { transition:stroke 0.3s; }
    249255body.dark-mode #pulseLine { stroke:#ffb347; }
     256
     257
     258/* ==========================================
     259   SETTINGS PAGE STYLES
     260   Following SitePulse design patterns
     261   ========================================== */
     262
     263.sitepulse_css_class select {
     264    background-color: var(--sp-card-2);
     265    color: var(--sp-text);
     266    border: 1px solid rgba(255,255,255,0.1);
     267    border-radius: 6px;
     268    padding: 8px 12px;
     269    min-width: 200px;
     270    cursor: pointer;
     271    transition: all 0.3s ease;
     272}
     273
     274.sitepulse_css_class select:focus {
     275    outline: none;
     276    border-color: var(--accent-1);
     277    box-shadow: 0 0 0 2px rgba(0,194,168,0.2);
     278}
     279
     280.sitepulse_css_class select option {
     281    background-color: var(--sp-card);
     282    color: var(--sp-text);
     283    padding: 10px;
     284}
     285
     286
     287/* Email blocking mode row styling */
     288.sitepulse_css_class #email_blocking_mode_row {
     289    background: rgba(0,194,168,0.05);
     290    border-left: 3px solid var(--accent-1);
     291    border-radius: 0 8px 8px 0;
     292    margin-left: 20px;
     293    transition: all 0.3s ease;
     294}
     295
     296.sitepulse_css_class #email_blocking_mode_row.hidden {
     297    opacity: 0;
     298    max-height: 0;
     299    overflow: hidden;
     300    margin: 0 0 0 20px;
     301    padding: 0;
     302}
     303
     304/* Submit button enhancement */
     305.sitepulse_css_class .button-primary {
     306    background: linear-gradient(135deg, var(--accent-1), var(--accent-2)) !important;
     307    border: none !important;
     308    color: white !important;
     309    padding: 10px 20px !important;
     310    border-radius: 6px !important;
     311    font-weight: 500 !important;
     312    text-shadow: none !important;
     313    box-shadow: 0 4px 15px rgba(0,194,168,0.3) !important;
     314    transition: all 0.3s ease !important;
     315}
     316
     317.sitepulse_css_class .button-primary:hover {
     318    background: linear-gradient(135deg, var(--accent-2), var(--accent-1)) !important;
     319    box-shadow: 0 6px 20px rgba(0,194,168,0.4) !important;
     320    transform: translateY(-1px) !important;
     321}
  • sitepulse/trunk/assets/css/frontend.css

    r3374045 r3383675  
     1/* Admin real time tracking button styles */
     2.sitepulse-realtime-tracking {
     3    display: flex;
     4    justify-content: center;
     5}
     6
     7.sitepulse-realtime-tracking .sitepulse-btn {
     8    border-radius: 5px !important;
     9    padding: 2px 10px !important;
     10    margin: 5px 0 !important;
     11}
     12
     13/* SitePulse Completion Info Styles - Matching Modal Theme */
     14#sitepulse-completion-info {
     15    background: #f8f9fa;
     16    border: 1px solid #e9ecef;
     17    border-radius: 8px;
     18    padding: 20px;
     19    margin: 20px 0;
     20    color: #555;
     21    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
     22    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
     23    max-height: 400px;
     24    overflow-y: auto;
     25    /* border-left: 4px solid #cc1111; */
     26}
     27
     28#sitepulse-completion-info::-webkit-scrollbar {
     29    width: 6px;
     30}
     31
     32#sitepulse-completion-info::-webkit-scrollbar-track {
     33    background: #f1f1f1;
     34    border-radius: 3px;
     35}
     36
     37.sitepulse-event {
     38    background: #ffffff;
     39    border: 1px solid #e9ecef;
     40    border-radius: 8px;
     41    padding: 15px;
     42    margin-bottom: 15px;
     43    position: relative;
     44    transition: all 0.2s ease;
     45    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
     46}
     47
     48.sitepulse-event:hover {
     49    border-color: #cc1111;
     50    transform: translateY(-1px);
     51    box-shadow: 0 3px 8px rgba(204, 17, 17, 0.15);
     52}
     53
     54.sitepulse-event:last-child {
     55    margin-bottom: 0;
     56}
     57
     58.sitepulse-event::before {
     59    content: '';
     60    position: absolute;
     61    top: 0;
     62    left: 0;
     63    width: 3px;
     64    height: 100%;
     65    background: #cc1111;
     66    border-radius: 3px 0 0 3px;
     67}
     68
     69.sitepulse-event strong {
     70    color: #cc1111;
     71    font-weight: 600;
     72    margin-right: 8px;
     73    min-width: 90px;
     74    display: inline-block;
     75    font-size: 13px;
     76}
     77
     78.sitepulse-event br {
     79    margin-bottom: 6px;
     80}
     81
     82/* Value styling for readability */
     83.sitepulse-event {
     84    line-height: 1.5;
     85    font-size: 14px;
     86    color: #555;
     87}
     88
     89/* Responsive design */
     90@media (max-width: 600px) {
     91    #sitepulse-completion-info {
     92        padding: 15px;
     93        margin: 15px 0;
     94        font-size: 13px;
     95    }
     96   
     97    .sitepulse-event {
     98        padding: 12px;
     99        margin-bottom: 12px;
     100    }
     101   
     102    .sitepulse-event strong {
     103        display: block;
     104        margin-bottom: 3px;
     105        min-width: auto;
     106        font-size: 12px;
     107    }
     108   
     109    .sitepulse-event br {
     110        display: none;
     111    }
     112}
     113
     114/* Animation for new events */
     115@keyframes slideInFromTop {
     116    from {
     117        opacity: 0;
     118        transform: translateY(-10px);
     119    }
     120    to {
     121        opacity: 1;
     122        transform: translateY(0);
     123    }
     124}
     125
     126.sitepulse-event {
     127    animation: slideInFromTop 0.3s ease-out;
     128}
     129
     130/* Loading state styling */
     131#sitepulse-completion-info.loading {
     132    position: relative;
     133    overflow: hidden;
     134}
     135
     136#sitepulse-completion-info.loading::after {
     137    content: '';
     138    position: absolute;
     139    top: 0;
     140    left: -100%;
     141    width: 100%;
     142    height: 100%;
     143    background: linear-gradient(90deg, transparent, rgba(102, 126, 234, 0.1), transparent);
     144    animation: shimmer 1.5s infinite;
     145}
     146
     147@keyframes shimmer {
     148    0% { left: -100%; }
     149    100% { left: 100%; }
     150}
     151
     152/* Modal CSS */
     153
     154.sitepulse-modal {
     155    display: none;
     156    position: fixed;
     157    z-index: 999;
     158    left: 0; top: 0;
     159    width: 100%; height: 100%;
     160    overflow: auto;
     161    background: rgba(0,0,0,0.8);
     162    backdrop-filter: blur(2px);
     163}
     164
     165.sitepulse-modal-content {
     166    background: #fff;
     167    margin: 8% auto;
     168    padding: 0;
     169    border-radius: 12px;
     170    width: 480px;
     171    max-width: 90vw;
     172    position: relative;
     173    box-shadow: 0 10px 30px rgba(0,0,0,0.3);
     174    animation: modalSlideIn 0.3s ease-out;
     175}
     176
     177@keyframes modalSlideIn {
     178    from {
     179        opacity: 0;
     180        transform: translateY(-50px) scale(0.9);
     181    }
     182    to {
     183        opacity: 1;
     184        transform: translateY(0) scale(1);
     185    }
     186}
     187
     188.sitepulse-close-btn {
     189    color: #999;
     190    position: absolute;
     191    top: 15px; right: 20px;
     192    font-size: 24px;
     193    font-weight: bold;
     194    cursor: pointer;
     195    z-index: 1000;
     196    transition: color 0.2s ease;
     197}
     198
     199.sitepulse-close-btn:hover {
     200    color: #333;
     201}
     202
     203.sitepulse-modal-header {
     204    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
     205    color: white;
     206    padding: 25px 30px 20px;
     207    border-radius: 12px 12px 0 0;
     208    position: relative;
     209}
     210
     211.sitepulse-modal-header h2 {
     212    margin: 0 0 15px 0;
     213    font-size: 24px;
     214    font-weight: 600;
     215    padding-right: 40px;
     216}
     217
     218.sitepulse-status-indicator {
     219    display: flex;
     220    align-items: center;
     221    gap: 8px;
     222    font-size: 14px;
     223    opacity: 0.9;
     224}
     225
     226.sitepulse-status-dot {
     227    width: 10px;
     228    height: 10px;
     229    border-radius: 50%;
     230    transition: all 0.3s ease;
     231}
     232
     233.sitepulse-status-dot.idle {
     234    background: #95a5a6;
     235}
     236
     237.sitepulse-status-dot.tracking {
     238    background: #27ae60;
     239    animation: pulse 2s infinite;
     240}
     241
     242.sitepulse-status-dot.error {
     243    background: #e74c3c;
     244}
     245
     246@keyframes pulse {
     247    0% { opacity: 1; }
     248    50% { opacity: 0.5; }
     249    100% { opacity: 1; }
     250}
     251
     252.sitepulse-modal-body {
     253    padding: 30px;
     254}
     255
     256.sitepulse-modal-content-section {
     257    transition: opacity 0.3s ease, height 0.3s ease;
     258}
     259
     260.sitepulse-tracking-description {
     261    color: #666;
     262    margin: 0 0 15px 0;
     263    font-size: 16px;
     264    line-height: 1.5;
     265}
     266
     267.sitepulse-tracking-description.setup-info {
     268    color: #555;
     269    font-size: 14px;
     270    line-height: 1.6;
     271    margin: 0 0 25px 0;
     272    padding: 15px;
     273    background: #f0f7ff;
     274    border-radius: 8px;
     275    border-left: 4px solid #667eea;
     276}
     277
     278.sitepulse-tracking-description.current-page {
     279    color: #333;
     280    font-weight: 500;
     281    margin: 0 0 20px 0;
     282}
     283
     284.sitepulse-tracking-description.tracking-explanation {
     285    color: #555;
     286    font-size: 14px;
     287    line-height: 1.6;
     288    margin: 0 0 25px 0;
     289    padding: 15px;
     290    background: #f8fdf8;
     291    border-radius: 8px;
     292    border-left: 4px solid #27ae60;
     293}
     294
     295#current-page-name {
     296    color: #667eea;
     297    font-weight: 600;
     298}
     299
     300.sitepulse-tracking-controls {
     301    display: flex;
     302    gap: 15px;
     303    margin-bottom: 25px;
     304}
     305
     306.sitepulse-btn {
     307    display: flex;
     308    align-items: center;
     309    gap: 8px;
     310    padding: 12px 24px;
     311    border: none;
     312    border-radius: 8px;
     313    font-size: 16px;
     314    font-weight: 500;
     315    cursor: pointer;
     316    transition: all 0.2s ease;
     317    min-width: 140px;
     318    justify-content: center;
     319}
     320
     321.sitepulse-btn-primary {
     322    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
     323    color: white;
     324}
     325
     326.sitepulse-btn-primary:hover {
     327    transform: translateY(-2px);
     328    box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4);
     329}
     330
     331.sitepulse-btn-secondary {
     332    background: #e74c3c;
     333    color: white;
     334}
     335
     336.sitepulse-btn-secondary:hover {
     337    background: #c0392b;
     338    transform: translateY(-2px);
     339    box-shadow: 0 5px 15px rgba(231, 76, 60, 0.4);
     340}
     341
     342.sitepulse-btn:disabled {
     343    opacity: 0.6;
     344    cursor: not-allowed;
     345    transform: none !important;
     346    box-shadow: none !important;
     347}
     348
     349.sitepulse-btn-icon {
     350    font-size: 18px;
     351}
     352
     353.sitepulse-tracking-info {
     354    background: #f8f9fa;
     355    border-radius: 8px;
     356    padding: 20px;
     357    border-left: 4px solid #667eea;
     358}
     359
     360.sitepulse-info-grid {
     361    display: grid;
     362    grid-template-columns: 1fr 1fr 1fr;
     363    gap: 15px;
     364}
     365
     366.sitepulse-info-item {
     367    text-align: center;
     368}
     369
     370.sitepulse-info-label {
     371    display: block;
     372    font-size: 12px;
     373    color: #666;
     374    text-transform: uppercase;
     375    letter-spacing: 0.5px;
     376    margin-bottom: 5px;
     377}
     378
     379.sitepulse-info-value {
     380    display: block;
     381    font-size: 18px;
     382    font-weight: 600;
     383    color: #333;
     384}
     385
     386/* Completion Modal Styles */
     387.sitepulse-completion-animation {
     388    display: flex;
     389    justify-content: center;
     390    margin-bottom: 20px;
     391}
     392
     393.sitepulse-checkmark-circle {
     394    width: 80px;
     395    height: 80px;
     396    border-radius: 50%;
     397    background: linear-gradient(135deg, #27ae60 0%, #2ecc71 100%);
     398    display: flex;
     399    align-items: center;
     400    justify-content: center;
     401    box-shadow: 0 5px 20px rgba(46, 204, 113, 0.3);
     402    animation: scaleIn 0.6s ease-out;
     403}
     404
     405.sitepulse-checkmark {
     406    color: white;
     407    font-size: 40px;
     408    font-weight: bold;
     409    animation: checkmarkPop 0.4s ease-out 0.2s both;
     410}
     411
     412@keyframes scaleIn {
     413    from {
     414        transform: scale(0);
     415        opacity: 0;
     416    }
     417    to {
     418        transform: scale(1);
     419        opacity: 1;
     420    }
     421}
     422
     423@keyframes checkmarkPop {
     424    from {
     425        transform: scale(0);
     426        opacity: 0;
     427    }
     428    50% {
     429        transform: scale(1.2);
     430    }
     431    to {
     432        transform: scale(1);
     433        opacity: 1;
     434    }
     435}
     436
     437.sitepulse-completion-title {
     438    text-align: center;
     439    color: #333;
     440    font-size: 24px;
     441    font-weight: 600;
     442    margin: 0 0 20px 0;
     443}
     444
     445.sitepulse-completion-message {
     446    color: #555;
     447    font-size: 16px;
     448    line-height: 1.6;
     449    margin: 0 0 15px 0;
     450    padding: 20px;
     451    background: #f8fdf8;
     452    border-radius: 8px;
     453    border-left: 4px solid #27ae60;
     454}
     455
     456.sitepulse-results-info {
     457    color: #666;
     458    font-size: 14px;
     459    line-height: 1.6;
     460    margin: 0 0 25px 0;
     461    text-align: center;
     462    font-style: italic;
     463}
     464
     465.sitepulse-completion-actions {
     466    display: flex;
     467    gap: 15px;
     468    justify-content: center;
     469    flex-wrap: wrap;
     470}
     471
     472.sitepulse-view-results-btn {
     473    background: linear-gradient(135deg, #27ae60 0%, #2ecc71 100%);
     474    color: white;
     475    text-decoration: none;
     476    transition: all 0.2s ease;
     477}
     478
     479.sitepulse-view-results-btn:hover {
     480    background: linear-gradient(135deg, #229954 0%, #27ae60 100%);
     481    transform: translateY(-2px);
     482    box-shadow: 0 5px 15px rgba(46, 204, 113, 0.4);
     483    color: white;
     484    text-decoration: none;
     485}
     486
     487#close-completed-modal {
     488    background: #95a5a6;
     489    color: white;
     490}
     491
     492#close-completed-modal:hover {
     493    background: #7f8c8d;
     494    transform: translateY(-2px);
     495    box-shadow: 0 5px 15px rgba(149, 165, 166, 0.4);
     496}
     497
     498/* Responsive Design */
     499@media (max-width: 600px) {
     500    .sitepulse-modal-content {
     501        margin: 5% auto;
     502        width: 95vw;
     503    }
     504   
     505    .sitepulse-modal-header {
     506        padding: 20px 25px 15px;
     507    }
     508   
     509    .sitepulse-modal-header h2 {
     510        font-size: 20px;
     511    }
     512   
     513    .sitepulse-modal-body {
     514        padding: 25px;
     515    }
     516   
     517    .sitepulse-tracking-controls {
     518        flex-direction: column;
     519    }
     520   
     521    .sitepulse-info-grid {
     522        grid-template-columns: 1fr;
     523        gap: 10px;
     524    }
     525}
  • sitepulse/trunk/assets/css/sitepulse_general.css

    r3374045 r3383675  
    217217    color:#fff;
    218218    padding:0 4px;
    219     border-radius:999px;
     219    border-radius:7px;
    220220    margin-left:6px;
     221}
     222
     223/* Tiny circular count */
     224#wp-admin-bar-wpsp .wshp-site-load-time {
     225    display: inline-block;
     226    min-width: 16px;
     227    height: 16px;
     228    line-height: 16px;
     229    font-size: 10px;
     230    text-align: center;
     231    background: #c4cebf26;
     232    color: #fff;
     233    padding: 0 4px;
     234    border-radius: 7px;
     235    margin-left: 6px;
     236}
     237
     238#wp-admin-bar-wpsp .wshp-site-load-time.green {
     239    color: #04ff00;
     240}
     241
     242#wp-admin-bar-wpsp .wshp-site-load-time.yellow {
     243    color: #ffec00;
     244}
     245
     246#wp-admin-bar-wpsp .wshp-site-load-time.red {
     247    color: #ff3b3b;
    221248}
    222249
  • sitepulse/trunk/assets/js/backend.js

    r3374045 r3383675  
    218218    }, 10000);
    219219
    220     $('.sp_profiler_clear_events').on('click', async function() {
     220    $('.sp_profiler_clear_events, .sp_curl_and_profiler_clear_events').on('click', async function() {
    221221        try {
    222222            const result = await setClearLoadEvents();
     
    233233    });
    234234
    235     $('.sp_curl_api_clear_events').on('click', async function() {
     235    $('.sp_curl_api_clear_events, .sp_curl_and_profiler_clear_events').on('click', async function() {
    236236        try {
    237237            const result = await setClearCurlApiEvents();
     
    503503    // Optional custom event support
    504504    window.addEventListener('sitepulse:refresh', () => refreshPulse(true));
     505
     506    // Toggle email blocking mode visibility
     507    function toggleEmailModeRow() {
     508        if ($('#sitepulse_email_blocking_enabled').is(':checked')) {
     509            $('#email_blocking_mode_row').show();
     510        } else {
     511            $('#email_blocking_mode_row').hide();
     512        }
     513    }
     514   
     515    // Initial state
     516    toggleEmailModeRow();
     517   
     518    // On change event
     519    $('#sitepulse_email_blocking_enabled').change(function() {
     520        toggleEmailModeRow();
     521    });
     522   
     523    // Add confirmation for dangerous operations
     524    $('#sitepulse_cron_disabled').change(function() {
     525        if ($(this).is(':checked')) {
     526            if (!confirm( SitePulse.cron_confirm_message )) {
     527                $(this).prop('checked', false);
     528            }
     529        }
     530    });
     531   
     532    $('#sitepulse_email_blocking_enabled').change(function() {
     533        if ($(this).is(':checked')) {
     534            if (!confirm( SitePulse.email_blocking_confirm_message )) {
     535                $(this).prop('checked', false);
     536                toggleEmailModeRow();
     537            }
     538        }
     539    });
    505540});
  • sitepulse/trunk/assets/js/frontend.js

    r3374045 r3383675  
    5757    }
    5858
    59     $('#curlSwitch, #loadSwitch').on('click', async function() {
     59    async function setRealTimeMode( real_time_status ) {
     60        const url = SitePulse.rest_url.replace(/\/$/, '') + '/sitepulse/v1/wpsprealtimemode/set_active';
     61        const res = await fetch(url, {
     62            method: 'POST',
     63            headers: {
     64                'Content-Type': 'application/json',
     65                'X-WP-Nonce': SitePulse.nonce
     66            },
     67            body: JSON.stringify({ real_time_status, _wpnonce: SitePulse.nonce })
     68        });
     69
     70        if ( ! res.ok ) {
     71            const err = await res.json().catch(()=>null);
     72            throw new Error( 'Request failed: ' + (err?.message || res.status) );
     73        }
     74
     75        if ( res.ok ) {
     76            // if ( real_time_status ) {
     77            //     $('#curlSwitch').closest('.switch-item').addClass('rainbow-border');
     78            // } else {
     79            //     $('#curlSwitch').closest('.switch-item').removeClass('rainbow-border');
     80            // }
     81        }
     82
     83        return res.json();
     84    }
     85
     86    async function getRealTimeMode() {
     87        const url = SitePulse.rest_url.replace(/\/$/, '') + '/sitepulse/v1/wpsprealtimemode/get_active';
     88        const res = await fetch(url, {
     89            method: 'POST',
     90            headers: {
     91                'Content-Type': 'application/json',
     92                'X-WP-Nonce': SitePulse.nonce
     93            },
     94            body: JSON.stringify({ _wpnonce: SitePulse.nonce })
     95        });
     96
     97        if ( ! res.ok ) {
     98            const err = await res.json().catch(()=>null);
     99            throw new Error( 'Request failed: ' + (err?.message || res.status) );
     100        }
     101
     102        return res.json();
     103    }
     104
     105    async function setSPReportMode(report_mode) {
     106        const url = SitePulse.rest_url.replace(/\/$/, '') + '/sitepulse/v1/sp_report_mode/set_active';
     107        const res = await fetch(url, {
     108            method: 'POST',
     109            headers: {
     110                'Content-Type': 'application/json',
     111                'X-WP-Nonce': SitePulse.nonce
     112            },
     113            body: JSON.stringify({ report_mode, _wpnonce: SitePulse.nonce })
     114        });
     115
     116        if ( ! res.ok ) {
     117            const err = await res.json().catch(()=>null);
     118            throw new Error( 'Request failed: ' + (err?.message || res.status) );
     119        }
     120
     121        return res.json();
     122    }
     123
     124    $('#curlSwitch').on('click', async function() {
    60125
    61126        var page_id = $(this).data('sitepulse-page-id');
    62127        var curlSwitch = $('#curlSwitch').is(':checked') ? 1 : 0;
    63         var loadSwitch = $('#loadSwitch').is(':checked') ? 1 : 0;
    64        
    65         try {
    66             const result = await setPageTPLoadActive( page_id, curlSwitch, loadSwitch );
     128       
     129        try {
     130            const result = await setPageTPLoadActive( page_id, curlSwitch, 0 );
    67131
    68132            setTimeout(() => {
     
    77141    });
    78142
     143    // Real-time tracking modal and functionality.
     144    let trackingInterval;
     145    let trackingStartTime;
     146    let isTracking = false;
     147   
     148    // Show modal on page load
     149    // $('#sitepulse-modal').show();
     150   
     151    // Close modal functionality
     152    $('#sitepulse-close-modal').on('click', function() {
     153        // Close modal and handle tracking state
     154        if ( isTracking ) {
     155            if ( confirm('Are you sure you want to close the SitePulse tracking modal? The Real-Time tracking session will be stopped.') ) {
     156                stopTracking();
     157                $('#sitepulse-modal').hide();
     158            }
     159        } else {
     160            // Close modal without stopping tracking
     161            $('#sitepulse-modal').hide();
     162        }
     163    });
     164
     165    // Is the real-time tracking element present?
     166    if ( $('[data-sitepulse-realtime="tracking"]').length > 0 ) {
     167        restoreTracking();
     168    }
     169
     170    // Realtime tracking was active and finished
     171    if ( $('[data-sitepulse-realtime="stopped"]').length > 0 && localStorage.getItem('sitepulse_realtime_tracking') === 'true' ) {
     172        localStorage.removeItem('sitepulse_realtime_tracking');
     173        var page_id = $('#curlSwitch').data('sitepulse-page-id');
     174        var curlSwitch = $('#curlSwitch').is(':checked') ? 1 : 0;
     175
     176        // Loop through SitePulse.post_load_events and log each event's details
     177        Object.entries(SitePulse.post_load_events).forEach(([id, event]) => {
     178            // Generate HTML for event details
     179            // Format time values to milliseconds with 3 decimal places
     180            function formatMs(val) {
     181                return (parseFloat(val) * 1000).toFixed(3) + ' ms';
     182            }
     183
     184            const eventHtml = `
     185            <div class="sitepulse-event">
     186                <strong>Event ID:</strong> ${id}<br>
     187                <strong>Hook:</strong> ${event.hook}<br>
     188                <strong>Priority:</strong> ${event.priority}<br>
     189                <strong>Signature:</strong> ${event.sig}<br>
     190                <strong>File/Line:</strong> ${event.fileline}<br>
     191                <strong>Calls:</strong> ${event.calls}<br>
     192                <strong>Total Time:</strong> ${formatMs(event.total_ms)}<br>
     193                <strong>Max Time:</strong> ${formatMs(event.max_ms)}<br>
     194                <strong>Avg:</strong> ${formatMs(event.avg_ms)}<br>
     195                <strong>Source:</strong> ${event.source}
     196            </div>`;
     197
     198            $('#sitepulse-completion-info').append(eventHtml);
     199        });
     200
     201        (async function() {
     202            try {
     203                const result = await setSPReportMode( 1 );
     204            } catch (err) {
     205                console.error('Failed to update Report mode:', err);
     206            }
     207
     208            try {
     209                const result = await setPageTPLoadActive( page_id, curlSwitch, 0 );
     210            } catch (err) {
     211                // Optionally handle error, e.g. show error message
     212                console.error('Failed to update Page tracker Events:', err);
     213            }
     214
     215            showCompletionModal();
     216        })();
     217    }
     218
     219    // // Close modal when clicking outside
     220    // $(window).on('click', function(event) {
     221    //     if (event.target.id === 'sitepulse-modal') {
     222    //         $('#sitepulse-modal').hide();
     223    //         if (isTracking) {
     224    //             stopTracking();
     225    //         }
     226    //     }
     227    // });
     228   
     229    // Realtime Tracking button in admin bar
     230    $('#realtime-tracking-btn').on('click', function() {
     231        $('#sitepulse-modal').show();
     232    });
     233
     234    // Start tracking button
     235    $('#start-tracking-btn').on('click', function() {
     236        startTracking();
     237    });
     238   
     239    // Stop tracking button
     240    $('#stop-tracking-btn').on('click', function() {
     241        stopTracking();
     242    });
     243   
     244    async function startTracking() {
     245        isTracking = true;
     246        trackingStartTime = Date.now();
     247
     248        // Update modal content when realtime activated
     249        $('#modal-title').text('Real-Time Tracking Enabled');
     250        $('#inactive-content').hide();
     251        $('#active-content').show();
     252        $('.sitepulse-tracking-description.current-page').text('Real-time tracking is active');
     253
     254        localStorage.setItem('sitepulse_realtime_tracking', 'true');
     255        localStorage.setItem('sitepulse_tracking_start_time', trackingStartTime.toString());
     256
     257        var page_id = $('#curlSwitch').data('sitepulse-page-id');
     258        var curlSwitch = $('#curlSwitch').is(':checked') ? 1 : 0;
     259       
     260        try {
     261            const result = await setPageTPLoadActive( page_id, curlSwitch, 1 );
     262        } catch (err) {
     263            // Optionally handle error, e.g. show error message
     264            console.error('Failed to update Page tracker Events:', err);
     265        }
     266
     267        try {
     268            const result = await setRealTimeMode( 1 );
     269
     270            if ( result.success ) {
     271                console.log('Real Time mode activated on server.');
     272            }
     273
     274        } catch (err) {
     275            // Optionally handle error, e.g. show error message
     276            console.error('Failed to update Page tracker Events:', err);
     277        }
     278
     279        // Update UI
     280        updateTrackingStatus('tracking', 'Tracking Active');
     281        $('#start-tracking-btn').hide();
     282        $('#stop-tracking-btn').show();
     283        $('#tracking-info').show();
     284        $('#tracking-state').text('Monitoring...');
     285       
     286        // Start the tracking interval
     287        trackingInterval = setInterval(function() {
     288            updateTrackingDisplay();
     289            simulateResourceDetection();
     290        }, 1000);
     291       
     292        try {
     293            const result = await setSPReportMode( 1 );
     294        } catch (err) {
     295            console.error('Failed to update Report mode:', err);
     296        }
     297
     298        console.log('SitePulse: Resource tracking started');
     299       
     300        setTimeout(() => {
     301            window.location.reload();
     302        }, 10000);
     303    }
     304
     305    async function restoreTracking() {
     306        isTracking = true;
     307
     308        // Restore tracking start time from localStorage or set to current time
     309        const savedStartTime = localStorage.getItem('sitepulse_tracking_start_time');
     310        trackingStartTime = savedStartTime ? parseInt(savedStartTime) : Date.now();
     311
     312        // Update modal content when realtime activated
     313        $('#modal-title').text('Real-Time Tracking Enabled');
     314        $('#inactive-content').hide();
     315        $('#active-content').show();
     316
     317        try {
     318            const result = await getRealTimeMode();
     319
     320            if ( result.success && result.real_time_status ) {
     321                setTimeout(() => {
     322                    window.location.reload();
     323                }, 10000);
     324            }
     325
     326            if ( result.success && result.real_time_status == false ) {
     327                stopTracking();
     328                console.log('Real Time mode is not active on server.');
     329            }
     330
     331        } catch (err) {
     332            // Optionally handle error, e.g. show error message
     333            console.error('Failed to update Page tracker Events:', err);
     334        }
     335
     336        // Update UI
     337        updateTrackingStatus('tracking', 'Tracking Active');
     338        $('#start-tracking-btn').hide();
     339        $('#stop-tracking-btn').show();
     340        $('#tracking-info').show();
     341        $('#tracking-state').text('Monitoring...');
     342       
     343        // Start the tracking interval
     344        trackingInterval = setInterval(function() {
     345            updateTrackingDisplay();
     346            simulateResourceDetection();
     347        }, 1000);
     348       
     349        console.log('SitePulse: Resource tracking started');
     350       
     351        // You can add your actual tracking logic here
     352        // For example, monitoring network requests, performance metrics, etc.
     353    }
     354
     355    async function stopTracking() {
     356        $('#modal-title').text('Real-Time Tracking Stopped');
     357        $('#inactive-content').show();
     358        $('#active-content').hide();
     359
     360        isTracking = false;
     361       
     362        if (trackingInterval) {
     363            clearInterval(trackingInterval);
     364        }
     365       
     366        // Clear localStorage tracking data
     367        localStorage.removeItem('sitepulse_tracking_start_time');
     368        localStorage.removeItem('sitepulse_tracking_elapsed');
     369        localStorage.removeItem('sitepulse_realtime_tracking');
     370       
     371        // Update UI
     372        updateTrackingStatus('idle', 'Ready to Track');
     373        $('#start-tracking-btn').show();
     374        $('#stop-tracking-btn').hide();
     375        $('#tracking-info').hide();
     376       
     377        var page_id = $('#curlSwitch').data('sitepulse-page-id');
     378        var curlSwitch = $('#curlSwitch').is(':checked') ? 1 : 0;
     379       
     380        try {
     381            const result = await setPageTPLoadActive( page_id, curlSwitch, 0 );
     382        } catch (err) {
     383            // Optionally handle error, e.g. show error message
     384            console.error('Failed to update Page tracker Events:', err);
     385        }
     386
     387        try {
     388            const result = await setRealTimeMode( 0 );
     389
     390            if ( result.success ) {
     391                console.log('Real Time mode activated on server.');
     392            }
     393
     394        } catch (err) {
     395            // Optionally handle error, e.g. show error message
     396            console.error('Failed to update Page tracker Events:', err);
     397        }
     398
     399        console.log('SitePulse: Resource tracking stopped');
     400    }
     401   
     402    function updateTrackingStatus(status, text) {
     403        const statusDot = $('.sitepulse-status-dot');
     404        statusDot.removeClass('idle tracking error').addClass(status);
     405        $('.status-text').text(text);
     406    }
     407   
     408    function updateTrackingDisplay() {
     409        if (!isTracking) return;
     410       
     411        const elapsed = Date.now() - trackingStartTime;
     412        const minutes = Math.floor(elapsed / 60000);
     413        const seconds = Math.floor((elapsed % 60000) / 1000);
     414       
     415        // Save elapsed time to localStorage for persistence across refreshes
     416        localStorage.setItem('sitepulse_tracking_elapsed', elapsed.toString());
     417       
     418        $('#tracking-duration').text(
     419            `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`
     420        );
     421
     422        $('#resources-count').text(SitePulse.post_load_events_count);
     423    }
     424
     425    function simulateResourceDetection() {
     426        // This is a simulation - replace with actual resource detection logic
     427        if (Math.random() < 0.3) { // 30% chance each second
     428            $('#tracking-state').text('Resource detected...');
     429           
     430            setTimeout(function() {
     431                if (isTracking) {
     432                    $('#tracking-state').text('Monitoring...');
     433                }
     434            }, 500);
     435        }
     436    }
     437   
     438    function showCompletionModal() {
     439        // Update modal title and show completion content
     440        $('#modal-title').text('SitePulse Tracking Complete');
     441        $('#inactive-content').hide();
     442        $('#active-content').hide();
     443        $('#completed-content').show();
     444       
     445        // Update status indicator
     446        updateTrackingStatus('idle', 'Tracking Complete');
     447       
     448        // Hide tracking controls and info
     449        $('.sitepulse-tracking-controls').hide();
     450        $('#tracking-info').hide();
     451       
     452        // Show the modal
     453        $('#sitepulse-modal').show();
     454    }
     455   
     456    // Close completion modal button
     457    $('#close-completed-modal').on('click', function() {
     458        $('#sitepulse-modal').hide();
     459        // Reset modal to default state
     460        resetModalToDefault();
     461    });
     462   
     463    function resetModalToDefault() {
     464        $('#modal-title').text('SitePulse Resource Tracking');
     465        $('#inactive-content').show();
     466        $('#active-content').hide();
     467        $('#completed-content').hide();
     468        $('.sitepulse-tracking-controls').show();
     469        updateTrackingStatus('idle', 'Ready to Track');
     470    }
     471   
     472    // Handle escape key to close modal
     473    $(document).on('keydown', function(event) {
     474        if (event.key === 'Escape' && $('#sitepulse-modal').is(':visible')) {
     475            $('#sitepulse-modal').hide();
     476            if (isTracking) {
     477                stopTracking();
     478            }
     479        }
     480    });
     481
    79482});
  • sitepulse/trunk/class/backend.php

    r3374045 r3383675  
    4343        wp_enqueue_script($this->plugin->setPrefix( SITEPULSE_PREFIX . '_backend_script'), SITEPULSE_ADMIN_ASSETS_JS_URL . 'backend.js', ['jquery'], filemtime(SITEPULSE_ADMIN_ASSETS_JS_PATH . 'backend.js'), true);
    4444        wp_localize_script( $this->plugin->setPrefix( SITEPULSE_PREFIX . '_backend_script'), 'SitePulse', [
     45            'cron_confirm_message' => esc_js(__('Are you sure you want to disable WordPress cron? This will stop all scheduled tasks including updates, backups, and other automated processes.', 'sitepulse')),
     46            'email_blocking_confirm_message' => esc_js(__('Are you sure you want to block all outgoing emails? This will prevent password resets, notifications, and other important emails from being sent.', 'sitepulse')),
    4547            'rest_url' => esc_url_raw( rest_url() ),
    4648            'nonce'    => wp_create_nonce( 'wp_rest' ),
     
    105107        $page_resource_load = add_submenu_page(
    106108            $this->plugin->setPrefix("sitepulse"),
    107             __('LoadSentinel', 'sitepulse'),
    108             __('LoadSentinel', 'sitepulse'),
     109            __('Insights', 'sitepulse'),
     110            __('Insights', 'sitepulse'),
    109111            'manage_options',
    110112            $this->plugin->setPrefix("sitepulse") . '_' . SITEPULSE_PROFILER_SLUG,
     
    123125            $this->plugin->setPrefix("sitepulse") . '_' . SITEPULSE_CURL_API_SLUG,
    124126            [&$this, 'render_curl_api'],
    125             2
     127            3
    126128        );
    127129
    128130        array_push($this->pages, $page_curl_api);
     131
     132        // Add a submenu for settings page
     133        $page_settings = add_submenu_page(
     134            $this->plugin->setPrefix("sitepulse"),
     135            __('Settings', 'sitepulse'),
     136            __('Settings', 'sitepulse'),
     137            'manage_options',
     138            $this->plugin->setPrefix("sitepulse") . '_settings',
     139            [&$this, 'render_page_sitepulse_settings'],
     140            10
     141        );
     142
     143        array_push($this->pages, $page_settings);
    129144    }
    130145
     
    233248    }
    234249
     250    public function render_page_sitepulse_settings() {
     251        if ( ! current_user_can('manage_options') ) return;
     252
     253        // Memory ram used
     254        $mem = $this->plugin->sp_get_memory_info();
     255
     256        // Initialize settings class
     257        require_once SITEPULSE_CLASS_PATH . 'settings.php';
     258        $sitepulse_settings = Sitepulse_Settings::getInstance();
     259
     260        // Handle form submission for SitePulse settings
     261        if ( isset( $_POST['submit'] ) && isset( $_POST['sitepulse_settings_nonce'] ) ) {
     262            if ( wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['sitepulse_settings_nonce'] ) ), 'sitepulse_settings_action' ) ) {
     263                $updated_settings = array();
     264               
     265                // Email blocking settings
     266                $updated_settings['email_blocking_enabled'] = isset( $_POST['sitepulse_email_blocking_enabled'] ) ? true : false;
     267                if ( isset( $_POST['sitepulse_email_blocking_mode'] ) ) {
     268                    $updated_settings['email_blocking_mode'] = sanitize_text_field( wp_unslash( $_POST['sitepulse_email_blocking_mode'] ) );
     269                }
     270               
     271                // Cron settings
     272                $updated_settings['cron_disabled'] = isset( $_POST['sitepulse_cron_disabled'] ) ? true : false;
     273               
     274                // Update settings
     275                if ( $sitepulse_settings->update_settings( $updated_settings ) ) {
     276                    echo '<div class="notice notice-success is-dismissible"><p>' . esc_html__( 'SitePulse settings updated successfully!', 'sitepulse' ) . '</p></div>';
     277                }
     278            } else {
     279                echo '<div class="notice notice-error is-dismissible"><p>' . esc_html__( 'Security check failed. Please try again.', 'sitepulse' ) . '</p></div>';
     280            }
     281        }
     282
     283        // List of activated plugins
     284        $active_plugins = get_option( 'active_plugins', [] );
     285
     286        // Get current settings
     287        $current_settings = $sitepulse_settings->get_all_settings();
     288        $status_info = $sitepulse_settings->get_status_info();
     289
     290        // Render all sections of the page
     291        require_once SITEPULSE_PATH . 'templates/backend/settings.php';
     292    }
     293
    235294    public function render_curl_api() {
    236295        if (!current_user_can('manage_options')) return;
  • sitepulse/trunk/class/frontend.php

    r3374045 r3383675  
    1717        // Register admin bar node on both admin and front-end (method checks capabilities)
    1818        add_action('admin_bar_menu', [&$this, 'admin_bar_node'], 90);
     19
     20        // Add to footer (before </body>)
     21        add_action('wp_footer', [ &$this, 'sitepulse_modal_html'], 100 );
    1922    }
     23
     24    // Frontend modal HTML output
     25    public static function sitepulse_modal_html() {
     26        $sp_current_tracked_pageid = get_option( "sitepulse_current_tracked_pageid" );
     27        $realtime_tracking = get_transient( "sitepulse_realtime_tracking" );
     28        $tracking_status = 'stopped';
     29
     30        if ( $realtime_tracking ) {
     31            // Ensure is_logging() is defined or replace with appropriate check
     32            if ( current_user_can( 'manage_options' ) ) {
     33                $tracking_status = 'tracking';
     34            }
     35        }
     36
     37        require_once SITEPULSE_PATH . 'templates/frontend/frontend.php';
     38    }
    2039
    2140    public static function admin_bar_node($wp_admin_bar) {
     
    6281            . '</span>';
    6382
     83        $time_load = $time_load_in_ms = self::get_current_load_time();
     84
     85        // $time_load seconds, minutes or hours formatting can be added here if needed
     86        $time_format_siteload = 'ms';
     87        if ( $time_load > 1000 ) {
     88            $time_load = number_format( $time_load / 1000, 1 );
     89            $time_format_siteload = 's';
     90        } elseif ( $time_load > 60000 ) {
     91            $time_load = number_format( $time_load / 60000, 1 );
     92            $time_format_siteload = 'min';
     93        } elseif ( $time_load > 3600000 ) {
     94            // Extreme conditions
     95            $time_load = number_format( $time_load / 3600000, 1 );
     96            $time_format_siteload = 'hr';
     97        }
     98
     99        // Time load color coding
     100        if ( $time_load_in_ms ) {
     101            if ( $time_load_in_ms < 1000 ) {
     102                // Green
     103                $time_load = '<span class="wshp-site-load-time green" aria-hidden="true"> ' . __('LoadTime', 'sitepulse') . ': ' . number_format($time_load, 1) . ' ' . $time_format_siteload . '</span>';
     104            } elseif ( $time_load_in_ms >= 1000 && $time_load_in_ms < 3000 ) {
     105                // Yellow
     106                $time_load = '<span class="wshp-site-load-time yellow" aria-hidden="true"> ' . __('LoadTime', 'sitepulse') . ': ' . number_format($time_load, 1) . ' ' . $time_format_siteload . '</span>';
     107            } else {
     108                // Red
     109                $time_load = '<span class="wshp-site-load-time red" aria-hidden="true"> ' . __('LoadTime', 'sitepulse') . ': ' . number_format($time_load, 1) . ' ' . $time_format_siteload . '</span>';
     110            }
     111        }
     112
    64113        $badge = $count ? '<span class="wshp-count wpsp-stats" aria-hidden="true">API: ' . intval($count) . '</span>' : '';
    65114        $badge .= $stats_count ? '<span class="wshp-count" aria-hidden="true">Load: ' . intval($stats_count) . '</span>' : '';
     115        $badge .= $time_load ? $time_load : '';
    66116
    67117        // Use very short label to conserve width; title contains full link
     
    141191                    <div class="switch-container">
    142192                        <div class="switch-item ' . $sp_pagechttp_rain . '">
    143                             <span class="switch-label">cURL Events</span>
     193                            <span class="switch-label">Track API Events for this page</span>
    144194                            <label class="switch">
    145195                                <input type="checkbox" ' . checked( $sp_pageloadhttp_curlstatus, true, false ) . ' id="curlSwitch" data-sitepulse-page-id="' . get_the_ID() . '">
    146                                 <span class="slider round"></span>
    147                             </label>
    148                         </div>
    149                         <div class="switch-item ' . $sp_pagelhttp_rain . '">
    150                             <span class="switch-label">Load Tracking</span>
    151                             <label class="switch">
    152                                 <input type="checkbox" ' . checked( $sp_pageloadhttp_loadstatus, true, false ) . ' id="loadSwitch" data-sitepulse-page-id="' . get_the_ID() . '">
    153196                                <span class="slider round"></span>
    154197                            </label>
     
    158201                        <div class="sitepulse-ontracking" style="' . $sp_pagelhttp_view . '">Tracking page: ' . $page_post_title . '<span class="wshp-count wpsp-stats">API: ' . $count_current_curl_page . '</span><span class="wshp-count">Load: ' . $count_current_load_page . '</span></div>
    159202                    </div>
     203                    <div class="sitepulse-container">
     204                        <div class="sitepulse-realtime-tracking">
     205                            <button id="realtime-tracking-btn" class="sitepulse-btn sitepulse-btn-primary">
     206                                <span class="sitepulse-btn-icon">📊</span>
     207                                Start Real-Time Tracking
     208                            </button>
     209                        </div>
     210                    </div>
    160211                ',
    161212                'meta'   => [
     
    165216                'href'   => false,
    166217            ]);
    167         }
     218            // Warning for unsupported page types and not show on admin pages
     219        } elseif ( ! is_admin() ) {
     220            $wp_admin_bar->add_node([
     221                'id'     => SITEPULSE_PREFIX . '_unsupported_page',
     222                'title'  => '<span class="sitepulse-warning-label"><span class="sitepulse-warning-icon" aria-hidden="true">⚠</span> ' . __('Tracking not supported on this page', 'sitepulse') . '</span>',
     223                'parent' => SITEPULSE_PREFIX,
     224                'href'   => false,
     225                'meta'   => [
     226                    'html'  => false,
     227                    'class' => 'sitepulse-unsupported',
     228                    'title' => __('Tracking is not available for this page type.', 'sitepulse'),
     229                ],
     230            ]);
     231        }
    168232    }
    169233
     
    178242            true // Load in footer
    179243        );
    180         wp_enqueue_script(
    181             $this->plugin->setPrefix(SITEPULSE_PREFIX . '_frontend_script'),
    182             SITEPULSE_ADMIN_ASSETS_JS_URL . 'frontend.js',
    183             ['jquery'],
    184             filemtime(SITEPULSE_ADMIN_ASSETS_JS_PATH . 'frontend.js'),
    185             true // Load in footer
    186         );
     244
     245        // Pass $post_load_events to frontend.js
     246        $sp_current_tracked_pageid = get_option( "sitepulse_current_tracked_pageid" );
     247        $post_load_events = [];
     248        if ( $sp_current_tracked_pageid ) {
     249            $transient = get_transient( 'sitepulse_load_single_page_' . $sp_current_tracked_pageid );
     250            if ( $transient ) {
     251                $post_load_events = $transient;
     252            }
     253        }
     254
     255        wp_enqueue_script(
     256            $this->plugin->setPrefix(SITEPULSE_PREFIX . '_frontend_script'),
     257            SITEPULSE_ADMIN_ASSETS_JS_URL . 'frontend.js',
     258            ['jquery'],
     259            filemtime(SITEPULSE_ADMIN_ASSETS_JS_PATH . 'frontend.js'),
     260            true // Load in footer
     261        );
     262
     263        $post_load_events = array_values($post_load_events);
     264        usort($post_load_events, function($a, $b) {
     265            return $b['total_ms'] <=> $a['total_ms'];
     266        });
     267
    187268        wp_localize_script(
    188269            $this->plugin->setPrefix( SITEPULSE_PREFIX . '_frontend_script'),
     
    190271                'rest_url' => esc_url_raw( rest_url() ),
    191272                'nonce'    => wp_create_nonce( 'wp_rest' ),
    192             ]
     273                'post_load_events' => array_slice($post_load_events, 0, 5),
     274                'post_load_events_count' => count( $post_load_events ),
     275            ]
    193276        );
    194277    }
     278
     279    /**
     280     * Get current page load time in milliseconds
     281     */
     282    public static function get_current_load_time() {       
     283        global $timestart;
     284
     285        if ( ! defined( 'SITEPULSE_SITELOAD_START' ) ) {
     286            $start = $timestart;
     287        } else {
     288            $start = SITEPULSE_SITELOAD_START;
     289        }
     290
     291        if ( ! $start ) return null;
     292       
     293        return (microtime(true) - $start) * 1000;
     294    }
    195295}
  • sitepulse/trunk/class/profiler.php

    r3374045 r3383675  
    109109                    foreach ($callbacks as $id => $cb) {
    110110                        // Avoid double-wrapping and skip our own closures
    111                         // var_dump($id);
    112                         // exit;
    113111                        $key = self::key($hook_name, $priority, $id);
    114112                        if (isset(self::$wrapped[$key])) continue;
     
    124122                            $elapsed = microtime(true) - $start;
    125123
    126                             Sitepulse_Profiler::record($hook_name, (int)$priority, (string)$id, $orig, $elapsed);
     124                            // Only record if execution time is measurable (> 0.0001 seconds = 0.1ms)
     125                            if ($elapsed > 0.0001) {
     126                                Sitepulse_Profiler::record($hook_name, (int)$priority, (string)$id, $orig, $elapsed);
     127                            }
    127128                            return $result;
    128129                        };
     
    196197            $fileline = self::fileline($callable);
    197198
     199            // Convert seconds to milliseconds for better readability
     200            $elapsed_ms = $elapsed * 1000;
     201
     202            if ( $elapsed_ms < 0.5 ) {
     203                // Ignore very fast callbacks
     204                return;
     205            }
     206
    198207            // Use a stable key per callback (hook+priority+signature+fileline)
    199208            $key = md5(implode('|', [$hook, $priority, $sig, $fileline]));
    200209
    201             if (!isset(self::$stats[$key])) {
     210            if ( ! isset( self::$stats[$key] ) ) {
    202211                $plugin_or_theme = '(unknown)';
    203212                if (strpos($fileline, 'wp-content/plugins/') === 0) {
     
    216225                    'calls'    => 0,
    217226                    'total'    => 0.0,
     227                    'total_ms' => 0.0,  // Total time in milliseconds
     228                    'avg_ms'   => 0.0,  // Average time in milliseconds
    218229                    'max'      => 0.0,
     230                    'max_ms'   => 0.0,  // Max time in milliseconds
    219231                    'source'   => $plugin_or_theme,
    220232                ];
    221233            }
     234           
    222235            self::$stats[$key]['calls']++;
    223236            self::$stats[$key]['total'] += $elapsed;
     237
     238            self::$stats[$key]['total_ms'] += $elapsed_ms;
     239            self::$stats[$key]['avg_ms'] = self::$stats[$key]['total_ms'] / self::$stats[$key]['calls'];
     240
    224241            if ($elapsed > self::$stats[$key]['max']) {
    225242                self::$stats[$key]['max'] = $elapsed;
    226             }
     243                self::$stats[$key]['max_ms'] = $elapsed_ms;
     244            }
     245        }
     246
     247        /**
     248         * Format elapsed time with appropriate units (ms for < 1s, s for >= 1s)
     249         * @param float $seconds Time in seconds
     250         * @return string Formatted time string
     251         */
     252        public static function format_time(float $seconds): string {
     253            if ($seconds >= 1.0) {
     254                return number_format($seconds, 3) . 's';
     255            } else {
     256                return number_format($seconds * 1000, 2) . 'ms';
     257            }
     258        }
     259
     260        /**
     261         * Get stats with formatted timing values
     262         * @return array Stats with formatted timing
     263         */
     264        public static function get_formatted_stats(): array {
     265            $formatted_stats = [];
     266            foreach (self::$stats as $key => $stat) {
     267                $formatted_stats[$key] = $stat;
     268                $formatted_stats[$key]['total_formatted'] = self::format_time($stat['total']);
     269                $formatted_stats[$key]['avg_formatted'] = self::format_time($stat['total'] / max(1, $stat['calls']));
     270                $formatted_stats[$key]['max_formatted'] = self::format_time($stat['max']);
     271            }
     272            return $formatted_stats;
    227273        }
    228274    }
  • sitepulse/trunk/inc/api_backend.php

    r3374045 r3383675  
    224224} );
    225225
     226// Register the REST API route for setting page load active status
     227add_action( 'rest_api_init', function() {
     228    register_rest_route( 'sitepulse/v1', '/wpsprealtimemode/set_active', [
     229        'methods'  => 'POST',
     230        'callback' => 'sitepulse_realtime_mode',
     231        'permission_callback' => function() {
     232            // adjust capability as needed
     233            return current_user_can( 'manage_options' );
     234        },
     235        'args' => [
     236            '_wpnonce' => [
     237                'required' => true,
     238                'description' => 'WP REST API nonce for authentication',
     239                'type' => 'string',
     240            ],
     241        ],
     242    ] );
     243} );
     244
     245add_action( 'rest_api_init', function() {
     246    register_rest_route( 'sitepulse/v1', '/wpsprealtimemode/get_active', [
     247        'methods'  => 'POST',
     248        'callback' => 'sitepulse_get_realtime_mode',
     249        'permission_callback' => function() {
     250            // adjust capability as needed
     251            return current_user_can( 'manage_options' );
     252        },
     253        'args' => [
     254            '_wpnonce' => [
     255                'required' => true,
     256                'description' => 'WP REST API nonce for authentication',
     257                'type' => 'string',
     258            ],
     259        ],
     260    ] );
     261} );
     262
     263// sitepulse_get_realtime_mode
     264function sitepulse_get_realtime_mode( WP_REST_Request $request ) {
     265    $is_active = get_transient( "sitepulse_realtime_tracking" );
     266
     267    return rest_ensure_response( [
     268        'success'     => true,
     269        'real_time_status' => $is_active ? true : false
     270    ] );
     271}
     272
     273function sitepulse_realtime_mode( WP_REST_Request $request ) {
     274
     275    $result = false;
     276    if ( isset( $request['real_time_status'] ) && $request['real_time_status'] ) {
     277        set_transient( "sitepulse_realtime_tracking", true, SITEPULSE_REALTIME_TRACKING_TIME );
     278        $result = true;
     279    } else {
     280        delete_transient( "sitepulse_realtime_tracking" );
     281    }
     282
     283    return rest_ensure_response( [
     284        'success'     => $result,
     285    ] );
     286}
     287
    226288function sitepulse_enable_clear_curl_api_events( WP_REST_Request $request ) {
    227289    $sitepulse_CurLoader = new Sitepulse_CurLoader();
     
    296358
    297359    // Enable the single report mode
    298     if ( $curlSwitch || $loadSwitch ) {
    299         update_option( "sitepulse_report_mode_active", 1 );
    300     }
    301 
    302     if ( ! $curlSwitch && ! $loadSwitch ) {
    303         update_option( "sitepulse_report_mode_active", 0 );
    304     }
     360    // if ( $curlSwitch || $loadSwitch ) {
     361    //     update_option( "sitepulse_report_mode_active", 1 );
     362    // }
     363
     364    // if ( ! $curlSwitch && ! $loadSwitch ) {
     365    //     update_option( "sitepulse_report_mode_active", 0 );
     366    // }
    305367
    306368    return rest_ensure_response( [
  • sitepulse/trunk/loader.php

    r3374045 r3383675  
    11<?php
    22/**
    3  * Plugin Name:       SitePulse
     3 * Plugin Name:       SitePulse - See What’s Powering (or Slowing) Your Site
    44 * Description:       SitePulse gives you real-time insights into your WordPress site’s performance, slow queries, and bottlenecks - so you can keep your site fast, healthy, and optimized.
    5  * Version:           1.0.8
     5 * Version:           1.1.0
    66 * Author:            Frederic Guzman
    77 * Author URI:        https://www.nilbug.com
     
    1313
    1414// Prevent to access the file from outside of WordPress
    15 if(!defined('ABSPATH')) {
     15if ( ! defined('ABSPATH') ) {
    1616    exit;
    1717}
     
    1919// SitePulse defines. He.
    2020define( 'SITEPULSE_PLUGIN_FILE', __FILE__ );
    21 define( 'SITEPULSE_DEBUG',                 false );
    22 define( 'SITEPULSE_STRESS_MODE',           false );
    23 define( 'SITEPULSE_VERSION',               '1.0.8' );
     21if ( ! defined( 'SITEPULSE_DEBUG' ) ) {
     22    define( 'SITEPULSE_DEBUG', false );
     23}
     24
     25if ( ! defined( 'SITEPULSE_STRESS_MODE' ) ) {
     26    define( 'SITEPULSE_STRESS_MODE', false );
     27}
     28
     29define( 'SITEPULSE_VERSION',               '1.1.0' );
    2430define( 'SITEPULSE_NAME',                  'SitePulse' );
    2531define( 'SITEPULSE_SLUG',                  'sitepulse' );
    2632define( 'SITEPULSE_PREFIX',                'wpsp' );
    2733define( 'SITEPULSE_PREFIX_SEPARATOR',        '_' );
    28 define( 'SITEPULSE_WEB_MAIN',              'https://wp-rocket.me/' );
    2934define( 'SITEPULSE_PATH',                  plugin_dir_path( __FILE__ ) . '/' );
    3035define( 'SITEPULSE_TEXT_DOMAIN_PATH',      SITEPULSE_PATH . 'languages/' . '/' );
     
    6166define( 'SITEPULSE_CURL_API_DEFAULT_THRESHOLD', 1 );
    6267
     68// Real time tracking constants
     69define( 'SITEPULSE_REALTIME_TRACKING_TIME', 30 ); // In seconds
     70
    6371// Load profiler class
    6472require_once SITEPULSE_CLASS_PATH . 'profiler.php';
    6573require_once SITEPULSE_CLASS_PATH . 'curloader.php';
    6674require_once SITEPULSE_PATH . 'inc/api_backend.php';
     75
     76// Enabling plugin profiler
     77update_option( 'sitepulse_plugins_profiler_enabled', true );
    6778
    6879class Sitepulse_Loader {
  • sitepulse/trunk/readme.txt

    r3374073 r3383675  
    1 === SitePulse ===
     1=== SitePulse - See What’s Powering (or Slowing) Your Site ===
    22Contributors: nilbug, frederickgzmn
    3 Tags: monitoring, profiler, performance, pagespeed, admin
     3Tags: performance, profiler, optimization, speed, insight, monitoring, hooks, load time
    44Tested up to: 6.8
    55Requires PHP: 7.4
    6 Stable tag: 1.0.8
     6Stable tag: 1.1.0
    77License: GPLv2 or later
    88License URI: https://www.gnu.org/licenses/gpl-2.0.html
    9 Lightweight uptime monitoring, API checks, and performance profiling dashboard for WordPress.
     9Understand what powers (or slows) your site - get real-time insights into page loads, plugins, and hooks directly from your dashboard.
    1010
    1111== Description ==
     
    3737== Screenshots ==
    38381. Dashboard overview (status and recent checks)
    39 2. API monitor screen
    40 3. Performance profiler output
    41 4. Frontend Page/Post Tracker Icons
    42 5. Frontend Page/Post Tracker Toggles
     392. insights screen
     403. Settings and system info
     414. Resources tracker
     425. Real time tracker running
    4343
    4444== Changelog ==
     45= 1.1.0 =
     46* User-friendly focus: Settings page, block cronjobs, block emails from SMTP or send email, real-time tracking frontend interface, load time per page, plugin space usage improved etc.
     47
    4548= 1.0.0 =
    4649* Initial public release: uptime monitoring, admin dashboard, basic profiler, and API endpoints.
  • sitepulse/trunk/templates/backend/dashboard.php

    r3374045 r3383675  
    2020    <div class="col-12">
    2121      <div class="sp-buttons">
     22        <button type="button" class="btn btn-outline-danger sp_curl_and_profiler_clear_events m-1">
     23          <?php echo esc_html__( 'Clear Event and Load Data', 'sitepulse' ); ?>
     24        </button>
     25
    2226        <div class="header-actions <?php if ( $lowhttp_enabled != 1 && $sitepulse_profiler_enabled != 1 ) { echo 'disabled'; } ?>">
    2327            <label class="form-switch me-2">
  • sitepulse/trunk/templates/backend/resource_load.php

    r3374045 r3383675  
    55?>
    66<div class="sp-dashboard container-fluid my-4">
    7     <div class="row">
    8       <div class="col-lg-6 col-12"></div>
    9       <div class="col-lg-6 col-12">
     7  <div class="row">
     8    <div class="col-lg-6 col-12"></div>
     9    <div class="col-lg-6 col-12">
    1010      <div class="sp-buttons">
    11           <button type="button" class="btn btn-outline-danger sp_profiler_clear_events m-1">
    12             <?php echo esc_html__( 'Clear Load Events', 'sitepulse' ); ?>
    13           </button>
    14           <div class="header-actions">
    15               <?php $sitepulse_profiler_enabled = get_option('sitepulse_profiler_enabled', null ); ?>
    16               <label class="form-switch me-2">
    17               <input type="checkbox" <?php checked($sitepulse_profiler_enabled, '1'); ?> id="sp-profiler" class="form-check-input sp_profiler">
    18               <span class="form-check-label"><?php echo esc_html__( 'LoadSentinel', 'sitepulse' ); ?></span>
    19               </label>
    20           </div>
    21           </div>
     11        <button type="button" class="btn btn-outline-danger sp_profiler_clear_events m-1">
     12          <?php echo esc_html__( 'Clear Load Events', 'sitepulse' ); ?>
     13        </button>
     14        <div class="header-actions">
     15          <?php $sitepulse_profiler_enabled = get_option('sitepulse_profiler_enabled', null ); ?>
     16          <label class="form-switch me-2">
     17            <input type="checkbox" <?php checked($sitepulse_profiler_enabled, '1'); ?> id="sp-profiler" class="form-check-input sp_profiler">
     18            <span class="form-check-label"><?php echo esc_html__( 'LoadSentinel', 'sitepulse' ); ?></span>
     19          </label>
     20        </div>
    2221      </div>
     22    </div>
    2323  </div>
    2424
     
    5757
    5858  <div class="row">
    59     <div>
    60       <div class="card">
    61           <div class="card-header d-flex justify-content-between align-items-center">
    62             <div>
    63               <strong><?php echo esc_html__( 'Plugins/Themes — Hooks', 'sitepulse' ); ?></strong>
    64               <div class="text-muted small"><?php echo esc_html__( 'Functions and methods from plugins/themes that hit the site', 'sitepulse' ); ?></div>
    65             </div>
    66             <div class="text-end">
    67               <?php
    68                 if ( $single_load_events ) {
    69                   if ( isset( $sp_current_tracked_pageid ) && ! empty( $sp_current_tracked_pageid ) && is_numeric( $sp_current_tracked_pageid ) ) {
    70                     $sitepulse_load_single_page_id = sanitize_text_field( wp_unslash( $sp_current_tracked_pageid ) );
    71                     $sitepulse_load_single_page_title = get_the_title( $sitepulse_load_single_page_id );
    72 
    73                     if ( $sitepulse_load_single_page_title ) {
    74                       ?>
    75                         <span class="badge bg-secondary"><?php echo esc_html__('Single Detail:', 'sitepulse'); ?> <?php echo esc_attr($sitepulse_load_single_page_title); ?></span>
    76                       <?php
    77                     }
     59    <div class="card">
     60        <div class="card-header d-flex justify-content-between align-items-center">
     61          <div>
     62            <strong><?php echo esc_html__( 'Plugins/Themes — Hooks', 'sitepulse' ); ?></strong>
     63            <div class="text-muted small"><?php echo esc_html__( 'Functions and methods from plugins/themes that hit the site', 'sitepulse' ); ?></div>
     64          </div>
     65          <div class="text-end">
     66            <?php
     67              if ( $single_load_events ) {
     68                if ( isset( $sp_current_tracked_pageid ) && ! empty( $sp_current_tracked_pageid ) && is_numeric( $sp_current_tracked_pageid ) ) {
     69                  $sitepulse_load_single_page_id = sanitize_text_field( wp_unslash( $sp_current_tracked_pageid ) );
     70                  $sitepulse_load_single_page_title = get_the_title( $sitepulse_load_single_page_id );
     71
     72                  if ( $sitepulse_load_single_page_title ) {
     73                    ?>
     74                      <span class="badge bg-secondary"><?php echo esc_html__('Single Detail:', 'sitepulse'); ?> <?php echo esc_attr($sitepulse_load_single_page_title); ?></span>
     75                    <?php
    7876                  }
    7977                }
     78              }
     79            ?>
     80        </div>
     81          <div class="text-end">
     82            <span class="badge bg-primary"><?php echo esc_html( sprintf(
     83              /* translators: %d: number of active plugins */
     84              esc_html__( 'Active plugins: %d', 'sitepulse' ),
     85              (int) $active_plugins_count
     86            ) ); ?></span>
     87            <span class="badge bg-outline-secondary ms-2"><?php echo esc_html( sprintf(
     88              /* translators: %d: number of hook events detected */
     89              esc_html__( 'Events: %d', 'sitepulse' ),
     90              (int) count( $stats )
     91            ) ); ?></span>
     92          </div>
     93        </div>
     94      <div class="card-body p-0">
     95        <ul class="list-group list-group-flush scroll-list">
     96          <?php
     97            // Sort by total time desc by default
     98            $stats = array_values($stats);
     99
     100            usort($stats, function($a, $b) {
     101                return $b['total_ms'] <=> $a['total_ms'];
     102            });
     103
     104            if ( empty( $stat ) && round( count( $stats ) ) == 0 ) {
    80105              ?>
    81           </div>
    82             <div class="text-end">
    83               <span class="badge bg-primary"><?php echo esc_html( sprintf(
    84                 /* translators: %d: number of active plugins */
    85                 esc_html__( 'Active plugins: %d', 'sitepulse' ),
    86                 (int) $active_plugins_count
    87               ) ); ?></span>
    88               <span class="badge bg-outline-secondary ms-2"><?php echo esc_html( sprintf(
    89                 /* translators: %d: number of hook events detected */
    90                 esc_html__( 'Events: %d', 'sitepulse' ),
    91                 (int) count( $stats )
    92               ) ); ?></span>
    93             </div>
    94           </div>
    95         <div class="card-body p-0">
    96           <ul class="list-group list-group-flush scroll-list">
    97             <?php
    98               // Sort by total time desc by default
    99               $stats = array_values($stats);
    100 
    101               usort($stats, function($a, $b) {
    102                   return $b['total'] <=> $a['total'];
    103               });
    104 
    105               if ( empty( $stat ) && round( count( $stats ) ) == 0 ) {
    106                 ?>
    107                 <li class="list-group-item">
    108                   <div class="text-end">
    109                     <div class="item-meta m-3">
    110                         <?php echo esc_html__( 'No plugin or theme activity detected. Please enable the monitor tracker to start collecting data.', 'sitepulse' ); ?>
    111                     </div>
     106              <li class="list-group-item">
     107                <div class="text-end">
     108                  <div class="item-meta m-3">
     109                      <?php echo esc_html__( 'No plugin or theme activity detected. Please enable the monitor tracker to start collecting data.', 'sitepulse' ); ?>
    112110                  </div>
    113                   <div class="text-end">
    114                     <span class="badge bg-success"><?php echo esc_html__( 'Log', 'sitepulse' ); ?></span>
    115                   </div>
    116                 </li>
     111                </div>
     112                <div class="text-end">
     113                  <span class="badge bg-success"><?php echo esc_html__( 'Log', 'sitepulse' ); ?></span>
     114                </div>
     115              </li>
     116              <?php
     117            }
     118
     119            foreach ( $stats as $stat ) :
     120              if ( ! empty( $stat ) ) {
     121                $avg = $stat['calls'] ? ($stat['total_ms'] / $stat['calls']) : 0;
     122
     123                // Assign badge class based on total time in ms
     124                $time_ms = $stat['total_ms'];
     125                if ($time_ms > 1000) {
     126                  $badge_class = 'bg-danger';
     127                } elseif ($time_ms > 500) {
     128                  $badge_class = 'bg-warning text-dark';
     129                } elseif ($time_ms > 200) {
     130                  $badge_class = 'bg-info text-dark';
     131                } elseif ($time_ms > 100) {
     132                  $badge_class = 'bg-secondary';
     133                } else {
     134                  $badge_class = 'bg-success';
     135                }
     136
     137                // Remove special characters from plugin_or_theme
     138                $plugin_or_theme = str_replace('-', ' ', $stat['source']);
     139
     140                  ?>
     141                  <li class="list-group-item row">
     142                          <div class="col-lg-6 col-12">
     143                              <div class="fw-semibold"> <?php echo esc_html( ucfirst( $plugin_or_theme ) ); ?> </div>
     144                              <div class="item-meta"><?php echo esc_html( $stat['sig'] ); ?> &middot; <span class="mono"><?php echo esc_html( $stat['fileline'] ); ?></span></div>
     145                          </div>
     146                          <div class="text-end col-lg-2 col-12">
     147                              <span class="badge <?php echo esc_attr( $badge_class ); ?>"><?php echo esc_html( sprintf(
     148                                /* translators: %s: total execution time in milliseconds */
     149                                esc_html__( 'Total: %s ms', 'sitepulse' ),
     150                                number_format( $time_ms, 2 )
     151                              ) ); ?></span>
     152                              <div class="text-muted small mt-1">
     153                              <p><?php echo esc_html( sprintf(
     154                                /* translators: %s: average execution time in milliseconds */
     155                                esc_html__( 'Avg: %s', 'sitepulse' ),
     156                                number_format( $avg, 2 )
     157                              ) ); ?></p>
     158                              </div>
     159                          </div>
     160                          <div class="text-end col-lg-2 col-12">
     161                              <span class="badge bg-success"><?php echo esc_html( sprintf(
     162                                /* translators: %s: WordPress hook name */
     163                                esc_html__( 'hook: %s', 'sitepulse' ),
     164                                $stat['hook']
     165                              ) ); ?></span>
     166                              <div class="text-muted small mt-1">
     167                              <?php echo esc_html( sprintf(
     168                                /* translators: %d: number of times the hook was called */
     169                                esc_html__( '%d calls', 'sitepulse' ),
     170                                (int) $stat['calls']
     171                              ) ); ?>
     172                              </div>
     173                          </div>
     174                  </li>
    117175                <?php
    118176              }
    119 
    120               foreach ( $stats as $stat ) :
    121                 if ( ! empty( $stat ) ) {
    122                   $avg = $stat['calls'] ? ($stat['total'] / $stat['calls']) : 0;
    123 
    124                   // Assign badge class based on total time in ms
    125                   $time_ms = $stat['total'] * 1000;
    126                   if ($time_ms > 1000) {
    127                     $badge_class = 'bg-danger';
    128                   } elseif ($time_ms > 500) {
    129                     $badge_class = 'bg-warning text-dark';
    130                   } elseif ($time_ms > 200) {
    131                     $badge_class = 'bg-info text-dark';
    132                   } elseif ($time_ms > 100) {
    133                     $badge_class = 'bg-secondary';
    134                   } else {
    135                     $badge_class = 'bg-success';
    136                   }
    137 
    138                   // Remove special characters from plugin_or_theme
    139                   $plugin_or_theme = str_replace('-', ' ', $stat['source']);
    140 
    141                     ?>
    142                     <li class="list-group-item row">
    143                             <div class="col-lg-6 col-12">
    144                                 <div class="fw-semibold"> <?php echo esc_html( ucfirst( $plugin_or_theme ) ); ?> </div>
    145                                 <div class="item-meta"><?php echo esc_html( $stat['sig'] ); ?> &middot; <span class="mono"><?php echo esc_html( $stat['fileline'] ); ?></span></div>
    146                             </div>
    147                             <div class="text-end col-lg-2 col-12">
    148                                 <span class="badge <?php echo esc_attr( $badge_class ); ?>"><?php echo esc_html( sprintf(
    149                                   /* translators: %s: total execution time in milliseconds */
    150                                   esc_html__( 'Total: %s ms', 'sitepulse' ),
    151                                   number_format( $time_ms, 2 )
    152                                 ) ); ?></span>
    153                                 <div class="text-muted small mt-1">
    154                                 <p><?php echo esc_html( sprintf(
    155                                   /* translators: %s: average execution time in milliseconds */
    156                                   esc_html__( 'Avg: %s', 'sitepulse' ),
    157                                   number_format( $avg * 1000, 2 )
    158                                 ) ); ?></p>
    159                                 </div>
    160                             </div>
    161                             <div class="text-end col-lg-2 col-12">
    162                                 <span class="badge bg-success"><?php echo esc_html( sprintf(
    163                                   /* translators: %s: WordPress hook name */
    164                                   esc_html__( 'hook: %s', 'sitepulse' ),
    165                                   $stat['hook']
    166                                 ) ); ?></span>
    167                                 <div class="text-muted small mt-1">
    168                                 <?php echo esc_html( sprintf(
    169                                   /* translators: %d: number of times the hook was called */
    170                                   esc_html__( '%d calls', 'sitepulse' ),
    171                                   (int) $stat['calls']
    172                                 ) ); ?>
    173                                 </div>
    174                             </div>
    175                     </li>
    176                   <?php
    177                 }
    178               endforeach; ?>
    179 
    180             <!-- example static items commented out -->
    181           </ul>
    182         </div>
     177            endforeach; ?>
     178
     179          <!-- example static items commented out -->
     180        </ul>
    183181      </div>
    184182    </div>
     183   
    185184   
    186185    <?php if ( isset($disk_write) && is_array($disk_write) ): ?>
Note: See TracChangeset for help on using the changeset viewer.