full widget support

This commit is contained in:
iBNu Maksum 2025-02-18 15:52:53 +07:00
parent fca86ac4dc
commit 023b4884d1
No known key found for this signature in database
GPG Key ID: 7FC82848810579E5
12 changed files with 188 additions and 810 deletions

File diff suppressed because one or more lines are too long

View File

@ -112,6 +112,11 @@ $result = ORM::for_table('tbl_appconfig')->find_many();
foreach ($result as $value) { foreach ($result as $value) {
$config[$value['setting']] = $value['value']; $config[$value['setting']] = $value['value'];
} }
if(empty($config['dashboard_cr'])){
$config['dashboard_cr'] = "12.7,5.12";
}
$_c = $config; $_c = $config;
if (empty($http_proxy) && !empty($config['http_proxy'])) { if (empty($http_proxy) && !empty($config['http_proxy'])) {
$http_proxy = $config['http_proxy']; $http_proxy = $config['http_proxy'];

View File

@ -1,233 +1,56 @@
<?php <?php
/** /**
* PHP Mikrotik Billing (https://github.com/hotspotbilling/phpnuxbill/) * PHP Mikrotik Billing (https://github.com/hotspotbilling/phpnuxbill/)
* by https://t.me/ibnux * by https://t.me/ibnux
**/ **/
_admin(); _admin();
$ui->assign('_title', Lang::T('Dashboard')); $ui->assign('_title', Lang::T('Dashboard'));
$ui->assign('_admin', $admin); $ui->assign('_admin', $admin);
if (isset($_GET['refresh'])) { if (isset($_GET['refresh'])) {
$files = scandir($CACHE_PATH); $files = scandir($CACHE_PATH);
foreach ($files as $file) { foreach ($files as $file) {
$ext = pathinfo($file, PATHINFO_EXTENSION); $ext = pathinfo($file, PATHINFO_EXTENSION);
if (is_file($CACHE_PATH . DIRECTORY_SEPARATOR . $file) && $ext == 'temp') { if (is_file($CACHE_PATH . DIRECTORY_SEPARATOR . $file) && $ext == 'temp') {
unlink($CACHE_PATH . DIRECTORY_SEPARATOR . $file); unlink($CACHE_PATH . DIRECTORY_SEPARATOR . $file);
} }
} }
r2(getUrl('dashboard'), 's', 'Data Refreshed'); r2(getUrl('dashboard'), 's', 'Data Refreshed');
} }
$reset_day = $config['reset_day'];
if (empty($reset_day)) { $reset_day = $config['reset_day'];
$reset_day = 1; if (empty($reset_day)) {
} $reset_day = 1;
//first day of month }
if (date("d") >= $reset_day) { //first day of month
$start_date = date('Y-m-' . $reset_day); if (date("d") >= $reset_day) {
} else { $start_date = date('Y-m-' . $reset_day);
$start_date = date('Y-m-' . $reset_day, strtotime("-1 MONTH")); } else {
} $start_date = date('Y-m-' . $reset_day, strtotime("-1 MONTH"));
}
$current_date = date('Y-m-d');
$month_n = date('n'); $current_date = date('Y-m-d');
$ui->assign('start_date', $start_date);
$iday = ORM::for_table('tbl_transactions') $ui->assign('current_date', $current_date);
->where('recharged_on', $current_date)
->where_not_equal('method', 'Customer - Balance') $widgets = ORM::for_table('tbl_widgets')->selects("enabled", 1)->order_by_asc("orders")->findArray();
->where_not_equal('method', 'Recharge Balance - Administrator') $count = count($widgets);
->sum('price'); for ($i = 0; $i < $count; $i++) {
try{
if ($iday == '') { if(file_exists($WIDGET_PATH . DIRECTORY_SEPARATOR . $widgets[$i]['widget'].".php")){
$iday = '0.00'; require_once $WIDGET_PATH . DIRECTORY_SEPARATOR . $widgets[$i]['widget'].".php";
} $widgets[$i]['content'] = (new $widgets[$i]['widget'])->getWidget($widgets[$i]);
$ui->assign('iday', $iday); }else{
$widgets[$i]['content'] = "Widget not found";
$imonth = ORM::for_table('tbl_transactions') }
->where_not_equal('method', 'Customer - Balance') } catch (Throwable $e) {
->where_not_equal('method', 'Recharge Balance - Administrator') $widgets[$i]['content'] = $e->getMessage();
->where_gte('recharged_on', $start_date) }
->where_lte('recharged_on', $current_date)->sum('price'); }
if ($imonth == '') {
$imonth = '0.00'; $ui->assign('widgets', $widgets);
} run_hook('view_dashboard'); #HOOK
$ui->assign('imonth', $imonth); $ui->display('admin/dashboard.tpl');
if ($config['enable_balance'] == 'yes'){
$cb = ORM::for_table('tbl_customers')->whereGte('balance', 0)->sum('balance');
$ui->assign('cb', $cb);
}
$u_act = ORM::for_table('tbl_user_recharges')->where('status', 'on')->count();
if (empty($u_act)) {
$u_act = '0';
}
$ui->assign('u_act', $u_act);
$u_all = ORM::for_table('tbl_user_recharges')->count();
if (empty($u_all)) {
$u_all = '0';
}
$ui->assign('u_all', $u_all);
$c_all = ORM::for_table('tbl_customers')->count();
if (empty($c_all)) {
$c_all = '0';
}
$ui->assign('c_all', $c_all);
if ($config['hide_uet'] != 'yes') {
//user expire
$query = ORM::for_table('tbl_user_recharges')
->where_lte('expiration', $current_date)
->order_by_desc('expiration');
$expire = Paginator::findMany($query);
// Get the total count of expired records for pagination
$totalCount = ORM::for_table('tbl_user_recharges')
->where_lte('expiration', $current_date)
->count();
// Pass the total count and current page to the paginator
$paginator['total_count'] = $totalCount;
// Assign the pagination HTML to the template variable
$ui->assign('expire', $expire);
}
//activity log
$dlog = ORM::for_table('tbl_logs')->limit(5)->order_by_desc('id')->find_many();
$ui->assign('dlog', $dlog);
$log = ORM::for_table('tbl_logs')->count();
$ui->assign('log', $log);
if ($config['hide_vs'] != 'yes') {
$cacheStocksfile = $CACHE_PATH . File::pathFixer('/VoucherStocks.temp');
$cachePlanfile = $CACHE_PATH . File::pathFixer('/VoucherPlans.temp');
//Cache for 5 minutes
if (file_exists($cacheStocksfile) && time() - filemtime($cacheStocksfile) < 600) {
$stocks = json_decode(file_get_contents($cacheStocksfile), true);
$plans = json_decode(file_get_contents($cachePlanfile), true);
} else {
// Count stock
$tmp = $v = ORM::for_table('tbl_plans')->select('id')->select('name_plan')->find_many();
$plans = array();
$stocks = array("used" => 0, "unused" => 0);
$n = 0;
foreach ($tmp as $plan) {
$unused = ORM::for_table('tbl_voucher')
->where('id_plan', $plan['id'])
->where('status', 0)->count();
$used = ORM::for_table('tbl_voucher')
->where('id_plan', $plan['id'])
->where('status', 1)->count();
if ($unused > 0 || $used > 0) {
$plans[$n]['name_plan'] = $plan['name_plan'];
$plans[$n]['unused'] = $unused;
$plans[$n]['used'] = $used;
$stocks["unused"] += $unused;
$stocks["used"] += $used;
$n++;
}
}
file_put_contents($cacheStocksfile, json_encode($stocks));
file_put_contents($cachePlanfile, json_encode($plans));
}
}
$cacheMRfile = File::pathFixer('/monthlyRegistered.temp');
//Cache for 1 hour
if (file_exists($cacheMRfile) && time() - filemtime($cacheMRfile) < 3600) {
$monthlyRegistered = json_decode(file_get_contents($cacheMRfile), true);
} else {
//Monthly Registered Customers
$result = ORM::for_table('tbl_customers')
->select_expr('MONTH(created_at)', 'month')
->select_expr('COUNT(*)', 'count')
->where_raw('YEAR(created_at) = YEAR(NOW())')
->group_by_expr('MONTH(created_at)')
->find_many();
$monthlyRegistered = [];
foreach ($result as $row) {
$monthlyRegistered[] = [
'date' => $row->month,
'count' => $row->count
];
}
file_put_contents($cacheMRfile, json_encode($monthlyRegistered));
}
$cacheMSfile = $CACHE_PATH . File::pathFixer('/monthlySales.temp');
//Cache for 12 hours
if (file_exists($cacheMSfile) && time() - filemtime($cacheMSfile) < 43200) {
$monthlySales = json_decode(file_get_contents($cacheMSfile), true);
} else {
// Query to retrieve monthly data
$results = ORM::for_table('tbl_transactions')
->select_expr('MONTH(recharged_on)', 'month')
->select_expr('SUM(price)', 'total')
->where_raw("YEAR(recharged_on) = YEAR(CURRENT_DATE())") // Filter by the current year
->where_not_equal('method', 'Customer - Balance')
->where_not_equal('method', 'Recharge Balance - Administrator')
->group_by_expr('MONTH(recharged_on)')
->find_many();
// Create an array to hold the monthly sales data
$monthlySales = array();
// Iterate over the results and populate the array
foreach ($results as $result) {
$month = $result->month;
$totalSales = $result->total;
$monthlySales[$month] = array(
'month' => $month,
'totalSales' => $totalSales
);
}
// Fill in missing months with zero sales
for ($month = 1; $month <= 12; $month++) {
if (!isset($monthlySales[$month])) {
$monthlySales[$month] = array(
'month' => $month,
'totalSales' => 0
);
}
}
// Sort the array by month
ksort($monthlySales);
// Reindex the array
$monthlySales = array_values($monthlySales);
file_put_contents($cacheMSfile, json_encode($monthlySales));
}
if ($config['router_check']) {
$routeroffs = ORM::for_table('tbl_routers')->selects(['id', 'name', 'last_seen'])->where('status', 'Offline')->where('enabled', '1')->order_by_desc('name')->find_array();
$ui->assign('routeroffs', $routeroffs);
}
$timestampFile = "$UPLOAD_PATH/cron_last_run.txt";
if (file_exists($timestampFile)) {
$lastRunTime = file_get_contents($timestampFile);
$ui->assign('run_date', date('Y-m-d h:i:s A', $lastRunTime));
}
// Assign the monthly sales data to Smarty
$ui->assign('start_date', $start_date);
$ui->assign('current_date', $current_date);
$ui->assign('monthlySales', $monthlySales);
$ui->assign('xfooter', '');
$ui->assign('monthlyRegistered', $monthlyRegistered);
$ui->assign('stocks', $stocks);
$ui->assign('plans', $plans);
run_hook('view_dashboard'); #HOOK
$ui->display('admin/dashboard.tpl');

View File

@ -1,56 +0,0 @@
<?php
/**
* PHP Mikrotik Billing (https://github.com/hotspotbilling/phpnuxbill/)
* by https://t.me/ibnux
**/
_admin();
$ui->assign('_title', Lang::T('Dashboard'));
$ui->assign('_admin', $admin);
if (isset($_GET['refresh'])) {
$files = scandir($CACHE_PATH);
foreach ($files as $file) {
$ext = pathinfo($file, PATHINFO_EXTENSION);
if (is_file($CACHE_PATH . DIRECTORY_SEPARATOR . $file) && $ext == 'temp') {
unlink($CACHE_PATH . DIRECTORY_SEPARATOR . $file);
}
}
r2(getUrl('dashboard'), 's', 'Data Refreshed');
}
$reset_day = $config['reset_day'];
if (empty($reset_day)) {
$reset_day = 1;
}
//first day of month
if (date("d") >= $reset_day) {
$start_date = date('Y-m-' . $reset_day);
} else {
$start_date = date('Y-m-' . $reset_day, strtotime("-1 MONTH"));
}
$current_date = date('Y-m-d');
$ui->assign('start_date', $start_date);
$ui->assign('current_date', $current_date);
$widgets = ORM::for_table('tbl_widgets')->selects("enabled", 1)->order_by_asc("orders")->findArray();
$count = count($widgets);
for ($i = 0; $i < $count; $i++) {
try{
if(file_exists($WIDGET_PATH . DIRECTORY_SEPARATOR . $widgets[$i]['widget'].".php")){
require_once $WIDGET_PATH . DIRECTORY_SEPARATOR . $widgets[$i]['widget'].".php";
$widgets[$i]['content'] = (new $widgets[$i]['widget'])->getWidget($widgets[$i]);
}else{
$widgets[$i]['content'] = "Widget not found";
}
} catch (Throwable $e) {
$widgets[$i]['content'] = $e->getMessage();
}
}
$ui->assign('widgets', $widgets);
run_hook('view_dashboard'); #HOOK
$ui->display('admin/dashboard_widget.tpl');

View File

@ -11,6 +11,13 @@ $ui->assign('_system_menu', 'settings');
$action = alphanumeric($routes['1']); $action = alphanumeric($routes['1']);
$ui->assign('_admin', $admin); $ui->assign('_admin', $admin);
$max = ORM::for_table('tbl_widgets')->max('position');
$max2 = substr_count($config['dashboard_cr'], '.')+substr_count($config['dashboard_cr'], ',')+1;
if($max2>$max){
$max = $max2;
}
$ui->assign('max', $max);
if ($action == 'add') { if ($action == 'add') {
$pos = alphanumeric($routes['2']); $pos = alphanumeric($routes['2']);
if ($_SERVER['REQUEST_METHOD'] == 'POST') { if ($_SERVER['REQUEST_METHOD'] == 'POST') {
@ -104,9 +111,20 @@ if ($action == 'add') {
} }
r2(getUrl('widgets'), 's', 'Widget order Saved Successfully'); r2(getUrl('widgets'), 's', 'Widget order Saved Successfully');
} else { } else {
if(_post("save") == 'struct'){
$d = ORM::for_table('tbl_appconfig')->where('setting', 'dashboard_cr')->find_one();
if ($d) {
$d->value = _post('dashboard_cr');
$d->save();
} else {
$d = ORM::for_table('tbl_appconfig')->create();
$d->setting = 'dashboard_cr';
$d->value = _post('dashboard_cr');
$d->save();
}
_alert("Dashboard Structure Saved Successfully", "success", getUrl('widgets'));
}
$widgets = ORM::for_table('tbl_widgets')->selects("position", 1)->order_by_asc("orders")->find_many(); $widgets = ORM::for_table('tbl_widgets')->selects("position", 1)->order_by_asc("orders")->find_many();
$max = ORM::for_table('tbl_widgets')->max('position');
$ui->assign('widgets', $widgets); $ui->assign('widgets', $widgets);
$ui->assign('max', $max);
$ui->display('admin/settings/widgets.tpl'); $ui->display('admin/settings/widgets.tpl');
} }

View File

@ -1049,5 +1049,8 @@
"Sending___": "Sending...", "Sending___": "Sending...",
"Message_sent_successfully_": "Message sent successfully.", "Message_sent_successfully_": "Message sent successfully.",
"Error_sending_message__": "Error sending message: ", "Error_sending_message__": "Error sending message: ",
"Failed_to_send_the_message__Please_try_again_": "Failed to send the message. Please try again." "Failed_to_send_the_message__Please_try_again_": "Failed to send the message. Please try again.",
"Dashboard_Structure": "Dashboard Structure",
"Read_documentation": "Read documentation",
"Structure": "Structure"
} }

View File

@ -1,420 +1,37 @@
{include file="sections/header.tpl"} {include file="sections/header.tpl"}
<div class="row"> {function showWidget pos=0}
{if in_array($_admin['user_type'],['SuperAdmin','Admin', 'Report'])} {foreach $widgets as $w}
<div class="col-lg-3 col-xs-6"> {if $w['position'] == $pos}
<div class="small-box bg-aqua"> {$w['content']}
<div class="inner"> {/if}
<h4 class="text-bold" style="font-size: large;"><sup>{$_c['currency_code']}</sup> {/foreach}
{number_format($iday,0,$_c['dec_point'],$_c['thousands_sep'])}</h4> {/function}
</div>
<div class="icon">
<i class="ion ion-clock"></i> {assign rows explode(".", $_c['dashboard_cr'])}
</div> {assign pos 1}
<a href="{Text::url('reports/by-date')}" class="small-box-footer">{Lang::T('Income Today')}</a> {foreach $rows as $cols}
{if $cols == 12}
<div class="row">
<div class="col-md-12">
{showWidget widgets=$widgets pos=$pos}
</div> </div>
</div> </div>
<div class="col-lg-3 col-xs-6"> {assign pos value=$pos+1}
<div class="small-box bg-green"> {else}
<div class="inner"> {assign colss explode(",", $cols)}
<h4 class="text-bold" style="font-size: large;"><sup>{$_c['currency_code']}</sup> <div class="row">
{number_format($imonth,0,$_c['dec_point'],$_c['thousands_sep'])}</h4> {foreach $colss as $c}
<div class="col-md-{$c}">
{showWidget widgets=$widgets pos=$pos}
</div> </div>
<div class="icon"> {assign pos value=$pos+1}
<i class="ion ion-android-calendar"></i> {/foreach}
</div>
<a href="{Text::url('reports/by-period')}" class="small-box-footer">{Lang::T('Income This Month')}</a>
</div>
</div> </div>
{/if} {/if}
<div class="col-lg-3 col-xs-6"> {/foreach}
<div class="small-box bg-yellow">
<div class="inner">
<h4 class="text-bold" style="font-size: large;">{$u_act}/{$u_all-$u_act}</h4>
</div>
<div class="icon">
<i class="ion ion-person"></i>
</div>
<a href="{Text::url('plan/list')}" class="small-box-footer">{Lang::T('Active')}/{Lang::T('Expired')}</a>
</div>
</div>
<div class="col-lg-3 col-xs-6">
<div class="small-box bg-red">
<div class="inner">
<h4 class="text-bold" style="font-size: large;">{$c_all}</h4>
</div>
<div class="icon">
<i class="ion ion-android-people"></i>
</div>
<a href="{Text::url('customers/list')}" class="small-box-footer">{Lang::T('Customers')}</a>
</div>
</div>
</div>
<ol class="breadcrumb">
<li>{Lang::dateFormat($start_date)}</li>
<li>{Lang::dateFormat($current_date)}</li>
{if $_c['enable_balance'] == 'yes' && in_array($_admin['user_type'],['SuperAdmin','Admin', 'Report'])}
<li onclick="window.location.href = '{Text::url('customers&search=&order=balance&filter=Active&orderby=desc')}'" style="cursor: pointer;">
{Lang::T('Customer Balance')} <sup>{$_c['currency_code']}</sup>
<b>{number_format($cb,0,$_c['dec_point'],$_c['thousands_sep'])}</b>
</li>
{/if}
</ol>
<div class="row">
<div class="col-md-7">
<!-- solid sales graph -->
{if $_c['hide_mrc'] != 'yes'}
<div class="box box-solid ">
<div class="box-header">
<i class="fa fa-th"></i>
<h3 class="box-title">{Lang::T('Monthly Registered Customers')}</h3>
<div class="box-tools pull-right">
<button type="button" class="btn bg-teal btn-sm" data-widget="collapse"><i class="fa fa-minus"></i>
</button>
<a href="{Text::url('dashboard&refresh')}" class="btn bg-teal btn-sm"><i class="fa fa-refresh"></i>
</a>
</div>
</div>
<div class="box-body border-radius-none">
<canvas class="chart" id="chart" style="height: 250px;"></canvas>
</div>
</div>
{/if}
<!-- solid sales graph -->
{if $_c['hide_tms'] != 'yes'}
<div class="box box-solid ">
<div class="box-header">
<i class="fa fa-inbox"></i>
<h3 class="box-title">{Lang::T('Total Monthly Sales')}</h3>
<div class="box-tools pull-right">
<button type="button" class="btn bg-teal btn-sm" data-widget="collapse"><i class="fa fa-minus"></i>
</button>
<a href="{Text::url('dashboard&refresh')}" class="btn bg-teal btn-sm"><i class="fa fa-refresh"></i>
</a>
</div>
</div>
<div class="box-body border-radius-none">
<canvas class="chart" id="salesChart" style="height: 250px;"></canvas>
</div>
</div>
{/if}
{if $_c['disable_voucher'] != 'yes' && $stocks['unused']>0 || $stocks['used']>0}
{if $_c['hide_vs'] != 'yes'}
<div class="panel panel-primary mb20 panel-hovered project-stats table-responsive">
<div class="panel-heading">Vouchers Stock</div>
<div class="table-responsive">
<table class="table table-condensed">
<thead>
<tr>
<th>{Lang::T('Package Name')}</th>
<th>unused</th>
<th>used</th>
</tr>
</thead>
<tbody>
{foreach $plans as $stok}
<tr>
<td>{$stok['name_plan']}</td>
<td>{$stok['unused']}</td>
<td>{$stok['used']}</td>
</tr>
</tbody>
{/foreach}
<tr>
<td>Total</td>
<td>{$stocks['unused']}</td>
<td>{$stocks['used']}</td>
</tr>
</table>
</div>
</div>
{/if}
{/if}
{if $_c['hide_uet'] != 'yes'}
<div class="panel panel-warning mb20 panel-hovered project-stats table-responsive">
<div class="panel-heading">{Lang::T('User Expired, Today')}</div>
<div class="table-responsive">
<table class="table table-condensed">
<thead>
<tr>
<th>{Lang::T('Username')}</th>
<th>{Lang::T('Created / Expired')}</th>
<th>{Lang::T('Internet Package')}</th>
<th>{Lang::T('Location')}</th>
</tr>
</thead>
<tbody>
{foreach $expire as $expired}
{assign var="rem_exp" value="{$expired['expiration']} {$expired['time']}"}
{assign var="rem_started" value="{$expired['recharged_on']} {$expired['recharged_time']}"}
<tr>
<td><a href="{Text::url('customers/viewu/',$expired['username'])}">{$expired['username']}</a></td>
<td><small data-toggle="tooltip" data-placement="top"
title="{Lang::dateAndTimeFormat($expired['recharged_on'],$expired['recharged_time'])}">{Lang::timeElapsed($rem_started)}</small>
/
<span data-toggle="tooltip" data-placement="top"
title="{Lang::dateAndTimeFormat($expired['expiration'],$expired['time'])}">{Lang::timeElapsed($rem_exp)}</span>
</td>
<td>{$expired['namebp']}</td>
<td>{$expired['routers']}</td>
</tr>
{/foreach}
</tbody>
</table>
</div>
&nbsp; {include file="pagination.tpl"}
</div>
{/if}
</div>
<div class="col-md-5">
{if $_c['router_check'] && count($routeroffs)> 0}
<div class="panel panel-danger">
<div class="panel-heading text-bold">{Lang::T('Routers Offline')}</div>
<div class="table-responsive">
<table class="table table-condensed">
<tbody>
{foreach $routeroffs as $ros}
<tr>
<td><a href="{Text::url('routers/edit/',$ros['id'])}" class="text-bold text-red">{$ros['name']}</a></td>
<td data-toggle="tooltip" data-placement="top" class="text-red"
title="{Lang::dateTimeFormat($ros['last_seen'])}">{Lang::timeElapsed($ros['last_seen'])}
</td>
</tr>
{/foreach}
</tbody>
</table>
</div>
</div>
{/if}
{if $run_date}
{assign var="current_time" value=$smarty.now}
{assign var="run_time" value=strtotime($run_date)}
{if $current_time - $run_time > 3600}
<div class="panel panel-cron-warning panel-hovered mb20 activities">
<div class="panel-heading"><i class="fa fa-clock-o"></i> &nbsp; {Lang::T('Cron has not run for over 1 hour. Please
check your setup.')}</div>
</div>
{else}
<div class="panel panel-cron-success panel-hovered mb20 activities">
<div class="panel-heading">{Lang::T('Cron Job last ran on')}: {$run_date}</div>
</div>
{/if}
{else}
<div class="panel panel-cron-danger panel-hovered mb20 activities">
<div class="panel-heading"><i class="fa fa-warning"></i> &nbsp; {Lang::T('Cron appear not been setup, please check
your cron setup.')}</div>
</div>
{/if}
{if $_c['hide_pg'] != 'yes'}
<div class="panel panel-success panel-hovered mb20 activities">
<div class="panel-heading">{Lang::T('Payment Gateway')}: {str_replace(',',', ',$_c['payment_gateway'])}
</div>
</div>
{/if}
{if $_c['hide_aui'] != 'yes'}
<div class="panel panel-info panel-hovered mb20 activities">
<div class="panel-heading">{Lang::T('All Users Insights')}</div>
<div class="panel-body">
<canvas id="userRechargesChart"></canvas>
</div>
</div>
{/if}
{if $_c['hide_al'] != 'yes'}
<div class="panel panel-info panel-hovered mb20 activities">
<div class="panel-heading"><a href="{Text::url('logs')}">{Lang::T('Activity Log')}</a></div>
<div class="panel-body">
<ul class="list-unstyled">
{foreach $dlog as $dlogs}
<li class="primary">
<span class="point"></span>
<span class="time small text-muted">{Lang::timeElapsed($dlogs['date'],true)}</span>
<p>{$dlogs['description']}</p>
</li>
{/foreach}
</ul>
</div>
</div>
{/if}
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/chart.js@3.5.1/dist/chart.min.js"></script>
<script type="text/javascript">
{if $_c['hide_mrc'] != 'yes'}
{literal}
document.addEventListener("DOMContentLoaded", function() {
var counts = JSON.parse('{/literal}{$monthlyRegistered|json_encode}{literal}');
var monthNames = [
'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'
];
var labels = [];
var data = [];
for (var i = 1; i <= 12; i++) {
var month = counts.find(count => count.date === i);
labels.push(month ? monthNames[i - 1] : monthNames[i - 1].substring(0, 3));
data.push(month ? month.count : 0);
}
var ctx = document.getElementById('chart').getContext('2d');
var chart = new Chart(ctx, {
type: 'bar',
data: {
labels: labels,
datasets: [{
label: 'Registered Members',
data: data,
backgroundColor: 'rgba(0, 0, 255, 0.5)',
borderColor: 'rgba(0, 0, 255, 0.7)',
borderWidth: 1
}]
},
options: {
responsive: true,
scales: {
x: {
grid: {
display: false
}
},
y: {
beginAtZero: true,
grid: {
color: 'rgba(0, 0, 0, 0.1)'
}
}
}
}
});
});
{/literal}
{/if}
{if $_c['hide_tmc'] != 'yes'}
{literal}
document.addEventListener("DOMContentLoaded", function() {
var monthlySales = JSON.parse('{/literal}{$monthlySales|json_encode}{literal}');
var monthNames = [
'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'
];
var labels = [];
var data = [];
for (var i = 1; i <= 12; i++) {
var month = findMonthData(monthlySales, i);
labels.push(month ? monthNames[i - 1] : monthNames[i - 1].substring(0, 3));
data.push(month ? month.totalSales : 0);
}
var ctx = document.getElementById('salesChart').getContext('2d');
var chart = new Chart(ctx, {
type: 'bar',
data: {
labels: labels,
datasets: [{
label: 'Monthly Sales',
data: data,
backgroundColor: 'rgba(2, 10, 242)', // Customize the background color
borderColor: 'rgba(255, 99, 132, 1)', // Customize the border color
borderWidth: 1
}]
},
options: {
responsive: true,
scales: {
x: {
grid: {
display: false
}
},
y: {
beginAtZero: true,
grid: {
color: 'rgba(0, 0, 0, 0.1)'
}
}
}
}
});
});
function findMonthData(monthlySales, month) {
for (var i = 0; i < monthlySales.length; i++) {
if (monthlySales[i].month === month) {
return monthlySales[i];
}
}
return null;
}
{/literal}
{/if}
{if $_c['hide_aui'] != 'yes'}
{literal}
document.addEventListener("DOMContentLoaded", function() {
// Get the data from PHP and assign it to JavaScript variables
var u_act = '{/literal}{$u_act}{literal}';
var c_all = '{/literal}{$c_all}{literal}';
var u_all = '{/literal}{$u_all}{literal}';
//lets calculate the inactive users as reported
var expired = u_all - u_act;
var inactive = c_all - u_all;
if (inactive < 0) {
inactive = 0;
}
// Create the chart data
var data = {
labels: ['Active Users', 'Expired Users', 'Inactive Users'],
datasets: [{
label: 'User Recharges',
data: [parseInt(u_act), parseInt(expired), parseInt(inactive)],
backgroundColor: ['rgba(4, 191, 13)', 'rgba(191, 35, 4)', 'rgba(0, 0, 255, 0.5'],
borderColor: ['rgba(0, 255, 0, 1)', 'rgba(255, 99, 132, 1)', 'rgba(0, 0, 255, 0.7'],
borderWidth: 1
}]
};
// Create chart options
var options = {
responsive: true,
aspectRatio: 1,
plugins: {
legend: {
position: 'bottom',
labels: {
boxWidth: 15
}
}
}
};
// Get the canvas element and create the chart
var ctx = document.getElementById('userRechargesChart').getContext('2d');
var chart = new Chart(ctx, {
type: 'pie',
data: data,
options: options
});
});
{/literal}
{/if}
</script>
{if $_c['new_version_notify'] != 'disable'} {if $_c['new_version_notify'] != 'disable'}
<script> <script>
window.addEventListener('DOMContentLoaded', function() { window.addEventListener('DOMContentLoaded', function() {
@ -456,4 +73,4 @@
</script> </script>
{/if} {/if}
{include file="sections/footer.tpl"} {include file="sections/footer.tpl"}

View File

@ -1,94 +0,0 @@
{include file="sections/header.tpl"}
{function showWidget pos=0}
{foreach $widgets as $w}
{if $w['position'] == $pos}
{$w['content']}
{/if}
{/foreach}
{/function}
{if $_c['dashboard'] == '1.55.1'}
{showWidget widgets=$widgets pos=1}
<div class="row">
<div class="col-md-6">
{showWidget widgets=$widgets pos=2}
</div>
<div class="col-md-6">
{showWidget widgets=$widgets pos=3}
</div>
</div>
{showWidget widgets=$widgets pos=4}
{elseif $_c['dashboard'] == '1.57.1'}
{showWidget widgets=$widgets pos=1}
<div class="row">
<div class="col-md-5">
{showWidget widgets=$widgets pos=2}
</div>
<div class="col-md-7">
{showWidget widgets=$widgets pos=3}
</div>
</div>
{showWidget widgets=$widgets pos=4}
{elseif $_c['dashboard'] == '1.1.1.1'}
{showWidget widgets=$widgets pos=1}
{showWidget widgets=$widgets pos=2}
{showWidget widgets=$widgets pos=3}
{showWidget widgets=$widgets pos=4}
{else}
{showWidget widgets=$widgets pos=1}
<div class="row">
<div class="col-md-7">
{showWidget widgets=$widgets pos=2}
</div>
<div class="col-md-5">
{showWidget widgets=$widgets pos=3}
</div>
</div>
{showWidget widgets=$widgets pos=4}
{/if}
{if $_c['new_version_notify'] != 'disable'}
<script>
window.addEventListener('DOMContentLoaded', function() {
$.getJSON("./version.json?" + Math.random(), function(data) {
var localVersion = data.version;
$('#version').html('Version: ' + localVersion);
$.getJSON(
"https://raw.githubusercontent.com/hotspotbilling/phpnuxbill/master/version.json?" +
Math
.random(),
function(data) {
var latestVersion = data.version;
if (localVersion !== latestVersion) {
$('#version').html('Latest Version: ' + latestVersion);
if (getCookie(latestVersion) != 'done') {
Swal.fire({
icon: 'info',
title: "New Version Available\nVersion: " + latestVersion,
toast: true,
position: 'bottom-right',
showConfirmButton: true,
showCloseButton: true,
timer: 30000,
confirmButtonText: '<a href="{Text::url('community')}#latestVersion" style="color: white;">Update Now</a>',
timerProgressBar: true,
didOpen: (toast) => {
toast.addEventListener('mouseenter', Swal.stopTimer)
toast.addEventListener('mouseleave', Swal
.resumeTimer)
}
});
setCookie(latestVersion, 'done', 7);
}
}
});
});
});
</script>
{/if}
{include file="sections/footer.tpl"}

View File

@ -1,6 +1,6 @@
{include file="sections/header.tpl"} {include file="sections/header.tpl"}
<form class="form-horizontal" method="post" role="form" action="{Text::url('')}settings/notifications-post"> <form class="form-horizontal" method="post" role="form" action="{Text::url('settings/notifications-post')}">
<input type="hidden" name="csrf_token" value="{$csrf_token}"> <input type="hidden" name="csrf_token" value="{$csrf_token}">
<div class="row"> <div class="row">
<div class="col-sm-12 col-md-12"> <div class="col-sm-12 col-md-12">
@ -26,7 +26,7 @@
<b>[[package]]</b> - {Lang::T('will be replaced with Package name')}.<br> <b>[[package]]</b> - {Lang::T('will be replaced with Package name')}.<br>
<b>[[price]]</b> - {Lang::T('will be replaced with Package price')}.<br> <b>[[price]]</b> - {Lang::T('will be replaced with Package price')}.<br>
<b>[[bills]]</b> - {Lang::T('additional bills for customers')}.<br> <b>[[bills]]</b> - {Lang::T('additional bills for customers')}.<br>
<b>[[payment_link]]</b> - <a href="{Text::url('docs')}/docs/#Reminder%20with%20payment%20link" <b>[[payment_link]]</b> - <a href="{$app_url}/docs/#Reminder%20with%20payment%20link"
target="_blank">read documentation</a>. target="_blank">read documentation</a>.
</p> </p>
</div> </div>
@ -45,7 +45,7 @@
<b>[[price]]</b> - {Lang::T('will be replaced with Package price')}.<br> <b>[[price]]</b> - {Lang::T('will be replaced with Package price')}.<br>
<b>[[expired_date]]</b> - {Lang::T('will be replaced with Expiration date')}.<br> <b>[[expired_date]]</b> - {Lang::T('will be replaced with Expiration date')}.<br>
<b>[[bills]]</b> - {Lang::T('additional bills for customers')}.<br> <b>[[bills]]</b> - {Lang::T('additional bills for customers')}.<br>
<b>[[payment_link]]</b> - <a href="{Text::url('docs')}/docs/#Reminder%20with%20payment%20link" <b>[[payment_link]]</b> - <a href="{$app_url}/docs/#Reminder%20with%20payment%20link"
target="_blank">read documentation</a>. target="_blank">read documentation</a>.
</p> </p>
</div> </div>
@ -64,7 +64,7 @@
<b>[[price]]</b> - {Lang::T('will be replaced with Package price')}.<br> <b>[[price]]</b> - {Lang::T('will be replaced with Package price')}.<br>
<b>[[expired_date]]</b> - {Lang::T('will be replaced with Expiration date')}.<br> <b>[[expired_date]]</b> - {Lang::T('will be replaced with Expiration date')}.<br>
<b>[[bills]]</b> - {Lang::T('additional bills for customers')}.<br> <b>[[bills]]</b> - {Lang::T('additional bills for customers')}.<br>
<b>[[payment_link]]</b> - <a href="{Text::url('docs')}/docs/#Reminder%20with%20payment%20link" <b>[[payment_link]]</b> - <a href="{$app_url}/docs/#Reminder%20with%20payment%20link"
target="_blank">read documentation</a>. target="_blank">read documentation</a>.
</p> </p>
</div> </div>
@ -83,7 +83,7 @@
<b>[[price]]</b> - {Lang::T('will be replaced with Package price')}.<br> <b>[[price]]</b> - {Lang::T('will be replaced with Package price')}.<br>
<b>[[expired_date]]</b> - {Lang::T('will be replaced with Expiration date')}.<br> <b>[[expired_date]]</b> - {Lang::T('will be replaced with Expiration date')}.<br>
<b>[[bills]]</b> - {Lang::T('additional bills for customers')}.<br> <b>[[bills]]</b> - {Lang::T('additional bills for customers')}.<br>
<b>[[payment_link]]</b> - <a href="{Text::url('docs')}/docs/#Reminder%20with%20payment%20link" <b>[[payment_link]]</b> - <a href="{$app_url}/docs/#Reminder%20with%20payment%20link"
target="_blank">read documentation</a>. target="_blank">read documentation</a>.
</p> </p>
</div> </div>

View File

@ -1,5 +1,8 @@
{include file="sections/header.tpl"} {include file="sections/header.tpl"}
<hr>
{function showWidget pos=0} {function showWidget pos=0}
<form method="post" action="{Text::url('widgets/pos/')}"> <form method="post" action="{Text::url('widgets/pos/')}">
<div class="panel panel-info"> <div class="panel panel-info">
@ -49,11 +52,64 @@
{/function} {/function}
<div class="row"> <div class="row">
{for $pos=1 to $max} <div class="col-md-3">
<div class="col-md-6 col-lg-4"> <div class="panel panel-info">
{showWidget widgets=$widgets pos=$pos} <div class="panel-heading">Dashboard Structure</div>
<div class="panel-body">
{assign rows explode(".", $_c['dashboard_cr'])}
{assign pos 1}
{foreach $rows as $cols}
{if $cols == 12}
<div class="row row-no-gutters">
<div class="col-xs-12" style="border: 1px;">
<a href="{Text::url('widgets/add/', $pos)}" class="btn btn-default btn-block">{$pos}</a>
</div>
</div>
{assign pos value=$pos+1}
{else}
{assign colss explode(",", $cols)}
<div class="row row-no-gutters">
{foreach $colss as $c}
<div class="col-xs-{$c}">
<a href="{Text::url('widgets/add/', $pos)}" class="btn btn-default btn-block">{$pos}</a>
</div>
{assign pos value=$pos+1}
{/foreach}
</div>
{/if}
{/foreach}
</div>
<div class="panel-footer">
<form method="post" action="{Text::url('widgets')}">
<div class="input-group">
<span class="input-group-addon"><a href="{$app_url}/docs/#Dashboard%20Structure"
target="_blank">{Lang::T("Structure")}</a></span>
<input type="text" name="dashboard_cr" value="{$_c['dashboard_cr']}" class="form-control"
placeholder="Dashboard">
</div>
<button type="submit" class="btn btn-primary btn-block" name="save" value="struct">{Lang::T("Save")}</button>
</form>
</div>
</div> </div>
{/for} </div>
<div class="col-md-9">
<div class="row">
<div class="col-md-6">
{for $pos=1 to $max}
{if $pos%2 != 0}
{showWidget widgets=$widgets pos=$pos}
{/if}
{/for}
</div>
<div class="col-md-6">
{for $pos=1 to $max}
{if $pos%2 == 0}
{showWidget widgets=$widgets pos=$pos}
{/if}
{/for}
</div>
</div>
</div>
</div> </div>
{include file="sections/footer.tpl"} {include file="sections/footer.tpl"}

View File

@ -40,7 +40,7 @@
<label class="col-md-3 control-label">{Lang::T('Position')}</label> <label class="col-md-3 control-label">{Lang::T('Position')}</label>
<div class="col-md-5"> <div class="col-md-5">
<select name="position" id="position" class="form-control"> <select name="position" id="position" class="form-control">
{for $pos=1 to 12} {for $pos=1 to $max}
<option value="{$pos}" {if $widget['position'] eq $pos}selected="selected" {/if}> <option value="{$pos}" {if $widget['position'] eq $pos}selected="selected" {/if}>
Area {$pos} Area {$pos}
</option> </option>

View File

@ -1,3 +1,3 @@
{ {
"version": "2025.2.12.1" "version": "2025.2.18"
} }