Create vnstat-dashboard

This commit is contained in:
Stille 2021-06-08 20:47:17 +08:00
parent b3cbd615c8
commit 31f2ee91dd
19 changed files with 1218 additions and 0 deletions

42
.github/workflows/vnstat-dashboard.yml vendored Normal file
View File

@ -0,0 +1,42 @@
name: "vnstat-dashboard docker build"
env:
PROJECT: vnstat-dashboard
on:
workflow_dispatch:
jobs:
build:
runs-on: ubuntu-latest
env:
ACTIONS_ALLOW_UNSECURE_COMMANDS: true
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Set tag
id: tag
run: |
TAG=$(cat ${{ env.PROJECT }}/Dockerfile | awk 'NR==4 {print $3}')
echo "::set-env name=TAG::$TAG"
- name: Docker Hub login
env:
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
run: |
echo "${DOCKER_PASSWORD}" | docker login --username ${DOCKER_USERNAME} --password-stdin
- name: Set up Docker Buildx
id: buildx
uses: crazy-max/ghaction-docker-buildx@v1
with:
buildx-version: latest
- name: Build Dockerfile
env:
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
run: |
docker buildx build \
--platform=linux/amd64,linux/arm64 \
--output "type=image,push=true" \
--file ${{ env.PROJECT }}/Dockerfile ./${{ env.PROJECT }} \
--tag $(echo "${DOCKER_USERNAME}" | tr '[:upper:]' '[:lower:]')/${{ env.PROJECT }}:latest \
--tag $(echo "${DOCKER_USERNAME}" | tr '[:upper:]' '[:lower:]')/${{ env.PROJECT }}:${TAG}

View File

@ -0,0 +1,9 @@
root = true
[*]
indent_style = space
indent_size = 4
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

View File

@ -0,0 +1,17 @@
FROM php:7.0-apache
MAINTAINER Alex Marston <alexander.marston@gmail.com>
ENV VERSION 1.0
# Install Git
RUN apt-get update && apt-get install -y git unzip
# Install Composer to handle dependencies
RUN curl -sS https://getcomposer.org/installer | php && mv composer.phar /usr/local/bin/composer
# Copy application source code to html directory
COPY ./app/ /var/www/html/
# Install dependencies
RUN composer install
RUN mkdir -p /var/lib/vnstat

View File

@ -0,0 +1,8 @@
# vnstat-dashboard for docker
GitHub [stilleshan/dockerfile](https://github.com/stilleshan/dockerfile)
Docker [stilleshan/vnstat-dashboard](https://hub.docker.com/r/stilleshan/vnstat-dashboard)
> *docker image support for X86 and ARM*
## 使用
本仓库参考 [tomangert/vnstat-dashboard](https://github.com/tomangert/vnstat-dashboard) 对原作者仓库 [alexandermarston/vnstat-dashboard](https://github.com/alexandermarston/vnstat-dashboard) 进行部分 bug 修复后构建 docker 镜像,主要用与自用和存档备份,具体使用教程请参考原作者仓库`README`文件.

View File

@ -0,0 +1 @@
theme: jekyll-theme-slate

View File

@ -0,0 +1,50 @@
/*
Copyright (C) 2019 Alexander Marston (alexander.marston@gmail.com)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/* http://stackoverflow.com/questions/17206631/why-are-bootstrap-tabs-displaying-tab-pane-divs-with-incorrect-widths-when-using */
/* bootstrap hack: fix content width inside hidden tabs */
.tab-content > .tab-pane:not(.active),
.pill-content > .pill-pane:not(.active) {
display: block;
height: 0;
overflow-y: hidden;
}
/* bootstrap hack end */
/* Sticky footer styles
-------------------------------------------------- */
html {
position: relative;
min-height: 100%;
}
body {
margin-bottom: 60px; /* Margin bottom by footer height */
}
.footer {
position: absolute;
bottom: 0;
width: 100%;
height: 60px; /* Set the fixed height of the footer here */
line-height: 60px; /* Vertically center the text there */
background-color: #f5f5f5;
}
.nav-tabs {
margin-bottom: 10px;
}
.navbar {
margin-bottom: 25px;
}

View File

@ -0,0 +1,5 @@
{
"require": {
"smarty/smarty": "~3.1"
}
}

View File

@ -0,0 +1,45 @@
<?php
/*
* Copyright (C) 2019 Alexander Marston (alexander.marston@gmail.com)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
// Uncomment to enable error reporting to the screen
/*ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
ferror_reporting(E_ALL);*/
// Set the default system Timezone
date_default_timezone_set('Europe/London');
// Path of vnstat
$vnstat_bin_dir = '/usr/bin/vnstat';
// Path of config file
/*$vnstat_config = '/etc/vnstat.conf';*/
// linear or logarithmic graphs. Uncomment for logarithmic
/*$graph_type = 'log';*/
// Set to true to set your own interfaces
$use_predefined_interfaces = false;
if ($use_predefined_interfaces == true) {
$interface_list = ["eth0", "eth1"];
$interface_name['eth0'] = "Internal #1";
$interface_name['eth1'] = "Internal #2";
}

View File

@ -0,0 +1,125 @@
<?php
/*
* Copyright (C) 2019 Alexander Marston (alexander.marston@gmail.com)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
$logk = log(1024);
function getScale($bytes)
{
global $logk;
$ui = floor(round(log($bytes)/$logk,3));
if ($ui < 0) { $ui = 0; }
if ($ui > 8) { $ui = 8; }
return $ui;
}
// Get the largest value in an array
function getLargestValue($array) {
return $max = array_reduce($array, function ($a, $b) {
return $a > $b['total'] ? $a : $b['total'];
});
}
function getBaseValue($array, $scale)
{
$big = pow(1024,9);
// Find the smallest non-zero value
$sml = array_reduce($array, function ($a, $b) {
if ((1 <= $b['rx']) && ($b['rx'] < $b['tx'])) {
$sm = $b['rx'];
} else {
$sm = $b['tx'];
}
if (($sm < 1) || ($a < $sm)) {
return $a;
} else {
return $sm;
}
}, $big);
if ($sml >= $big/2) {
$sml = 1;
}
// divide by scale then round down to a power of 10
$base = pow(10,floor(round(log10($sml/pow(1024,$scale)),3)));
// convert back to bytes
$baseByte = $base * pow(1024, $scale);
// Don't make the bar invisable - must be > 5% difference
if ($sml / $baseByte < 1.05) {
$base = $base / 10;
}
return $base;
}
function formatSize($bytes, $vnstatJsonVersion, $decimals = 2) {
// json version 1 = convert from KiB
// json version 2 = convert from bytes
if ($vnstatJsonVersion == 1) {
$bytes *= 1024; // convert from kibibytes to bytes
}
return formatBytes($bytes, $decimals);
}
function getLargestPrefix($scale)
{
$suffixes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
return $suffixes[$scale];
}
function formatBytes($bytes, $decimals = 3) {
$scale = getScale($bytes);
return round($bytes/pow(1024, $scale), $decimals) .' '. getLargestPrefix($scale);
}
function formatBytesTo($bytes, $scale, $decimals = 4) {
if ($bytes == 0) {
return '0';
}
return number_format(($bytes / pow(1024, $scale)), $decimals, ".", "");
}
function kibibytesToBytes($kibibytes, $vnstatJsonVersion) {
if ($vnstatJsonVersion == 1) {
return $kibibytes *= 1024;
} else {
return $kibibytes;
}
}
function sortingFunction($item1, $item2) {
if ($item1['time'] == $item2['time']) {
return 0;
} else {
return $item1['time'] > $item2['time'] ? -1 : 1;
}
};
?>

View File

@ -0,0 +1,284 @@
<?php
/*
* Copyright (C) 2019 Alexander Marston (alexander.marston@gmail.com)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
class vnStat {
protected $executablePath;
protected $vnstatVersion;
protected $vnstatJsonVersion;
protected $vnstatData;
public function __construct ($executablePath) {
if (isset($executablePath)) {
$this->executablePath = $executablePath;
// Execute a command to output a json dump of the vnstat data
$vnstatStream = popen("$this->executablePath --json", 'r');
// Is the stream valid?
if (is_resource($vnstatStream)) {
$streamBuffer = '';
while (!feof($vnstatStream)) {
$streamBuffer .= fgets($vnstatStream);
}
// Close the handle
pclose($vnstatStream);
$this->processVnstatData($streamBuffer);
} else {
}
} else {
die();
}
}
private function processVnstatData($vnstatJson) {
$decodedJson = json_decode($vnstatJson, true);
// Check the JSON is valid
if (json_last_error() != JSON_ERROR_NONE) {
throw new Exception('JSON is invalid');
}
$this->vnstatData = $decodedJson;
$this->vnstatVersion = $decodedJson['vnstatversion'];
$this->vnstatJsonVersion = $decodedJson['jsonversion'];
}
public function getVnstatVersion() {
return $this->vnstatVersion;
}
public function getVnstatJsonVersion() {
return $this->vnstatJsonVersion;
}
public function getInterfaces() {
// Create a placeholder array
$vnstatInterfaces = [];
foreach($this->vnstatData['interfaces'] as $interface) {
if ($this->vnstatJsonVersion == 1) {
array_push($vnstatInterfaces, $interface['id']);
} else {
array_push($vnstatInterfaces, $interface['name']);
}
}
return $vnstatInterfaces;
}
public function getInterfaceData($timeperiod, $type, $interface) {
// If json version equals 1, add an 's' onto the end of each type.
// e.g. 'top' becomes 'tops'
$typeAppend = '';
if ($this->vnstatJsonVersion == 1) {
$typeAppend = 's';
}
// Blank placeholder
$trafficData = [];
$i = -1;
// Get the array index for the chosen interface
if ($this->vnstatJsonVersion == 1) {
$arrayIndex = array_search($interface, array_column($this->vnstatData['interfaces'], 'id'));
} else {
$arrayIndex = array_search($interface, array_column($this->vnstatData['interfaces'], 'name'));
}
if ($timeperiod == 'top10') {
if ($type == 'table') {
foreach ($this->vnstatData['interfaces'][$arrayIndex]['traffic']['top'.$typeAppend] as $traffic) {
if (is_array($traffic)) {
$i++;
$trafficData[$i]['label'] = date('d/m/Y', strtotime($traffic['date']['month'] . "/" . $traffic['date']['day'] . "/" . $traffic['date']['year']));;
$trafficData[$i]['rx'] = formatSize($traffic['rx'], $this->vnstatJsonVersion);
$trafficData[$i]['tx'] = formatSize($traffic['tx'], $this->vnstatJsonVersion);
$trafficData[$i]['total'] = formatSize(($traffic['rx'] + $traffic['tx']), $this->vnstatJsonVersion);
$trafficData[$i]['totalraw'] = ($traffic['rx'] + $traffic['tx']);
}
}
}
}
if (($this->vnstatJsonVersion > 1) && ($timeperiod == 'five')) {
if ($type == 'table') {
foreach ($this->vnstatData['interfaces'][$arrayIndex]['traffic']['fiveminute'] as $traffic) {
if (is_array($traffic)) {
$i++;
$trafficData[$i]['label'] = date("d/m/Y H:i", mktime($traffic['time']['hour'], $traffic['time']['minute'], 0, $traffic['date']['month'], $traffic['date']['day'], $traffic['date']['year']));
$trafficData[$i]['time'] = mktime($traffic['time']['hour'], $traffic['time']['minute'], 0, $traffic['date']['month'], $traffic['date']['day'], $traffic['date']['year']);
$trafficData[$i]['rx'] = formatSize($traffic['rx'], $this->vnstatJsonVersion);
$trafficData[$i]['tx'] = formatSize($traffic['tx'], $this->vnstatJsonVersion);
$trafficData[$i]['total'] = formatSize(($traffic['rx'] + $traffic['tx']), $this->vnstatJsonVersion);
}
}
} else if ($type == 'graph') {
foreach ($this->vnstatData['interfaces'][$arrayIndex]['traffic']['fiveminute'] as $traffic) {
if (is_array($traffic)) {
$i++;
$trafficData[$i]['label'] = sprintf("Date(%d, %d, %d, %d, %d)", $traffic['date']['year'], $traffic['date']['month']-1, $traffic['date']['day'], $traffic['time']['hour'], $traffic['time']['minute']);
$trafficData[$i]['time'] = mktime($traffic['time']['hour'], $traffic['time']['minute'], 0, $traffic['date']['month'], $traffic['date']['day'], $traffic['date']['year']);
$trafficData[$i]['rx'] = kibibytesToBytes($traffic['rx'], $this->vnstatJsonVersion);
$trafficData[$i]['tx'] = kibibytesToBytes($traffic['tx'], $this->vnstatJsonVersion);
$trafficData[$i]['total'] = kibibytesToBytes(($traffic['rx'] + $traffic['tx']), $this->vnstatJsonVersion);
}
}
}
}
if ($timeperiod == 'hourly') {
if ($type == 'table') {
foreach ($this->vnstatData['interfaces'][$arrayIndex]['traffic']['hour'.$typeAppend] as $traffic) {
if (is_array($traffic)) {
$i++;
if ($this->vnstatJsonVersion == 1) {
$hour = $traffic['id'];
} else {
$hour = $traffic['time']['hour'];
}
$trafficData[$i]['label'] = date("d/m/Y H:i", mktime($hour, 0, 0, $traffic['date']['month'], $traffic['date']['day'], $traffic['date']['year']));
$trafficData[$i]['time'] = mktime($hour, 0, 0, $traffic['date']['month'], $traffic['date']['day'], $traffic['date']['year']);
$trafficData[$i]['rx'] = formatSize($traffic['rx'], $this->vnstatJsonVersion);
$trafficData[$i]['tx'] = formatSize($traffic['tx'], $this->vnstatJsonVersion);
$trafficData[$i]['total'] = formatSize(($traffic['rx'] + $traffic['tx']), $this->vnstatJsonVersion);
}
}
} else if ($type == 'graph') {
foreach ($this->vnstatData['interfaces'][$arrayIndex]['traffic']['hour'.$typeAppend] as $traffic) {
if (is_array($traffic)) {
$i++;
if ($this->vnstatJsonVersion == 1) {
$hour = $traffic['id'];
} else {
$hour = $traffic['time']['hour'];
}
$trafficData[$i]['label'] = sprintf("Date(%d, %d, %d, %d)", $traffic['date']['year'], $traffic['date']['month']-1, $traffic['date']['day'], $hour);
$trafficData[$i]['time'] = mktime($hour, 0, 0, $traffic['date']['month'], $traffic['date']['day'], $traffic['date']['year']);
$trafficData[$i]['rx'] = kibibytesToBytes($traffic['rx'], $this->vnstatJsonVersion);
$trafficData[$i]['tx'] = kibibytesToBytes($traffic['tx'], $this->vnstatJsonVersion);
$trafficData[$i]['total'] = kibibytesToBytes(($traffic['rx'] + $traffic['tx']), $this->vnstatJsonVersion);
}
}
}
}
if ($timeperiod == 'daily') {
if ($type == 'table') {
foreach ($this->vnstatData['interfaces'][$arrayIndex]['traffic']['day'.$typeAppend] as $traffic) {
if (is_array($traffic)) {
$i++;
$trafficData[$i]['label'] = date('d/m/Y', mktime(0, 0, 0, $traffic['date']['month'], $traffic['date']['day'], $traffic['date']['year']));
$trafficData[$i]['time'] = mktime(0, 0, 0, $traffic['date']['month'], $traffic['date']['day'], $traffic['date']['year']);
$trafficData[$i]['rx'] = formatSize($traffic['rx'], $this->vnstatJsonVersion);
$trafficData[$i]['tx'] = formatSize($traffic['tx'], $this->vnstatJsonVersion);
$trafficData[$i]['total'] = formatSize(($traffic['rx'] + $traffic['tx']), $this->vnstatJsonVersion);
}
}
} else if ($type == 'graph') {
foreach ($this->vnstatData['interfaces'][$arrayIndex]['traffic']['day'.$typeAppend] as $traffic) {
if (is_array($traffic)) {
$i++;
$trafficData[$i]['label'] = sprintf("Date(%d, %d, %d)", $traffic['date']['year'], $traffic['date']['month']-1, $traffic['date']['day']);
$trafficData[$i]['time'] = mktime(0, 0, 0, $traffic['date']['month'], $traffic['date']['day'], $traffic['date']['year']);
$trafficData[$i]['rx'] = kibibytesToBytes($traffic['rx'], $this->vnstatJsonVersion);
$trafficData[$i]['tx'] = kibibytesToBytes($traffic['tx'], $this->vnstatJsonVersion);
$trafficData[$i]['total'] = kibibytesToBytes(($traffic['rx'] + $traffic['tx']), $this->vnstatJsonVersion);
}
}
}
}
if ($timeperiod == 'monthly') {
if ($type == 'table') {
foreach ($this->vnstatData['interfaces'][$arrayIndex]['traffic']['month'.$typeAppend] as $traffic) {
if (is_array($traffic)) {
$i++;
$trafficData[$i]['label'] = date('F Y', mktime(0, 0, 0, $traffic['date']['month'], 10, $traffic['date']['year']));
$trafficData[$i]['time'] = mktime(0, 0, 0, $traffic['date']['month'], 10, $traffic['date']['year']);
$trafficData[$i]['rx'] = formatSize($traffic['rx'], $this->vnstatJsonVersion);
$trafficData[$i]['tx'] = formatSize($traffic['tx'], $this->vnstatJsonVersion);
$trafficData[$i]['total'] = formatSize(($traffic['rx'] + $traffic['tx']), $this->vnstatJsonVersion);
}
}
} else if ($type == 'graph') {
foreach ($this->vnstatData['interfaces'][$arrayIndex]['traffic']['month'.$typeAppend] as $traffic) {
if (is_array($traffic)) {
$i++;
$trafficData[$i]['label'] = sprintf("Date(%d, %d, %d)", $traffic['date']['year'], $traffic['date']['month'] - 1, 10);
$trafficData[$i]['time'] = mktime(0, 0, 0, $traffic['date']['month'], 10, $traffic['date']['year']);
$trafficData[$i]['rx'] = kibibytesToBytes($traffic['rx'], $this->vnstatJsonVersion);
$trafficData[$i]['tx'] = kibibytesToBytes($traffic['tx'], $this->vnstatJsonVersion);
$trafficData[$i]['total'] = kibibytesToBytes(($traffic['rx'] + $traffic['tx']), $this->vnstatJsonVersion);
}
}
}
}
if ($timeperiod != 'top10') {
usort($trafficData, 'sortingFunction');
}
if ($type == 'graph') {
// Get the largest value and then prefix (B, KB, MB, GB, etc)
$trafficLargestValue = getLargestValue($trafficData);
$trafficScale = getScale($trafficLargestValue);
$trafficLargestPrefix = getLargestPrefix($trafficScale);
$trafficBase = getBaseValue($trafficData, $trafficScale);
if (($trafficBase < .0099) && ($trafficScale >= 1))
{
$trafficScale = $trafficScale - 1;
$trafficLargestPrefix = getLargestPrefix($trafficScale);
$trafficBase = getBaseValue($trafficData, $trafficScale);
}
foreach($trafficData as &$value) {
$value['rx'] = formatBytesTo($value['rx'], $trafficScale);
$value['tx'] = formatBytesTo($value['tx'], $trafficScale);
$value['total'] = formatBytesTo($value['total'], $trafficScale);
}
unset($value);
$trafficData[0]['delimiter'] = $trafficLargestPrefix;
$trafficData[0]['base'] = $trafficBase;
}
return $trafficData;
}
}
?>

View File

@ -0,0 +1,117 @@
<?php
/*
* Copyright (C) 2019 Alexander Marston (alexander.marston@gmail.com)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
// Require includes
require __DIR__ . '/vendor/autoload.php';
require __DIR__ . '/includes/utilities.php';
require __DIR__ . '/includes/vnstat.php';
require __DIR__ . '/includes/config.php';
if (isset($vnstat_config)) {
$vnstat_cmd = $vnstat_bin_dir.' --config '.$vnstat_config;
} else {
$vnstat_cmd = $vnstat_bin_dir;
}
if (empty($graph_type)) {
$graph_type = 'linear';
}
// Initiaite vnStat class
$vnstat = new vnStat($vnstat_cmd);
// Initiate Smarty
$smarty = new Smarty();
// Set the current year
$smarty->assign('year', date("Y"));
// Set the list of interfaces
$interface_list = $vnstat->getInterfaces();
// Set the current interface
$thisInterface = "";
if (isset($_GET['i'])) {
$interfaceChosen = rawurldecode($_GET['i']);
if (in_array($interfaceChosen, $interface_list, true)) {
$thisInterface = $interfaceChosen;
} else {
$thisInterface = reset($interface_list);
}
} else {
// Assume they mean the first interface
$thisInterface = reset($interface_list);
}
$smarty->assign('graph_type', $graph_type);
$smarty->assign('current_interface', $thisInterface);
// Assign interface options
$smarty->assign('interface_list', $interface_list);
// JsonVersion
$smarty->assign('jsonVersion', $vnstat->getVnstatJsonVersion());
// Populate table data
if ($vnstat->getVnstatJsonVersion() > 1) {
$fiveData = $vnstat->getInterfaceData('five', 'table', $thisInterface);
$smarty->assign('fiveTableData', $fiveData);
}
$hourlyData = $vnstat->getInterfaceData('hourly', 'table', $thisInterface);
$smarty->assign('hourlyTableData', $hourlyData);
$dailyData = $vnstat->getInterfaceData('daily', 'table', $thisInterface);
$smarty->assign('dailyTableData', $dailyData);
$monthlyData = $vnstat->getInterfaceData('monthly', 'table', $thisInterface);
$smarty->assign('monthlyTableData', $monthlyData);
$top10Data = $vnstat->getInterfaceData('top10', 'table', $thisInterface);
$smarty->assign('top10TableData', $top10Data);
// Populate graph data
if ($vnstat->getVnstatJsonVersion() > 1) {
$fiveGraphData = $vnstat->getInterfaceData('five', 'graph', $thisInterface);
$smarty->assign('fiveGraphData', $fiveGraphData);
$smarty->assign('fiveLargestPrefix', $fiveGraphData[0]['delimiter']);
$smarty->assign('fiveBase', $fiveGraphData[0]['base']);
}
$hourlyGraphData = $vnstat->getInterfaceData('hourly', 'graph', $thisInterface);
$smarty->assign('hourlyGraphData', $hourlyGraphData);
$smarty->assign('hourlyLargestPrefix', $hourlyGraphData[0]['delimiter']);
$smarty->assign('hourlyBase', $hourlyGraphData[0]['base']);
$dailyGraphData = $vnstat->getInterfaceData('daily', 'graph', $thisInterface);
$smarty->assign('dailyGraphData', $dailyGraphData);
$smarty->assign('dailyLargestPrefix', $dailyGraphData[0]['delimiter']);
$smarty->assign('dailyBase', $dailyGraphData[0]['base']);
$monthlyGraphData = $vnstat->getInterfaceData('monthly', 'graph', $thisInterface);
$smarty->assign('monthlyGraphData', $monthlyGraphData);
$smarty->assign('monthlyLargestPrefix', $monthlyGraphData[0]['delimiter']);
// Display the page
$smarty->display('templates/site_index.tpl');
?>

View File

@ -0,0 +1,16 @@
<footer class="footer">
<div class="container">
<span class="text-muted">Copyright (C) {$year} Alexander Marston -
<a href="https://github.com/alexandermarston/vnstat-dashboard">vnstat-dashboard</a>
</span>
</div>
</footer>
<script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>
{include file="module_graph_js.tpl"}
</body>
</html>

View File

@ -0,0 +1,46 @@
<div class="container">
<ul class="nav nav-tabs" id="graphTab" role="tablist">
{if $jsonVersion gt 1}
<li class="nav-item">
<a class="nav-link active" id="five-graph-tab" data-toggle="tab" href="#five-graph" role="tab" aria-controls="five-graph" aria-selected="true">Five Minute Graph</a>
</li>
<li class="nav-item">
<a class="nav-link" id="hourly-graph-tab" data-toggle="tab" href="#hourly-graph" role="tab" aria-controls="hourly-graph" aria-selected="false">Hourly Graph</a>
</li>
{else}
<li class="nav-item">
<a class="nav-link active" id="hourly-graph-tab" data-toggle="tab" href="#hourly-graph" role="tab" aria-controls="hourly-graph" aria-selected="true">Hourly Graph</a>
</li>
{/if}
<li class="nav-item">
<a class="nav-link" id="daily-graph-tab" data-toggle="tab" href="#daily-graph" role="tab" aria-controls="daily-graph" aria-selected="false">Daily Graph</a>
</li>
<li class="nav-item">
<a class="nav-link" id="monthly-graph-tab" data-toggle="tab" href="#monthly-graph" role="tab" aria-controls="monthly-graph" aria-selected="false">Monthly Graph</a>
</li>
</ul>
<div class="tab-content">
{if $jsonVersion gt 1}
<div class="tab-pane fade show active" id="five-graph" role="tabpanel" aria-labelledby="five-graph-tab">
<div id="fiveNetworkTrafficGraph" style="height: 300px;"></div>
</div>
<div class="tab-pane fade" id="hourly-graph" role="tabpanel" aria-labelledby="hourly-graph-tab">
<div id="hourlyNetworkTrafficGraph" style="height: 300px;"></div>
</div>
{else}
<div class="tab-pane fade show active" id="hourly-graph" role="tabpanel" aria-labelledby="hourly-graph-tab">
<div id="hourlyNetworkTrafficGraph" style="height: 300px;"></div>
</div>
{/if}
<div class="tab-pane fade" id="daily-graph" role="tabpanel" aria-labelledby="daily-graph-tab">
<div id="dailyNetworkTrafficGraph" style="height: 300px;"></div>
</div>
<div class="tab-pane fade" id="monthly-graph" role="tabpanel" aria-labelledby="monthly-graph-tab">
<div id="monthlyNetworkTrafficGraph" style="height: 300px;"></div>
</div>
</div>
</div>

View File

@ -0,0 +1,256 @@
<script type="text/javascript">
google.charts.load('current', { packages: [ 'bar' ] });
google.charts.load("current", { packages: [ 'corechart' ] });
google.charts.setOnLoadCallback(drawFiveChart);
google.charts.setOnLoadCallback(drawHourlyChart);
google.charts.setOnLoadCallback(drawDailyChart);
google.charts.setOnLoadCallback(drawMonthlyChart);
function drawFiveChart()
{
{if $jsonVersion gt 1}
var data = new google.visualization.DataTable();
data.addColumn('datetime', 'Time');
data.addColumn('number', 'Traffic In');
data.addColumn('number', 'Traffic Out');
data.addColumn('number', 'Total Traffic');
data.addRows([
{foreach from=$fiveGraphData key=key item=value}
[new {$value.label}, {$value.rx}, {$value.tx}, {$value.total}],
{/foreach}
]);
let endD = (new {$fiveGraphData[0]['label']}).getTime();
let options = {
title: 'Five minute Network Traffic',
orientation: 'horizontal',
legend: { position: 'right' },
explorer: {
axis: 'horizontal',
zoomDelta: 1.1,
maxZoomIn: 0.1,
maxZoomOut: 10.0
},
vAxis: {
format: '###.### {$fiveLargestPrefix}'
{if $graph_type == 'log'}
,scaleType: 'log',
baseline: {$fiveBase}
{/if}
},
hAxis: {
direction: -1,
format: 'd/H:mm',
{if $jsonVersion > 1}
title: 'Day/Hour:Minute (Scroll to zoom, Drag to pan)',
{else}
title: 'Day/Hour:Minute',
{/if}
viewWindow: {
min: 'Date('+(endD-7050000).toString()+')',
max: 'Date('+(endD+150000).toString()+')'
},
ticks: [
{foreach from=$fiveGraphData key=key item=value}
new {$value.label},
{/foreach}
]
}
};
var formatDate = new google.visualization.DateFormat({ pattern: 'dd/MM/yyyy HH:mm' });
formatDate.format(data, 0);
var formatNumber = new google.visualization.NumberFormat({ pattern: '##.## {$fiveLargestPrefix}' });
formatNumber.format(data, 1);
formatNumber.format(data, 2);
formatNumber.format(data, 3);
let chart = new google.visualization.BarChart(document.getElementById('fiveNetworkTrafficGraph'));
chart.draw(data, google.charts.Bar.convertOptions(options));
{/if}
}
function drawHourlyChart()
{
var data = new google.visualization.DataTable();
data.addColumn('date', 'Hour');
data.addColumn('number', 'Traffic In');
data.addColumn('number', 'Traffic Out');
data.addColumn('number', 'Total Traffic');
data.addRows([
{foreach from=$hourlyGraphData key=key item=value}
[new {$value.label}, {$value.rx}, {$value.tx}, {$value.total}],
{/foreach}
]);
let endD = (new {$hourlyGraphData[0]['label']}).getTime();
let options = {
title: 'Hourly Network Traffic',
orientation: 'horizontal',
legend: { position: 'right' },
explorer: {
axis: 'horizontal',
zoomDelta: 1.1,
maxZoomIn: 0.1,
maxZoomOut: 10.0
},
vAxis: {
format: '###.### {$hourlyLargestPrefix}'
{if $graph_type == 'log'}
,scaleType: 'log',
baseline: {$hourlyBase}
{/if}
},
hAxis: {
{if $jsonVersion > 1}
title: 'Day/Hour (Scroll to zoom, Drag to pan)',
{else}
title: 'Day/Hour',
{/if}
format: 'd/H',
direction: -1,
viewWindow: {
min: 'Date('+(endD-84600000).toString()+')',
max: 'Date('+(endD+1800000).toString()+')'
},
ticks: [
{foreach from=$hourlyGraphData key=key item=value}
new {$value.label},
{/foreach}
]
}
};
var formatDate = new google.visualization.DateFormat({ pattern: 'dd/MM/yyyy HH:mm' });
formatDate.format(data, 0);
var formatNumber = new google.visualization.NumberFormat({ pattern: '##.## {$hourlyLargestPrefix}' });
formatNumber.format(data, 1);
formatNumber.format(data, 2);
formatNumber.format(data, 3);
let chart = new google.visualization.BarChart(document.getElementById('hourlyNetworkTrafficGraph'));
chart.draw(data, google.charts.Bar.convertOptions(options));
}
function drawDailyChart()
{
var data = new google.visualization.DataTable();
data.addColumn('date', 'Day');
data.addColumn('number', 'Traffic In');
data.addColumn('number', 'Traffic Out');
data.addColumn('number', 'Total Traffic');
data.addRows([
{foreach from=$dailyGraphData key=key item=value}
[new {$value.label}, {$value.rx}, {$value.tx}, {$value.total}],
{/foreach}
]);
let endD = (new {$dailyGraphData[0]['label']}).getTime();
let options = {
title: 'Daily Network Traffic',
orientation: 'horizontal',
legend: { position: 'right' },
explorer: {
axis: 'horizontal',
zoomDelta: 1.1,
maxZoomIn: 0.1,
maxZoomOut: 10.0
},
vAxis: {
format: '###.### {$dailyLargestPrefix}'
{if $graph_type == 'log'}
,scaleType: 'log',
baseline: {$dailyBase}
{/if}
},
hAxis: {
{if $jsonVersion > 1}
title: 'Day (Scroll to zoom, Drag to pan)',
{else}
title: 'Day',
{/if}
format: 'dd/MM/YYYY',
viewWindow: {
min: 'Date('+(endD-2548800000).toString()+')',
max: 'Date('+(endD+43200000).toString()+')'
},
direction: -1,
ticks: [
{foreach from=$dailyGraphData key=key item=value}
new {$value.label},
{/foreach}
]
}
};
var formatDate = new google.visualization.DateFormat({ pattern: 'dd/MM/yyyy' });
formatDate.format(data, 0);
var formatNumber = new google.visualization.NumberFormat({ pattern: '##.## {$dailyLargestPrefix}' });
formatNumber.format(data, 1);
formatNumber.format(data, 2);
formatNumber.format(data, 3);
let chart = new google.visualization.BarChart(document.getElementById('dailyNetworkTrafficGraph'));
chart.draw(data, google.charts.Bar.convertOptions(options));
}
function drawMonthlyChart()
{
var data = new google.visualization.DataTable();
data.addColumn('date', 'Month');
data.addColumn('number', 'Traffic In');
data.addColumn('number', 'Traffic Out');
data.addColumn('number', 'Total Traffic');
data.addRows([
{foreach from=$monthlyGraphData key=key item=value}
[new {$value.label}, {$value.rx}, {$value.tx}, {$value.total}],
{/foreach}
]);
let options = {
title: 'Monthly Network Traffic',
orientation: 'horizontal',
legend: { position: 'right' },
explorer: {
axis: 'horizontal',
zoomDelta: 1.1,
maxZoomIn: 0.1,
maxZoomOut: 10.0
},
vAxis: {
format: '##.## {$monthlyLargestPrefix}'
},
hAxis: {
title: 'Month',
format: 'MMMM YYYY',
direction: -1
}
};
var formatDate = new google.visualization.DateFormat({ pattern: 'MMMM YYYY' });
formatDate.format(data, 0);
var formatNumber = new google.visualization.NumberFormat({ pattern: '##.## {$monthlyLargestPrefix}' });
formatNumber.format(data, 1);
formatNumber.format(data, 2);
formatNumber.format(data, 3);
let chart = new google.visualization.BarChart(document.getElementById('monthlyNetworkTrafficGraph'));
chart.draw(data, google.charts.Bar.convertOptions(options));
}
</script>

View File

@ -0,0 +1,33 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>Network Traffic</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- Bootstrap CSS -->
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
<!-- Custom CSS -->
<link rel="stylesheet" href="./assets/css/style.css">
</head>
<body>
<nav class="navbar sticky-top navbar-light bg-light">
<div class="container">
<a class="navbar-brand" href="#">Network Traffic ({$current_interface})</a>
<div class="dropdown">
<button class="btn btn-secondary dropdown-toggle" type="button" id="dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
Interface Selection
</button>
<div class="dropdown-menu" aria-labelledby="dropdownMenuButton">
{foreach from=$interface_list item=value}
<a class="dropdown-item" href="?i={$value}">{$value}</a>
{/foreach}
</div>
</div>
</div>
</nav>

View File

@ -0,0 +1,146 @@
<div class="container">
<ul class="nav nav-tabs" id="tableTab" role="tablist">
{if $jsonVersion gt 1}
<li class="nav-item">
<a class="nav-link active" id="five-table-tab" data-toggle="tab" href="#five-table" role="tab" aria-controls="five-table" aria-selected="true">Five Minute</a>
</li>
<li class="nav-item">
<a class="nav-link" id="hourly-table-tab" data-toggle="tab" href="#hourly-table" role="tab" aria-controls="hourly-table" aria-selected="false">Hourly</a>
</li>
{else}
<li class="nav-item">
<a class="nav-link active" id="hourly-table-tab" data-toggle="tab" href="#hourly-table" role="tab" aria-controls="hourly-table" aria-selected="true">Hourly</a>
</li>
{/if}
<li class="nav-item">
<a class="nav-link" id="daily-table-tab" data-toggle="tab" href="#daily-table" role="tab" aria-controls="daily-table" aria-selected="false">Daily</a>
</li>
<li class="nav-item">
<a class="nav-link" id="monthly-table-tab" data-toggle="tab" href="#monthly-table" role="tab" aria-controls="monthly-table" aria-selected="false">Monthly</a>
</li>
<li class="nav-item">
<a class="nav-link" id="top10-table-tab" data-toggle="tab" href="#top10-table" role="tab" aria-controls="top10-table" aria-selected="false">Top 10</a>
</li>
</ul>
<div class="tab-content" id="tableTabContent">
{if $jsonVersion gt 1}
<div class="tab-pane fade show active" id="five-table" role="tabpanel" aria-labelledby="five-table-tab">
<table class="table table-bordered">
<thead>
<tr>
<th>Time</th>
<th>Received</th>
<th>Sent</th>
<th>Total</th>
</tr>
</thead>
<tbody>
{foreach from=$fiveTableData key=key item=value}
<tr>
<td>{$value.label}</td>
<td>{$value.rx}</td>
<td>{$value.tx}</td>
<td>{$value.total}</td>
</tr>
{/foreach}
</tbody>
</table>
</div>
<div class="tab-pane fade" id="hourly-table" role="tabpanel" aria-labelledby="hourly-table-tab">
{else}
<div class="tab-pane fade show active" id="hourly-table" role="tabpanel" aria-labelledby="hourly-table-tab">
{/if}
<table class="table table-bordered">
<thead>
<tr>
<th>Hour</th>
<th>Received</th>
<th>Sent</th>
<th>Total</th>
</tr>
</thead>
<tbody>
{foreach from=$hourlyTableData key=key item=value}
<tr>
<td>{$value.label}</td>
<td>{$value.rx}</td>
<td>{$value.tx}</td>
<td>{$value.total}</td>
</tr>
{/foreach}
</tbody>
</table>
</div>
<div class="tab-pane fade" id="daily-table" role="tabpanel" aria-labelledby="daily-table-tab">
<table class="table table-bordered">
<thead>
<tr>
<th>Day</th>
<th>Received</th>
<th>Sent</th>
<th>Total</th>
</tr>
</thead>
<tbody>
{foreach from=$dailyTableData key=key item=value}
<tr>
<td>{$value.label}</td>
<td>{$value.rx}</td>
<td>{$value.tx}</td>
<td>{$value.total}</td>
</tr>
{/foreach}
</tbody>
</table>
</div>
<div class="tab-pane fade" id="monthly-table" role="tabpanel" aria-labelledby="monthly-table-tab">
<table class="table table-bordered">
<thead>
<tr>
<th>Month</th>
<th>Received</th>
<th>Sent</th>
<th>Total</th>
</tr>
</thead>
<tbody>
{foreach from=$monthlyTableData key=key item=value}
<tr>
<td>{$value.label}</td>
<td>{$value.rx}</td>
<td>{$value.tx}</td>
<td>{$value.total}</td>
</tr>
{/foreach}
</tbody>
</table>
</div>
<div class="tab-pane fade" id="top10-table" role="tabpanel" aria-labelledby="top10-table-tab">
<table class="table table-bordered">
<thead>
<tr>
<th>Day</th>
<th>Received</th>
<th>Sent</th>
<th>Total</th>
</tr>
</thead>
<tbody>
{foreach from=$top10TableData key=key item=value}
<tr>
<td>{$value.label}</td>
<td>{$value.rx}</td>
<td>{$value.tx}</td>
<td>{$value.total}</td>
</tr>
{/foreach}
</tbody>
</table>
</div>
</div>
</div>

View File

@ -0,0 +1,7 @@
{include file="module_header.tpl"}
{include file="module_graph.tpl"}
{include file="module_table.tpl"}
{include file="module_footer.tpl"}

View File

@ -0,0 +1,11 @@
version: '3'
services:
vnstat-dashboard:
build: .
network_mode: "host"
container_name: vnstat-dashboard
volumes:
- /var/lib/vnstat:/var/lib/vnstat
- /usr/bin/vnstat:/usr/local/bin/vnstat
- /etc/localtime:/etc/localtime:ro
- /var/run/docker.sock:/var/run/docker.sock

View File