Shaw cable traffic dashboard widget
-
Hi all,
I've created a dashboard widget (for v2.x) that will log into the Shaw cable secure website, and extract the internet traffic chart data, and display the results in a quick-reference table in a dashboard widget. I figured I'd share it with this group in case someone else might find it useful.
Code for the widget is below, save the file as shawtraffic.widget.php and place under /usr/local/www/widgets/widgets/ on your pfSense system.
Cheers,
Derek./* Shaw traffic dashboard widget for pfsense v0.1 Copyright 2013 Derek Rachul (drachul@gmail.com) All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1\. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2\. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ $nocsrf = true; require_once("guiconfig.inc"); require_once("pfsense-utils.inc"); require_once("functions.inc"); // configuration defaults $displayentries = 1000; $refresh = 3600; $username = ""; $password = ""; // save configuration for shaw traffic if(isset($_POST['shawtrafficsubmit'])) { $config['widgets']['shawtrafficrefresh'] = $_POST['shawtrafficrefresh']; $config['widgets']['shawtrafficentries'] = $_POST['shawtrafficentries']; $config['widgets']['shawtrafficusername'] = $_POST['shawtrafficusername']; $config['widgets']['shawtrafficpassword'] = $_POST['shawtrafficpassword']; write_config("Saved Shaw traffic Widget via Dashboard"); header("Location: /"); } // update configuration variables if available if (isset($config['widgets']['shawtrafficrefresh'])) $refresh = $config['widgets']['shawtrafficrefresh']; if (isset($config['widgets']['shawtrafficentries'])) $displayentries = $config['widgets']['shawtrafficentries']; if (isset($config['widgets']['shawtrafficusername'])) $username = $config['widgets']['shawtrafficusername']; if (isset($config['widgets']['shawtrafficpassword'])) $password = $config['widgets']['shawtrafficpassword']; // output a table with if ($_REQUEST['getshawtraf']) { $xml = get_chartdata($username,$password); $traffic = parse_chartdata($xml); $content = traffic_content($traffic,$displayentries); output_table($content); // exit so we don't output any of the content below exit; } else { $nocollect = 0; $waitmessage = "collecting traffic data..."; if (!strlen($username) or !strlen($password)) { $nocollect = 1; $waitmessage = "!!! must configure username/password !!!"; } } ?> <form action="/widgets/widgets/shawtraffic.widget.php" method="post" name="iforma"> | Username: | | | Password: | | | Most recent entries to display: | <select name="shawtrafficentries" class="formfld" id="shawtrafficentries"><option value="1" <?php="" if="" ($displayentries="=" "1")="" echo="" "selected";?="">>1</option> <option value="3" <?php="" if="" ($displayentries="=" "3")="" echo="" "selected";?="">>3</option> <option value="5" <?php="" if="" ($displayentries="=" "5")="" echo="" "selected";?="">>5</option> <option value="10" <?php="" if="" ($displayentries="=" "10")="" echo="" "selected";?="">>10</option> <option value="1000" <?php="" if="" ($displayentries="=" "1000")="" echo="" "selected";?="">>All</option></select> | | Refresh time: | <select name="shawtrafficrefresh" class="formfld" id="shawtrafficrefresh"><option value="600" <?php="" if="" ($refresh="=" "600")="" echo="" "selected";?="">>10 mins</option> <option value="3600" <?php="" if="" ($refresh="=" "3600")="" echo="" "selected";?="">>1 hour</option></select> | </form> | period | download | upload | total | limit | | | /* Support functionality for traffic reading/parsing/output */ function output_table($content) { echo '' . '' . $content . ' | period | download | upload | total | limit | '; } function traffic_content($traffic,$maxentries) { $content = ""; $traffic_count = count($traffic); if ($traffic_count) { $last_idx = $traffic_count - 1; for ($idx = $last_idx, $displayed = 0; ($idx >= 0) && ($displayed < $maxentries); $idx--, $displayed++) { $total = $traffic[$idx]['Total']; $limit = $traffic[$idx]['Limit']; $ratio = $total / $limit; $bgcolor = 'lightgreen'; if ($ratio > 0.9) { $bgcolor = '#ef6666'; } else if ($ratio > 0.75) { $bgcolor = 'khaki'; } $content .= '' . '' . $traffic[$idx]['Period'] . '' . '' . $traffic[$idx]['Download'] . 'GB' . '' . $traffic[$idx]['Upload'] . 'GB' . ' | ' . $total . 'GB | ' . '' . $limit . 'GB' . ''; } } else { $content = 'no data available'; } return $content; } function get_chartdata($user,$pass) { $login_url = "https://signon.shaw.ca/?application=manage"; $usage_url = "https://my.shaw.ca/CustomerCentre/InternetModemUsage/SplitUsage"; $cookie_file_path = "/tmp/cookies.txt"; // begin script $ch = curl_init(); // extra headers $headers[] = "Accept: */*"; $headers[] = "Connection: Keep-Alive"; // basic curl options for all requests curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); curl_setopt($ch, CURLOPT_HEADER, 0); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($ch, CURLOPT_USERAGENT, $_SERVER['HTTP_USER_AGENT']); # mirror the user agent from the browser curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1); #curl_setopt($ch, CURLOPT_COOKIEFILE, $cookie_file_path); curl_setopt($ch, CURLOPT_COOKIEJAR, $cookie_file_path); $postfields="username=" . $user . "&password=" . $pass . "&signon=Sign+In&realm=occ&persistent=checked"; // do login curl_setopt($ch, CURLOPT_URL, $login_url); curl_setopt($ch, CURLOPT_POSTFIELDS, $postfields); curl_setopt($ch, CURLOPT_POST, 1); $result = curl_exec($ch); if ($result === false) { output_table('failed to get traffic data: ' . curl_error($ch) . ''); exit; } else if (preg_match('/The sign in attempt was not successful/i', $result)) { output_table('failed to get traffic data: invalid Shaw user/pass'); exit; } // fetch traffic page curl_setopt($ch, CURLOPT_URL, $usage_url); curl_setopt($ch, CURLOPT_POST, 0); $result = curl_exec($ch); if ($result === false) { output_table('failed to get traffic data: ' . curl_error($ch) . ''); exit; } curl_close($ch); // extract traffic data preg_match('/chart_modemUsageChart\.setDataXML\("([^"]+)"\)/is', $result, $matches); // replace linefeeds with dashes $xml = str_replace(' ', '-', $matches[1]); return $xml; } function parse_chartdata($xml) { $parser = new xmlToArrayParser($xml); if ($parser->parse_error) { output_table('failed to parse traffic data: ' . $parser->get_xml_error() . ''); exit; } $data = $parser->array; $traffic = array(); $categories = $data['chart']['categories']['category']; $dataset = $data['chart']['dataset']; $uploads = 0; $downloads = 0; $limits = 0; for ($set = 0; $set < count($dataset); $set++) { $_set = $dataset[$set]; $_set_data = $_set['set']; switch ($_set['attrib']['seriesName']) { case "Downloads": $downloads = $_set_data; break; case "Uploads": $uploads = $_set_data; break; case "Usage Limit": $limits = $_set_data; break; default: break; } } for ($cat = 0; $cat < count($categories); $cat++) { $down = $downloads[$cat]['attrib']['value']; $up = $uploads[$cat]['attrib']['value']; $tot = sprintf("%.2f", $down + $up); $traffic[] = array( "Period" => $categories[$cat]['attrib']['label'], "Download" => $down, "Upload" => $up, "Total" => $tot, "Limit" => $limits[$cat]['attrib']['value'] ); } return $traffic; } /* code taken from http://php.net/manual/en/ref.xml.php#106743 */ class xmlToArrayParser { /** The array created by the parser can be assigned to any variable: $anyVarArr = $domObj->array.*/ public $array = array(); public $parse_error = false; private $parser; private $pointer; /** Constructor: $domObj = new xmlToArrayParser($xml); */ public function __construct($xml) { $this->pointer =& $this->array; $this->parser = xml_parser_create("UTF-8"); xml_set_object($this->parser, $this); xml_parser_set_option($this->parser, XML_OPTION_CASE_FOLDING, false); xml_set_element_handler($this->parser, "tag_open", "tag_close"); xml_set_character_data_handler($this->parser, "cdata"); $this->parse_error = xml_parse($this->parser, ltrim($xml))? false : true; } /** Free the parser. */ public function __destruct() { xml_parser_free($this->parser);} /** Get the xml error if an an error in the xml file occured during parsing. */ public function get_xml_error() { if($this->parse_error) { $errCode = xml_get_error_code ($this->parser); $thisError = "Error Code [". $errCode ."] \"**" . xml_error_string($errCode)."**\", at char ".xml_get_current_column_number($this->parser) . " on line ".xml_get_current_line_number($this->parser).""; }else $thisError = $this->parse_error; return $thisError; } private function tag_open($parser, $tag, $attributes) { $this->convert_to_array($tag, 'attrib'); $idx=$this->convert_to_array($tag, 'cdata'); if(isset($idx)) { $this->pointer[$tag][$idx] = Array('@idx' => $idx,'@parent' => &$this->pointer); $this->pointer =& $this->pointer[$tag][$idx]; }else { $this->pointer[$tag] = Array('@parent' => &$this->pointer); $this->pointer =& $this->pointer[$tag]; } if (!empty($attributes)) { $this->pointer['attrib'] = $attributes; } } /** Adds the current elements content to the current pointer[cdata] array. */ private function cdata($parser, $cdata) { $this->pointer['cdata'] = trim($cdata); } private function tag_close($parser, $tag) { $current = & $this->pointer; if(isset($this->pointer['@idx'])) {unset($current['@idx']);} $this->pointer = & $this->pointer['@parent']; unset($current['@parent']); if(isset($current['cdata']) && count($current) == 1) { $current = $current['cdata'];} else if(empty($current['cdata'])) {unset($current['cdata']);} } /** Converts a single element item into array(element[0]) if a second element of the same name is encountered. */ private function convert_to_array($tag, $item) { if(isset($this->pointer[$tag][$item])) { $content = $this->pointer[$tag]; $this->pointer[$tag] = array((0) => $content); $idx = 1; }else if (isset($this->pointer[$tag])) { $idx = count($this->pointer[$tag]); if(!isset($this->pointer[$tag][0])) { foreach ($this->pointer[$tag] as $key => $value) { unset($this->pointer[$tag][$key]); $this->pointer[$tag][0][$key] = $value; }}}else $idx = null; return $idx; } } ?>
-
Small update to handle unlimited tiers.
*edit: another small change to remove a div-by-zero error
/* Shaw traffic dashboard widget for pfsense v0.3 Copyright 2013 Derek Rachul (drachul@gmail.com) All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1\. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2\. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ $nocsrf = true; require_once("guiconfig.inc"); require_once("pfsense-utils.inc"); require_once("functions.inc"); // configuration defaults $displayentries = 1000; $refresh = 3600; $username = ""; $password = ""; // save configuration for shaw traffic if(isset($_POST['shawtrafficsubmit'])) { $config['widgets']['shawtrafficrefresh'] = $_POST['shawtrafficrefresh']; $config['widgets']['shawtrafficentries'] = $_POST['shawtrafficentries']; $config['widgets']['shawtrafficusername'] = $_POST['shawtrafficusername']; $config['widgets']['shawtrafficpassword'] = $_POST['shawtrafficpassword']; write_config("Saved Shaw traffic Widget via Dashboard"); header("Location: /"); } // update configuration variables if available if (isset($config['widgets']['shawtrafficrefresh'])) $refresh = $config['widgets']['shawtrafficrefresh']; if (isset($config['widgets']['shawtrafficentries'])) $displayentries = $config['widgets']['shawtrafficentries']; if (isset($config['widgets']['shawtrafficusername'])) $username = $config['widgets']['shawtrafficusername']; if (isset($config['widgets']['shawtrafficpassword'])) $password = $config['widgets']['shawtrafficpassword']; // output a table with if ($_REQUEST['getshawtraf']) { $xml = get_chartdata($username,$password); $traffic = parse_chartdata($xml); $content = traffic_content($traffic,$displayentries); output_table($content); // exit so we don't output any of the content below exit; } else { $nocollect = 0; $waitmessage = "collecting traffic data..."; if (!strlen($username) or !strlen($password)) { $nocollect = 1; $waitmessage = "!!! must configure username/password !!!"; } } ?> <form action="/widgets/widgets/shawtraffic.widget.php" method="post" name="iforma"> | Username: | | | Password: | | | Most recent entries to display: | <select name="shawtrafficentries" class="formfld" id="shawtrafficentries"><option value="1" <?php="" if="" ($displayentries="=" "1")="" echo="" "selected";?="">>1</option> <option value="3" <?php="" if="" ($displayentries="=" "3")="" echo="" "selected";?="">>3</option> <option value="5" <?php="" if="" ($displayentries="=" "5")="" echo="" "selected";?="">>5</option> <option value="10" <?php="" if="" ($displayentries="=" "10")="" echo="" "selected";?="">>10</option> <option value="1000" <?php="" if="" ($displayentries="=" "1000")="" echo="" "selected";?="">>All</option></select> | | Refresh time: | <select name="shawtrafficrefresh" class="formfld" id="shawtrafficrefresh"><option value="600" <?php="" if="" ($refresh="=" "600")="" echo="" "selected";?="">>10 mins</option> <option value="3600" <?php="" if="" ($refresh="=" "3600")="" echo="" "selected";?="">>1 hour</option></select> | </form> | period | download | upload | total | limit | | | /* Support functionality for traffic reading/parsing/output */ function output_table($content) { echo '' . '' . $content . ' | period | download | upload | total | limit | '; } function traffic_content($traffic,$maxentries) { $content = ""; $traffic_count = count($traffic); if ($traffic_count) { $last_idx = $traffic_count - 1; for ($idx = $last_idx, $displayed = 0; ($idx >= 0) && ($displayed < $maxentries); $idx--, $displayed++) { $total = $traffic[$idx]['Total']; $limit = $traffic[$idx]['Limit']; $bgcolor = 'lightgreen'; if ($limit != 0) { $ratio = $total / $limit; if ($ratio > 0.9) { $bgcolor = '#ef6666'; } else if ($ratio > 0.75) { $bgcolor = 'khaki'; } $limit .= 'GB'; } else { $limit = 'Unlimited'; } $content .= '' . '' . $traffic[$idx]['Period'] . '' . '' . $traffic[$idx]['Download'] . 'GB' . '' . $traffic[$idx]['Upload'] . 'GB' . ' | ' . $total . 'GB | ' . '' . $limit . '' . ''; } } else { $content = 'no data available'; } return $content; } function get_chartdata($user,$pass) { $login_url = "https://signon.shaw.ca/?application=manage"; $usage_url = "https://my.shaw.ca/CustomerCentre/InternetModemUsage/SplitUsage"; $cookie_file_path = "/tmp/cookies.txt"; // begin script $ch = curl_init(); // extra headers $headers[] = "Accept: */*"; $headers[] = "Connection: Keep-Alive"; // basic curl options for all requests curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); curl_setopt($ch, CURLOPT_HEADER, 0); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($ch, CURLOPT_USERAGENT, $_SERVER['HTTP_USER_AGENT']); # mirror the user agent from the browser curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1); #curl_setopt($ch, CURLOPT_COOKIEFILE, $cookie_file_path); curl_setopt($ch, CURLOPT_COOKIEJAR, $cookie_file_path); $postfields="username=" . $user . "&password=" . $pass . "&signon=Sign+In&realm=occ&persistent=checked"; // do login curl_setopt($ch, CURLOPT_URL, $login_url); curl_setopt($ch, CURLOPT_POSTFIELDS, $postfields); curl_setopt($ch, CURLOPT_POST, 1); $result = curl_exec($ch); if ($result === false) { output_table('failed to get traffic data: ' . curl_error($ch) . ''); exit; } else if (preg_match('/The sign in attempt was not successful/i', $result)) { output_table('failed to get traffic data: invalid Shaw user/pass'); exit; } // fetch traffic page curl_setopt($ch, CURLOPT_URL, $usage_url); curl_setopt($ch, CURLOPT_POST, 0); $result = curl_exec($ch); if ($result === false) { output_table('failed to get traffic data: ' . curl_error($ch) . ''); exit; } curl_close($ch); // extract traffic data preg_match('/chart_modemUsageChart\.setDataXML\("([^"]+)"\)/is', $result, $matches); // replace linefeeds with dashes $xml = str_replace(' ', '-', $matches[1]); return $xml; } function parse_chartdata($xml) { $parser = new xmlToArrayParser($xml); if ($parser->parse_error) { output_table('failed to parse traffic data: ' . $parser->get_xml_error() . ''); exit; } $data = $parser->array; $traffic = array(); $categories = $data['chart']['categories']['category']; $dataset = $data['chart']['dataset']; $uploads = 0; $downloads = 0; $limits = 0; for ($set = 0; $set < count($dataset); $set++) { $_set = $dataset[$set]; $_set_data = $_set['set']; switch ($_set['attrib']['seriesName']) { case "Downloads": $downloads = $_set_data; break; case "Uploads": $uploads = $_set_data; break; case "Usage Limit": $limits = $_set_data; break; default: break; } } for ($cat = 0; $cat < count($categories); $cat++) { $down = $downloads[$cat]['attrib']['value']; $up = $uploads[$cat]['attrib']['value']; $tot = sprintf("%.2f", $down + $up); $traffic[] = array( "Period" => $categories[$cat]['attrib']['label'], "Download" => $down, "Upload" => $up, "Total" => $tot, "Limit" => $limits[$cat]['attrib']['value'] ); } return $traffic; } /* code taken from http://php.net/manual/en/ref.xml.php#106743 */ class xmlToArrayParser { /** The array created by the parser can be assigned to any variable: $anyVarArr = $domObj->array.*/ public $array = array(); public $parse_error = false; private $parser; private $pointer; /** Constructor: $domObj = new xmlToArrayParser($xml); */ public function __construct($xml) { $this->pointer =& $this->array; $this->parser = xml_parser_create("UTF-8"); xml_set_object($this->parser, $this); xml_parser_set_option($this->parser, XML_OPTION_CASE_FOLDING, false); xml_set_element_handler($this->parser, "tag_open", "tag_close"); xml_set_character_data_handler($this->parser, "cdata"); $this->parse_error = xml_parse($this->parser, ltrim($xml))? false : true; } /** Free the parser. */ public function __destruct() { xml_parser_free($this->parser);} /** Get the xml error if an an error in the xml file occured during parsing. */ public function get_xml_error() { if($this->parse_error) { $errCode = xml_get_error_code ($this->parser); $thisError = "Error Code [". $errCode ."] \"**" . xml_error_string($errCode)."**\", at char ".xml_get_current_column_number($this->parser) . " on line ".xml_get_current_line_number($this->parser).""; }else $thisError = $this->parse_error; return $thisError; } private function tag_open($parser, $tag, $attributes) { $this->convert_to_array($tag, 'attrib'); $idx=$this->convert_to_array($tag, 'cdata'); if(isset($idx)) { $this->pointer[$tag][$idx] = Array('@idx' => $idx,'@parent' => &$this->pointer); $this->pointer =& $this->pointer[$tag][$idx]; }else { $this->pointer[$tag] = Array('@parent' => &$this->pointer); $this->pointer =& $this->pointer[$tag]; } if (!empty($attributes)) { $this->pointer['attrib'] = $attributes; } } /** Adds the current elements content to the current pointer[cdata] array. */ private function cdata($parser, $cdata) { $this->pointer['cdata'] = trim($cdata); } private function tag_close($parser, $tag) { $current = & $this->pointer; if(isset($this->pointer['@idx'])) {unset($current['@idx']);} $this->pointer = & $this->pointer['@parent']; unset($current['@parent']); if(isset($current['cdata']) && count($current) == 1) { $current = $current['cdata'];} else if(empty($current['cdata'])) {unset($current['cdata']);} } /** Converts a single element item into array(element[0]) if a second element of the same name is encountered. */ private function convert_to_array($tag, $item) { if(isset($this->pointer[$tag][$item])) { $content = $this->pointer[$tag]; $this->pointer[$tag] = array((0) => $content); $idx = 1; }else if (isset($this->pointer[$tag])) { $idx = count($this->pointer[$tag]); if(!isset($this->pointer[$tag][0])) { foreach ($this->pointer[$tag] as $key => $value) { unset($this->pointer[$tag][$key]); $this->pointer[$tag][0][$key] = $value; }}}else $idx = null; return $idx; } } ?>
-
Not working on 2.0.3
Parshe error: syntax error, unexpected T_LNUMBER, expecting ',' or ';' -
@Lolipop:
Not working on 2.0.3
Parshe error: syntax error, unexpected T_LNUMBER, expecting ',' or ';'I had another small issue where it'd complain about a division by zero error (fixed in edited code in above post), but it's working here on 2.0.2 - I'll have to install 2.0.3 to see what else changed.