APM - Track clicks on webtop resources

Problem this snippet solves:

This configuration sample provide to the administrator a way to log all APM webtop resources clicked by a logged-in user. Those logs can be exported to external systems like Splunk to do some Analytics.

Those configuration objects are currently for experimentation only. It is not recommended for production yet.

How to use this snippet:

Insert this javascript snippet to the end of the hometab.inc file that you can find and modify in Advanced Customization >> Your Webtop Name.

window.onclick = function(e) { 
    if (e.target.parentNode.className == "favorite") {
        var xhttp = new XMLHttpRequest();
        console.log(e.target.parentNode.id);
        var uri = "/analytics?t=" + Math.random() + "&r=" + encodeURIComponent(window.btoa(e.target.parentNode.id)) + "&d=" + Date.now();
        xhttp.open("GET", uri, true);
        xhttp.send();
    }
    if (e.target.parentNode.className == "image" && e.target.parentNode.parentNode.className == "favorite") {
        var xhttp = new XMLHttpRequest();
        console.log(e.target.parentNode.id);
        var uri = "/analytics?t=" + Math.random() + "&r=" + encodeURIComponent(window.btoa(e.target.parentNode.parentNode.id)) + "&d=" + Date.now();
        xhttp.open("GET", uri, true);
        xhttp.send();
    }
};

Then, add the irule provided in the attachment of this article to the Virtual Server with the access profile.

Here is an example of logged accesses :

time=08/14/17 20:19:24, clientip=10.20.30.5, user=test, session=a7585749, res=/Common/fullvpn-test
time=08/14/17 20:19:22, clientip=10.20.30.5, user=test, session=a7585749, res=/Common/debian-test

Code :

when RULE_INIT {
    # Required Before v13.x
    set static::ACCESS_LOG_PREFIX  "0149ffff:5:"
}
when ACCESS_ACL_ALLOWED {
    if { [HTTP::cookie exists MRHSession] and [ACCESS::session exists -state_allow -sid [HTTP::cookie MRHSession]] and [HTTP::path] eq "/analytics" } {
        set time [clock format [expr { [URI::query [HTTP::uri] "d"]/1000 }] -format "%D %T"]
        set res [b64decode [URI::decode [URI::query [HTTP::uri] "r"]]]
        set user [ACCESS::session data get session.logon.last.username]
        set session [HTTP::cookie value LastMRH_Session]
        set clientip [ACCESS::session data get session.user.clientip]

        #
        # Basic logging. Remote or local logging settings can be configured
        #
        
        # log local0. "time=$time, clientip=$clientip, user=$user, session=$session, res=$res"
        
        #
        # ACCESS logging before v13.x
        #
        
        log -noname accesscontrol.local1.notice "$static::ACCESS_LOG_PREFIX /Common/ap-ad-auth:Common:$session: time=$time, clientip=$clientip, user=$user, res=$res"
        
        #
        # ACCESS::log is available in v13.x. Log saved in Access report too.
        #
        
        # ACCESS::log accesscontrol.notice "time=$time, clientip=$clientip, user=$user, res=$res"
        
        ACCESS::respond 200 noserver
    }
}

Tested this on version:

11.5
Updated Jun 06, 2023
Version 2.0
  • Hi Yann,

    you can use

    ACCESS_ACL_ALLOWED
    instead of
    HTTP_REQUEST
    . it prevent session lookup and limit irule suspend in high load architectures.

    when ACCESS_ACL_ALLOWED {
        if { [HTTP::path] eq "/analytics" } {
            set time [clock format [expr { [URI::query [HTTP::uri] "d"]/1000 }] -format "%D %T"]
            set res [b64decode [URI::decode [URI::query [HTTP::uri] "r"]]]
            set user [ACCESS::session data get session.logon.last.username]
            set session [HTTP::cookie value LastMRH_Session]
            set clientip [ACCESS::session data get session.user.clientip]
            log local0. "time=$time, clientip=$clientip, user=$user, session=$session, res=$res"
            ACCESS::respond 200 noserver
        }
    }
    
  • Hi Stanislas,

     

    Thank you for your suggestion. I modified the original code with your update.

     

    Thank you for your help

     

    BR

     

    Yann

     

  • Anything different using this on v12.1? i put the javascript code in the hometab.inc file but it doesn't seem to show up or function. I found the hometab.inc in the webtop as it wasn't in the access profiles.

     

    This should do what the customer is requesting.. to obtain analytics on what items are being used on the access portal.. and by whom.

     

    Thanks for any pointers here.. Much appreciated..

     

  • Hi Brad,

     

    Do you have some logs or error displayed in the console of your browser ?

     

    Which browser do you use for testing ?

     

    Are you using the same hostname for the webtop and analytics URI ? Otherwise, you may face a CORS issue.

     

    Will run the script in my labs for testing

     

    Hope it helps

     

    Yann

     

  • I moved it up a bit in the hometab.inc. It seems that putting it at the bottom was not bringing it in. I then added the iRule to the virtual server and now it is logging just fine.

     

    Placed it before var HangupErrorUserInitiated = 1; it probably could go after that, too. but it works..

     

    Some testing.. it isn't logging 100% of selections. Not sure why and will have to add a data capture. Usually it will log when used with IE but sometimes it misses one. Chrome is showing more misses on the logging. around 30-40% it seems.

     

    thanks.

     

  • after lots of trying it seems it isn't working with any reliability. the onclick event does not trigger most of the time.. it doesn't make a difference if it is chrome or IE browser both seem to 'miss' the event.

     

    if anyone has worked on this more and it is reliable we would really like to hear from you!!! it is something that has been desired. These analytics would be so helpful to know what items are being used.

     

    Thank you!

     

  • Hi Brad,

     

    I have done several tests using all kind of available resources on the webtop and I'm able to log everything. I found that when someone click on the icon of the resource and not the text, the script fails. So I added a piece of code to handle this case :

     

    if (e.target.parentNode.className == "image" && e.target.parentNode.parentNode.className == "favorite") {
        var xhttp = new XMLHttpRequest();
        console.log(e.target.parentNode.id);
        var uri = "/analytics?t=" + Math.random() + "&r=" + encodeURIComponent(window.btoa(e.target.parentNode.parentNode.id)) + "&d=" + Date.now();
        xhttp.open("GET", uri, true);
        xhttp.send();
    }
    

    In fact, the class of all icons is image, not favorite. So I check the parent element of the parent of the icon.

     

    I tested again and now it looks like it matches 100% of clicks. Let me know if it works fine for you.

     

    Have a nice day

     

    Yann

     

  • Well close.. Still didn't seem to be hitting 100%. But the following modification seems to be working much better..

    window.onclick = function (e) {
        if (e.target.parentNode.className == "favorite" || e.target.parentNode.className == "image" || e.target.parentNode.className == "caption") {
            var xhttp = new XMLHttpRequest();
            console.log(e.target.parentNode.id);
            var parentNodeId = "";
    
            //populate parent's parent
            if (e.target.parentNode.parentNode.id) {
                parentNodeId = e.target.parentNode.parentNode.id;
            }
            //overwrite parent's parent if parent not null
            if (e.target.parentNode.id) {
                parentNodeId = e.target.parentNode.id;
            }
    
            //open link
            if (parentNodeId) {
                var uri = "/analytics?t=" + Math.random() + "&r=" + encodeURIComponent(window.btoa(parentNodeId)) + "&d=" + Date.now();
                xhttp.open("GET", uri, true);
                xhttp.send();
            }
            //log failed result
            else {
                console.log("Parent ID is NULL");
            }
        }
    };
    
    
    
  • Hi Brad,

     

    My Bad, I forgot to include caption too. I never use them by the way.

     

    Thank you for your great work, I will update my code with your script.

     

    Have a nice day

     

    Yann

     

  • To make this work on Modern Customization the JavaScript needs to be changed. It will be installed in advanced customization under "Webtops" > "[webtop name]" > "user-webtop.js".

    window.onclick = function(e) { 
        if (e.target.className == "apmui-webtop-resource") {
            var tID = e.target.id;
        	console.log(tID);
    		var xhttp = new XMLHttpRequest();
    		var uri = "/analytics?t=" + Math.random() + "&r=" + encodeURIComponent(window.btoa(tID)) + "&d=" + Date.now();
            xhttp.open("GET", uri, true);
            xhttp.send();
        }
        if (e.target.parentNode.className == "apmui-webtop-resource") {
            var tID = e.target.parentNode.id;
    		console.log(tID);
    		var xhttp = new XMLHttpRequest();
    		var uri = "/analytics?t=" + Math.random() + "&r=" + encodeURIComponent(window.btoa(tID)) + "&d=" + Date.now();
            xhttp.open("GET", uri, true);
            xhttp.send();
        }
    	if (e.target.parentNode.parentNode.className == "apmui-webtop-resource") {
            var tID = e.target.parentNode.parentNode.id;
    		console.log(tID);
    		var xhttp = new XMLHttpRequest();
    		var uri = "/analytics?t=" + Math.random() + "&r=" + encodeURIComponent(window.btoa(tID)) + "&d=" + Date.now();
            xhttp.open("GET", uri, true);
            xhttp.send();
        }
    	if (e.target.parentNode.parentNode.parentNode.className == "apmui-webtop-resource") {
            var tID = e.target.parentNode.parentNode.parentNode.id;
    		console.log(tID);
    		var xhttp = new XMLHttpRequest();
    		var uri = "/analytics?t=" + Math.random() + "&r=" + encodeURIComponent(window.btoa(tID)) + "&d=" + Date.now();
            xhttp.open("GET", uri, true);
            xhttp.send();
        }
    };