Upload files to "system/plugin/ui"

Signed-off-by: nestict <icttechnest@gmail.com>
This commit is contained in:
nestict 2025-05-24 12:37:49 +02:00
parent 3a49bf9afa
commit 717cb65516

View File

@ -0,0 +1,943 @@
{include file="sections/header.tpl"}
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">
<script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
<script src="https://cdn.datatables.net/1.10.23/js/jquery.dataTables.min.js"></script>
<link rel="stylesheet" href="https://cdn.datatables.net/1.10.23/css/jquery.dataTables.min.css">
<link rel="stylesheet" href="https://cdn.datatables.net/buttons/1.7.1/css/buttons.dataTables.min.css">
<script src="https://cdn.datatables.net/buttons/1.7.1/js/dataTables.buttons.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/apexcharts"></script>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="https://cdn.datatables.net/1.11.3/js/jquery.dataTables.min.js"></script>
<script src="https://cdn.datatables.net/1.11.3/js/dataTables.bootstrap5.min.js"></script>
<style>
.modal {
display: none;
position: fixed;
z-index: 1;
left: 0;
top: 0;
width: 100%;
height: 100%;
overflow: auto;
background-color: rgba(0, 0, 0, 0.4);
}
.modal-content {
background-color: #fefefe;
margin: 15% auto;
padding: 0px;
border: 1px solid #888;
width: 80%;
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);
max-width: 600px;
}
.close {
color: #aaa;
float: right;
font-size: 28px;
font-weight: bold;
cursor: pointer;
}
.close:hover,
.close:focus {
color: black;
text-decoration: none;
}
.card-body {
padding: 1rem;
}
.card-header {
padding: .75rem 1.25rem;
margin-bottom: 0;
background-color: none;
border-bottom: 1px solid rgba(0,0,0,.125);
}
.card-title {
margin-bottom: .75rem;
}
.form-group {
margin-bottom: 1rem;
}
.table-responsive {
display: block;
width: 100%;
overflow-x: auto;
-webkit-overflow-scrolling: touch;
-ms-overflow-style: -ms-autohiding-scrollbar;
}
.table {
width: 100%;
margin-bottom: 1rem;
color: #212529;
}
.container {
padding-top: 20px;
}
#ppp-table_wrapper {
padding: 15px;
}
#ppp-table th, #ppp-table td {
text-align: center;
padding: 6px;
}
.table-striped tbody tr:nth-of-type(odd) {
background-color: #f9f9f9;
}
.panel-default {
border-color: #ddd;
}
.panel-heading {
background-color: #f5f5f5;
border-color: #ddd;
}
.nav-tabs {
margin-bottom: 15px;
}
.nav-tabs > li > a {
border-radius: 0;
color: #555;
background-color: #f9f9f9;
border-color: #ddd;
}
.nav-tabs > li.active > a,
.nav-tabs > li.active > a:focus,
.nav-tabs > li.active > a:hover {
background-color: #fff;
color: #333;
border: 1px solid #ddd;
border-bottom-color: transparent;
cursor: default;
}
.table-striped tbody tr:nth-of-type(odd) {
background-color: #f9f9f9;
}
.table th {
background-color: #f5f5f5;
color: #333;
font-weight: bold;
padding: 8px;
}
.table-striped > tbody > tr > td {
background-color: #fff;
}
.status-connect {
color: #5cb85c;
}
.status-disconnect {
color: #d9534f;
}
.modalsupport {
display: none;
position: fixed;
z-index: 1;
left: 0;
top: 0;
width: 100%;
height: 100%;
overflow: auto;
background-color: rgb(0,0,0);
background-color: rgba(0,0,0,0.4);
justify-content: center;
align-items: center;
}
.modalsupport-content {
background-color: #fefefe;
margin: auto;
padding: 0px;
border: 1px solid #888;
width: 80%;
max-width: 500px;
box-shadow: 0 5px 15px rgba(0,0,0,0.3);
text-align: center;
}
.modalsupport-close {
color: #aaa;
float: right;
font-size: 28px;
font-weight: bold;
}
.modalsupport-close:hover,
.modalsupport-close:focus {
color: black;
text-decoration: none;
cursor: pointer;
}
.card {
border: none;
}
.card-header {
background-color: none;
border-bottom: none;
}
.card-body {
padding: 20px;
}
.donate-button {
margin-top: 10px;
}
.modalsupport img {
width: 100px;
height: auto;
margin-top: 15px;
}
.dataSize {
white-space: nowrap;
}
.action-icons i {
cursor: pointer;
margin-right: 10px;
color: #007bff;
}
.action-icons i:hover {
color: #0056b3;
}
.modal-title {
text-align: center;
width: 100%;
display: block;
font-size: 20px;
font-weight: bold;
margin-top: 20px;
}
.table-bordered {
width: 100%;
max-width: 100%;
table-layout: fixed;
}
.table-bordered th, .table-bordered td {
width: auto;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
background: none;
border: none;
padding: 5px;
vertical-align: middle;
text-align: center;
}
.advanced-search-container {
margin-bottom: 20px;
padding: 15px;
background-color: #f9f9f9;
border: 1px solid #ddd;
}
.form-inline .form-group {
margin-right: 10px;
}
.dataTables_filter {
display: none;
}
@media (max-width: 768px) {
.panel-default {
padding: 10px;
margin: 0;
}
.panel-heading {
padding: 5px 15px;
}
.panel-body {
padding: 5px 10px;
}
.table th, .table td {
font-size: 15px; /* Mengurangi ukuran font pada tabel */
}
}
.traffic-icon {
display: inline-block;
width: 10px;
height: 10px;
border-radius: 50%;
margin-right: 5px;
vertical-align: middle;
}
.traffic-icon-green {
background-color: green;
}
.traffic-icon-yellow {
background-color: yellow;
}
.traffic-icon-red {
background-color: red;
}
.text-left {
text-align: left !important;
}
</style>
<div class="container">
<div class="row">
<div class="col-xs-12 col-sm-12 col-md-12 col-lg-12">
<!-- Form dan navigasi tabs -->
<form class="form-horizontal" method="post" role="form" action="{$_url}plugin/pppoe">
<div class="form-group">
<label for="routerSelect" class="col-sm-2 control-label">Select Router</label>
<div class="col-sm-10">
<select id="routerSelect" name="router" class="form-control" onchange="window.location.href=this.value;">
{foreach $routers as $r}
<option value="{$_url}plugin/pppoe/{$r['id']}" {if $r['id'] == $router}selected{/if}>
{$r['name']}
</option>
{/foreach}
</select>
</div>
</div>
</form>
<div class="advanced-search-container">
<form id="advancedSearchForm" class="form-inline">
<div class="form-group">
<label for="searchUsername">Username:</label>
<input type="text" class="form-control" id="searchUsername" placeholder="Enter username">
</div>
<div class="form-group">
<label for="searchStatus">Status:</label>
<select class="form-control" id="searchStatus">
<option value="">Any</option>
<option value="Connected">Connected</option>
<option value="Disconnected">Disconnected</option>
</select>
</div>
</form>
</div>
<div class="panel panel-default">
<div class="table-responsive">
<div class="panel-body">
<table class="table table-striped" id="ppp-table">
<thead>
<tr>
<th>ID</th>
<th>Username</th>
<th>IP Address</th>
<th>Uptime</th>
<th>Service</th>
<th>Caller ID</th>
<th>Device</th>
<th>Download</th>
<th>Upload</th>
<th>Total Usage</th>
<th>Status</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<!-- Isi tabel akan dimasukkan melalui JavaScript -->
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
<div id="detailsModal" class="modal">
<div class="modal-content">
<span class="close">&times;</span>
<div class="container-fluid mt-5">
<div class="card">
<div class="card-header">
<h5 class="modal-title">Data Usage for <span id="modalUsername"></span></h5>
</div>
<div class="card-body">
<div class="table-responsive mt-4">
<table class="table table-bordered">
<thead>
<tr>
<input type="hidden" id="interface" value="">
<th id="tabletx"><i class="fa fa-download"></i></th>
<th id="tablerx"><i class="fa fa-upload"></i></th>
</tr>
</thead>
</table>
</div>
<div id="chart" class="mt-3"></div>
<div id="dailyChart" class="mt-3"></div>
</div>
</div>
</div>
</div>
</div>
<script>
var $j = jQuery.noConflict();
$j(document).ready(function() {
var table = $j('#ppp-table').DataTable({
responsive: true,
columns: [
{ data: 'id', visible: false },
{
data: 'username',
className: 'text-left',
render: function(data, type, row) {
return '<div style="width: 150px;"><i class="traffic-icon traffic-icon-green"></i> ' + data + '</div>';
}
},
{ data: 'address' },
{ data: 'uptime' },
{ data: 'service' },
{ data: 'caller_id' },
{ data: 'manufacturer' }, // New column for device/manufacturer
{ data: 'tx', className: 'dataSize' },
{ data: 'rx', className: 'dataSize' },
{ data: 'total', className: 'dataSize' },
{
data: 'status',
render: function(data) {
if (data === 'Connected') {
return '<small class="label bg-green">Connected</small>';
} else if (data === 'Disconnected') {
return '<small class="label bg-red">Disconnected</small>';
} else {
return '';
}
}
},
{
data: null,
render: function(data, type, row) {
return '<div class="action-icons" style="display: flex; align-items: center;">' +
'<i class="fa fa-area-chart view-details" style="color: blue; cursor: pointer;" title="View Traffic" data-username="' + row.username + '" data-id="' + row.id + '"></i> ' +
'<i class="fa fa-retweet reconnect-button" style="color: red; cursor: pointer;" title="Reconnect" data-username="' + row.username + '" data-id="' + row.id + '"></i> ' +
'<button class="btn btn-sm ' + (row.is_bound ? 'btn-danger' : 'btn-success') + ' bind-button" data-username="' + row.username + '" data-caller-id="' + row.caller_id + '" data-is-bound="' + row.is_bound + '">' +
(row.is_bound ? 'Unbind' : 'Bind') + '</button>' +
'</div>';
}
}
],
order: [[0, 'asc']],
pageLength: 10,
lengthMenu: [[10, 25, 50, -1], [10, 25, 50, 'All']],
dom: 'Bfrtip',
buttons: ['reset', 'pageLength'],
paging: true,
info: true,
searching: true,
ajax: {
url: '{$_url}plugin/pppoe_get_combined_users/{$router}',
dataSrc: ''
}
});
// Fungsi untuk mendapatkan batas maksimum
function getMaxLimit(data) {
if (data.hasOwnProperty('max_limit')) {
return data.max_limit.toString();
} else {
return 'N/A';
}
}
// Handle view details icon clicks
$j('#ppp-table tbody').on('click', '.view-details', function(e) {
e.preventDefault();
var username = $j(this).data('username');
var id = $j(this).data('id');
viewDetails(id, username);
});
// Handle reconnect icon clicks
$j('#ppp-table tbody').on('click', '.reconnect-button', function(e) {
e.preventDefault();
var username = $j(this).data('username');
var id = $j(this).data('id');
reconnect(id, username);
});
// Function to handle view details
function viewDetails(id, username) {
console.log("Viewing details for:", username);
$j('#modalUsername').text(username);
$j.ajax({
url: '{$_url}plugin/pppoe_get_combined_users',
method: 'GET',
dataType: 'json',
success: function(response) {
var user = response.find(function(item) {
return (item.username && item.username.toString().toLowerCase() === username.toString().toLowerCase());
});
if (username !== null && user !== null && user.username !== null) {
var interfaceValue = '<pppoe-' + user.username + '>';
$j('#interface').val(interfaceValue);
$j('#selectedInterface').text(interfaceValue);
$j('#detailsModal').css('display', 'block');
createChart();
createDailyChart(username); // Pass the username to createDailyChart
} else {
alert('User not found.');
}
},
error: function(xhr, textStatus, errorThrown) {
alert('Failed to retrieve user data.');
console.error('AJAX error:', textStatus, errorThrown);
}
});
}
// Function to handle reconnect
function reconnect(id, username) {
if (confirm('Are you sure you want to disconnect user ' + username + '?')) {
$.ajax({
url: '{$_url}plugin/pppoe_monitor_router_delete_ppp_user/{$router}', // Perbaiki URL AJAX
method: 'POST',
data: { id: id, username: username },
success: function(response) {
if (response.success) {
alert('User ' + username + ' has been disconnected.');
setTimeout(function() {
table.ajax.reload();
}, 2000);
} else {
alert('Failed to disconnect user ' + username + ': ' + (response.message || 'Unknown error'));
}
},
error: function(xhr, textStatus, errorThrown) {
alert('Failed to disconnect user ' + username + ': ' + (errorThrown || 'Unknown error'));
console.error('AJAX error:', textStatus, errorThrown);
}
});
}
}
// Close modal on click of close button
$j('.close').click(function() {
$j('#detailsModal').css('display', 'none');
});
// Close modal on click outside the modal
$j(window).click(function(event) {
if (event.target == document.getElementById('detailsModal')) {
$j('#detailsModal').css('display', 'none');
}
});
// Handle advanced search form submission
$j(document).ready(function() {
$j('#advancedSearchForm').on('submit', function(e) {
e.preventDefault(); // Mencegah pengiriman form secara default
// Mendapatkan nilai dari input
var username = $j('#searchUsername').val();
var status = $j('#searchStatus').val();
// Melakukan pencarian dan menggambar ulang tabel
table.column(1).search(username).draw(); // Kolom 1 untuk username
table.column(9).search(status).draw(); // Kolom 9 untuk status
});
// Menambahkan ikon search ke dalam tombol
var searchButton = $j('<button type="submit" class="btn btn-primary"><i class="fas fa-search"></i></button>');
$j('#advancedSearchForm').append(searchButton);
});
});
var chart;
var chartData = {
txData: [],
rxData: []
};
function createChart() {
var options = {
chart: {
height: 350,
type: 'area',
animations: {
enabled: true,
easing: 'linear',
speed: 200,
animateGradually: {
enabled: true,
delay: 150
},
dynamicAnimation: {
enabled: true,
speed: 200
}
},
events: {
mounted: function() {
updateTrafficValues();
setInterval(updateTrafficValues, 3000);
}
}
},
stroke: {
curve: 'smooth'
},
series: [
{ name: 'Download', data: chartData.txData },
{ name: 'Upload', data: chartData.rxData }
],
xaxis: {
type: 'datetime',
labels: {
formatter: function(value) {
return new Date(value).toLocaleTimeString();
}
}
},
yaxis: {
title: {
text: 'Real Time Data Usage'
},
labels: {
formatter: function(value) {
return formatBytes(value);
}
}
},
tooltip: {
x: {
format: 'HH:mm:ss'
},
y: {
formatter: function(value) {
return formatBytes(value) + 'ps';
}
}
},
dataLabels: {
enabled: false,
formatter: function(value) {
return formatBytes(value);
}
}
};
chart = new ApexCharts(document.querySelector("#chart"), options);
chart.render();
}
var dailyChart; // Declare dailyChart variable globally
function createDailyChart(username) {
var currentDate = new Date();
var startOfMonth = new Date(currentDate.getFullYear(), currentDate.getMonth(), 1).getTime();
var endOfMonth = new Date(currentDate.getFullYear(), currentDate.getMonth() + 1, 0).getTime();
generateDailyData(username)
.then(dailyData => {
var dailyTotals = dailyData.download.map((item, index) => ({
x: item.x,
y: item.y + dailyData.upload[index].y
}));
if (dailyChart) {
dailyChart.destroy();
}
var options = {
chart: {
height: 350,
type: 'bar',
animations: {
enabled: true,
easing: 'linear',
speed: 800,
animateGradually: {
enabled: true,
delay: 150
},
dynamicAnimation: {
enabled: true,
speed: 200
}
},
toolbar: {
show: true,
}
},
plotOptions: {
bar: {
horizontal: false,
columnWidth: '15%',
endingShape: 'rounded'
},
},
dataLabels: {
enabled: false
},
stroke: {
show: true,
width: 2,
colors: ['transparent']
},
series: [{
name: 'Download',
data: dailyData.upload
}, {
name: 'Upload',
data: dailyData.download
}, {
name: 'Daily Totals',
data: dailyTotals
}],
xaxis: {
type: 'datetime',
min: startOfMonth,
max: endOfMonth,
labels: {
formatter: function(value) {
return new Date(value).toLocaleDateString();
}
}
},
yaxis: {
title: {
text: 'Total Usage'
},
labels: {
formatter: function(value) {
return formatBytesPerSecond(value);
}
}
},
fill: {
opacity: 1
},
tooltip: {
y: {
formatter: function(val) {
return formatBytes(val);
}
}
},
responsive: [
{
breakpoint: 480,
options: {
plotOptions: {
bar: {
columnWidth: '100%'
}
}
}
}
]
};
dailyChart = new ApexCharts(document.querySelector("#dailyChart"), options);
dailyChart.render();
})
.catch(error => {
console.error("Failed to fetch daily usage data:", error);
});
}
// ========================================== NEW FITUR ==========================================//
function generateDailyData(username, startDate, endDate) {
return new Promise((resolve, reject) => {
$j.ajax({
url: '{$_url}plugin/pppoe_monitor_router_daily_data_usage/{$router}',
data: {
username: username,
start_date: startDate,
end_date: endDate
},
dataType: 'json',
success: function(data) {
console.log("Raw data from server for username", username, ":", data);
var dailyData = {
download: [],
upload: []
};
// Iterate over dates in data and find the correct user data
for (var date in data) {
var users = data[date].users;
// Handle username as number case
var userData = users.find(user => user.username === username || user.username == parseInt(username));
if (userData) {
var rxBytes = convertToBytes(userData.rx);
var txBytes = convertToBytes(userData.tx);
// Store data in dailyData based on date
dailyData.download.push({ x: new Date(date).getTime(), y: rxBytes });
dailyData.upload.push({ x: new Date(date).getTime(), y: txBytes });
}
}
console.log("Filtered daily data for username", username, ":", dailyData);
resolve(dailyData);
},
error: function(xhr, textStatus, errorThrown) {
console.error("AJAX Error in generateDailyData:", textStatus, errorThrown);
console.log("Status:", xhr.status);
console.log("Response Text:", xhr.responseText);
reject(errorThrown);
}
});
});
}
function convertToBytes(value) {
let [number, unit] = value.split(' ');
number = parseFloat(number);
switch (unit) {
case 'GB':
return number * 1024 * 1024 * 1024;
case 'MB':
return number * 1024 * 1024;
case 'KB':
return number * 1024;
default:
return number;
}
}
// ========================================== NEW FITUR ==========================================//
function formatBytesPerSecond(bytes) {
if (bytes === 0) {
return '0 Bps';
}
var k = 1024;
var sizes = ['Bps', 'KBps', 'MBps', 'GBps', 'TBps', 'PBps', 'EBps', 'ZBps', 'YBps'];
var i = Math.floor(Math.log(bytes) / Math.log(k));
var formattedValue = parseFloat((bytes / Math.pow(k, i)).toFixed(2));
return formattedValue + ' ' + sizes[i];
}
// Fungsi untuk mengubah ukuran dalam byte menjadi format yang lebih mudah dibaca
function formatBytes(bytes, decimals = 2) {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const dm = decimals < 0 ? 0 : decimals;
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
}
// Function to update traffic values and icons
function updateTrafficValues() {
var username = $j('#modalUsername').text().trim();
var interfaceValue = $j('#interface').val();
if (!username || !interfaceValue) {
console.error("Username or interface is undefined or empty.");
return;
}
$j.ajax({
url: '{$_url}plugin/pppoe_monitor_router_traffic/{$router}',
dataType: 'json',
data: { username: username, interface: interfaceValue },
success: function(data) {
var timestamp = new Date().getTime();
var txData = parseInt(data.rows.tx[0]) || 0;
var rxData = parseInt(data.rows.rx[0]) || 0;
// Log data tx dan rx untuk debugging
console.log('txData:', txData, 'rxData:', rxData);
// Update chart data
chartData.txData.push({ x: timestamp, y: txData });
chartData.rxData.push({ x: timestamp, y: rxData });
var maxDataPoints = 10;
if (chartData.txData.length > maxDataPoints) {
chartData.txData.shift();
chartData.rxData.shift();
}
// Update series on the chart
chart.updateSeries([
{ name: 'Download', data: chartData.txData },
{ name: 'Upload', data: chartData.rxData }
]);
// Find the icon element for the specific user based on username
var userRow = $j('#ppp-table tbody tr').filter(function() {
return $j(this).find('td').eq(1).text().trim() === username;
});
var iconElement = userRow.find('.traffic-icon');
// Define thresholds for traffic levels
var thresholdHigh = 2000; // Adjust these values as needed
var thresholdMedium = 1500; // Adjust these values as needed
// Adjust icon color based on traffic levels
if (txData > thresholdHigh || rxData > thresholdHigh) {
iconElement.removeClass('traffic-icon-green traffic-icon-yellow').addClass('traffic-icon-red');
} else if (txData > thresholdMedium || rxData > thresholdMedium) {
iconElement.removeClass('traffic-icon-green traffic-icon-red').addClass('traffic-icon-yellow');
} else {
iconElement.removeClass('traffic-icon-yellow traffic-icon-red').addClass('traffic-icon-green');
}
},
error: function(xhr, textStatus, errorThrown) {
console.error("Status: " + textStatus);
console.error("Error: " + errorThrown);
}
});
}
// Function to update traffic icons based on table data
function updateTrafficIcons(response) {
$j('#ppp-table tbody tr').each(function(index) {
var row = table.row(this).data();
if (row) {
var txValue = parseInt(row.tx, 10);
var rxValue = parseInt(row.rx, 10);
var iconElement = $j(this).find('.traffic-icon');
var maxLimit = row.max_limit;
if (maxLimit === '1M/2M') {
if (txValue >= 2 * 1024 * 1024 || rxValue >= 2 * 1024 * 1024) {
iconElement.removeClass().addClass('traffic-icon traffic-icon-red');
} else if (txValue >= 1.5 * 1024 * 1024 || rxValue >= 1.5 * 1024 * 1024) {
iconElement.removeClass().addClass('traffic-icon traffic-icon-yellow');
} else {
iconElement.removeClass().addClass('traffic-icon traffic-icon-green');
}
} else {
// Default logic for other max limits
if (txValue >= 2 * 1024 * 1024 || rxValue >= 2 * 1024 * 1024) {
iconElement.removeClass().addClass('traffic-icon traffic-icon-red');
} else if (txValue >= 1.5 * 1024 * 1024 || rxValue >= 1.5 * 1024 * 1024) {
iconElement.removeClass().addClass('traffic-icon traffic-icon-yellow');
} else {
iconElement.removeClass().addClass('traffic-icon traffic-icon-green');
}
}
}
});
}
// Donation Popup
document.addEventListener('DOMContentLoaded', function() {
setTimeout(function() {
document.getElementById('donationPopup').style.display = 'flex';
}, 1000);
});
document.getElementById('donationPopup').querySelector('.modalsupport-close').addEventListener('click', function() {
document.getElementById('donationPopup').style.display = 'none';
});
window.addEventListener('click', function(event) {
if (event.target === document.getElementById('donationPopup')) {
document.getElementById('donationPopup').style.display = 'none';
}
});
document.getElementById('donationPopup').querySelector('.donate-button').addEventListener('click', function() {
window.open('https://buymeacoffee.com/kevindonisaputra', '_blank');
});
</script>
{include file="sections/footer.tpl"}