RADIUSdesk

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revisionPrevious revision
Next revision
Previous revision
technical_discussions:click_to_connect_extra [2021/10/24 17:16] – [Introduction] admintechnical_discussions:click_to_connect_extra [2021/10/24 19:28] (current) – [Modify the Login Pages for the Extra Options] admin
Line 15: Line 15:
  
 ===== Files To Edit ===== ===== Files To Edit =====
 +==== ExtJs side (Dynamic Details Applet)  ====
 +  * Locate the file pnlDynamicDetailClickToConnect.js.
 +  * It is typically under ///var/www/rdcore/rd/classic/src/view/dynamicDetails/pnlDynamicDetailClickToConnect.js//
 +  * We will define two combo boxes below **cmbReSupply**
 +<code javascript>
 +var cmbReSupplyP = Ext.create('Ext.form.ComboBox', {
 +    fieldLabel      : 'Re-Supply Phone Interval',
 +    store           : reSupply,
 +    queryMode       : 'local',
 +    displayField    : 'name',
 +    valueField      : 'id',
 +    name            : 'ctc_resupply_phone_interval',
 +    itemId          : 'cmbReSupplyPhone',
 +    labelCls        : 'lblRd',
 +    allowBlank      : false,
 +    forceSelection  : true,
 +    value           : 0
 +});
 +
 +var cmbReSupplyDn = Ext.create('Ext.form.ComboBox', {
 +    fieldLabel      : 'Re-Supply DN Interval',
 +    store           : reSupply,
 +    queryMode       : 'local',
 +    displayField    : 'name',
 +    valueField      : 'id',
 +    name            : 'ctc_resupply_dn_interval',
 +    itemId          : 'cmbReSupplyDn',
 +    labelCls        : 'lblRd',
 +    allowBlank      : false,
 +    forceSelection  : true,
 +    value           : 0
 +});
 +</code>
 +  * Then we will add check boxes for these below the email checkbox
 +<code javascript>
 +{
 +    xtype       : 'checkbox',      
 +    fieldLabel  : 'Require Phone To Connect',
 +    itemId      : 'chkCtcRequirePhone',
 +    name        : 'ctc_require_phone',
 +    inputValue  : 'ctc_require_phone',
 +    checked     : false,
 +    disabled    : true
 +},
 +cmbReSupplyP,
 +{
 +    xtype       : 'checkbox',      
 +    fieldLabel  : 'Require DN To Connect',
 +    itemId      : 'chkCtcRequireDn',
 +    name        : 'ctc_require_dn',
 +    inputValue  : 'ctc_require_dn',
 +    checked     : false,
 +    disabled    : true
 +},
 +cmbReSupplyDn
 +</code>
 +  * You will notice that the items we added are disabled by default.
 +  * We need to also modify the View Controller to include these new components when certain action are done.
 +  * Locate the file vcDynamicDetailClickToConnect.js.
 +  * It is typically under ///var/www/rdcore/rd/classic/src/view/dynamicDetails/vcDynamicDetailClickToConnect.js//
 +  * Compare the following with your original code.
 +<code javascript>
 +Ext.define('Rd.view.dynamicDetails.vcDynamicDetailClickToConnect', {
 +    extend  : 'Ext.app.ViewController',
 +    alias   : 'controller.vcDynamicDetailClickToConnect',
 +    control: {
 +        '#chkClickToConnect' : {
 +            change: 'chkClickToConnectChange'
 +        },
 +        '#chkCtcRequireEmail' : {
 +            change:  'chkCtcRequireEmailChange'
 +        },
 +        '#chkCtcRequirePhone' : {
 +            change:  'chkCtcRequirePhoneChange'
 +        },
 +        '#chkCtcRequireDn' : {
 +            change:  'chkCtcRequireDnChange'
 +        }
 +    },
 +    chkClickToConnectChange: function(chk){
 +        var me      = this;
 +        var form    = chk.up('form');
 +        var un      = form.down('#txtConnectUsername');
 +        var sx      = form.down('#txtConnectSuffix');
 +        var cd      = form.down('#nrConnectDelay');
 +        var co      = form.down('#chkConnectOnly');
 +        var re      = form.down('#chkCtcRequireEmail');
 +        var rs      = form.down('#cmbReSupply');
 +        var rp      = form.down('#chkCtcRequirePhone');
 +        var rsp     = form.down('#cmbReSupplyPhone');
 +        var rdn     = form.down('#chkCtcRequireDn');
 +        var rsdn    = form.down('#cmbReSupplyDn');      
 +        var value   = chk.getValue();
 +        if(value){
 +            un.setDisabled(false);
 +            sx.setDisabled(false);
 +            cd.setDisabled(false);
 +            co.setDisabled(false);
 +            re.setDisabled(false); 
 +            rs.setDisabled(false);
 +            rp.setDisabled(false); 
 +            rsp.setDisabled(false);
 +            rdn.setDisabled(false); 
 +            rsdn.setDisabled(false);                   
 +        }else{
 +            un.setDisabled(true);
 +            sx.setDisabled(true);
 +            cd.setDisabled(true);
 +            co.setDisabled(true);
 +            re.setDisabled(true); 
 +            rs.setDisabled(true);
 +            rp.setDisabled(true); 
 +            rsp.setDisabled(true);
 +            rdn.setDisabled(true); 
 +            rsdn.setDisabled(true);    
 +        }
 +    },
 +    chkCtcRequireEmailChange: function(chk){
 +        var me      = this;
 +        var form    = chk.up('form');
 +        var value   = chk.getValue();
 +        var rs      = form.down('#cmbReSupply');
 +        if(value){
 +            rs.setDisabled(false);
 +        }else{
 +            rs.setDisabled(true);
 +        }       
 +    },
 +    chkCtcRequirePhoneChange: function(chk){
 +        var me      = this;
 +        var form    = chk.up('form');
 +        var value   = chk.getValue();
 +        var rs      = form.down('#cmbReSupplyPhone');
 +        if(value){
 +            rs.setDisabled(false);
 +        }else{
 +            rs.setDisabled(true);
 +        }       
 +    },
 +    chkCtcRequireDnChange: function(chk){
 +        var me      = this;
 +        var form    = chk.up('form');
 +        var value   = chk.getValue();
 +        var rs      = form.down('#cmbReSupplyDn');
 +        if(value){
 +            rs.setDisabled(false);
 +        }else{
 +            rs.setDisabled(true);
 +        }       
 +    }
 +});
 +</code>
 +  * Finally we need to add those extra fields to the grid displaying these items
 +  * Locate the file gridDynamicDetailEmails.js.
 +  * It is typically under ///var/www/rdcore/rd/classic/src/view/dynamicDetails/gridDynamicDetailEmails.js//
 +  * See the following snippet
 +<code javascript>
 +{ text: 'E-Mail',         dataIndex: 'email',    tdCls: 'gridTree', flex: 1,stateId: 'StateGridDynamicDetailEmails2'},
 +{ text: 'Phone',          dataIndex: 'phone',    tdCls: 'gridTree', flex: 1,stateId: 'StateGridDynamicDetailEmails2a'},
 +{ text: 'DN',             dataIndex: 'dn',       tdCls: 'gridTree', flex: 1,stateId: 'StateGridDynamicDetailEmails2b'},
 +
 +    text        : 'Captive MAC',
 +    dataIndex   : 'cp_mac', 
 +    tdCls       : 'gridTree',
 +    hidden      : true, 
 +    flex        : 1,
 +    stateId : 'DD_Email_B'
 +},
 +</code>
 +  * Remember to run **Sencha Command** after these to generate the optimized JavaScript code.
 +  * There is a dedicated Wiki Page in this site for it.
 +  * Next we will see what we need to modify on the CakePHP3 App Side.
 +
 +==== CakePHP3 App Side ====
 +  * We need to add extra fields to the **dynamic_details** sql table inside the **rd** database.
 +  * See the following SQL Patch snippet which add those fields if not present
 +<code sql>
 +if not exists (select * from information_schema.columns
 +    where column_name = 'ctc_require_phone' and table_name = 'dynamic_details' and table_schema = 'rd') then
 +    alter table dynamic_details add column `ctc_require_phone` tinyint(1) NOT NULL DEFAULT '0';
 +end if;
 +
 +if not exists (select * from information_schema.columns
 +    where column_name = 'ctc_resupply_phone_interval' and table_name = 'dynamic_details' and table_schema = 'rd') then
 +    alter table dynamic_details add column `ctc_resupply_phone_interval` int(4) NOT NULL DEFAULT '0';
 +end if;
 +
 +if not exists (select * from information_schema.columns
 +    where column_name = 'ctc_require_dn' and table_name = 'dynamic_details' and table_schema = 'rd') then
 +    alter table dynamic_details add column `ctc_require_dn` tinyint(1) NOT NULL DEFAULT '0';
 +end if;
 +
 +if not exists (select * from information_schema.columns
 +    where column_name = 'ctc_resupply_dn_interval' and table_name = 'dynamic_details' and table_schema = 'rd') then
 +    alter table dynamic_details add column `ctc_resupply_dn_interval` int(4) NOT NULL DEFAULT '0';
 +end if;
 +</code>
 +  * We also need to add extra field to the **data_collectors** sql table inside the **rd** database.
 +  * See the following SQL Patch snippet which add those fields if not present
 +<code sql>
 +if not exists (select * from information_schema.columns
 +    where column_name = 'phone' and table_name = 'data_collectors' and table_schema = 'rd') then
 +    alter table data_collectors add column `phone` varchar(36) NOT NULL DEFAULT '';
 +end if;
 +
 +if not exists (select * from information_schema.columns
 +    where column_name = 'dn' and table_name = 'data_collectors' and table_schema = 'rd') then
 +    alter table data_collectors add column `dn` varchar(36) NOT NULL DEFAULT '';
 +end if;
 +</code>
 +  * After taking care of the database lets see what needs to be modified to the CakePHP 3 controllers
 +  * Locate the file DynamicDetailsController.php.
 +  * It is typically under ///var/www/rdcore/cake3/rd_cake/src/Controller/DynamicDetailsController.php//
 +  * There is not much that needs modification since CakePHP takes care of most things behind the scenes.
 +  * Just locate the **editClickToConnect** method and modify **$check_items**
 +<code php>
 +$check_items = [
 + 'connect_check',
 + 'connect_only',
 + 'ctc_require_email',
 + 'ctc_require_phone',
 + 'ctc_require_dn',
 +];
 +</code>
 +  * Locate the file DataCollectorsController.php.
 +  * It is typically under ///var/www/rdcore/cake3/rd_cake/src/Controller/DataCollectorsController.php//
 +  * We will modify the login page eventually to have various options displayed in the Pop-Up for Click-To-Connect so we need to modify the DataCollectorsController to only check the email field **IF PRESENT**
 +  * See the following snippets 
 +<code php>
 +public function addMac(){
 +    if ($this->request->is('post')) { 
 +        if($this->request->getData('email')){
 +            if(!$this->_test_email($this->request->data['email'])){
 +                return;
 +            }
 +        }
 +        $dd = $this->_find_dynamic_detail_id();
 +</code>
 +  * And this one 
 +<code php>
 +private function _addOrEdit($user,$type= 'add') {
 +
 +    //__ Authentication + Authorization __
 +    
 +    $user_id    = $user['id'];
 +    
 +    if($this->request->getData('email')){
 +        if(!$this->_test_email($this->request->data['email'])){
 +            return;
 +        }
 +    }
 +    
 +    $this->request->data['public_ip'] = $this->request->clientIp();
 +</code>
 +  * Also the **macCheck** method need to give feedback on those new methods in order so we can determine which fields must be shown in the Pop-Up
 +<code php>
 +public function macCheck(){
 +
 +    $data   = $this->request->data;      
 +    $q_r    = $this->_find_dynamic_detail_id();
 +    
 +    $data['ctc_require_email'] = false; // By defaul don't ask for email;     
 +                       
 +    if($q_r){
 +
 +        //Once we found the Dynamic Login Page; We need to figure out if we need to ask for an email for this person
 +        //For that we need to look for a combo **dynamic_detail_id** and **mac**
 +        //IF found look at the modify timestamp and if it 'expired' ask for it again
 +        //If not found ask for it (ctc_require_email == true)
 +        //Else we set ctc_require_email == false since we found the combo and it has not expired yet  
 +        $dd_id          = $q_r->dynamic_detail_id;
 +        $dd_resuply_int = $q_r->dynamic_detail->ctc_resupply_email_interval;
 +        $dd_resuply_intP = $q_r->dynamic_detail->ctc_resupply_phone_interval;
 +        $data['dd_id' = $dd_id;
 +        
 +        if($q_r->dynamic_detail->ctc_require_email == true){
 +            $q_dd = $this->{$this->main_model}->find()
 +                ->where([$this->main_model.'.dynamic_detail_id' => $dd_id,$this->main_model.'.mac' => $this->request->data['mac']])
 +                ->first();
 +            if($q_dd){
 +                if($dd_resuply_int > 0){ //This has an expiry date lets compare
 +                
 +                    $expiry_time    = $q_dd->modified->toUnixString()+($dd_resuply_int * 24 * 60 *60);
 +                    $now            = new FrozenTime();
 +                    if($expiry_time < $now->toUnixString()){
 +                        //It already expired ask for a new one
 +                        $data['ctc_require_email'] = true; 
 +                    }   
 +                }
 +            }else{
 +                $data['ctc_require_email'] = true; //We did not found it so have to supply email
 +            }
 +        }
 +        
 +        if($q_r->dynamic_detail->ctc_require_phone == true){
 +            $q_dd = $this->{$this->main_model}->find()
 +                ->where([$this->main_model.'.dynamic_detail_id' => $dd_id,$this->main_model.'.mac' => $this->request->data['mac']])
 +                ->first();
 +            if($q_dd){
 +                if($dd_resuply_intP > 0){ //This has an expiry date lets compare                  
 +                    $expiry_time    = $q_dd->modified->toUnixString()+($dd_resuply_intP * 24 * 60 *60);
 +                    $now            = new FrozenTime();
 +                    if($expiry_time < $now->toUnixString()){
 +                        //It already expired ask for a new one
 +                        $data['ctc_require_email'] = true; 
 +                    }   
 +                }
 +            }else{
 +                $data['ctc_require_phone'] = true; //We did not found it so have to supply email
 +            }
 +        }
 +        
 +        if($q_r->dynamic_detail->ctc_require_dn == true){
 +            $q_dd = $this->{$this->main_model}->find()
 +                ->where([$this->main_model.'.dynamic_detail_id' => $dd_id,$this->main_model.'.mac' => $this->request->data['mac']])
 +                ->first();
 +            if($q_dd){
 +                if($dd_resuply_intP > 0){ //This has an expiry date lets compare                  
 +                    $expiry_time    = $q_dd->modified->toUnixString()+($dd_resuply_intP * 24 * 60 *60);
 +                    $now            = new FrozenTime();
 +                    if($expiry_time < $now->toUnixString()){
 +                        //It already expired ask for a new one
 +                        $data['ctc_require_dn'] = true; 
 +                    }   
 +                }
 +            }else{
 +                $data['ctc_require_dn'] = true; //We did not found it so have to supply dn
 +            }
 +        }      
 +                  
 +    } 
 +    
 +    $this->set(array(
 +        'data' => $data,
 +        'success' => true,
 +        '_serialize' => array('data','success')
 +    ));
 +    
 +}
 +</code>
 +
 +  * This brings us to the end of the CakePHP 3 modifications.
 +  * The only remaining part is the login page themselves.
 +
 +==== Modify the Login Pages for the Extra Options ====
 +  *  Locate the file rdConnect.js.
 +  * It is typically under ///var/www/login/cp/js/rdConnect.js//
 +  * We need to add a flag to set if we created the pop-up window (**ctcFormDone**)
 +<code javascript>
 +cMinWidth           = 240; //240 mobile 300 desktop
 +scrollHeight        = 1000;
 +
 +var ctcFormDone     = false;
 +
 +fDebug          = function(message){  
 +    if(cDebug){
 +        console.log(message)  
 +    }
 +};
 +
 +</code>
 +  * Next we have to add some **OR** coonditions when the user click on the **Click To Connect** button
 +<code javascript>
 +var email_check = location.protocol+'//'+document.location.hostname+"/cake3/rd_cake/data-collectors/mac-check.json";
 +
 +webix.ajax().timeout(3000).post(
 +    email_check,formData,
 +    error   : function(text, data, XmlHttpRequest){
 +        console.log("ERROR -> Getting Info for MAC");    
 +    },
 +    success : function(text, data, XmlHttpRequest){
 +        if(data.json().success == true){            
 +            if((data.json().data.ctc_require_email == true)||(data.json().data.ctc_require_phone == true)||(data.json().data.ctc_require_dn == true)){
 +                if(ctcFormDone == false){ //If not already done 
 +                    buildClickToConnectForm(data.json().data);
 +                }                       
 +                showForm("winEmail", b);
 +            }else{
 +                onBtnClickToConnectClick();
 +            } 
 +        }else{
 +            console.log("OTHER ERROR");   
 +        }
 +    }
 +});
 +</code>
 +  * Lets then see the function that is called **buildClickToConnectForm**. 
 +  * This is called only once.
 +  * We also include validation rules for those fields.
 +<code javascript>
 +var buildClickToConnectForm = function(data){
 +
 +    console.log("Click To Connect Form");
 +    console.log(data);
 +    var e1 = {
 +      view      : "template",
 +      template  : "Please supply to get <b>Guest Access</b>"
 +    };
 +    var e2 = { view:"text", label:'Email', name:"email" };
 +    var e3 = { view:"text", label:'Phone', name:"phone" };
 +    var e4 = { view:"text", label:'DN',    name:"dn" };
 +    var b1 = { view:"button", value: "Submit", click:function(){
 +     if (this.getParentView().validate()){ //validate form
 +         var button      = this;
 +         var formData    = new FormData();
 +         var mac_address = getParameterByName('mac');
 +            formData.append("mac", mac_address);
 +         
 +            var values      = this.getParentView().getValues();
 +            console.log(values);
 +            if(values.email){
 +                formData.append("email", values.email);  
 +            }
 +            if(values.phone){
 +                formData.append("phone", values.phone);  
 +            }
 +            if(values.dn){
 +                formData.append("dn", values.dn);  
 +            }
 +            
 +
 +            //We also add the following
 +            var called      = getParameterByName('called');
 +            formData.append("cp_mac", called);
 +
 +            var nasid       = getParameterByName('nasid');
 +            formData.append("nasid", nasid);
 +
 +            //This might not always be included
 +            var ssid        = getParameterByName('ssid');
 +            if(ssid !== ''){
 +                formData.append("ssid", ssid);
 +            }   
 +            
 +            var add_mac  = location.protocol+'//'+document.location.hostname+"/cake3/rd_cake/data-collectors/add-mac.json";
 +            webix.ajax().timeout(3000).post(
 +                add_mac,
 +                formData,
 +                { 
 +                error   : function(text, data, XmlHttpRequest){
 +                    console.log("ERROR -> Adding MAC");    
 +                },
 +                success : function(text, data, XmlHttpRequest){
 +                    if(data.json().success == true){
 +                      //console.log("ADDED MAC NOW TRY TO CONNECT");
 +                      webix.message("All is correct");
 +                      button.getTopParentView().hide(); //hide window
 +                      onBtnClickToConnectClick();
 +                        
 +                    }else{
 +                        //console.log("OTHER ERROR");
 +                        webix.message({ type:"error", text:data.json().message });   
 +                    }
 +                }
 +            });          
 +            
 +        }
 +     else
 +     webix.message({ type:"error", text:"Form data is invalid" });
 +    }};
 +    
 +    var elements = [e1];
 +    var height   = 140;
 +    var rules   = {};
 +        
 +    if(data.ctc_require_email == true){
 +        elements.push(e2);
 +        height = height+60;
 +        rules.email = webix.rules.isEmail;
 +    }
 +    if(data.ctc_require_phone == true){
 +        elements.push(e3);
 +        height      = height+60;
 +        rules.phone = function(value){
 +            if (! /^[0-9]{8}$/.test(value)) {
 +              webix.message("Phone number must be 8 digits long"); 
 +              return false;
 +            }
 +            return true;
 +        };
 +    }
 +    if(data.ctc_require_dn == true){
 +        elements.push(e4);
 +        height = height+60;
 +        rules.dn = function(value){
 +            if (! /^[0-9]{7}$/.test(value)) {
 +              webix.message("DN number must be 7 digits long"); 
 +              return false;
 +            }
 +            return true;
 +        };
 +    }
 +    elements.push(b1);
 +    
 +    frmEmail = {
 +     view        :"form",
 +     borderless  :true,
 +     elements    : elements,
 +     rules       :rules,
 +     elementsConfig:{
 +     labelPosition:"top",
 +     }
 +    };
 +     
 +     webix.ui({
 +        view    : "popup",
 +        id      : "winEmail",
 +        head    : false,
 +        height  : height,
 +        body    :webix.copy(frmEmail)
 +    });
 +    
 +    ctcFormDone = true;     
 +}
 +
 +</code>
 +  * That brings us to the totality of the changes required.
 +  * These login pages does not need any further optimization. (They are done using Webix) 
 +
 +
 +
 +
 +
 +