• Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Search
  • Register
  • Login
Netgate Discussion Forum
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Search
  • Register
  • Login

Email Notification - OpenVPN Client Connect (Common Name)

OpenVPN
20
138
39.1k
Loading More Posts
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • G
    Gertjan @JeGr
    last edited by Sep 10, 2021, 8:22 AM

    @jegr said in Email Notification - OpenVPN Client Connect (Common Name):

    Yes, but without explicit-exit-notify UDP clients don't inform the server of their disconnect

    See above what I have in the custom options.
    My connection plain vanilla : is UDP based, port 1194 etc.

    I connect, I wait 20+ seconds, and then I slide the connection connection on my iPhone - OpenVPN Connect App to off, I see this line immediately in the pfSense OpenVPN server log :

    login-to-view

    You can clearly see the 25 seconds delay.

    gmail also notifies me immediately on both events.

    No "help me" PM's please. Use the forum, the community will thank you.
    Edit : and where are the logs ??

    1 Reply Last reply Reply Quote 0
    • S
      Stewart @JeGr
      last edited by Sep 10, 2021, 1:13 PM

      @jegr said in Email Notification - OpenVPN Client Connect (Common Name):

      @gertjan said in Email Notification - OpenVPN Client Connect (Common Name):

      I was disconnecting manually my client, by sliding the on/off button in the client while testing.

      Yes, but without explicit-exit-notify UDP clients don't inform the server of their disconnect. So the server just stops getting traffic, waits for the tiemeout/keepalive time and then internally de-registers the client as unavailable/disconnected and runs the script.
      TCP clients don't have that problem because of TCP (d'uh! ;)) and explicit-exit-notify brings that to UDP - but with its own problems (e.g. mixed style multi-configs with udp&tcp remote statements for automatic fallback etc. aren't possible ATM).

      As I did a bit of an updated version of that script for noplan, I tested around that myself and could see that behavior every time. One just has to be patient :)

      Cheers
      \jens

      My problem with being patient is that while waiting no other script will launch. So if someone connects or disconnects within that 90 second window (or whatever that window may be) the scripts don't launch. At least, that's what it appears is happening. I don't see a reason why it would be that way, though.

      N 1 Reply Last reply Sep 13, 2021, 12:23 PM Reply Quote 0
      • N
        noplan @Stewart
        last edited by Sep 13, 2021, 12:23 PM

        @stewart said in Email Notification - OpenVPN Client Connect (Common Name):

        while waiting no other script will launch. So if someone connects or disconnects within that 90 second window (or whatever that window may be) the scripts don't launch.

        r u sure ?
        cuz we don't see it ... have tested after reading your post ... can't confirm it.
        brNP

        S 1 Reply Last reply Sep 13, 2021, 1:29 PM Reply Quote 0
        • S
          Stewart @noplan
          last edited by Sep 13, 2021, 1:29 PM

          @noplan said in Email Notification - OpenVPN Client Connect (Common Name):

          @stewart said in Email Notification - OpenVPN Client Connect (Common Name):

          while waiting no other script will launch. So if someone connects or disconnects within that 90 second window (or whatever that window may be) the scripts don't launch.

          r u sure ?
          cuz we don't see it ... have tested after reading your post ... can't confirm it.
          brNP

          Maybe it's because I'm using the same user over and over to test? When I connect the first time the log gets generated (I have the script writing to a log file instead of emailing). After that, I can connect and disconnect and no other lines are written to the log file. Not even the first disconnect gets written. If I wait until after the disconnect line gets written then I can connect and the line gets logged.

          N A 2 Replies Last reply Sep 13, 2021, 1:46 PM Reply Quote 0
          • N
            noplan @Stewart
            last edited by Sep 13, 2021, 1:46 PM

            @stewart

            sounds interesting

            a) use a 2nd user
            b) connect with user 1
            c) connect with user 2 when user 1 is connected
            d) disconnect user 1
            e) shortly after done d) disconnect user 2

            both users are written in your file ?

            be so kind and show us logFile and script
            brNP

            S 1 Reply Last reply Sep 13, 2021, 2:13 PM Reply Quote 0
            • A
              Armstrong @Stewart
              last edited by Sep 13, 2021, 2:08 PM

              @stewart

              Are you perhaps experiencing the same thing I was in this post https://forum.netgate.com/post/897352 ?

              The disconnect lags well behind the actual disconnection.

              S 1 Reply Last reply Sep 13, 2021, 2:14 PM Reply Quote 0
              • S
                Stewart @noplan
                last edited by Sep 13, 2021, 2:13 PM

                @noplan said in Email Notification - OpenVPN Client Connect (Common Name):

                @stewart

                sounds interesting

                a) use a 2nd user
                b) connect with user 1
                c) connect with user 2 when user 1 is connected
                d) disconnect user 1
                e) shortly after done d) disconnect user 2

                both users are written in your file ?

                be so kind and show us logFile and script
                brNP

                I'll get a second pc up and running to test with. It'll take a few.

                S 1 Reply Last reply Sep 13, 2021, 3:12 PM Reply Quote 0
                • S
                  Stewart @Armstrong
                  last edited by Sep 13, 2021, 2:14 PM

                  @armstrong said in Email Notification - OpenVPN Client Connect (Common Name):

                  @stewart

                  Are you perhaps experiencing the same thing I was in this post https://forum.netgate.com/post/897352 ?

                  The disconnect lags well behind the actual disconnection.

                  Yes and no. Yes, I'm experiencing it and it can be resolved by adding explicit-exit-notify to the client. No, in that the issue I'm dealing with is that during that delay it seems like no scripts are running.

                  N 1 Reply Last reply Sep 13, 2021, 2:52 PM Reply Quote 0
                  • N
                    noplan @Stewart
                    last edited by Sep 13, 2021, 2:52 PM

                    for me this whole case is pretty interesting
                    because we're using this script like a ton of admins too to get information on the openVPN usage
                    and noOne except @JeGr (as always 🤡 ) was looking for

                    but without explicit-exit-notify UDP clients don't inform the server of their disconnect

                    so plz keep us posted with all the details you got
                    NP

                    S 1 Reply Last reply Sep 15, 2021, 5:21 PM Reply Quote 0
                    • S
                      Stewart @Stewart
                      last edited by Sep 13, 2021, 3:12 PM

                      @stewart said in Email Notification - OpenVPN Client Connect (Common Name):

                      @noplan said in Email Notification - OpenVPN Client Connect (Common Name):

                      @stewart

                      sounds interesting

                      a) use a 2nd user
                      b) connect with user 1
                      c) connect with user 2 when user 1 is connected
                      d) disconnect user 1
                      e) shortly after done d) disconnect user 2

                      both users are written in your file ?

                      be so kind and show us logFile and script
                      brNP

                      I'll get a second pc up and running to test with. It'll take a few.

                      I can confirm that the logging only affects that account. I simulated with a second PC. Had it connect and then disconnect. While waiting, I had the first machine connect and disconnect. It shows all of the logs as expected. It seems the lack of processing is limited to that one account.

                      N 1 Reply Last reply Sep 13, 2021, 3:58 PM Reply Quote 0
                      • N
                        noplan @Stewart
                        last edited by Sep 13, 2021, 3:58 PM

                        @stewart

                        strange...

                        But good news for us that everything is works as expected

                        1 Reply Last reply Reply Quote 0
                        • viktor_gV
                          viktor_g Netgate
                          last edited by Sep 15, 2021, 11:44 AM

                          You can create a feature request for this:
                          https://docs.netgate.com/pfsense/en/latest/development/feature-requests.html

                          N 1 Reply Last reply Sep 15, 2021, 11:56 AM Reply Quote 0
                          • N
                            noplan @viktor_g
                            last edited by Sep 15, 2021, 11:56 AM

                            @viktor_g said in Email Notification - OpenVPN Client Connect (Common Name):

                            You can create a feature request for this:

                            for what ?
                            the eMail notification ?

                            yes notification would be cool, but cooler but not job of the firewall
                            would be a reporting tool with output like in post#76

                            anyhow reading this from top to bottom
                            usin search function of this forum there will pop up a pretty professional tool
                            based on this a lot of people are using to get information about their openVPN connections
                            especially when you have to monitor contractors and other folks gettin access to your network

                            or am i gettin it wrong ?

                            br NP

                            1 Reply Last reply Reply Quote 0
                            • S
                              Stewart @noplan
                              last edited by Stewart Sep 16, 2021, 7:25 PM Sep 15, 2021, 5:21 PM

                              @noplan

                              I wanted to update on what I've created so far. I hope this all comes through properly. There is still experimental stuff in there as I figure it out and the code is sloppy in some places (especially as I hacked in the stuff to make it blend in to the pfSense theming) but the basic stuff is all there.

                              connect.sh

                              #!/usr/local/bin/php -q
                              
                              <?php
                              //      Gather Info
                                      $date = date('F j, Y');
                                      $time = date('g:i a');
                                      require_once("/etc/inc/notices.inc");
                              
                              //      Compile Info String to Send
                                      $local_connect_value = "USER: " . getenv('common_name') . "; CONNECTED; CONNECTED ON: " . $date . "; at " .$time . "; IP CONNECTED FROM: " . getenv('trusted_ip') . " INTERNAL IP ASSIGNED: " . getenv('ifconfig_pool_remote_ip') . "\n";
                              
                              //      Send Email Notification of Event
                              //      notify_all_remote($local_connect_value);
                              
                              $filename="/var/log/OpenVPN_Users.log";
                              $fp = fopen($filename, 'a');
                              fwrite($fp,$local_connect_value);
                              fclose($fp);
                              
                              ?>
                              
                              

                              disconnect.sh

                              #!/usr/local/bin/php -q
                              <?php
                              //      Gather Info
                                      require_once("/etc/inc/notices.inc");
                              //      $date = date('F j, Y, g:i a');
                                      $date = date('F j, Y');
                                      $time = date('g:i a');
                                      $durationSeconds = getenv('time_duration');
                              
                              //      Function to calculate time from seconds to human readable format
                                      function seconds2human($ss) {
                                              $s = $ss%60;
                                              $m = floor(($ss%3600)/60);
                                              $h = floor(($ss%86400)/3600);
                                              $d = floor(($ss%2592000)/86400);
                                              $M = floor($ss/2592000);
                                              return "$d days $h hours $m minutes $s seconds";
                                      }
                              
                              //      Compile Info String to Send
                                      $local_connect_value .= "USER: " . getenv('common_name') . "; DISCONNECTED; DISCONNECTED ON: " . $date . "; at " . $time . "; DURATION: " . $durationSeconds . " seconds or " . seconds2human($durationSeconds) . " DATA UPLOADED (RECEIVED): " . round(((getenv('bytes_received'))/1048576),2) . " MB DATA DOWNLOADED (SENT): " . round(((getenv('bytes_sent'))/1048576),2) ." MB\n";
                              
                              //      Send Email Notification of Event
                              //      notify_all_remote($local_connect_value);
                              
                              
                              //      Output to Log File
                                      $filename="/var/log/OpenVPN_Users.log";
                                      $fp = fopen($filename, 'a');
                                      fwrite($fp,$local_connect_value);
                                      fclose($fp);
                              
                              ?>
                              
                              
                              

                              OpenVPNUsers.log

                              USER: LTest; CONNECTED; CONNECTED ON: September 15, 2021; at 3:32 pm; IP CONNECTED FROM: 111.111.111.111 INTERNAL IP ASSIGNED: 10.0.100.2
                              USER: LTest; DISCONNECTED; DISCONNECTED ON: September 15, 2021; at 3:32 pm; DURATION: 17 seconds or 0 days 0 hours 0 minutes 17 seconds DATA UPLOADED (RECEIVED): 0.28 MB DATA DOWNLOADED (SENT): 0.17 MB
                              
                              

                              /usr/local/www/vpn_openvpn_userlogging.php

                              <?php
                              /*
                               * vpn_openvpn_userlogging.php
                               *
                               * Sept 2021
                               */
                              
                              ##|+PRIV
                              ##|*IDENT=page-openvpn-userlogging
                              ##|*NAME=OpenVPN: User Logging
                              ##|*DESCR=View OpenVPN User Connections.
                              ##|*MATCH=vpn_openvpn_userlogging.php*
                              ##|-PRIV
                              
                              require_once("guiconfig.inc");
                              require_once("openvpn.inc");
                              require_once("pfsense-utils.inc");
                              require_once("pkg-utils.inc");
                              
                              global $openvpn_topologies, $openvpn_tls_modes;
                              
                              $shortcut_section = "openvpn";
                              include("head.inc");
                              
                              
                              
                              $tab_array = array();
                              $tab_array[] = array(gettext("Servers"), false, "vpn_openvpn_server.php");
                              $tab_array[] = array(gettext("Clients"), true, "vpn_openvpn_client.php");
                              $tab_array[] = array(gettext("Client Specific Overrides"), false, "vpn_openvpn_csc.php");
                              $tab_array[] = array(gettext("Wizards"), false, "wizard.php?xml=openvpn_wizard.xml");
                              add_package_tabs("OpenVPN", $tab_array);
                              display_top_tabs($tab_array);
                              
                              
                              
                              // Function to calculate time from seconds to human readable format
                              function seconds2human($ss) {
                                      $s = $ss%60;
                                      $m = floor(($ss%3600)/60);
                                      $h = floor(($ss%86400)/3600);
                                      $d = floor(($ss%2592000)/86400);
                                      $M = floor($ss/2592000);
                                      return "$d days $h hours $m minutes $s seconds";
                              }
                              
                              //Variables
                              $logFilename = '/var/log/OpenVPN_Users.log';            //Log to be read in to generate information from
                              $userFilename = '/tmp/OpenVPN_UniqueUsers.log';         //Scratchpad that stores usernames that then get read back into an array
                              $nameSelected = $_POST["user_selected"];                //Receive the user selected and filter based on it
                              $secondsCounter = 0;                                    //Running Tally of Time
                              $dataUploadedTotal = 0;                                 //Running Tally of Data Uploaded
                              $dataDownloadedTotal = 0;                               //Running Tally of Data Downloaded
                              
                              //Delete the $userFilename log so it can be filled fresh with each run
                              unlink($userFilename);
                              ?>
                              
                              
                              <body>
                              
                              
                              <div class="panel panel-default">
                              
                                      <div class="panel-heading"><h2 class="panel-title"><?=gettext('Client Connections')?>
                              <?php
                                      if ($nameSelected !== "" && $nameSelected !== "Choose one"){
                                              echo "<br />".'User Selected='.$nameSelected;
                                      }
                              ?>
                                      </h2></div>
                                      <div class="table-responsive">
                                      <table id="tblResults" class="table table-striped table-hover table-condensed sortable-theme-bootstrap" data-sortable>
                              
                                              <thead><tr><th width=8%>USER</th><th width=12%>EVENT</th><th width=13%>EVENT DATE</th><th width=7%>EVENT TIME</th><th width=60%>INFORMATION</th></tr></thead><tbody>
                              
                                              <?php
                                              $dataArray = [];                                              // The nested array to hold all the arrays
                                              if (($h = fopen("{$logFilename}", "r")) !== FALSE) {             // Open the file for reading
                                                      while (($data = fgetcsv($h, 500, ";")) !== FALSE){
                                                              $dataArray[] = $data;
                                                              echo '<tr>';
                                                              $i=0;
                                                              foreach ($data as $column) {
                                                                      $i++;
                                                                      if (($fh = fopen("{$userFilename}", "a")) !== FALSE) {}             // Open the file for writing
                                                                      if ($i==1){
                                                                              fwrite($fh,substr(htmlspecialchars($column)."\n",6));       // Write name of connected user
                                                                              if ($nameSelected == "" || $nameSelected == "Choose one"){
                                                                                      echo '<td>'.substr(htmlspecialchars($column),6).'</td>';
                                                                              }else{
                                                                                      if (substr(htmlspecialchars($column),6)==$nameSelected) {
                                                                                              echo '<td>'.substr(htmlspecialchars($column),6).'</td>';
                                                                                      }else{
                                                                                              $i=99;
                                                                                      }
                                                                              }
                                                                      }elseif($i==2){
                                                                              echo '<td>'.htmlspecialchars($column).'</td>';
                                                                      }elseif($i==3){
                                                                              if (strpos($column,"DISCONNECTED") !== false){
                                                                                      $eventStatus="DISCONNECT";
                                                                                      echo '<td>'.substr(htmlspecialchars($column),18).'</td>';
                                                                              }else{
                                                                                      $eventStatus="CONNECT";
                                                                                      echo '<td>'.substr(htmlspecialchars($column),15).'</td>';
                                                                              }
                                                                      }elseif($i==4){
                                                                              echo '<td>'.substr(htmlspecialchars($column),3).'</td>';
                                                                      }elseif($i==5){
                                                                              if ($eventStatus=="CONNECT") {
                                                                                      $needle = "IP";
                                                                                      $insertCode = "<br/>";
                                                                                      $pos = strpos($column, $needle);
                                                                                      $newColumn = substr_replace($column, $insertCode , $pos , 0);
                                                                                      $newColumn = str_replace("INTERNAL","<BR/> INTERNAL",$column);
                                                                                      echo '<td>'.$newColumn.'</td>';
                                                                              }elseif($eventStatus=="DISCONNECT") {
                                                                                      //Manipulate Time
                                                                                      $seconds=strstr(substr(htmlspecialchars($column),10), "s", true);
                                                                                      $secondsCounter += $seconds;
                                                                                      //Display Data
                                                                                      echo '<td>'.str_replace("DATA","<BR/> DATA",$column).'</td>';
                                                                                      //Manipulate Data Uploaded
                                                                                      $dataUploaded = strstr(substr(strstr($column, "): ", false),3)," ", true);
                                                                                      $dataUploadedTotal += $dataUploaded;
                                                                                      //Manipulate Data Downloaded
                                                                                      $dataDownloaded = strstr(substr(strstr(substr(strstr($column, "): ", false),3), "): ", false),3)," ", true);
                                                                                      $dataDownloadedTotal += $dataDownloaded;
                                                                              }
                                                                      }
                                                              }
                                                              echo '</tr>'.PHP_EOL;
                                                              fclose($fh);
                                                      }
                                              }
                              
                                      echo '<tr><td></td><td></td><td></td><td></td><td>Total Time Connected: <strong>'.seconds2human($secondsCounter) . '</strong>';
                                      echo '<br />Total Data Uploaded (Received): <strong>' . $dataUploadedTotal . ' MB</strong>';
                                      echo '<br />Total Data Downloaded (Sent): <strong>' . $dataDownloadedTotal . ' MB</strong></td></tr>';
                              
                              
                              
                                      echo "</tbody></table>";
                              echo "</div>";
                              
                              
                              
                              
                              
                              $arrUserlist = file("$userFilename", FILE_IGNORE_NEW_LINES);
                              echo "<form method='post' action='vpn_openvpn_userlogging.php'>";
                              echo "<select name='user_selected'><option selected='selected'>Choose one</option>";
                              
                              foreach(array_unique($arrUserlist) as $item){
                                      ?><option value="<?php echo $item; ?>"><?php echo $item; ?></option><?php
                              }
                              echo "</select><input type='submit' value='Submit'></form>";
                              
                              ?>
                              
                              <!--This section isn't used yet.  Can't get it to implement the datatable and still keep the formatting properly. -->
                              
                              <!-- This loads up the basic jQuery library (version 3.6.0) -->
                              <script src="//code.jquery.com/jquery-3.6.0.min.js"></script>
                              
                              <!-- This loads up the CSS styling for the resulting DataTable (e.g. sorting icons, basic formatting, etc) -->
                              <link rel="stylesheet" type="text/css" href="//cdn.datatables.net/1.11.1/css/jquery.dataTables.min.css" />
                              
                              <!-- This loads up the jQuery plugin that enables the use of the DataTable() function used below -->
                              <script src="//cdn.datatables.net/1.11.1/js/jquery.dataTables.min.js"></script>
                              
                              <!-- This will (when the page loads completely) find the table with the ID of "tblResults" and format it with the DataTables plugin -->
                              
                              <script>
                              // When the page is loaded...
                              $(document).ready( function () {
                              
                                  // This one command does all the work
                                  $('#tblResults').DataTable();
                              
                              } );
                              </script>
                              
                              </body>
                              </html>
                              
                              
                              <?php include("foot.inc");?>
                              
                              

                              What I'd like to do is make the table filterable based on users and date range. I was going to start trying it in php but maybe it's already a built-in class that's been made like how it is already sortable. I've also had Javascript recommended to me. I don't want to reinvent the wheel so let me know if I'm missing something. I'd also like to add in a function to sum up all of the time of the disconnected sessions to see how long someone has been connected. That way administrators can see how long a user was connected over a day, or week, or however long they want to see for. I'd also like to tally up the data usage for visibility.

                              Edit: Since nobody has replied since I posted it, I've updated the code for whomever is interested. I guess all that's left is to filter by date range.

                              Edit 2: Sample Output
                              login-to-view

                              1 Reply Last reply Reply Quote 0
                              • S
                                Stewart
                                last edited by Stewart Sep 30, 2021, 8:32 PM Sep 30, 2021, 8:15 PM

                                I don't know how much interest there is in this since I haven't received feedback from the code I've posted, but here is the updated code I have so far.

                                connect.sh - Script that gets launched whenever someone connects to the VPN

                                #!/usr/local/bin/php -q
                                
                                <?php
                                //      Gather Info
                                        $date = date('Y-m-d');
                                        $time = date('g:i a');
                                        require_once("/etc/inc/notices.inc");
                                
                                //      Compile Info String to Send
                                        $local_connect_value = "USER: " . getenv('common_name') . "; CONNECTED; " . $date . "; at " .$time . "; IP CONNECTED FROM: " . getenv('trusted_ip') . " INTERNAL IP ASSIGNED: " . getenv('ifconfig_pool_remote_ip') . "\n";
                                
                                //      Send Email Notification of Event
                                //      notify_all_remote($local_connect_value);
                                
                                $filename="/var/log/OpenVPN_Users.log";
                                $fp = fopen($filename, 'a');
                                fwrite($fp,$local_connect_value);
                                fclose($fp);
                                
                                ?>
                                
                                

                                disconnect.sh - Script that gets launched whenever someone disconnects the OpenVPN

                                #!/usr/local/bin/php -q
                                <?php
                                //      Gather Info
                                        require_once("/etc/inc/notices.inc");
                                        $date = date('Y-m-d');
                                        $time = date('g:i a');
                                        $durationSeconds = getenv('time_duration');
                                
                                //      Function to calculate time from seconds to human readable format
                                        function seconds2human($ss) {
                                                $s = $ss%60;
                                                $m = floor(($ss%3600)/60);
                                                $h = floor(($ss%86400)/3600);
                                                $d = floor(($ss%2592000)/86400);
                                                $M = floor($ss/2592000);
                                                return "$d days $h hours $m minutes $s seconds";
                                        }
                                
                                //      Compile Info String to Send
                                        $local_connect_value .= "USER: " . getenv('common_name') . "; DISCONNECTED; " . $date . "; at " . $time . "; DURATION: " . $durationSeconds . " seconds or " . seconds2human($durationSeconds) . " DATA UPLOADED (RECEIVED): ". round(((getenv('bytes_received'))/1048576),2) . " MB DATA DOWNLOADED (SENT): " . round(((getenv('bytes_sent'))/1048576),2) ." MB\n";
                                
                                //      Send Email Notification of Event
                                //      notify_all_remote($local_connect_value);
                                
                                
                                //      Output to Log File
                                        $filename="/var/log/OpenVPN_Users.log";
                                        $fp = fopen($filename, 'a');
                                        fwrite($fp,$local_connect_value);
                                        fclose($fp);
                                
                                ?>
                                

                                /var/log/OpenVPN_Users.log - File that logs the info from the connect and disconnect scripts
                                This is sample logs:

                                USER: LTest; CONNECTED; 2021-09-15; at 3:32 pm; IP CONNECTED FROM: 111.111.111.111 INTERNAL IP ASSIGNED: 10.0.100.2
                                USER: LTest; DISCONNECTED; 2021-09-15; at 3:32 pm; DURATION: 17 seconds or 0 days 0 hours 0 minutes 17 seconds DATA UPLOADED (RECEIVED): 0.28 MB DATA DOWNLOADED (SENT): 0.17 MB
                                

                                /usr/local/www/vpn_openvpn_userlogging.php - Web Page to view with

                                <?php
                                /*
                                 * vpn_openvpn_userlogging.php
                                 *
                                 * Sept 2021
                                 */
                                
                                ##|+PRIV
                                ##|*IDENT=page-openvpn-userlogging
                                ##|*NAME=OpenVPN: User Logging
                                ##|*DESCR=View OpenVPN User Connections.
                                ##|*MATCH=vpn_openvpn_userlogging.php*
                                ##|-PRIV
                                
                                require_once("guiconfig.inc");
                                require_once("openvpn.inc");
                                require_once("pfsense-utils.inc");
                                require_once("pkg-utils.inc");
                                
                                global $openvpn_topologies, $openvpn_tls_modes;
                                
                                $shortcut_section = "openvpn";
                                include("head.inc");
                                
                                $tab_array = array();
                                $tab_array[] = array(gettext("Servers"), false, "vpn_openvpn_server.php");
                                $tab_array[] = array(gettext("Clients"), true, "vpn_openvpn_client.php");
                                $tab_array[] = array(gettext("Client Specific Overrides"), false, "vpn_openvpn_csc.php");
                                $tab_array[] = array(gettext("Wizards"), false, "wizard.php?xml=openvpn_wizard.xml");
                                add_package_tabs("OpenVPN", $tab_array);
                                display_top_tabs($tab_array);
                                ?>
                                
                                <head><style>
                                /* ************************* This style is used for the popup date picker ************************* */
                                /* (A) POPUP */
                                .picker-wrap {
                                  position: fixed;
                                  top: 0;
                                  left: 0;
                                  width: 100vw;
                                  height: 100vh;
                                  background: rgba(0,0,0,0.5);
                                  opacity: 0;
                                  visibility: hidden;
                                  transition: opacity 0.2s;
                                }
                                .picker-wrap.show {
                                  opacity: 1;
                                  visibility: visible;
                                }
                                .picker-wrap .picker {
                                  margin: 50vh auto 0 auto;
                                  transform: translateY(-50%);
                                }
                                
                                /* (B) CONTAINER */
                                .picker {
                                  max-width: 300px;
                                  background: #444444;
                                  padding: 10px;
                                }
                                
                                /* (C) MONTH + YEAR */
                                .picker-m, .picker-y {
                                  width: 50%;
                                  padding: 5px;
                                  box-sizing: border-box;
                                  font-size: 16px;
                                }
                                
                                /* (D) DAY */
                                .picker-d table {
                                  color: #fff;
                                  border-collapse: separate;
                                  width: 100%;
                                  margin-top: 10px;
                                }
                                .picker-d table td {
                                  width: 14.28%; /* 7 EQUAL COLUMNS */
                                  padding: 5px;
                                  text-align: center;
                                }
                                /* HEADER CELLS */
                                .picker-d-h td {
                                  font-weight: bold;
                                }
                                /* BLANK DATES */
                                .picker-d-b {
                                  background: #4e4e4e;
                                }
                                /* TODAY */
                                .picker-d-td {
                                  background: #d84f4f;
                                }
                                /* PICKABLE DATES */
                                .picker-d-d:hover {
                                  cursor: pointer;
                                  background: #a33c3c;
                                }
                                /* UNPICKABLE DATES */
                                .picker-d-dd {
                                  color: #888;
                                  background: #4e4e4e;
                                }
                                /* ************************* End style used for the popup date picker ************************* */
                                </style></head>
                                
                                
                                
                                <?php
                                // ************************* Start of Functions, Variables, and Pre-Work *************************
                                // Function to calculate time from seconds to human readable format
                                function seconds2human($ss) {
                                	$s = $ss%60;
                                	$m = floor(($ss%3600)/60);
                                	$h = floor(($ss%86400)/3600);
                                	$d = floor(($ss%2592000)/86400);
                                	$M = floor($ss/2592000);
                                	return "$d days $h hours $m minutes $s seconds";
                                }
                                
                                //Variables
                                //$logFilename = '/var/log/OpenVPN_Users.log';		//Log to be read in to generate information from
                                //$userFilename = '/tmp/OpenVPN_UniqueUsers.log';		//Scratchpad that stores usernames that then get read back into an array
                                //$nameSelected = $_POST["user_selected"];		//Receive the user selected and filter based on it
                                //$secondsCounter = 0;					//Running Tally of Time
                                //$dataUploadedTotal = 0;					//Running Tally of Data Uploaded
                                //$dataDownloadedTotal = 0;				//Running Tally of Data Downloaded
                                
                                
                                //Variables
                                $logFilename = '/var/log/OpenVPN_Users.log';            //Log to be read in to generate information from
                                $userFilename = '/tmp/OpenVPN_UniqueUsers.log';         //Scratchpad that stores usernames that then get read back into an array
                                $nameSelected = $_POST["user_selected"];                //Receive the user selected and filter based on it
                                $dateSelected = $_POST["input-pop"];                    //Receive date and filter based on it
                                $secondsCounter = 0;                                    //Running Tally of Time
                                $dataUploadedTotal = 0;                                 //Running Tally of Data Uploaded
                                $dataDownloadedTotal = 0;                               //Running Tally of Data Downloaded
                                $passesDateFilter = "";
                                $dataArray = [];                                        //Used to store all
                                unlink($userFilename);                                  //Delete the $userFilename log so it can be filled fresh with each run
                                
                                
                                //Delete the $userFilename log so it can be filled fresh with each run
                                unlink($userFilename);
                                // ************************* End of Functions, Variables, and Pre-Work *************************
                                ?>
                                
                                <body><div class='panel panel-default'><div class='panel-heading'><h2 class='panel-title'>
                                <?=gettext('Client Connections')?>
                                
                                <? 
                                ob_start();  // ************************* HTML Store and Load Point *************************
                                ?>
                                
                                </h2></div>
                                <div class="table-responsive">
                                <table id="tblResults" class="table table-striped table-hover table-condensed sortable-theme-bootstrap" data-sortable>
                                <thead><tr><th width=8%>USER</th><th width=12%>EVENT</th><th width=13%>EVENT DATE</th><th width=7%>EVENT TIME</th><th width=60%>INFORMATION</th></tr></thead><tbody>
                                
                                <?php
                                // Outputs
                                $fh = fopen("{$userFilename}", "a");
                                
                                // Input
                                if (($h = fopen("{$logFilename}", "r")) !== FALSE)
                                {
                                  // Open the file for reading
                                  while (($data = fgetcsv($h, 500, ";")) !== FALSE)
                                  {
                                    // ************************* Assign the Array Information *************************
                                    // Assign the $data record to variables for more readable code
                                    $recUser    = $data[0];     // User
                                    $recEvent   = $data[1];     // Event
                                    $recDate    = $data[2];     // Event Date
                                    $recTime    = $data[3];     // Event Time
                                    $recInfo    = $data[4];     // Event Information
                                
                                    // Write name of connected user to scratch file
                                    fwrite($fh,substr(htmlspecialchars($recUser)."\n",6));
                                
                                    // ************************* Perform Checks to see if row should be displayed *************************
                                    // Evaluate if the row passes the filters
                                    $passesNameFilter = ($nameSelected == "" || $nameSelected == "Select User" || $nameSelected == substr(htmlspecialchars($recUser),6));
                                    $passesDateFilter = ($dateSelected == "" || $dateSelected == trim(htmlspecialchars($recDate)));
                                
                                    // If either of the filters fails to pass...
                                    if($passesNameFilter == false || $passesDateFilter == false)
                                    {
                                      // Continue on and exit the while loop if name/date criterea aren't met
                                      continue;
                                    }
                                
                                    // If both filters pass, capture the raw row in $dataArray
                                    $dataArray[] = $data;
                                
                                    // ************************* For a row to be displayed, prepare the informatin *************************
                                    // Prepare the data from recdata (received data) to htmldata (data to be used in formatting and parsing)
                                    $htmlUser = substr(htmlspecialchars($recUser),6);
                                    $htmlEvent = htmlspecialchars($recEvent);
                                    $htmlTime = substr(htmlspecialchars($recTime),3);
                                    $htmlInfo = htmlspecialchars($recInfo);
                                    $htmlDate = trim(htmlspecialchars($recDate));
                                
                                    // Event Info - Parse the Information Column Depending on Event Type
                                    if (strpos($recEvent,"DISCONNECTED") !== false){
                                      //Manipulate Time
                                      $seconds=strstr(substr(htmlspecialchars($recInfo),10), "s", true);
                                      $secondsCounter += $seconds;
                                      //Display Data
                                      $htmlInfo = str_replace("DATA","<BR/> DATA",$recInfo);
                                      //Manipulate Data Uploaded
                                      $dataUploaded = strstr(substr(strstr($recInfo, "): ", false),3)," ", true);
                                      $dataUploadedTotal += $dataUploaded;
                                      //Manipulate Data Downloaded
                                      $dataDownloaded = strstr(substr(strstr(substr(strstr($recInfo, "): ", false),3), "): ", false),3)," ", true);
                                      $dataDownloadedTotal += $dataDownloaded;
                                    }elseif (strpos($recEvent,"CONNECTED") != false) {
                                      $needle = "IP";
                                      $insertCode = "<br/>";
                                      $pos = strpos($recInfo, $needle);
                                      $htmlInfo = substr_replace($recInfo, $insertCode , $pos , 0);
                                      $htmlInfo = str_replace("INTERNAL","<BR/> INTERNAL",$recInfo);
                                    }
                                
                                    // ************************* Display the Information *************************
                                    // Now render the row
                                    echo "<tr>";
                                    echo "<td>{$htmlUser}<br/></td>";
                                    echo "<td>{$htmlEvent}<br/></td>";
                                    echo "<td>{$htmlDate}<br/></td>";
                                    echo "<td>{$htmlTime}<br/></td>";
                                    echo "<td>{$htmlInfo}<br/></td>";
                                    echo "</tr>".PHP_EOL;
                                  }
                                
                                    // ************************* Show Totals at the Bottom of the Screen *************************
                                    echo '<tr><td></td><td></td><td></td><td></td><td>Total Time Connected: <strong>'.seconds2human($secondsCounter) . '</strong>';
                                    echo '<br />Total Data Uploaded (Received): <strong>' . $dataUploadedTotal . ' MB</strong>';
                                    echo '<br />Total Data Downloaded (Sent): <strong>' . $dataDownloadedTotal . ' MB</strong></td></tr>';
                                
                                
                                }
                                
                                // Close the file with the usernames
                                fclose($fh);
                                
                                echo "</tbody></table></div>";
                                $the_html_code = ob_get_clean();
                                echo "</div>";
                                
                                // ************************* Build User List and Date Popup Picker *************************
                                $arrUserlist = file("$userFilename", FILE_IGNORE_NEW_LINES);
                                echo "<form method='post' action='vpn_openvpn_userlogging.php'>";
                                
                                //Generate Name Selection Drop Down
                                //echo "<select name='user_selected'><option selected='selected'>$nameSelected</option>";
                                echo "<select name='user_selected'><option selected='selected'>Select User</option>";
                                foreach(array_unique($arrUserlist) as $item){
                                        ?><option value="<?php echo $item; ?>"><?php echo $item; ?></option><?php
                                }
                                echo "</select>";
                                
                                //POPUP DATE PICKER
                                echo "<input type='text' name='input-pop' id='input-pop' placeholder='Select Date'/>";
                                
                                //Submit Button
                                echo "<input type='submit' value='Submit'></form>";
                                
                                echo $the_html_code;  // ************************* HTML Code is compiled.  Send it back to the top. *************************
                                
                                ?>
                                
                                
                                
                                
                                <!--- ************************* Javascript for Date Picker ************************* ---> 
                                <script>
                                   window.addEventListener("load", function(){
                                      picker.attach({
                                         target: "input-pop"
                                      });
                                   });
                                </script>
                                
                                
                                <script>
                                var picker = {
                                  // (A) ATTACH DATEPICKER TO TARGET
                                  // target : datepicker will populate this field
                                  // container : datepicker will be generated in this container
                                  // startmon : start on Monday (default false)
                                  // disableday : array of days to disable, e.g. [2,7] to disable Tue and Sun
                                  attach : function (opt) {
                                    // (A1) CREATE NEW DATEPICKER
                                    var dp = document.createElement("div");
                                    dp.dataset.target = opt.target;
                                    dp.dataset.startmon = opt.startmon ? "1" : "0";
                                    dp.classList.add("picker");
                                    if (opt.disableday) {
                                      dp.dataset.disableday = JSON.stringify(opt.disableday);
                                    }
                                
                                    // (A2) DEFAULT TO CURRENT MONTH + YEAR - NOTE: UTC+0!
                                    var today = new Date(),
                                        thisMonth = today.getUTCMonth(), // Note: Jan is 0
                                        thisYear = today.getUTCFullYear(),
                                        months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun",
                                                  "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
                                
                                    // (A3) MONTH SELECT
                                    var select = document.createElement("select"),
                                        option = null;
                                    select.classList.add("picker-m");
                                    for (var mth in months) {
                                      option = document.createElement("option");
                                      option.value = parseInt(mth) + 1;
                                      option.text = months[mth];
                                      select.appendChild(option);
                                    }
                                    select.selectedIndex = thisMonth;
                                    select.addEventListener("change", function(){ picker.draw(this); });
                                    dp.appendChild(select);
                                
                                    // (A4) YEAR SELECT
                                    var yRange = 10; // Year range to show, I.E. from thisYear-yRange to thisYear+yRange
                                    select = document.createElement("select");
                                    select.classList.add("picker-y");
                                    for (var y = thisYear-yRange; y < thisYear+yRange; y++) {
                                      option = document.createElement("option");
                                      option.value = y;
                                      option.text = y;
                                      select.appendChild(option);
                                    }
                                    select.selectedIndex = yRange;
                                    select.addEventListener("change", function(){ picker.draw(this); });
                                    dp.appendChild(select);
                                
                                    // (A5) DAY SELECT
                                    var days = document.createElement("div");
                                    days.classList.add("picker-d");
                                    dp.appendChild(days);
                                
                                    // (A6) ATTACH DATE PICKER TO TARGET CONTAINER + DRAW THE DATES
                                    picker.draw(select);
                                
                                    // (A6-I) INLINE DATE PICKER
                                    if (opt.container) { document.getElementById(opt.container).appendChild(dp); }
                                
                                    // (A6-P) POPUP DATE PICKER
                                    else {
                                      // (A6-P-1) MARK THIS AS A "POPUP"
                                      var uniqueID = 0;
                                      while (document.getElementById("picker-" + uniqueID) != null) {
                                        uniqueID = Math.floor(Math.random() * (100 - 2)) + 1;
                                      }
                                      dp.dataset.popup = "1";
                                      dp.dataset.dpid = uniqueID;
                                
                                      // (A6-P-2) CREATE WRAPPER
                                      var wrapper = document.createElement("div");
                                      wrapper.id = "picker-" + uniqueID;
                                      wrapper.classList.add("picker-wrap");
                                      wrapper.appendChild(dp);
                                
                                      // (A6-P-3) ATTACH ONCLICK TO SHOW/HIDE DATEPICKER
                                      var target = document.getElementById(opt.target);
                                      target.dataset.dp = uniqueID;
                                      target.readOnly = true; // Prevent onscreen keyboar on mobile devices
                                      target.onfocus = function () {
                                        document.getElementById("picker-" + this.dataset.dp).classList.add("show");
                                      };
                                      wrapper.addEventListener("click", function (evt) {
                                        if (evt.target.classList.contains("picker-wrap")) {
                                          this.classList.remove("show");
                                        }
                                      });
                                
                                      // (A6-P-4) ATTACH POPUP DATEPICKER TO DOCUMENT
                                      document.body.appendChild(wrapper);
                                    }
                                  },
                                
                                
                                  // (B) DRAW THE DAYS IN MONTH
                                  // el : HTML reference to either year or month selector
                                  draw : function (el) {
                                    // (B1) GET DATE PICKER COMPONENTS
                                    var parent = el.parentElement,
                                        year = parent.getElementsByClassName("picker-y")[0].value,
                                        month = parent.getElementsByClassName("picker-m")[0].value,
                                        days = parent.getElementsByClassName("picker-d")[0];
                                
                                    // (B2) DATE RANGE CALCULATION - NOTE: UTC+0!
                                    var daysInMonth = new Date(Date.UTC(year, month, 0)).getUTCDate(),
                                        startDay = new Date(Date.UTC(year, month-1, 1)).getUTCDay(), // Note: Sun = 0
                                        endDay = new Date(Date.UTC(year, month-1, daysInMonth)).getUTCDay(),
                                        startDay = startDay==0 ? 7 : startDay,
                                        endDay = endDay==0 ? 7 : endDay;
                                
                                    // (B3) GENERATE DATE SQUARES (IN ARRAY FIRST)
                                    var squares = [],
                                        disableday = null;
                                    if (parent.dataset.disableday) {
                                      disableday = JSON.parse(parent.dataset.disableday);
                                    }
                                
                                    // (B4) EMPTY SQUARES BEFORE FIRST DAY OF MONTH
                                    if (parent.dataset.startmon=="1" && startDay!=1) {
                                      for (var i=1; i<startDay; i++) { squares.push("B"); }
                                    }
                                    if (parent.dataset.startmon=="0" && startDay!=7) {
                                      for (var i=0; i<startDay; i++) { squares.push("B"); }
                                    }
                                
                                    // (B5) DAYS OF MONTH
                                    // (B5-1) ALL DAYS ENABLED, JUST ADD
                                    if (disableday==null) {
                                      for (var i=1; i<=daysInMonth; i++) { squares.push([i, false]);  }
                                    }
                                
                                    // (B5-2) SOME DAYS DISABLED
                                    else {
                                      var thisday = startDay;
                                      for (var i=1; i<=daysInMonth; i++) {
                                        // CHECK IF DAY IS DISABLED
                                        var disabled = disableday.includes(thisday);
                                        // DAY OF MONTH, DISABLED
                                        squares.push([i, disabled]);
                                        // NEXT DAY
                                        thisday++;
                                        if (thisday==8) { thisday = 1; }
                                      }
                                    }
                                
                                    // (B6) EMPTY SQUARES AFTER LAST DAY OF MONTH
                                    if (parent.dataset.startmon=="1" && endDay!=7) {
                                      for (var i=endDay; i<7; i++) { squares.push("B"); }
                                    }
                                    if (parent.dataset.startmon=="0" && endDay!=6) {
                                      for (var i=endDay; i<(endDay==7?13:6); i++) { squares.push("B"); }
                                    }
                                
                                    // (B7) DRAW HTML
                                    var daynames = ["Mon", "Tue", "Wed", "Thur", "Fri", "Sat"];
                                    if (parent.dataset.startmon=="1") { daynames.push("Sun"); }
                                    else { daynames.unshift("Sun"); }
                                
                                    // (B7-1) HTML DATE HEADER
                                    var table = document.createElement("table"),
                                        row = table.insertRow(),
                                        cell = null;
                                    row.classList.add("picker-d-h");
                                    for (let d of daynames) {
                                      cell = row.insertCell();
                                      cell.innerHTML = d;
                                    }
                                
                                    // (B7-2) HTML DATE CELLS
                                    var total = squares.length,
                                        row = table.insertRow(),
                                        today = new Date(),
                                        todayDate = null;
                                    if (today.getUTCMonth()+1 == month && today.getUTCFullYear() == year) {
                                      todayDate = today.getUTCDate();
                                    }
                                    for (var i=0; i<total; i++) {
                                      if (i!=total && i%7==0) { row = table.insertRow(); }
                                      cell = row.insertCell();
                                      if (squares[i] == "B") {
                                        cell.classList.add("picker-d-b");
                                      } else {
                                        cell.innerHTML = squares[i][0];
                                        // NOT ALLOWED TO CHOOSE THIS DAY
                                        if (squares[i][1]) {
                                          cell.classList.add("picker-d-dd");
                                        }
                                        // ALLOWED TO CHOOSE THIS DAY
                                        else {
                                          if (i == todayDate) { cell.classList.add("picker-d-td"); }
                                          cell.classList.add("picker-d-d");
                                          cell.addEventListener("click", function(){ picker.pick(this); });
                                        }
                                      }
                                    }
                                
                                    // (B7-3) ATTACH NEW CALENDAR TO DATEPICKER
                                    days.innerHTML = "";
                                    days.appendChild(table);
                                  },
                                
                                  // (C) CHOOSE A DATE
                                  // el : HTML reference to selected date cell
                                  pick : function (el) {
                                    // (C1) GET ALL COMPONENTS
                                    var parent = el.parentElement;
                                    while (!parent.classList.contains("picker")) {
                                      parent = parent.parentElement;
                                    }
                                
                                    // (C2) GET FULL SELECTED YEAR MONTH DAY
                                    var year = parent.getElementsByClassName("picker-y")[0].value,
                                        month = parent.getElementsByClassName("picker-m")[0].value,
                                        day = el.innerHTML;
                                
                                    // YYYY-MM-DD FORMAT - CHANGE FORMAT HERE IF YOU WANT !
                                    if (parseInt(month)<10) { month = "0" + month; }
                                    if (parseInt(day)<10) { day = "0" + day; }
                                    var fullDate = year + "-" + month + "-" + day;
                                
                                    // (C3) UPDATE SELECTED DATE
                                    document.getElementById(parent.dataset.target).value = fullDate;
                                
                                    // (C4) POPUP ONLY - CLOSE THE POPUP
                                    if (parent.dataset.popup == "1") {
                                      document.getElementById("picker-" + parent.dataset.dpid).classList.remove("show");
                                    }
                                  }
                                };
                                </script>
                                
                                </body>
                                </html>
                                
                                <?php include("foot.inc");?>
                                

                                Here is the output:
                                login-to-view

                                I don't know if I have the ability to actually integrate it at all. I'm guessing I'll need to submit this somewhere but I don't know what that process is.

                                Our clients have been finding that allowing employees work from home isn't nearly as effective as having them in the office. We've been asked by several companies now to see how much the employees are actually connecting in over the VPN to get their work done. Please let me know if this is useful for you or if you have any feedback.

                                N 1 Reply Last reply Oct 7, 2021, 8:02 AM Reply Quote 0
                                • N
                                  noplan @Stewart
                                  last edited by Oct 7, 2021, 8:02 AM

                                  @stewart

                                  I'm gonna test it as soon as I'm back in office

                                  1 Reply Last reply Reply Quote 0
                                  • P
                                    psp
                                    last edited by Oct 7, 2021, 12:06 PM

                                    @Stewart
                                    Very appreciated here. Thanks!

                                    1 Reply Last reply Reply Quote 0
                                    • S
                                      Stewart
                                      last edited by Oct 7, 2021, 12:41 PM

                                      I'm finishing up some updates. It includes selecting a date range instead of a particular day and defaults to the current month instead of showing the whole log file when it opens. I have some out of office appointments today but hope to be able to post the update later today.

                                      1 Reply Last reply Reply Quote 1
                                      • S
                                        Stewart
                                        last edited by Oct 7, 2021, 9:02 PM

                                        Here is the updated /usr/local/www/vpn_openvpn_userlogging.php

                                        <?php
                                        /*
                                         * vpn_openvpn_userlogging.php
                                         *
                                         * Sept 2021
                                         */
                                        
                                        ##|+PRIV
                                        ##|*IDENT=page-openvpn-userlogging
                                        ##|*NAME=OpenVPN: User Logging
                                        ##|*DESCR=View OpenVPN User Connections.
                                        ##|*MATCH=vpn_openvpn_userlogging.php*
                                        ##|-PRIV
                                        
                                        require_once("guiconfig.inc");
                                        require_once("openvpn.inc");
                                        require_once("pfsense-utils.inc");
                                        require_once("pkg-utils.inc");
                                        
                                        global $openvpn_topologies, $openvpn_tls_modes;
                                        
                                        $shortcut_section = "openvpn";
                                        include("head.inc");
                                        
                                        $tab_array = array();
                                        $tab_array[] = array(gettext("Servers"), false, "vpn_openvpn_server.php");
                                        $tab_array[] = array(gettext("Clients"), true, "vpn_openvpn_client.php");
                                        $tab_array[] = array(gettext("Client Specific Overrides"), false, "vpn_openvpn_csc.php");
                                        $tab_array[] = array(gettext("Wizards"), false, "wizard.php?xml=openvpn_wizard.xml");
                                        add_package_tabs("OpenVPN", $tab_array);
                                        display_top_tabs($tab_array);
                                        ?>
                                        
                                        <head><style>
                                        /* ************************* This style is used for the popup date picker ************************* */
                                        /* (A) POPUP */
                                        .picker-wrap {
                                          position: fixed;
                                          top: 0;
                                          left: 0;
                                          width: 100vw;
                                          height: 100vh;
                                          background: rgba(0,0,0,0.5);
                                          opacity: 0;
                                          visibility: hidden;
                                          transition: opacity 0.2s;
                                        }
                                        .picker-wrap.show {
                                          opacity: 1;
                                          visibility: visible;
                                        }
                                        .picker-wrap .picker {
                                          margin: 50vh auto 0 auto;
                                          transform: translateY(-50%);
                                        }
                                        
                                        /* (B) CONTAINER */
                                        .picker {
                                          max-width: 300px;
                                          background: #444444;
                                          padding: 10px;
                                        }
                                        
                                        /* (C) MONTH + YEAR */
                                        .picker-m, .picker-y {
                                          width: 50%;
                                          padding: 5px;
                                          box-sizing: border-box;
                                          font-size: 16px;
                                        }
                                        
                                        /* (D) DAY */
                                        .picker-d table {
                                          color: #fff;
                                          border-collapse: separate;
                                          width: 100%;
                                          margin-top: 10px;
                                        }
                                        .picker-d table td {
                                          width: 14.28%; /* 7 EQUAL COLUMNS */
                                          padding: 5px;
                                          text-align: center;
                                        }
                                        /* HEADER CELLS */
                                        .picker-d-h td {
                                          font-weight: bold;
                                        }
                                        /* BLANK DATES */
                                        .picker-d-b {
                                          background: #4e4e4e;
                                        }
                                        /* TODAY */
                                        .picker-d-td {
                                          background: #d84f4f;
                                        }
                                        /* PICKABLE DATES */
                                        .picker-d-d:hover {
                                          cursor: pointer;
                                          background: #a33c3c;
                                        }
                                        /* UNPICKABLE DATES */
                                        .picker-d-dd {
                                          color: #888;
                                          background: #4e4e4e;
                                        }
                                        /* ************************* End style used for the popup date picker ************************* */
                                        </style></head>
                                        
                                        
                                        
                                        <?php
                                        // ************************* PHP Functions *************************
                                        // Function to calculate time from seconds to human readable format
                                        function seconds2human($ss) {
                                        	$s = $ss%60;
                                        	$m = floor(($ss%3600)/60);
                                        	$h = floor(($ss%86400)/3600);
                                        	$d = floor(($ss%2592000)/86400);
                                        	$M = floor($ss/2592000);
                                        	return "$d days $h hours $m minutes $s seconds";
                                        }
                                        
                                        // ************************* Global Variables *************************
                                        $logFilename = '/var/log/OpenVPN_Users.log';            //Log to be read in to generate information from
                                        $userFilename = '/tmp/OpenVPN_UniqueUsers.log';         //Scratchpad that stores usernames that then get read back into an array
                                        $secondsCounter = 0;                                    //Running Tally of Time
                                        $dataUploadedTotal = 0;                                 //Running Tally of Data Uploaded
                                        $dataDownloadedTotal = 0;                               //Running Tally of Data Downloaded
                                        $passesDateFilter = "";					// 
                                        $dataArray = [];                                        //Used to store all
                                        
                                        
                                        
                                        // ************************* Pre-Fill User Option Boxes *************************
                                        // Pre-Fill Start Date Selection Options					//
                                        if (isset($_POST["input-startpop"])) {						// If Submit is pressed
                                        	if ($_POST["input-startpop"]=="") {					//
                                        		$startDateSelected = date('Y-m-01');				// 	If the Start Date info is blank, set it to the first of the current calendar month
                                        	}else{									//
                                        		$startDateSelected = $_POST["input-startpop"];			// 	Or If Start Date is filled, pre-fill the Start Date with the info carried from POST
                                        	}									//
                                        }else{										//
                                        	$startDateSelected = date('Y-m-01');					// Or on page load fill the Start Date with the first of the current calendar month.
                                        }										//
                                        
                                        // Pre-Fill End Date Selection Options						//
                                        if (isset($_POST["input-endpop"])) {						// If Submit is pressed
                                        	if ($_POST["input-endpop"]=="") {					//
                                        		$endDateSelected = date('Y-m-31');				// 	If the End Date info is blank, set it to the last of the current calendar month
                                        	}else{									//
                                        		$endDateSelected = $_POST["input-endpop"];			// 	Or If the End Date is filled, pre-fill the Start Date with the info carried from POST
                                        	}									//
                                        }else{										//
                                        	$endDateSelected = date('Y-m-31');					// On Page load fill the End Date with the last of the current calendar month.
                                        }										//
                                        
                                        // Pre-Fill Shown Option for Users Dropdown					//
                                        if (isset($_POST["user_selected"])) {						// If Submit is pressed
                                        	$nameSelected = $_POST["user_selected"];				//Receive the user selected and filter based on it
                                        }else{										//
                                        	$nameSelected = "All Users";						//Receive the user selected and filter based on it
                                        }										//
                                        
                                        
                                        
                                        // Delete the $userFilename log so it can be filled fresh with each run
                                        // This could be moved to the end but it's been left at the top for diagnostic purposes
                                        unlink($userFilename);
                                        
                                        
                                        
                                        // ************************* End of Functions, Variables, and Pre-Work *************************
                                        ?>
                                        
                                        
                                        
                                        <body><div class='panel panel-default'><div class='panel-heading'><h2 class='panel-title'>
                                        <?=gettext('Client Connections')?>
                                        
                                        <?
                                        // ************************* HTML Store and Load Point *************************
                                        ob_start(); ?>
                                        
                                        
                                        
                                        </h2></div>
                                        <div class="table-responsive">
                                        <table id="tblResults" class="table table-striped table-hover table-condensed sortable-theme-bootstrap" data-sortable>
                                        <thead><tr><th width=8%>USER</th><th width=12%>EVENT</th><th width=13%>EVENT DATE</th><th width=7%>EVENT TIME</th><th width=60%>INFORMATION</th></tr></thead><tbody>
                                        
                                        <?php
                                        $fh = fopen("{$userFilename}", "a");						// Opens a scratchpad that stores usernames that then get read back into an array
                                        if (($h = fopen("{$logFilename}", "r")) !== FALSE)				// Opens the logfile created by the connect.sh and disconnect.sh scripts to parse
                                        {
                                          // ************************* Open the file for reading  and loop through each line *************************
                                          while (($data = fgetcsv($h, 500, ";")) !== FALSE)				// Go through line of log file and break out on each semicolon
                                          {
                                            // ************************* Assign the Array Information *************************
                                            // Assign the $data record to variables for more readable code
                                            $recUser    = $data[0];							// Store User in arr[0]
                                            $recEvent   = $data[1];							// Store Event
                                            $recDate    = $data[2];							// Store Event Date
                                            $recTime    = $data[3];							// Store Event Time
                                            $recInfo    = $data[4];							// Store Event Information
                                        
                                            fwrite($fh,substr(htmlspecialchars($recUser)."\n",6));			// Write the connected user to scratch file to be pulled into the user list later
                                        
                                            // ************************* Perform Checks to see if row should be displayed *************************
                                            $startDateConv=strtotime($startDateSelected);				// Set start date value for comparison
                                            $endDateConv=strtotime($endDateSelected);					// Set end date value for comparison
                                            $recDateConv=strtotime($recDate);						// Set the date value of the received entry for comparison
                                        
                                            // ************************* Perform Evaluation Checks *************************
                                            $passesNameFilter = ($nameSelected == "" || $nameSelected == "All Users" || $nameSelected == substr(htmlspecialchars($recUser),6));
                                            $passesDateFilter = ($recDateConv >= $startDateConv) && ($recDateConv <= $endDateConv);							
                                        
                                            // If either of the filters fails to pass...
                                            if($passesNameFilter == false || $passesDateFilter == false)
                                            {
                                              // Continue on and exit the while loop if name/date criterea aren't met
                                              continue;
                                            }
                                        
                                            // If both filters pass, capture the raw row in $dataArray
                                            $dataArray[] = $data;
                                        
                                        
                                        
                                            // ************************* For a row to be displayed, prepare the informatin *************************
                                            // Prepare the data from recdata (received data) to htmldata (data to be used in formatting and parsing)
                                            $htmlUser = substr(htmlspecialchars($recUser),6);
                                            $htmlEvent = htmlspecialchars($recEvent);
                                            $htmlTime = substr(htmlspecialchars($recTime),3);
                                            $htmlInfo = htmlspecialchars($recInfo);
                                            $htmlDate = trim(htmlspecialchars($recDate));
                                        
                                            // Event Info - Parse the Information Column Depending on Event Type
                                            if (strpos($recEvent,"DISCONNECTED") !== false){
                                              //Manipulate Time
                                              $seconds=strstr(substr(htmlspecialchars($recInfo),10), "s", true);
                                              $secondsCounter += $seconds;
                                              //Display Data
                                              $htmlInfo = str_replace("DATA","<BR/> DATA",$recInfo);
                                              //Manipulate Data Uploaded
                                              $dataUploaded = strstr(substr(strstr($recInfo, "): ", false),3)," ", true);
                                              $dataUploadedTotal += $dataUploaded;
                                              //Manipulate Data Downloaded
                                              $dataDownloaded = strstr(substr(strstr(substr(strstr($recInfo, "): ", false),3), "): ", false),3)," ", true);
                                              $dataDownloadedTotal += $dataDownloaded;
                                            }elseif (strpos($recEvent,"CONNECTED") != false) {
                                              $needle = "IP";
                                              $insertCode = "<br/>";
                                              $pos = strpos($recInfo, $needle);
                                              $htmlInfo = substr_replace($recInfo, $insertCode , $pos , 0);
                                              $htmlInfo = str_replace("INTERNAL","<BR/> INTERNAL",$recInfo);
                                            }
                                        
                                            // ************************* Display the Information *************************
                                            echo "<tr>";
                                            echo "<td>{$htmlUser}<br/></td>";
                                            echo "<td>{$htmlEvent}<br/></td>";
                                            echo "<td>{$htmlDate}<br/></td>";
                                            echo "<td>{$htmlTime}<br/></td>";
                                            echo "<td>{$htmlInfo}<br/></td>";
                                            echo "</tr>".PHP_EOL;
                                          }
                                        
                                            // ************************* After all lines have looped, show totals at the bottom of the screen *************************
                                            echo '<tr><td></td><td></td><td></td><td></td><td>Total Time Connected: <strong>'.seconds2human($secondsCounter) . '</strong>';
                                            echo '<br />Total Data Uploaded (Received): <strong>' . $dataUploadedTotal . ' MB</strong>';
                                            echo '<br />Total Data Downloaded (Sent): <strong>' . $dataDownloadedTotal . ' MB</strong></td></tr>';
                                        
                                        
                                        }
                                        
                                        // Close the file with the usernames
                                        fclose($fh);
                                        
                                        echo "</tbody></table></div>";
                                        $the_html_code = ob_get_clean();
                                        echo "</div>";
                                        
                                        // ************************* Build User List and Date Popup Picker *************************
                                        $arrUserlist = file("$userFilename", FILE_IGNORE_NEW_LINES);
                                        echo "<form method='post' action='vpn_openvpn_userlogging.php'>";
                                        
                                        // ************************* Generate Name Selection Drop Down *************************
                                        echo "<select name='user_selected'><option selected='selected'>$nameSelected</option>";
                                        ?><option value="All Users">All Users</option><?php
                                        foreach(array_unique($arrUserlist) as $item){
                                                ?><option value="<?php echo $item; ?>"><?php echo $item; ?></option><?php
                                        }
                                        echo "</select>";
                                        
                                        // ************************* User Interaction Secion *************************
                                        echo "<input type='text' name='input-startpop' id='input-startpop' placeholder='".$startDateSelected."'/>";	// Start Date Pop-up Picker
                                        echo "<input type='text' name='input-endpop' id='input-endpop' placeholder='".$endDateSelected."'/>";		// Stop Date Pop-up Picker
                                        echo "<input type='submit' value='Submit'></form>";								// Submit Button
                                        
                                        
                                        
                                        // ************************* HTML Code is compiled.  Send it back to the top. *************************
                                        echo $the_html_code;  
                                        
                                        ?>
                                        
                                        
                                        
                                        
                                        <!--- ************************* Javascript for Date Picker ************************* ---> 
                                        <script>
                                           window.addEventListener("load", function(){
                                              picker.attach({
                                                 target: "input-startpop"
                                              });
                                           });
                                        </script>
                                        <script>
                                           window.addEventListener("load", function(){
                                              picker.attach({
                                                 target: "input-endpop"
                                              });
                                           });
                                        </script>
                                        
                                        
                                        <script>
                                        var picker = {
                                          // (A) ATTACH DATEPICKER TO TARGET
                                          // target : datepicker will populate this field
                                          // container : datepicker will be generated in this container
                                          // startmon : start on Monday (default false)
                                          // disableday : array of days to disable, e.g. [2,7] to disable Tue and Sun
                                          attach : function (opt) {
                                            // (A1) CREATE NEW DATEPICKER
                                            var dp = document.createElement("div");
                                            dp.dataset.target = opt.target;
                                            dp.dataset.startmon = opt.startmon ? "1" : "0";
                                            dp.classList.add("picker");
                                            if (opt.disableday) {
                                              dp.dataset.disableday = JSON.stringify(opt.disableday);
                                            }
                                        
                                            // (A2) DEFAULT TO CURRENT MONTH + YEAR - NOTE: UTC+0!
                                            var today = new Date(),
                                                thisMonth = today.getUTCMonth(), // Note: Jan is 0
                                                thisYear = today.getUTCFullYear(),
                                                months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun",
                                                          "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
                                        
                                            // (A3) MONTH SELECT
                                            var select = document.createElement("select"),
                                                option = null;
                                            select.classList.add("picker-m");
                                            for (var mth in months) {
                                              option = document.createElement("option");
                                              option.value = parseInt(mth) + 1;
                                              option.text = months[mth];
                                              select.appendChild(option);
                                            }
                                            select.selectedIndex = thisMonth;
                                            select.addEventListener("change", function(){ picker.draw(this); });
                                            dp.appendChild(select);
                                        
                                            // (A4) YEAR SELECT
                                            var yRange = 10; // Year range to show, I.E. from thisYear-yRange to thisYear+yRange
                                            select = document.createElement("select");
                                            select.classList.add("picker-y");
                                            for (var y = thisYear-yRange; y < thisYear+yRange; y++) {
                                              option = document.createElement("option");
                                              option.value = y;
                                              option.text = y;
                                              select.appendChild(option);
                                            }
                                            select.selectedIndex = yRange;
                                            select.addEventListener("change", function(){ picker.draw(this); });
                                            dp.appendChild(select);
                                        
                                            // (A5) DAY SELECT
                                            var days = document.createElement("div");
                                            days.classList.add("picker-d");
                                            dp.appendChild(days);
                                        
                                            // (A6) ATTACH DATE PICKER TO TARGET CONTAINER + DRAW THE DATES
                                            picker.draw(select);
                                        
                                            // (A6-I) INLINE DATE PICKER
                                            if (opt.container) { document.getElementById(opt.container).appendChild(dp); }
                                        
                                            // (A6-P) POPUP DATE PICKER
                                            else {
                                              // (A6-P-1) MARK THIS AS A "POPUP"
                                              var uniqueID = 0;
                                              while (document.getElementById("picker-" + uniqueID) != null) {
                                                uniqueID = Math.floor(Math.random() * (100 - 2)) + 1;
                                              }
                                              dp.dataset.popup = "1";
                                              dp.dataset.dpid = uniqueID;
                                        
                                              // (A6-P-2) CREATE WRAPPER
                                              var wrapper = document.createElement("div");
                                              wrapper.id = "picker-" + uniqueID;
                                              wrapper.classList.add("picker-wrap");
                                              wrapper.appendChild(dp);
                                        
                                              // (A6-P-3) ATTACH ONCLICK TO SHOW/HIDE DATEPICKER
                                              var target = document.getElementById(opt.target);
                                              target.dataset.dp = uniqueID;
                                              target.readOnly = true; // Prevent onscreen keyboar on mobile devices
                                              target.onfocus = function () {
                                                document.getElementById("picker-" + this.dataset.dp).classList.add("show");
                                              };
                                              wrapper.addEventListener("click", function (evt) {
                                                if (evt.target.classList.contains("picker-wrap")) {
                                                  this.classList.remove("show");
                                                }
                                              });
                                        
                                              // (A6-P-4) ATTACH POPUP DATEPICKER TO DOCUMENT
                                              document.body.appendChild(wrapper);
                                            }
                                          },
                                        
                                        
                                          // (B) DRAW THE DAYS IN MONTH
                                          // el : HTML reference to either year or month selector
                                          draw : function (el) {
                                            // (B1) GET DATE PICKER COMPONENTS
                                            var parent = el.parentElement,
                                                year = parent.getElementsByClassName("picker-y")[0].value,
                                                month = parent.getElementsByClassName("picker-m")[0].value,
                                                days = parent.getElementsByClassName("picker-d")[0];
                                        
                                            // (B2) DATE RANGE CALCULATION - NOTE: UTC+0!
                                            var daysInMonth = new Date(Date.UTC(year, month, 0)).getUTCDate(),
                                                startDay = new Date(Date.UTC(year, month-1, 1)).getUTCDay(), // Note: Sun = 0
                                                endDay = new Date(Date.UTC(year, month-1, daysInMonth)).getUTCDay(),
                                                startDay = startDay==0 ? 7 : startDay,
                                                endDay = endDay==0 ? 7 : endDay;
                                        
                                            // (B3) GENERATE DATE SQUARES (IN ARRAY FIRST)
                                            var squares = [],
                                                disableday = null;
                                            if (parent.dataset.disableday) {
                                              disableday = JSON.parse(parent.dataset.disableday);
                                            }
                                        
                                            // (B4) EMPTY SQUARES BEFORE FIRST DAY OF MONTH
                                            if (parent.dataset.startmon=="1" && startDay!=1) {
                                              for (var i=1; i<startDay; i++) { squares.push("B"); }
                                            }
                                            if (parent.dataset.startmon=="0" && startDay!=7) {
                                              for (var i=0; i<startDay; i++) { squares.push("B"); }
                                            }
                                        
                                            // (B5) DAYS OF MONTH
                                            // (B5-1) ALL DAYS ENABLED, JUST ADD
                                            if (disableday==null) {
                                              for (var i=1; i<=daysInMonth; i++) { squares.push([i, false]);  }
                                            }
                                        
                                            // (B5-2) SOME DAYS DISABLED
                                            else {
                                              var thisday = startDay;
                                              for (var i=1; i<=daysInMonth; i++) {
                                                // CHECK IF DAY IS DISABLED
                                                var disabled = disableday.includes(thisday);
                                                // DAY OF MONTH, DISABLED
                                                squares.push([i, disabled]);
                                                // NEXT DAY
                                                thisday++;
                                                if (thisday==8) { thisday = 1; }
                                              }
                                            }
                                        
                                            // (B6) EMPTY SQUARES AFTER LAST DAY OF MONTH
                                            if (parent.dataset.startmon=="1" && endDay!=7) {
                                              for (var i=endDay; i<7; i++) { squares.push("B"); }
                                            }
                                            if (parent.dataset.startmon=="0" && endDay!=6) {
                                              for (var i=endDay; i<(endDay==7?13:6); i++) { squares.push("B"); }
                                            }
                                        
                                            // (B7) DRAW HTML
                                            var daynames = ["Mon", "Tue", "Wed", "Thur", "Fri", "Sat"];
                                            if (parent.dataset.startmon=="1") { daynames.push("Sun"); }
                                            else { daynames.unshift("Sun"); }
                                        
                                            // (B7-1) HTML DATE HEADER
                                            var table = document.createElement("table"),
                                                row = table.insertRow(),
                                                cell = null;
                                            row.classList.add("picker-d-h");
                                            for (let d of daynames) {
                                              cell = row.insertCell();
                                              cell.innerHTML = d;
                                            }
                                        
                                            // (B7-2) HTML DATE CELLS
                                            var total = squares.length,
                                                row = table.insertRow(),
                                                today = new Date(),
                                                todayDate = null;
                                            if (today.getUTCMonth()+1 == month && today.getUTCFullYear() == year) {
                                              todayDate = today.getUTCDate();
                                            }
                                            for (var i=0; i<total; i++) {
                                              if (i!=total && i%7==0) { row = table.insertRow(); }
                                              cell = row.insertCell();
                                              if (squares[i] == "B") {
                                                cell.classList.add("picker-d-b");
                                              } else {
                                                cell.innerHTML = squares[i][0];
                                                // NOT ALLOWED TO CHOOSE THIS DAY
                                                if (squares[i][1]) {
                                                  cell.classList.add("picker-d-dd");
                                                }
                                                // ALLOWED TO CHOOSE THIS DAY
                                                else {
                                                  if (i == todayDate) { cell.classList.add("picker-d-td"); }
                                                  cell.classList.add("picker-d-d");
                                                  cell.addEventListener("click", function(){ picker.pick(this); });
                                                }
                                              }
                                            }
                                        
                                            // (B7-3) ATTACH NEW CALENDAR TO DATEPICKER
                                            days.innerHTML = "";
                                            days.appendChild(table);
                                          },
                                        
                                          // (C) CHOOSE A DATE
                                          // el : HTML reference to selected date cell
                                          pick : function (el) {
                                            // (C1) GET ALL COMPONENTS
                                            var parent = el.parentElement;
                                            while (!parent.classList.contains("picker")) {
                                              parent = parent.parentElement;
                                            }
                                        
                                            // (C2) GET FULL SELECTED YEAR MONTH DAY
                                            var year = parent.getElementsByClassName("picker-y")[0].value,
                                                month = parent.getElementsByClassName("picker-m")[0].value,
                                                day = el.innerHTML;
                                        
                                            // YYYY-MM-DD FORMAT - CHANGE FORMAT HERE IF YOU WANT !
                                            if (parseInt(month)<10) { month = "0" + month; }
                                            if (parseInt(day)<10) { day = "0" + day; }
                                            var fullDate = year + "-" + month + "-" + day;
                                        
                                            // (C3) UPDATE SELECTED DATE
                                            document.getElementById(parent.dataset.target).value = fullDate;
                                        
                                            // (C4) POPUP ONLY - CLOSE THE POPUP
                                            if (parent.dataset.popup == "1") {
                                              document.getElementById("picker-" + parent.dataset.dpid).classList.remove("show");
                                            }
                                          }
                                        };
                                        </script>
                                        <!--- ************************* End Javascript for Date Picker ************************* ---> 
                                        
                                        </body>
                                        </html>
                                        
                                        <?php include("foot.inc");?>
                                        
                                        

                                        The update includes a few enhancements such as date range, better selection defaults, and log file limiting as well as improved notation. Give a spin and let me know how it works out for you.

                                        1 Reply Last reply Reply Quote 4
                                        • S
                                          Stewart
                                          last edited by Oct 12, 2021, 7:18 PM

                                          @noplan @psp

                                          Have you had a chance to test out the scripts? Any feedback?

                                          P W 2 Replies Last reply Oct 12, 2021, 8:56 PM Reply Quote 0
                                          • First post
                                            Last post
                                          Copyright 2025 Rubicon Communications LLC (Netgate). All rights reserved.