start work on traffic-view refactoring

Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
This commit is contained in:
Michael Kaufmann
2022-09-07 12:56:02 +02:00
parent 5f95293e0d
commit 6f2652f9dd
5 changed files with 252 additions and 44 deletions

View File

@@ -26,7 +26,7 @@
const AREA = 'customer'; const AREA = 'customer';
require __DIR__ . '/lib/init.php'; require __DIR__ . '/lib/init.php';
use Froxlor\Database\Database; use Froxlor\Traffic\Traffic;
use Froxlor\Settings; use Froxlor\Settings;
use Froxlor\UI\Panel\UI; use Froxlor\UI\Panel\UI;
use Froxlor\UI\Request; use Froxlor\UI\Request;
@@ -37,13 +37,19 @@ if (Settings::IsInList('panel.customer_hide_options', 'traffic')) {
Response::redirectTo('customer_index.php'); Response::redirectTo('customer_index.php');
} }
$range = Request::get('range', 'days:30');
if ($page === null || $page == 'overview') { if ($page === null || $page == 'overview') {
} elseif ($page == 'current') { } elseif ($page == 'current') {
} }
UI::view('user/traffic.html.twig', [ try {
'metrics' => \Froxlor\Traffic\Traffic::getCustomerMetrics($userinfo), $context = Traffic::getCustomerStats($userinfo, $range);
'chart' => \Froxlor\Traffic\Traffic::getCustomerChart($userinfo, 30), } catch (Exception $e) {
]); Response::dynamicError($e->getMessage());
}
// pass metrics to the view
UI::view('user/traffic.html.twig', $context);

View File

@@ -32,18 +32,33 @@ class Traffic
{ {
public static function getCustomerStats($userinfo, $range = null): array public static function getCustomerStats($userinfo, $range = null): array
{ {
$trafficCollection = (new Collection(\Froxlor\Api\Commands\Traffic::class, $userinfo, self::getParamsByRange($range, ['customer_traffic' => true,]))) $trafficCollectionObj = (new Collection(\Froxlor\Api\Commands\Traffic::class, $userinfo, self::getParamsByRange($range, ['customer_traffic' => true,])));
->has('customer', Customers::class, 'customerid', 'customerid') if ($userinfo['adminsession'] == 1) {
->get(); $trafficCollectionObj->has('customer', Customers::class, 'customerid', 'customerid');
}
$trafficCollection = $trafficCollectionObj->get();
// build stats for each user // build stats for each user
$users = []; $users = [];
$years = [];
$months = [];
foreach ($trafficCollection['data']['list'] as $item) { foreach ($trafficCollection['data']['list'] as $item) {
// per user total
$users[$item['customerid']]['loginname'] = $item['customer']['loginname']; $users[$item['customerid']]['loginname'] = $item['customer']['loginname'];
$users[$item['customerid']]['total'] += ($item['http'] + $item['ftp_up'] + $item['ftp_down'] + $item['mail']); $users[$item['customerid']]['total'] += ($item['http'] + $item['ftp_up'] + $item['ftp_down'] + $item['mail']);
$users[$item['customerid']]['http'] += $item['http']; $users[$item['customerid']]['http'] += $item['http'];
$users[$item['customerid']]['ftp'] += ($item['ftp_up'] + $item['ftp_down']); $users[$item['customerid']]['ftp'] += ($item['ftp_up'] + $item['ftp_down']);
$users[$item['customerid']]['mail'] += $item['mail']; $users[$item['customerid']]['mail'] += $item['mail'];
// per year
$years[$item['year']]['total']['total'] += ($item['http'] + $item['ftp_up'] + $item['ftp_down'] + $item['mail']);
$years[$item['year']]['total']['http'] += $item['http'];
$years[$item['year']]['total']['ftp'] += ($item['ftp_up'] + $item['ftp_down']);
$years[$item['year']]['total']['mail'] += $item['mail'];
// per month
$months[$item['year']][$item['month']]['total'] += ($item['http'] + $item['ftp_up'] + $item['ftp_down'] + $item['mail']);
$months[$item['year']][$item['month']]['http'] += $item['http'];
$months[$item['year']][$item['month']]['ftp'] += ($item['ftp_up'] + $item['ftp_down']);
$months[$item['year']][$item['month']]['mail'] += $item['mail'];
} }
// calculate overview for given range from users // calculate overview for given range from users
@@ -58,10 +73,12 @@ class Traffic
return [ return [
'metrics' => $metrics, 'metrics' => $metrics,
'users' => $users, 'users' => $users,
'years' => $years,
'months' => $months
]; ];
} }
private static function getParamsByRange(string $range, array $params = []) private static function getParamsByRange($range = null, array $params = [])
{ {
$dateParams = []; $dateParams = [];

View File

@@ -356,6 +356,7 @@ return [
'store_defaultindex' => 'Standard-Index-Datei im Kundenordner erstellen', 'store_defaultindex' => 'Standard-Index-Datei im Kundenordner erstellen',
'phpfpm_settings' => 'PHP-FPM', 'phpfpm_settings' => 'PHP-FPM',
'traffic' => 'Traffic', 'traffic' => 'Traffic',
'traffic_sub' => 'Details der Traffic-Nutzung',
'customertraffic' => 'Kunden', 'customertraffic' => 'Kunden',
'assignedmax' => 'Zugewiesen / Max.', 'assignedmax' => 'Zugewiesen / Max.',
'usedmax' => 'Benutzt / Max.', 'usedmax' => 'Benutzt / Max.',

View File

@@ -358,6 +358,7 @@ return [
'store_defaultindex' => 'Store default index-file to customers docroot', 'store_defaultindex' => 'Store default index-file to customers docroot',
'phpfpm_settings' => 'PHP-FPM', 'phpfpm_settings' => 'PHP-FPM',
'traffic' => 'Traffic', 'traffic' => 'Traffic',
'traffic_sub' => 'Details on traffic usage',
'domaintraffic' => 'Domains', 'domaintraffic' => 'Domains',
'customertraffic' => 'Customers', 'customertraffic' => 'Customers',
'assignedmax' => 'Assigned / Max', 'assignedmax' => 'Assigned / Max',

View File

@@ -4,9 +4,10 @@
<div> <div>
<h5 class="mb-1"> <h5 class="mb-1">
<i class="fa fa-chart-area me-1"></i> Traffic <i class="fa fa-chart-area me-1"></i>
{{ lng('admin.traffic') }}
</h5> </h5>
<span class="text-muted">View your traffic</span> <span class="text-muted">{{ lng('admin.traffic_sub') }}</span>
</div> </div>
{% endblock %} {% endblock %}
@@ -29,6 +30,15 @@
</select> </select>
</div> </div>
<div class="row row-cols-2 g-0 bg-white rounded shadow-sm mb-4">
<div class="col p-3 border-end">
<canvas id="trafficsummary" style="max-height:30vh;"></canvas>
</div>
<div class="col p-3 border-end">
<canvas id="customersummary" style="max-height:30vh;"></canvas>
</div>
</div>
<!-- Overview for given range --> <!-- Overview for given range -->
<div class="row row-cols-4 g-0 bg-white rounded shadow-sm mb-4"> <div class="row row-cols-4 g-0 bg-white rounded shadow-sm mb-4">
<div class="col p-3 border-end"> <div class="col p-3 border-end">
@@ -49,40 +59,213 @@
</div> </div>
</div> </div>
<!-- Overview for given range by user --> {% if userinfo.adminsession == 1 %}
<h4 class="page-header">Traffic by customers</h4> <!-- Overview for given range by user -->
{% if users is not empty %} <h4 class="page-header">Traffic by customers</h4>
<div class="card table-responsive"> {% if users is not empty %}
<table class="table table-borderless table-striped align-middle mb-0 px-3"> <div class="card table-responsive">
<thead> <table class="table table-borderless table-striped align-middle mb-0 px-3">
<tr> <thead>
<th scope="col">{{ lng('login.username') }}</th> <tr>
<th scope="col">Total</th> <th scope="col">{{ lng('login.username') }}</th>
<th scope="col">HTTP</th> <th scope="col">Total</th>
<th scope="col">FTP</th> <th scope="col">HTTP</th>
<th scope="col">Mail <th scope="col">FTP</th>
</th> <th scope="col">Mail
</tr> </th>
</thead> </tr>
<tbody> </thead>
{% for user in users %} <tbody>
<tr> {% for uid,user in users %}
<td>{{ user.loginname }}</td> <tr>
<td>{{ user.total|formatBytes }}</td> <td>
<td>{{ user.http|formatBytes }}</td> <a href="{{ linker({'section':'customers','page':'customers','action':'su','id':uid}) }}">{{ user.loginname }}</a>
<td>{{ user.ftp|formatBytes }}</td> </td>
<td>{{ user.mail|formatBytes }}</td> <td>{{ user.total|formatBytes }}</td>
</tr> <td>{{ user.http|formatBytes }}</td>
{% endfor %} <td>{{ user.ftp|formatBytes }}</td>
</tbody> <td>{{ user.mail|formatBytes }}</td>
</table> </tr>
</div> {% endfor %}
{% else %} </tbody>
<div class="card"> </table>
<div class="card-body">
<p>No data for given range found.</p>
</div> </div>
</div> {% else %}
<div class="card">
<div class="card-body">
<p>No data for given range found.</p>
</div>
</div>
{% endif %}
{% endif %} {% endif %}
<script>
const labelsS = ['HTTP', 'FTP', 'Mail'];
const dataS = {
labels: labelsS,
datasets: [{
label: 'Traffic summary',
backgroundColor: ['rgb(255, 99, 132)', 'rgb(200, 199, 132)', 'rgb(255, 99, 0)'],
data: [{value: {{ metrics.http }}, formatted: '{{ metrics.http|formatBytes }}'}, {value: {{ metrics.ftp }}, formatted: '{{ metrics.ftp|formatBytes }}'}, {value: {{ metrics.mail }}, formatted: '{{ metrics.mail|formatBytes }}'}]
}]
};
const configS = {
type: 'pie',
data: dataS,
options: {
parsing: {
key: 'value'
},
responsive: true,
plugins: {
title: {
display: true,
text: 'Total traffic'
},
legend: {
position: 'right'
},
tooltip: {
enabled: true,
usePointStyle: true,
callbacks: {
label: (data) => {
return data.label + ' ' + data.raw.formatted
},
},
},
}
}
};
const sChart = new Chart(document.getElementById('trafficsummary'), configS);
{% if userinfo.adminsession == 1 %}
const labelsC = [];
const dataValues = [];
{% for user in users|sort((a, b) => a.total <=> b.total)|slice(0, 5) %}
labelsC.push('{{ user.loginname }}');
dataValues.push({value: {{ user.total }}, formatted: '{{ user.total|formatBytes }}'});
{% endfor %}
const dataC = {
labels: labelsC,
datasets: [{
label: 'Top 5 customers',
backgroundColor: ['rgb(255, 99, 132)', 'rgb(200, 199, 132)', 'rgb(255, 99, 0)', 'rgb(100, 100, 132)', 'rgb(240, 150, 232)'],
data: dataValues
}]
};
const configC = {
type: 'pie',
data: dataC,
options: {
parsing: {
key: 'value'
},
responsive: true,
plugins: {
title: {
display: true,
text: 'Top 5 customers'
},
legend: {
position: 'right'
},
tooltip: {
enabled: true,
usePointStyle: true,
callbacks: {
label: (data) => {
return data.label + ' ' + data.raw.formatted
},
},
},
}
}
};
const cChart = new Chart(document.getElementById('customersummary'), configC);
{% else %}
const labelsC = [];
const dataValues = [];
{% for yr,year in years %}
labelsC.push('{{ yr }}');
//dataValues.push({value: {{ year.total.total }}, formatted: '{{ year.total.total|formatBytes }}', year: '{{ yr }}'});
{% endfor %}
const dataC = {
labels: labelsC,
datasets: [
{
label: 'HTTP traffic',
backgroundColor: 'rgb(255, 99, 132)',
data: [{% for yr,year in years %}{value: {{ year.total.http }}, formatted: '{{ year.total.http|formatBytes }}', year: '{{ yr }}'},{% endfor %}],
parsing: {
xAxisKey: 'year'
}
},
{
label: 'FTP traffic',
backgroundColor: 'rgb(200, 199, 132)',
data: [{% for yr,year in years %}{value: {{ year.total.ftp }}, formatted: '{{ year.total.ftp|formatBytes }}', year: '{{ yr }}'},{% endfor %}],
parsing: {
xAxisKey: 'year'
}
},
{
label: 'Mail traffic',
backgroundColor: 'rgb(255, 99, 0)',
data: [{% for yr,year in years %}{value: {{ year.total.mail }}, formatted: '{{ year.total.mail|formatBytes }}', year: '{{ yr }}'},{% endfor %}],
parsing: {
xAxisKey: 'year'
}
},
]
};
const configC = {
type: 'bar',
data: dataC,
options: {
parsing: {
yAxisKey: 'value'
},
responsive: true,
scales: {
x: {
stacked: true,
},
y: {
stacked: true
}
},
plugins: {
title: {
display: true,
text: 'All-time by year'
},
tooltip: {
enabled: true,
usePointStyle: true,
callbacks: {
label: (data) => {
return data.raw.formatted
},
},
},
}
}
};
const cChart = new Chart(document.getElementById('customersummary'), configC);
{% endif %}
</script>
{% endblock %} {% endblock %}