Skip to content

Instantly share code, notes, and snippets.

@kice
Last active November 14, 2022 17:59
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kice/96060dab4bfd653af80d9226c3c03980 to your computer and use it in GitHub Desktop.
Save kice/96060dab4bfd653af80d9226c3c03980 to your computer and use it in GitHub Desktop.
OPNsense dashboard widget for bandwidthd
<?php
/*
* Copyright (C) 2014 Deciso B.V.
* Copyright (C) 2009 Scott Ullrich <sullrich@gmail.com>
*
* 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.
*/
require_once("guiconfig.inc");
function parse_bandwidthd_html($file)
{
$lines = file($file);
$result = array();
foreach ($lines as $line) {
preg_match('/<TR(?: ?bgcolor=lightblue)?><TD><a href="(#(?:(?:\d+\.\d+\.\d+\.\d+)|(?:Total))\-\d)">((?:(?:\d+\.\d+\.\d+\.\d+)|(?:Total)))<\/a><\/TD>((?:<td align="right"><tt>(?:\d+(?:\.\d+)?\w?)(?:&nbsp;)?<\/tt><\/td>)+)<\/TR>/i', $line, $matches);
if (count($matches) == 0) {
continue;
}
$url = $matches[1];
$host = $matches[2];
$stats_text = $matches[3];
preg_match_all('/<td align="right"><tt>(\d+(?:\.\d+)?\w?)(?:&nbsp;)?<\/tt><\/td>/i', $stats_text, $stats);
array_push($result, array(
"url" => $url,
"host" => $host,
"stats" => $stats[1],
));
}
return $result;
}
if ($_POST) {
// TODO
exit;
}
// default install path
$htdocs_path = "/usr/local/bandwidthd/htdocs";
if ($config['widgets']['bandwidthd_htdocs']) {
$htdocs_path = $config['widgets']['bandwidthd_htdocs'];
}
$bandwidth_url = "/bandwidthd";
if ($config['widgets']['bandwidthd_url']) {
$bandwidth_url = $config['widgets']['bandwidthd_url'];
}
$show_top_n = 10;
if ($config['widgets']['show_top_n']) {
$show_top_n = $config['widgets']['show_top_n'];
}
if ($_GET['graph'] == "true") {
if (!isset($_GET['filename'])) {
exit;
}
$filename = $_GET['filename'];
$filepath = $htdocs_path . '/' . $_GET['filename'];
if (!file_exists($filepath)) {
exit;
}
$pic_type_s = explode(".", $filename);
$pic_type = $pic_type_s[1];
$filesize = filesize($filepath);
$fp = fopen($filepath, 'rb');
header("Content-Disposition: inline; filename=\"{$filename}\"");
header("Content-Type: image/{$pic_type}");
header("Content-Length: " . $filesize);
fpassthru($fp);
exit;
}
$bandwidth_graphs = array(
"Total" => array(
"url" => "/",
"graphs" => array("Total-1-S.png", "Total-1-R.png"),
),
);
$stat_data = array();
$bandwidth_html = $htdocs_path."/index.html";
if (file_exists($bandwidth_html)) {
$stat_data["daily"] = parse_bandwidthd_html($bandwidth_html);
}
$bandwidth_html = $htdocs_path."/index2.html";
if (file_exists($bandwidth_html)) {
$stat_data["weekly"] = parse_bandwidthd_html($bandwidth_html);
}
$bandwidth_html = $htdocs_path."/index3.html";
if (file_exists($bandwidth_html)) {
$stat_data["monthly"] = parse_bandwidthd_html($bandwidth_html);
}
$bandwidth_html = $htdocs_path."/index4.html";
if (file_exists($bandwidth_html)) {
$stat_data["yearly"] = parse_bandwidthd_html($bandwidth_html);
}
$bandwidth_stats = array();
foreach ($stat_data as $interval => $stats_list) {
foreach ($stats_list as $data) {
$host = $data['host'];
$url = $data['url'];
$stats = $data['stats'];
if (!array_key_exists($host, $bandwidth_stats)) {
$bandwidth_stats[$host] = array();
}
$bandwidth_stats[$host][$interval] = array(
"Total" => $stats[0],
"Sent" => $stats[1],
"Received" => $stats[2],
);
}
}
?>
<script>
const message = `
<?= print_r($bandwidth_stats, true)?>
`;
console.log(message);
</script>
<div id="bandwidthd-settings" class="widgetconfigdiv" style="display:none;">
<form action="/widgets/widgets/picture.widget.php" method="post" name="iforma" enctype="multipart/form-data">
<table class="table table-striped">
<tr>
<td>
<label>Bandwidthd htdoc Path</label>
<input type="text" name="bandwidthd_htdoc_path" id="bandwidthd_htdoc_path" value="" autocomplete="off">
</td>
</tr>
<tr>
<td>
<label>Bandwidthd URL</label>
<input type="text" name="bandwidthd_url" id="bandwidthd_url" value="" autocomplete="off">
</td>
</tr>
<tr>
<td>
<input id="submit_bandwidthd_widget" name="submit_bandwidthd_widget" type="submit" class="btn btn-primary formbtn" value="<?= html_safe(gettext('Save')) ?>" />
</td>
</tr>
</table>
</form>
</div>
<!-- hide widget if none is defined in the configuration -->
<table id="bandwidthd-widgets" class="table table-condensed">
<?php
foreach ($bandwidth_graphs as $host => $data) :
?>
<tr><th><a href=<?= $bandwidth_url.$data["url"]?> target="_blank"><?= $host?></a></th></tr>
<tr><td>
<label>Send - <?= $bandwidth_stats[$host]["daily"]["Sent"]?></label>
<a href='/widgets/widgets/bandwidthd.widget.php?graph=true&filename=<?= html_safe($data["graphs"][0])?>' target='_blank'>
<img style="border:0px solid; width:100%; height:100%"
src="/widgets/widgets/bandwidthd.widget.php?graph=true&filename=<?= html_safe($data["graphs"][0])?>" alt="graph" />
</a>
<label>Received - <?= $bandwidth_stats[$host]["daily"]["Received"]?></label>
<a href='/widgets/widgets/bandwidthd.widget.php?graph=true&filename=<?= html_safe($data["graphs"][1])?>' target='_blank'>
<img style="border:0px solid; width:100%; height:100%"
src="/widgets/widgets/bandwidthd.widget.php?graph=true&filename=<?= html_safe($data["graphs"][1])?>" alt="graph" />
</a>
</td></tr>
<?php
endforeach;
?>
</table>
<!-- needed to show the settings widget icon -->
<script>
//<![CDATA[
//$("#bandwidthd-configure").removeClass("disabled");
//]]>
</script>

Bandwidthd Graph OPNsense Dashboard Widget

Usage:

Install Bandwidthd

Reference

Bandwidthd is very old and might break, use it at your own risk

pkg install bandwidthd

After installation, change dev settings in /usr/local/bandwidthd/etc/bandwidthd.conf, normally use your LAN interface. Check the interface names in the Interface->Assignments in the OPNSense UI, or use ifconfig.

ln -s /usr/local/bandwidthd/htdocs/ /usr/local/www/bandwidthd

Start bandwidthd

/usr/local/etc/rc.d/bandwidthd start

Open bandwidthd page on your OPNsense, it should be like http://192.168.1.X/bandwidthd

Install this Widget

pkg install wget https://gist.githubusercontent.com/kice/96060dab4bfd653af80d9226c3c03980/raw/f26ec810e83ae57a0dc9e9e92841303744ad05d1/bandwidthd.widget.php
wget -qO /usr/local/www/widgets/widgets/bandwidthd.widget.php
  • Add widget trough Opnsense GUI

Known issues

If traffic is more than 1TB, bandwidthd will insert new line to HTML, causing actual traffic cannot correctly display on the widget. But it dose not effect the widget to display the traffic graph, and it is hard to parse HTML with PHP without third party library, so no plan to fix it now.

About this widget

You might need to setup htdoc path in the code.

$htdocs_path = "<your bandwidthd install path>/htdocs";

And the bandwidthd url

$bandwidth_url = "<your bandwidth url>";

There are left over code I might implement later, such as:

  1. Config htdocs path, bandwidthd url, or graph via Webui (this might be security risk though).
  2. Show the table of bandwidthd ip traffic in the widget (devices list is too long to fit).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment