diff --git a/CHANGELOG.md b/CHANGELOG.md
index 21df89e6..8f9e0862 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,13 @@
# CHANGELOG
+## 2024.9.13
+
+- Add Selling Mikrotik VPN By @agstrxyz
+- Theme Redesign by @Focuslinkstech
+- Fix That and this
+
+
## 2024.8.28
- add Router Status Offline/Online by @Focuslinkstech
diff --git a/system/autoload/Admin.php b/system/autoload/Admin.php
index 66052cda..18d016c6 100644
--- a/system/autoload/Admin.php
+++ b/system/autoload/Admin.php
@@ -13,25 +13,27 @@ class Admin
{
global $db_pass, $config;
$enable_session_timeout = $config['enable_session_timeout'];
- if ($enable_session_timeout) {
- $timeout = 60;
- if ($config['session_timeout_duration']) {
- $timeout = intval($config['session_timeout_duration']);
+ $session_timeout_duration = $config['session_timeout_duration'] ? intval($config['session_timeout_duration'] * 60) : intval(60 * 60); // Convert minutes to seconds
+
+ // Check if the session is active and valid
+ if (isset($_SESSION['aid']) && isset($_SESSION['aid_expiration'])) {
+ if ($_SESSION['aid_expiration'] > time()) {
+ if ($enable_session_timeout) {
+ $_SESSION['aid_expiration'] = time() + $session_timeout_duration;
+ }
+ return $_SESSION['aid'];
+ }
+ // Session expired, log out the user
+ elseif ($enable_session_timeout && $_SESSION['aid_expiration'] <= time()) {
+ self::removeCookie();
+ session_destroy();
+ _alert(Lang::T('Session has expired. Please log in again.'), 'danger', "admin");
+ return 0;
}
- $session_timeout_duration = $timeout * 60; // Convert minutes to seconds
}
- if (isset($_SESSION['aid']) && isset($_SESSION['aid_expiration']) && $_SESSION['aid_expiration'] > time()) {
- return $_SESSION['aid'];
- } elseif ($enable_session_timeout && isset($_SESSION['aid']) && isset($_SESSION['aid_expiration']) && $_SESSION['aid_expiration'] <= time()) {
- self::removeCookie();
- session_destroy();
- _alert(Lang::T('Session has expired. Please log in again.'), 'danger', "admin");
- return 0;
- }
- // Check if cookie is set and valid
+ // Check if the cookie is set and valid
elseif (isset($_COOKIE['aid'])) {
- // id.time.sha1
$tmp = explode('.', $_COOKIE['aid']);
if (sha1($tmp[0] . '.' . $tmp[1] . '.' . $db_pass) == $tmp[2]) {
if (time() - $tmp[1] < 86400 * 7) {
diff --git a/system/autoload/Message.php b/system/autoload/Message.php
index a7fbae9b..cdc04525 100644
--- a/system/autoload/Message.php
+++ b/system/autoload/Message.php
@@ -154,6 +154,9 @@ class Message
$mail->Body = $body;
}
$mail->send();
+ if (!$mail->send()) {
+ _log(Lang::T("Email not sent, Mailer Error: ") . $mail->ErrorInfo);
+ }
//
}
@@ -201,10 +204,10 @@ class Message
return "$via: $msg";
}
- public static function sendBalanceNotification($cust, $balance, $balance_now, $message, $via)
+ public static function sendBalanceNotification($cust, $target, $balance, $balance_now, $message, $via)
{
global $config;
- $msg = str_replace('[[name]]', $cust['fullname'] . ' (' . $cust['username'] . ')', $message);
+ $msg = str_replace('[[name]]', $target['fullname'] . ' (' . $target['username'] . ')', $message);
$msg = str_replace('[[current_balance]]', Lang::moneyFormat($balance_now), $msg);
$msg = str_replace('[[balance]]', Lang::moneyFormat($balance), $msg);
$phone = $cust['phonenumber'];
@@ -219,6 +222,7 @@ class Message
} else if ($via == 'wa') {
Message::sendWhatsapp($phone, $msg);
}
+ self::addToInbox($cust['id'], Lang::T('Balance Notification'), $msg);
}
return "$via: $msg";
}
@@ -258,4 +262,15 @@ class Message
Message::sendWhatsapp($cust['phonenumber'], $textInvoice);
}
}
+
+
+ public static function addToInbox($to_customer_id, $subject, $body, $from = 'System'){
+ $v = ORM::for_table('tbl_customers_inbox')->create();
+ $v->from = $from;
+ $v->customer_id = $to_customer_id;
+ $v->subject = $subject;
+ $v->date_created = date('Y-m-d H:i:s');
+ $v->body = nl2br($body);
+ $v->save();
+ }
}
diff --git a/system/controllers/customers.php b/system/controllers/customers.php
index c38d3461..c2c9c68c 100644
--- a/system/controllers/customers.php
+++ b/system/controllers/customers.php
@@ -673,10 +673,10 @@ switch ($action) {
default:
run_hook('list_customers'); #HOOK
- $search = _post('search');
- $order = _post('order', 'username');
- $filter = _post('filter', 'Active');
- $orderby = _post('orderby', 'asc');
+ $search = _req('search');
+ $order = _req('order', 'username');
+ $filter = _req('filter', 'Active');
+ $orderby = _req('orderby', 'asc');
$order_pos = [
'username' => 0,
'created_at' => 8,
diff --git a/system/controllers/dashboard.php b/system/controllers/dashboard.php
index 4d1c9849..9f1185b0 100644
--- a/system/controllers/dashboard.php
+++ b/system/controllers/dashboard.php
@@ -214,6 +214,12 @@ if ($config['router_check']) {
$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);
diff --git a/system/controllers/home.php b/system/controllers/home.php
index 147476b5..20e1d1b6 100644
--- a/system/controllers/home.php
+++ b/system/controllers/home.php
@@ -71,8 +71,9 @@ if (_post('send') == 'balance') {
$d->pg_url_payment = 'balance';
$d->status = 2;
$d->save();
- Message::sendBalanceNotification($user, $balance, ($user['balance'] - $balance), Lang::getNotifText('balance_send'), $config['user_notification_payment']);
- Message::sendBalanceNotification($target, $balance, ($target['balance'] + $balance), Lang::getNotifText('balance_received'), $config['user_notification_payment']);
+ //
+ Message::sendBalanceNotification($user, $target, $balance, ($user['balance'] - $balance), Lang::getNotifText('balance_send'), $config['user_notification_payment']);
+ Message::sendBalanceNotification($target, $user, $balance, ($target['balance'] + $balance), Lang::getNotifText('balance_received'), $config['user_notification_payment']);
Message::sendTelegram("#u$user[username] send balance to #u$target[username] \n" . Lang::moneyFormat($balance));
r2(U . 'home', 's', Lang::T('Sending balance success'));
}
@@ -316,7 +317,15 @@ if (!empty($_SESSION['nux-mac']) && !empty($_SESSION['nux-ip'] && !empty($_SESSI
}
}
}
-
+
+$tcf = ORM::for_table('tbl_customers_fields')
+ ->where('customer_id', $user['id'])
+ ->find_many();
+$vpn = ORM::for_table('tbl_port_pool')
+ ->find_one();
+$ui->assign('cf', $tcf);
+$ui->assign('vpn', $vpn);
+
$ui->assign('unpaid', ORM::for_table('tbl_payment_gateway')
->where('username', $user['username'])
->where('status', 1)
diff --git a/system/controllers/order.php b/system/controllers/order.php
index 0d4b2a3c..988db104 100644
--- a/system/controllers/order.php
+++ b/system/controllers/order.php
@@ -113,12 +113,19 @@ switch ($action) {
->where('type', 'Hotspot')
->where('prepaid', 'yes')
->find_many();
+ $plans_vpn = ORM::for_table('tbl_plans')
+ ->where('plan_type', $account_type)
+ ->where('enabled', '1')->where('is_radius', 0)
+ ->where('type', 'VPN')
+ ->where('prepaid', 'yes')
+ ->find_many();
}
$ui->assign('routers', $routers);
$ui->assign('radius_pppoe', $radius_pppoe);
$ui->assign('radius_hotspot', $radius_hotspot);
$ui->assign('plans_pppoe', $plans_pppoe);
$ui->assign('plans_hotspot', $plans_hotspot);
+ $ui->assign('plans_vpn', $plans_vpn);
run_hook('customer_view_order_plan'); #HOOK
$ui->display('user-ui/orderPlan.tpl');
break;
diff --git a/system/controllers/pool.php b/system/controllers/pool.php
index 5528e751..ba599f22 100644
--- a/system/controllers/pool.php
+++ b/system/controllers/pool.php
@@ -147,6 +147,132 @@ switch ($action) {
} else {
r2(U . 'pool/edit/' . $id, 'e', $msg);
}
+
+ case 'port':
+ $ui->assign('xfooter', '');
+
+ $name = _post('name');
+ if ($name != '') {
+ $query = ORM::for_table('tbl_port_pool')->where_like('pool_name', '%' . $name . '%')->order_by_desc('id');
+ $d = Paginator::findMany($query, ['name' => $name]);
+ } else {
+ $query = ORM::for_table('tbl_port_pool')->order_by_desc('id');
+ $d = Paginator::findMany($query);
+ }
+
+ $ui->assign('d', $d);
+ run_hook('view_port'); #HOOK
+ $ui->display('port.tpl');
+ break;
+
+ case 'add-port':
+ $r = ORM::for_table('tbl_routers')->find_many();
+ $ui->assign('r', $r);
+ run_hook('view_add_port'); #HOOK
+ $ui->display('port-add.tpl');
+ break;
+
+ case 'edit-port':
+ $id = $routes['2'];
+ $d = ORM::for_table('tbl_port_pool')->find_one($id);
+ if ($d) {
+ $ui->assign('d', $d);
+ run_hook('view_edit_port'); #HOOK
+ $ui->display('port-edit.tpl');
+ } else {
+ r2(U . 'pool/port', 'e', Lang::T('Account Not Found'));
+ }
+ break;
+
+ case 'delete-port':
+ $id = $routes['2'];
+ run_hook('delete_port'); #HOOK
+ $d = ORM::for_table('tbl_port_pool')->find_one($id);
+ if ($d) {
+ $d->delete();
+
+ r2(U . 'pool/port', 's', Lang::T('Data Deleted Successfully'));
+ }
+ break;
+
+ case 'sync':
+ $pools = ORM::for_table('tbl_port_pool')->find_many();
+ $log = '';
+ foreach ($pools as $pool) {
+ if ($pool['routers'] != 'radius') {
+ (new MikrotikPppoe())->update_pool($pool, $pool);
+ $log .= 'DONE: ' . $pool['port_name'] . ': ' . $pool['range_port'] . '
';
+ }
+ }
+ r2(U . 'pool/list', 's', $log);
+ break;
+ case 'add-port-post':
+ $name = _post('name');
+ $port_range = _post('port_range');
+ $public_ip = _post('public_ip');
+ $routers = _post('routers');
+ run_hook('add_pool'); #HOOK
+ $msg = '';
+ if (Validator::Length($name, 30, 2) == false) {
+ $msg .= 'Name should be between 3 to 30 characters' . '
';
+ }
+ if ($port_range == '' or $routers == '') {
+ $msg .= Lang::T('All field is required') . '
';
+ }
+
+ $d = ORM::for_table('tbl_port_pool')->where('routers', $routers)->find_one();
+ if ($d) {
+ $msg .= Lang::T('Routers already have ports, each router can only have 1 port range!') . '
';
+ }
+ if ($msg == '') {
+ $b = ORM::for_table('tbl_port_pool')->create();
+ $b->public_ip = $public_ip;
+ $b->port_name = $name;
+ $b->range_port = $port_range;
+ $b->routers = $routers;
+ $b->save();
+ r2(U . 'pool/port', 's', Lang::T('Data Created Successfully'));
+ } else {
+ r2(U . 'pool/add-port', 'e', $msg);
+ }
+ break;
+
+
+ case 'edit-port-post':
+ $name = _post('name');
+ $public_ip = _post('public_ip');
+ $range_port = _post('range_port');
+ $routers = _post('routers');
+ run_hook('edit_port'); #HOOK
+ $msg = '';
+ $msg = '';
+ if (Validator::Length($name, 30, 2) == false) {
+ $msg .= 'Name should be between 3 to 30 characters' . '
';
+ }
+ if ($range_port == '' or $routers == '') {
+ $msg .= Lang::T('All field is required') . '
';
+ }
+
+ $id = _post('id');
+ $d = ORM::for_table('tbl_port_pool')->find_one($id);
+ $old = ORM::for_table('tbl_port_pool')->find_one($id);
+ if (!$d) {
+ $msg .= Lang::T('Data Not Found') . '
';
+ }
+
+ if ($msg == '') {
+ $d->port_name = $name;
+ $d->public_ip = $public_ip;
+ $d->range_port = $range_port;
+ $d->routers = $routers;
+ $d->save();
+
+
+
+ r2(U . 'pool/port', 's', Lang::T('Data Updated Successfully'));
+ } else {
+ r2(U . 'pool/edit-port/' . $id, 'e', $msg);
+ }
break;
default:
diff --git a/system/controllers/services.php b/system/controllers/services.php
index aaa69635..9bc32e5e 100644
--- a/system/controllers/services.php
+++ b/system/controllers/services.php
@@ -901,6 +901,380 @@ switch ($action) {
r2(U . 'services/balance-add', 'e', $msg);
}
break;
+ case 'vpn':
+ $ui->assign('_title', Lang::T('VPN Plans'));
+ $ui->assign('xfooter', '');
+
+ $name = _post('name');
+ $name = _req('name');
+ $type1 = _req('type1');
+ $type2 = _req('type2');
+ $type3 = _req('type3');
+ $bandwidth = _req('bandwidth');
+ $valid = _req('valid');
+ $device = _req('device');
+ $status = _req('status');
+ $router = _req('router');
+ $ui->assign('type1', $type1);
+ $ui->assign('type2', $type2);
+ $ui->assign('type3', $type3);
+ $ui->assign('bandwidth', $bandwidth);
+ $ui->assign('valid', $valid);
+ $ui->assign('device', $device);
+ $ui->assign('status', $status);
+ $ui->assign('router', $router);
+
+ $append_url = "&type1=" . urlencode($type1)
+ . "&type2=" . urlencode($type2)
+ . "&type3=" . urlencode($type3)
+ . "&bandwidth=" . urlencode($bandwidth)
+ . "&valid=" . urlencode($valid)
+ . "&device=" . urlencode($device)
+ . "&status=" . urlencode($status)
+ . "&router=" . urlencode($router);
+
+ $bws = ORM::for_table('tbl_plans')->distinct()->select("id_bw")->where('tbl_plans.type', 'VPN')->findArray();
+ $ids = array_column($bws, 'id_bw');
+ if(count($ids)){
+ $ui->assign('bws', ORM::for_table('tbl_bandwidth')->select("id")->select('name_bw')->where_id_in($ids)->findArray());
+ }else{
+ $ui->assign('bws', []);
+ }
+ $ui->assign('type2s', ORM::for_table('tbl_plans')->getEnum("plan_type"));
+ $ui->assign('type3s', ORM::for_table('tbl_plans')->getEnum("typebp"));
+ $ui->assign('valids', ORM::for_table('tbl_plans')->getEnum("validity_unit"));
+ $ui->assign('routers', array_column(ORM::for_table('tbl_plans')->distinct()->select("routers")->whereNotEqual('routers', '')->findArray(), 'routers'));
+ $devices = [];
+ $files = scandir($DEVICE_PATH);
+ foreach ($files as $file) {
+ $ext = pathinfo($file, PATHINFO_EXTENSION);
+ if ($ext == 'php') {
+ $devices[] = pathinfo($file, PATHINFO_FILENAME);
+ }
+ }
+ $ui->assign('devices', $devices);
+ $query = ORM::for_table('tbl_bandwidth')
+ ->left_outer_join('tbl_plans', array('tbl_bandwidth.id', '=', 'tbl_plans.id_bw'))
+ ->where('tbl_plans.type', 'VPN');
+ if (!empty($type1)) {
+ $query->where('tbl_plans.prepaid', $type1);
+ }
+ if (!empty($type2)) {
+ $query->where('tbl_plans.plan_type', $type2);
+ }
+ if (!empty($type3)) {
+ $query->where('tbl_plans.typebp', $type3);
+ }
+ if (!empty($bandwidth)) {
+ $query->where('tbl_plans.id_bw', $bandwidth);
+ }
+ if (!empty($valid)) {
+ $query->where('tbl_plans.validity_unit', $valid);
+ }
+ if (!empty($router)) {
+ if ($router == 'radius') {
+ $query->where('tbl_plans.is_radius', '1');
+ } else {
+ $query->where('tbl_plans.routers', $router);
+ }
+ }
+ if (!empty($device)) {
+ $query->where('tbl_plans.device', $device);
+ }
+ if (in_array($status, ['0', '1'])) {
+ $query->where('tbl_plans.enabled', $status);
+ }
+ if ($name != '') {
+ $query->where_like('tbl_plans.name_plan', '%' . $name . '%');
+ }
+ $d = Paginator::findMany($query, ['name' => $name], 20, $append_url);
+
+ $ui->assign('d', $d);
+ run_hook('view_list_vpn'); #HOOK
+ $ui->display('vpn.tpl');
+ break;
+
+ case 'vpn-add':
+ $ui->assign('_title', Lang::T('VPN Plans'));
+ $d = ORM::for_table('tbl_bandwidth')->find_many();
+ $ui->assign('d', $d);
+ $r = ORM::for_table('tbl_routers')->find_many();
+ $ui->assign('r', $r);
+ $devices = [];
+ $files = scandir($DEVICE_PATH);
+ foreach ($files as $file) {
+ $ext = pathinfo($file, PATHINFO_EXTENSION);
+ if ($ext == 'php') {
+ $devices[] = pathinfo($file, PATHINFO_FILENAME);
+ }
+ }
+ $ui->assign('devices', $devices);
+ run_hook('view_add_vpn'); #HOOK
+ $ui->display('vpn-add.tpl');
+ break;
+
+ case 'vpn-edit':
+ $ui->assign('_title', Lang::T('VPN Plans'));
+ $id = $routes['2'];
+ $d = ORM::for_table('tbl_plans')->find_one($id);
+ if ($d) {
+ if (empty($d['device'])) {
+ if ($d['is_radius']) {
+ $d->device = 'Radius';
+ } else {
+ $d->device = 'MikrotikVpn';
+ }
+ $d->save();
+ }
+ $ui->assign('d', $d);
+ $p = ORM::for_table('tbl_pool')->where('routers', ($d['is_radius']) ? 'radius' : $d['routers'])->find_many();
+ $ui->assign('p', $p);
+ $b = ORM::for_table('tbl_bandwidth')->find_many();
+ $ui->assign('b', $b);
+ $r = [];
+ if ($d['is_radius']) {
+ $r = ORM::for_table('tbl_routers')->find_many();
+ }
+ $ui->assign('r', $r);
+ $devices = [];
+ $files = scandir($DEVICE_PATH);
+ foreach ($files as $file) {
+ $ext = pathinfo($file, PATHINFO_EXTENSION);
+ if ($ext == 'php') {
+ $devices[] = pathinfo($file, PATHINFO_FILENAME);
+ }
+ }
+ $ui->assign('devices', $devices);
+ //select expired plan
+ if ($d['is_radius']) {
+ $exps = ORM::for_table('tbl_plans')->selects('id', 'name_plan')->where('type', 'VPN')->where("is_radius", 1)->findArray();
+ } else {
+ $exps = ORM::for_table('tbl_plans')->selects('id', 'name_plan')->where('type', 'VPN')->where("routers", $d['routers'])->findArray();
+ }
+ $ui->assign('exps', $exps);
+ run_hook('view_edit_vpn'); #HOOK
+ $ui->display('vpn-edit.tpl');
+ } else {
+ r2(U . 'services/vpn', 'e', Lang::T('Account Not Found'));
+ }
+ break;
+
+ case 'vpn-delete':
+ $id = $routes['2'];
+
+ $d = ORM::for_table('tbl_plans')->find_one($id);
+ if ($d) {
+ run_hook('delete_vpn'); #HOOK
+
+ $dvc = Package::getDevice($d);
+ if ($_app_stage != 'demo') {
+ if (file_exists($dvc)) {
+ require_once $dvc;
+ (new $d['device'])->remove_plan($d);
+ } else {
+ new Exception(Lang::T("Devices Not Found"));
+ }
+ }
+ $d->delete();
+
+ r2(U . 'services/vpn', 's', Lang::T('Data Deleted Successfully'));
+ }
+ break;
+
+ case 'vpn-add-post':
+ $name = _post('name_plan');
+ $plan_type = _post('plan_type');
+ $radius = _post('radius');
+ $id_bw = _post('id_bw');
+ $price = _post('price');
+ $validity = _post('validity');
+ $validity_unit = _post('validity_unit');
+ $routers = _post('routers');
+ $device = _post('device');
+ $pool = _post('pool_name');
+ $enabled = _post('enabled');
+ $prepaid = _post('prepaid');
+ $expired_date = _post('expired_date');
+
+
+ $msg = '';
+ if (Validator::UnsignedNumber($validity) == false) {
+ $msg .= 'The validity must be a number' . '
';
+ }
+ if (Validator::UnsignedNumber($price) == false) {
+ $msg .= 'The price must be a number' . '
';
+ }
+ if ($name == '' or $id_bw == '' or $price == '' or $validity == '' or $pool == '') {
+ $msg .= Lang::T('All field is required') . '
';
+ }
+ if (empty($radius)) {
+ if ($routers == '') {
+ $msg .= Lang::T('All field is required') . '
';
+ }
+ }
+
+ $d = ORM::for_table('tbl_plans')->where('name_plan', $name)->find_one();
+ if ($d) {
+ $msg .= Lang::T('Name Plan Already Exist') . '
';
+ }
+ run_hook('add_vpn'); #HOOK
+ if ($msg == '') {
+ $b = ORM::for_table('tbl_bandwidth')->where('id', $id_bw)->find_one();
+ if ($b['rate_down_unit'] == 'Kbps') {
+ $unitdown = 'K';
+ $raddown = '000';
+ } else {
+ $unitdown = 'M';
+ $raddown = '000000';
+ }
+ if ($b['rate_up_unit'] == 'Kbps') {
+ $unitup = 'K';
+ $radup = '000';
+ } else {
+ $unitup = 'M';
+ $radup = '000000';
+ }
+ $rate = $b['rate_up'] . $unitup . "/" . $b['rate_down'] . $unitdown;
+ $radiusRate = $b['rate_up'] . $radup . '/' . $b['rate_down'] . $raddown . '/' . $b['burst'];
+ $rate = trim($rate . " " . $b['burst']);
+ $d = ORM::for_table('tbl_plans')->create();
+ $d->type = 'VPN';
+ $d->name_plan = $name;
+ $d->id_bw = $id_bw;
+ $d->price = $price;
+ $d->plan_type = $plan_type;
+ $d->validity = $validity;
+ $d->validity_unit = $validity_unit;
+ $d->pool = $pool;
+ if (!empty($radius)) {
+ $d->is_radius = 1;
+ $d->routers = '';
+ } else {
+ $d->is_radius = 0;
+ $d->routers = $routers;
+ }
+ if ($prepaid == 'no') {
+ if ($expired_date > 28 && $expired_date < 1) {
+ $expired_date = 20;
+ }
+ $d->expired_date = $expired_date;
+ } else {
+ $d->expired_date = 0;
+ }
+ $d->enabled = $enabled;
+ $d->prepaid = $prepaid;
+ $d->device = $device;
+ $d->save();
+
+ $dvc = Package::getDevice($d);
+ if ($_app_stage != 'demo') {
+ if (file_exists($dvc)) {
+ require_once $dvc;
+ (new $d['device'])->add_plan($d);
+ } else {
+ new Exception(Lang::T("Devices Not Found"));
+ }
+ }
+ r2(U . 'services/vpn', 's', Lang::T('Data Created Successfully'));
+ } else {
+ r2(U . 'services/vpn-add', 'e', $msg);
+ }
+ break;
+
+ case 'edit-vpn-post':
+ $id = _post('id');
+ $plan_type = _post('plan_type');
+ $name = _post('name_plan');
+ $id_bw = _post('id_bw');
+ $price = _post('price');
+ $validity = _post('validity');
+ $validity_unit = _post('validity_unit');
+ $routers = _post('routers');
+ $device = _post('device');
+ $pool = _post('pool_name');
+ $plan_expired = _post('plan_expired');
+ $enabled = _post('enabled');
+ $prepaid = _post('prepaid');
+ $expired_date = _post('expired_date');
+ $on_login = _post('on_login');
+ $on_logout = _post('on_logout');
+
+ $msg = '';
+ if (Validator::UnsignedNumber($validity) == false) {
+ $msg .= 'The validity must be a number' . '
';
+ }
+ if (Validator::UnsignedNumber($price) == false) {
+ $msg .= 'The price must be a number' . '
';
+ }
+ if ($name == '' or $id_bw == '' or $price == '' or $validity == '' or $pool == '') {
+ $msg .= Lang::T('All field is required') . '
';
+ }
+
+ $d = ORM::for_table('tbl_plans')->where('id', $id)->find_one();
+ $old = ORM::for_table('tbl_plans')->where('id', $id)->find_one();
+ if ($d) {
+ } else {
+ $msg .= Lang::T('Data Not Found') . '
';
+ }
+ run_hook('edit_vpn'); #HOOK
+ if ($msg == '') {
+ $b = ORM::for_table('tbl_bandwidth')->where('id', $id_bw)->find_one();
+ if ($b['rate_down_unit'] == 'Kbps') {
+ $unitdown = 'K';
+ $raddown = '000';
+ } else {
+ $unitdown = 'M';
+ $raddown = '000000';
+ }
+ if ($b['rate_up_unit'] == 'Kbps') {
+ $unitup = 'K';
+ $radup = '000';
+ } else {
+ $unitup = 'M';
+ $radup = '000000';
+ }
+ $rate = $b['rate_up'] . $unitup . "/" . $b['rate_down'] . $unitdown;
+ $radiusRate = $b['rate_up'] . $radup . '/' . $b['rate_down'] . $raddown . '/' . $b['burst'];
+ $rate = trim($rate . " " . $b['burst']);
+
+ $d->name_plan = $name;
+ $d->id_bw = $id_bw;
+ $d->price = $price;
+ $d->plan_type = $plan_type;
+ $d->validity = $validity;
+ $d->validity_unit = $validity_unit;
+ $d->routers = $routers;
+ $d->pool = $pool;
+ $d->plan_expired = $plan_expired;
+ $d->enabled = $enabled;
+ $d->prepaid = $prepaid;
+ $d->device = $device;
+ $d->on_login = $on_login;
+ $d->on_logout = $on_logout;
+ if ($prepaid == 'no') {
+ if ($expired_date > 28 && $expired_date < 1) {
+ $expired_date = 20;
+ }
+ $d->expired_date = $expired_date;
+ } else {
+ $d->expired_date = 0;
+ }
+ $d->save();
+
+ $dvc = Package::getDevice($d);
+ if ($_app_stage != 'demo') {
+ if (file_exists($dvc)) {
+ require_once $dvc;
+ (new $d['device'])->update_plan($old, $d);
+ } else {
+ new Exception(Lang::T("Devices Not Found"));
+ }
+ }
+ r2(U . 'services/vpn', 's', Lang::T('Data Updated Successfully'));
+ } else {
+ r2(U . 'services/vpn-edit/' . $id, 'e', $msg);
+ }
+ break;
default:
$ui->display('a404.tpl');
}
diff --git a/system/controllers/settings.php b/system/controllers/settings.php
index 33e4abe9..96cb497b 100644
--- a/system/controllers/settings.php
+++ b/system/controllers/settings.php
@@ -295,6 +295,16 @@ switch ($action) {
$d->value = _post('pppoe_plan');
$d->save();
}
+ $d = ORM::for_table('tbl_appconfig')->where('setting', 'vpn_plan')->find_one();
+ if ($d) {
+ $d->value = _post('vpn_plan');
+ $d->save();
+ } else {
+ $d = ORM::for_table('tbl_appconfig')->create();
+ $d->setting = 'vpn_plan';
+ $d->value = _post('vpn_plan');
+ $d->save();
+ }
$currency_code = $_POST['currency_code'];
$d = ORM::for_table('tbl_appconfig')->where('setting', 'currency_code')->find_one();
diff --git a/system/cron.php b/system/cron.php
index e4e6719c..81d9f423 100644
--- a/system/cron.php
+++ b/system/cron.php
@@ -1,6 +1,27 @@
where('enabled', '1')->find_many();
if (!$routers) {
echo "No active routers found in the database.\n";
@@ -186,14 +187,20 @@ if ($config['router_check']) {
Message::SendEmail($adminEmail, $subject, $message);
sendTelegram($message);
}
-
- if (defined('PHP_SAPI') && PHP_SAPI === 'cli') {
- echo "Cronjob finished\n";
- } else {
- echo "";
- }
-
- flock($lock, LOCK_UN);
- fclose($lock);
- unlink($lockFile);
+ echo "Router monitoring finished\n";
}
+
+
+if (defined('PHP_SAPI') && PHP_SAPI === 'cli') {
+ echo "Cronjob finished\n";
+} else {
+ echo "";
+}
+
+flock($lock, LOCK_UN);
+fclose($lock);
+unlink($lockFile);
+
+$timestampFile = "$UPLOAD_PATH/cron_last_run.txt";
+file_put_contents($timestampFile, time());
+
diff --git a/system/devices/MikrotikPppoe.php b/system/devices/MikrotikPppoe.php
index f9d46dd0..71ac800a 100644
--- a/system/devices/MikrotikPppoe.php
+++ b/system/devices/MikrotikPppoe.php
@@ -10,14 +10,14 @@
use PEAR2\Net\RouterOS;
-class MikrotikPppoeCustom
+class MikrotikPppoe
{
// show Description
function description()
{
return [
'title' => 'Mikrotik PPPOE',
- 'description' => 'To handle connection between PHPNuxBill with Mikrotik PPPOE using Custom Username, IP and Password',
+ 'description' => 'To handle connection between PHPNuxBill with Mikrotik PPPOE',
'author' => 'ibnux',
'url' => [
'Github' => 'https://github.com/hotspotbilling/phpnuxbill/',
@@ -51,6 +51,8 @@ class MikrotikPppoeCustom
}
if (!empty($customer['pppoe_ip'])) {
$setRequest->setArgument('remote-address', $customer['pppoe_ip']);
+ }else{
+ $setRequest->setArgument('remote-address', '0.0.0.0');
}
$setRequest->setArgument('profile', $plan['name_plan']);
$setRequest->setArgument('comment', $customer['fullname'] . ' | ' . $customer['email'] . ' | ' . implode(', ', User::getBillNames($customer['id'])));
diff --git a/system/devices/MikrotikPppoeCustom.php b/system/devices/MikrotikVpn.php
similarity index 72%
rename from system/devices/MikrotikPppoeCustom.php
rename to system/devices/MikrotikVpn.php
index 83406144..a8f41fec 100644
--- a/system/devices/MikrotikPppoeCustom.php
+++ b/system/devices/MikrotikVpn.php
@@ -1,27 +1,20 @@
'Mikrotik PPPOE Custom',
- 'description' => 'To handle connection between PHPNuxBill with Mikrotik PPPOE using Custom Username, IP and Password',
- 'author' => 'ibnux',
+ 'title' => 'Mikrotik Vpn',
+ 'description' => 'To handle connection between PHPNuxBill with Mikrotik VPN',
+ 'author' => 'agstr',
'url' => [
- 'Github' => 'https://github.com/hotspotbilling/phpnuxbill/',
- 'Telegram' => 'https://t.me/phpnuxbill',
+ 'Github' => 'https://github.com/agstrxyz',
+ 'Telegram' => 'https://t.me/agstrxyz',
+ 'Youtube' => 'https://www.youtube.com/@agstrxyz',
'Donate' => 'https://paypal.me/ibnux'
]
];
@@ -34,8 +27,7 @@ class MikrotikPppoeCustom
$client = $this->getClient($mikrotik['ip_address'], $mikrotik['username'], $mikrotik['password']);
$cid = self::getIdByCustomer($customer, $client);
if (empty($cid)) {
- //customer not exists, add it
- $this->addPpoeUser($client, $plan, $customer);
+ $this->addVpnUser($client, $plan, $customer);
}else{
$setRequest = new RouterOS\Request('/ppp/secret/set');
$setRequest->setArgument('numbers', $cid);
@@ -50,18 +42,17 @@ class MikrotikPppoeCustom
$setRequest->setArgument('name', $customer['username']);
}
if (!empty($customer['pppoe_ip'])) {
- $setRequest->setArgument('local-address', $customer['pppoe_ip']);
+ $setRequest->setArgument('remote-address', $customer['pppoe_ip']);
}else{
- $setRequest->setArgument('local-address', '0.0.0.0');
+ $setRequest->setArgument('remote-address', '0.0.0.0');
}
$setRequest->setArgument('profile', $plan['name_plan']);
$setRequest->setArgument('comment', $customer['fullname'] . ' | ' . $customer['email'] . ' | ' . implode(', ', User::getBillNames($customer['id'])));
$client->sendSync($setRequest);
- //disconnect then
if(isset($isChangePlan) && $isChangePlan){
- $this->removePpoeActive($client, $customer['username']);
+ $this->removeVpnActive($client, $customer['username']);
if (!empty($customer['pppoe_username'])) {
- $this->removePpoeActive($client, $customer['pppoe_username']);
+ $this->removeVpnActive($client, $customer['pppoe_username']);
}
}
}
@@ -75,29 +66,27 @@ class MikrotikPppoeCustom
$p = ORM::for_table("tbl_plans")->find_one($plan['plan_expired']);
if($p){
$this->add_customer($customer, $p);
- $this->removePpoeActive($client, $customer['username']);
+ $this->removeVpnActive($client, $customer['username']);
if (!empty($customer['pppoe_username'])) {
- $this->removePpoeActive($client, $customer['pppoe_username']);
+ $this->removeVpnActive($client, $customer['pppoe_username']);
}
return;
}
}
- $this->removePpoeUser($client, $customer['username']);
+ $this->removeVpnUser($client, $customer['username'], $customer['id']);
if (!empty($customer['pppoe_username'])) {
- $this->removePpoeUser($client, $customer['pppoe_username']);
+ $this->removeVpnUser($client, $customer['pppoe_username'], $customer['id']);
}
- $this->removePpoeActive($client, $customer['username']);
+ $this->removeVpnActive($client, $customer['username']);
if (!empty($customer['pppoe_username'])) {
- $this->removePpoeActive($client, $customer['pppoe_username']);
+ $this->removeVpnActive($client, $customer['pppoe_username']);
}
}
- // customer change username
public function change_username($plan, $from, $to)
{
$mikrotik = $this->info($plan['routers']);
$client = $this->getClient($mikrotik['ip_address'], $mikrotik['username'], $mikrotik['password']);
- //check if customer exists
$printRequest = new RouterOS\Request('/ppp/secret/print');
$printRequest->setQuery(RouterOS\Query::where('name', $from));
$cid = $client->sendSync($printRequest)->getProperty('.id');
@@ -106,8 +95,7 @@ class MikrotikPppoeCustom
$setRequest->setArgument('numbers', $cid);
$setRequest->setArgument('name', $to);
$client->sendSync($setRequest);
- //disconnect then
- $this->removePpoeActive($client, $from);
+ $this->removeVpnActive($client, $from);
}
}
@@ -116,7 +104,6 @@ class MikrotikPppoeCustom
$mikrotik = $this->info($plan['routers']);
$client = $this->getClient($mikrotik['ip_address'], $mikrotik['username'], $mikrotik['password']);
- //Add Pool
$bw = ORM::for_table("tbl_bandwidth")->find_one($plan['id_bw']);
if ($bw['rate_down_unit'] == 'Kbps') {
@@ -144,9 +131,7 @@ class MikrotikPppoeCustom
);
}
- /**
- * Function to ID by username from Mikrotik
- */
+
function getIdByCustomer($customer, $client){
$printRequest = new RouterOS\Request('/ppp/secret/print');
$printRequest->setQuery(RouterOS\Query::where('name', $customer['username']));
@@ -306,7 +291,7 @@ class MikrotikPppoeCustom
return new RouterOS\Client($iport[0], $user, $pass, ($iport[1]) ? $iport[1] : null);
}
- function removePpoeUser($client, $username)
+ function removeVpnUser($client, $username, $cstid)
{
global $_app_stage;
if ($_app_stage == 'demo') {
@@ -319,12 +304,13 @@ class MikrotikPppoeCustom
$removeRequest = new RouterOS\Request('/ppp/secret/remove');
$removeRequest->setArgument('numbers', $id);
$client->sendSync($removeRequest);
+ $this->rmNat($client, $cstid);
}
- function addPpoeUser($client, $plan, $customer)
+ function addVpnUser($client, $plan, $customer)
{
$setRequest = new RouterOS\Request('/ppp/secret/add');
- $setRequest->setArgument('service', 'pppoe');
+ $setRequest->setArgument('service', 'any');
$setRequest->setArgument('profile', $plan['name_plan']);
$setRequest->setArgument('comment', $customer['fullname'] . ' | ' . $customer['email'] . ' | ' . implode(', ', User::getBillNames($customer['id'])));
if (!empty($customer['pppoe_password'])) {
@@ -338,12 +324,21 @@ class MikrotikPppoeCustom
$setRequest->setArgument('name', $customer['username']);
}
if (!empty($customer['pppoe_ip'])) {
- $setRequest->setArgument('local-address', $customer['pppoe_ip']);
- }
+ $ips = $customer['pppoe_ip'];
+ $setRequest->setArgument('remote-address', $customer['pppoe_ip']);
+ } else {
+ $ips = $this->checkIpAddr($plan['pool'], $customer['id']);
+ $setRequest->setArgument('remote-address', $ips);
+
+ }
+ $this->addNat($client, $plan, $customer, $ips);
$client->sendSync($setRequest);
+ $customer->service_type = 'VPN';
+ $customer->pppoe_ip = $ips;
+ $customer->save();
}
- function removePpoeActive($client, $username)
+ function removeVpnActive($client, $username)
{
global $_app_stage;
if ($_app_stage == 'demo') {
@@ -359,18 +354,6 @@ class MikrotikPppoeCustom
$client->sendSync($removeRequest);
}
- function getIpHotspotUser($client, $username)
- {
- global $_app_stage;
- if ($_app_stage == 'demo') {
- return null;
- }
- $printRequest = new RouterOS\Request(
- '/ip hotspot active print',
- RouterOS\Query::where('user', $username)
- );
- return $client->sendSync($printRequest)->getProperty('address');
- }
function addIpToAddressList($client, $ip, $listName, $comment = '')
{
@@ -385,6 +368,7 @@ class MikrotikPppoeCustom
->setArgument('comment', $comment)
->setArgument('list', $listName)
);
+
}
function removeIpFromAddressList($client, $ip)
@@ -404,4 +388,117 @@ class MikrotikPppoeCustom
->setArgument('numbers', $id)
);
}
+
+ function addNat($client, $plan, $cust, $ips)
+ {
+ global $_app_stage;
+ if ($_app_stage == 'demo') {
+ return null;
+ }
+ $this->checkPort($cust['id'], 'Winbox', $plan['routers']);
+ $this->checkPort($cust['id'], 'Api', $plan['routers']);
+ $this->checkPort($cust['id'], 'Web', $plan['routers']);
+ $tcf = ORM::for_table('tbl_customers_fields')
+ ->where('customer_id', $cust['id'])
+ ->find_many();
+ $ip = ORM::for_table('tbl_port_pool')
+ ->where('routers', $plan['routers'])
+ ->find_one();
+ foreach ($tcf as $cf) {
+ $dst = $cf['field_value'];
+ $cmnt = $cf['field_name'];
+ if ($cmnt == 'Winbox') {
+ $tp = '8291'; }
+ if ($cmnt == 'Web') {
+ $tp = '80'; }
+ if ($cmnt == 'Api') {
+ $tp = '8728'; }
+ if ($cmnt == 'Winbox' || $cmnt == 'Web' || $cmnt == 'Api') {
+ $addRequest = new RouterOS\Request('/ip/firewall/nat/add');
+ $client->sendSync(
+ $addRequest
+ ->setArgument('chain', 'dstnat')
+ ->setArgument('protocol', 'tcp')
+ ->setArgument('dst-port', $dst)
+ ->setArgument('action', 'dst-nat')
+ ->setArgument('to-addresses', $ips)
+ ->setArgument('to-ports', $tp)
+ ->setArgument('dst-address', $ip['public_ip'])
+ ->setArgument('comment', $cmnt.' || '.$cust['username'])
+ );
+ }
+ }
+ }
+
+ function rmNat($client, $cstid)
+ {
+ global $_app_stage;
+ if ($_app_stage == 'demo') {
+ return null;
+ }
+
+ $cst = ORM::for_table('tbl_customers')->find_one($cstid);
+ $printRequest = new RouterOS\Request('/ip/firewall/nat/print');
+ $printRequest->setQuery(RouterOS\Query::where('to-addresses', $cst['pppoe_ip']));
+ $nats = $client->sendSync($printRequest);
+ foreach ($nats as $nat) {
+ $id = $client->sendSync($printRequest)->getProperty('.id');
+ $removeRequest = new RouterOS\Request('/ip/firewall/nat/remove');
+ $removeRequest->setArgument('numbers', $id);
+ $client->sendSync($removeRequest);
+ }
+ }
+
+
+ function checkPort($id, $portn, $router)
+ {
+ $tcf = ORM::for_table('tbl_customers_fields')
+ ->where('customer_id', $id)
+ ->where('field_name', $portn)
+ ->find_one();
+ $ports = ORM::for_table('tbl_port_pool')
+ ->where('routers', $router)
+ ->find_one();
+ $port = explode('-',$ports['range_port']);
+ if (empty($tcf) && !empty($ports)) {
+ repeat:
+ $portr = rand($port['0'], $port['1']);
+ if (ORM::for_table('tbl_customers_fields')->where('field_value', $portr)->find_one()) {
+ if($portr == $port['1'])
+ {
+ return;
+ }
+ goto repeat;
+ }
+ $cf = ORM::for_table('tbl_customers_fields')->create();
+ $cf->customer_id = $id;
+ $cf->field_name = $portn;
+ $cf->field_value = $portr;
+ $cf->save();
+ }
+ }
+
+ function checkIpAddr($pname, $id) {
+ $c = ORM::for_table('tbl_customers')->find_one($id);
+ $ipp = ORM::for_table('tbl_pool')
+ ->where('pool_name', $pname)
+ ->find_one();
+ $ip_r = explode('-',$ipp['range_ip']);
+ $ip_1 = explode('.',$ip_r['0']);
+ $ip_2 = explode('.',$ip_r['1']);
+ repeat:
+ $ipt = rand($ip_1['3'], $ip_2['3']);
+ $ips = $ip_1['0'].'.'.$ip_1['1'].'.'.$ip_1['2'].'.'.$ipt;
+ if (empty($c['pppoe_ip'])) {
+ if (ORM::for_table('tbl_customers')->where('pppoe_ip' ,$ips)->find_one()) {
+ if ($ip_2['3'] == $ipt)
+ {
+ return;
+ }
+ goto repeat;
+ }
+ return $ips;
+ }
+ }
+
}
diff --git a/system/updates.json b/system/updates.json
index bce2f4ef..8d66fc0c 100644
--- a/system/updates.json
+++ b/system/updates.json
@@ -155,5 +155,11 @@
"2024.8.28" : [
"ALTER TABLE `tbl_routers` ADD `status` ENUM('Online', 'Offline') DEFAULT 'Online' AFTER `coordinates`;",
"ALTER TABLE `tbl_routers` ADD `last_seen` DATETIME AFTER `status`;"
+ ],
+ "2024.9.13" : [
+ "ALTER TABLE `tbl_plans` CHANGE `type` `type` ENUM('Hotspot','PPPOE','VPN','Balance') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL;",
+ "ALTER TABLE `tbl_customers` CHANGE `service_type` `service_type` ENUM('Hotspot','PPPoE','VPN','Others') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT 'Others' COMMENT 'For selecting user type';",
+ "ALTER TABLE `tbl_transactions` CHANGE `type` `type` ENUM('Hotspot','PPPOE','VPN','Balance') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL;",
+ "CREATE TABLE IF NOT EXISTS `tbl_port_pool` ( `id` int(10) NOT NULL AUTO_INCREMENT , `public_ip` varchar(40) NOT NULL, `port_name` varchar(40) NOT NULL, `range_port` varchar(40) NOT NULL, `routers` varchar(40) NOT NULL, PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;"
]
}
\ No newline at end of file
diff --git a/ui/ui/app-localisation.tpl b/ui/ui/app-localisation.tpl
index 936c3abb..571d5588 100644
--- a/ui/ui/app-localisation.tpl
+++ b/ui/ui/app-localisation.tpl
@@ -128,6 +128,14 @@
{Lang::T('Change title in user Plan order')}
+
diff --git a/ui/ui/customers.tpl b/ui/ui/customers.tpl
index f4434ccb..7829ffcd 100644
--- a/ui/ui/customers.tpl
+++ b/ui/ui/customers.tpl
@@ -29,7 +29,7 @@
@@ -63,8 +63,8 @@
@@ -72,7 +72,7 @@
@@ -81,7 +81,7 @@
@@ -104,38 +104,40 @@
-
-
-
- |
- {Lang::T('Internet Package')} |
- {Lang::T('Limit')} |
- |
-
- {Lang::T('Expired')} |
- |
-
-
- {Lang::T('Name')} |
- {Lang::T('Type')} |
- Bandwidth |
- {Lang::T('Category')} |
- {Lang::T('Price')} |
- {Lang::T('Validity')} |
- {Lang::T('Time')} |
- {Lang::T('Data')} |
- {Lang::T('Location')} |
- {Lang::T('Device')} |
- {Lang::T('Internet Package')} |
- {Lang::T('Date')} |
- {Lang::T('ID')} |
- {Lang::T('Manage')} |
-
-
-
- {foreach $d as $ds}
+
+
+
+
+ |
+ {Lang::T('Internet Package')} |
+
+ {Lang::T('Limit')} |
+ |
+
+ {Lang::T('Expired')} |
+ |
+
+
+ {Lang::T('Name')} |
+ {Lang::T('Type')} |
+ Bandwidth |
+ {Lang::T('Category')} |
+ {Lang::T('Price')} |
+ {Lang::T('Validity')} |
+ {Lang::T('Time')} |
+ {Lang::T('Data')} |
+ {Lang::T('Location')} |
+ {Lang::T('Device')} |
+ {Lang::T('Internet Package')} |
+ {Lang::T('Date')} |
+ {Lang::T('ID')} |
+ {Lang::T('Manage')} |
+
+
+
+ {foreach $d as $ds}
+ }class="warning" title="Postpaid" {/if}>
{$ds['name_plan']} |
{if $ds['prepaid'] == no}Postpaid{else}Prepaid{/if} {$ds['plan_type']} |
{$ds['name_bw']} |
@@ -146,11 +148,11 @@
{$ds['data_limit']} {$ds['data_unit']} |
{if $ds['is_radius']}
- RADIUS
+ RADIUS
{else}
- {if $ds['routers']!=''}
- {$ds['routers']}
- {/if}
+ {if $ds['routers']!=''}
+ {$ds['routers']}
+ {/if}
{/if}
|
{$ds['device']} |
@@ -163,13 +165,14 @@
{Lang::T('Edit')}
+ onclick="return confirm('{Lang::T('Delete')}?')"
+ class="btn btn-danger btn-xs">
- {/foreach}
-
-
+ {/foreach}
+
+
+
-{include file="sections/footer.tpl"}
+{include file="sections/footer.tpl"}
\ No newline at end of file
diff --git a/ui/ui/plan.tpl b/ui/ui/plan.tpl
index 4cf71280..32111f4a 100644
--- a/ui/ui/plan.tpl
+++ b/ui/ui/plan.tpl
@@ -5,19 +5,19 @@
{if in_array($_admin['user_type'],['SuperAdmin','Admin'])}
-
- {*
*}
+
+ {*
*}
{/if}
-{Lang::T('Active Customers')}
+ {Lang::T('Active Customers')}
@@ -70,34 +70,39 @@
-
-
-
- {Lang::T('Username')} |
- {Lang::T('Plan Name')} |
- {Lang::T('Type')} |
- {Lang::T('Created On')} |
- {Lang::T('Expires On')} |
- {Lang::T('Method')} |
- {Lang::T('Location')} |
- {Lang::T('Manage')} |
-
-
-
- {foreach $d as $ds}
-
+
+
+
{include file="pagination.tpl"}
@@ -131,10 +137,10 @@
var res = prompt("Extend for many days?", "3");
if (res) {
if (confirm("Extend for " + res + " days?")) {
- window.location.href = "{$_url}plan/extend/"+idP+"/"+res+"&stoken={App::getToken()}";
+ window.location.href = "{$_url}plan/extend/" + idP + "/" + res + "&stoken={App::getToken()}";
}
}
}
-{include file="sections/footer.tpl"}
+{include file="sections/footer.tpl"}
\ No newline at end of file
diff --git a/ui/ui/port-add.tpl b/ui/ui/port-add.tpl
new file mode 100644
index 00000000..b689a896
--- /dev/null
+++ b/ui/ui/port-add.tpl
@@ -0,0 +1,51 @@
+{include file="sections/header.tpl"}
+
+
+
+
+
{Lang::T('Add Port Pool')}
+
+
+
+
+
+{include file="sections/footer.tpl"}
diff --git a/ui/ui/port-edit.tpl b/ui/ui/port-edit.tpl
new file mode 100644
index 00000000..46413314
--- /dev/null
+++ b/ui/ui/port-edit.tpl
@@ -0,0 +1,49 @@
+{include file="sections/header.tpl"}
+
+
+
+
+
{Lang::T('Edit Port')}
+
+
+
+
+
+{include file="sections/footer.tpl"}
diff --git a/ui/ui/port.tpl b/ui/ui/port.tpl
new file mode 100644
index 00000000..77fd2661
--- /dev/null
+++ b/ui/ui/port.tpl
@@ -0,0 +1,76 @@
+{include file="sections/header.tpl"}
+
+
+
+
+
+
+ {Lang::T('Port Pool')} - VPN Tunnels
+
+
+
+
+
+
+
+ {Lang::T('Port Name')} |
+ {Lang::T('Public IP')} |
+ {Lang::T('Range Port')} |
+ {Lang::T('Routers')} |
+ {Lang::T('Manage')} |
+ ID |
+
+
+
+ {foreach $d as $ds}
+
+ {$ds['port_name']} |
+ {$ds['public_ip']} |
+ {$ds['range_port']} |
+ {$ds['routers']} |
+
+ {Lang::T('Edit')}
+
+ |
+ {$ds['id']} |
+
+ {/foreach}
+
+
+
+ {include file="pagination.tpl"}
+
+
{Lang::T('Create expired Internet Plan')}
+
{Lang::T('When customer expired, you can move it to Expired Internet Plan')}
+
+
+
+
+
+
+{include file="sections/footer.tpl"}
diff --git a/ui/ui/pppoe.tpl b/ui/ui/pppoe.tpl
index 3c17d6b1..9dfc8711 100644
--- a/ui/ui/pppoe.tpl
+++ b/ui/ui/pppoe.tpl
@@ -35,8 +35,8 @@
@@ -44,9 +44,9 @@
@@ -54,8 +54,8 @@
@@ -63,8 +63,8 @@
@@ -72,7 +72,7 @@
@@ -81,7 +81,7 @@
@@ -104,36 +104,38 @@
-
-
-
- |
- {Lang::T('Internet Plan')} |
- |
-
- {Lang::T('Expired')} |
- |
-
-
- {Lang::T('Name')} |
- {Lang::T('Type')} |
- {Lang::T('Bandwidth')} |
- {Lang::T('Price')} |
- {Lang::T('Validity')} |
- {Lang::T('IP Pool')} |
- {Lang::T('Internet Plan')} |
- {Lang::T('Date')} |
- {Lang::T('Location')} |
- {Lang::T('Device')} |
- {Lang::T('Manage')} |
- ID |
-
-
-
- {foreach $d as $ds}
-
+
+
+
+
+ |
+ {Lang::T('Internet Plan')} |
+ |
+
+ {Lang::T('Expired')} |
+ |
+
+
+ {Lang::T('Name')} |
+ {Lang::T('Type')} |
+ {Lang::T('Bandwidth')} |
+ {Lang::T('Price')} |
+ {Lang::T('Validity')} |
+ {Lang::T('IP Pool')} |
+ {Lang::T('Internet Plan')} |
+ {Lang::T('Date')} |
+ {Lang::T('Location')} |
+ {Lang::T('Device')} |
+ {Lang::T('Manage')} |
+ ID |
+
+
+
+ {foreach $d as $ds}
+
{$ds['name_plan']} |
- {$ds['plan_type']} {if $ds['prepaid'] != 'yes'}{Lang::T('Postpaid')}{else}{Lang::T('Prepaid')}{/if} |
+ {$ds['plan_type']} {if $ds['prepaid'] !=
+ 'yes'}{Lang::T('Postpaid')}{else}{Lang::T('Prepaid')}{/if} |
{$ds['name_bw']} |
{Lang::moneyFormat($ds['price'])} |
{$ds['validity']} {$ds['validity_unit']} |
@@ -144,11 +146,11 @@
{if $ds['prepaid'] == no}{$ds['expired_date']}{/if} |
{if $ds['is_radius']}
- RADIUS
+ RADIUS
{else}
- {if $ds['routers']!=''}
- {$ds['routers']}
- {/if}
+ {if $ds['routers']!=''}
+ {$ds['routers']}
+ {/if}
{/if}
|
{$ds['device']} |
@@ -161,9 +163,10 @@
{$ds['id']} |
- {/foreach}
-
-
+ {/foreach}
+
+
+
-{include file="sections/footer.tpl"}
+{include file="sections/footer.tpl"}
\ No newline at end of file
diff --git a/ui/ui/recharge.tpl b/ui/ui/recharge.tpl
index b896ab50..96370d58 100644
--- a/ui/ui/recharge.tpl
+++ b/ui/ui/recharge.tpl
@@ -22,6 +22,7 @@
+
diff --git a/ui/ui/scripts/custom.js b/ui/ui/scripts/custom.js
index 844f5ffc..87734583 100644
--- a/ui/ui/scripts/custom.js
+++ b/ui/ui/scripts/custom.js
@@ -96,7 +96,7 @@ $(function() {
});
};
- }else{
+ } else if ($('#POE').is(':checked')) {
$.ajax({
type: "POST",
dataType: "html",
@@ -117,6 +117,27 @@ $(function() {
}
});
});
+ } else {
+ $.ajax({
+ type: "POST",
+ dataType: "html",
+ url: "index.php?_route=autoload/server",
+ success: function(msg){
+ $("#server").html(msg);
+ }
+ });
+ $("#server").change(function(){
+ var server = $("#server").val();
+ $.ajax({
+ type: "POST",
+ dataType: "html",
+ url: "index.php?_route=autoload/plan",
+ data: "jenis=VPN&server="+server,
+ success: function(msg){
+ $("#plan").html(msg);
+ }
+ });
+ });
}
});
});
diff --git a/ui/ui/sections/header.tpl b/ui/ui/sections/header.tpl
index e19b8073..89c354d6 100644
--- a/ui/ui/sections/header.tpl
+++ b/ui/ui/sections/header.tpl
@@ -148,12 +148,45 @@
color: inherit;
background-color: transparent;
border-color: transparent;
+ border-bottom-right-radius: 21px;
+ border-bottom-left-radius: 21px;
}
- .panel-primary>.panel-heading {
- color: inherit;
- background-color: transparent;
- border-color: transparent;
+ .panel-success>.panel-heading {
+ border-bottom-right-radius: 21px;
+ border-bottom-left-radius: 21px;
+ }
+
+ .panel-cron-success>.panel-heading {
+ border-bottom-right-radius: 21px;
+ border-bottom-left-radius: 21px;
+ color: #fff;
+ background-color: #169210;
+ border-color: #25e01c;
+
+ }
+
+ .panel-cron-warning>.panel-heading {
+ border-bottom-right-radius: 21px;
+ border-bottom-left-radius: 21px;
+ color: #350808;
+ background-color: #efeb0a;
+ border-color: #efeb0a;
+ }
+ .panel-cron-danger>.panel-heading {
+ border-bottom-right-radius: 21px;
+ border-bottom-left-radius: 21px;
+ color: #fff;
+ background-color: #e61212;
+ border-color: #df1335;
+ }
+
+ .panel-danger>.panel-heading {
+ color: #a94442;
+ background-color: #f2dede;
+ border-color: #ebccd1;
+ border-bottom-right-radius: 21px;
+ border-bottom-left-radius: 21px;
}
.panel-heading {
@@ -656,9 +689,6 @@
}
.toggle-container {
- position: absolute;
- top: 17px;
- right: 15px;
cursor: pointer;
}
@@ -668,19 +698,6 @@
transition: color 0.5s ease;
}
- @media (max-width: 600px) {
- .toggle-container {
- top: 15px;
- right: 60px;
- }
-
- .toggle-container .toggle-icon {
- font-size: 20px;
- color: rgb(100 116 139);
- transition: color 0.5s ease;
- }
- }
-
.dark-mode .toggle-container .toggle-icon {
color: #ffdd57;
}
@@ -1157,9 +1174,9 @@
-
+
PPPOE
+ VPN
Bandwidth
{if $_c['enable_balance'] == 'yes'}
@@ -1327,6 +1346,8 @@
href="{$_url}routers">Routers
IP Pool
+ Port Pool
{Lang::T('Routers Maps')}
{$_MENU_NETWORK}
diff --git a/ui/ui/user-ui/dashboard.tpl b/ui/ui/user-ui/dashboard.tpl
index 361e6487..65e24c39 100644
--- a/ui/ui/user-ui/dashboard.tpl
+++ b/ui/ui/user-ui/dashboard.tpl
@@ -88,6 +88,8 @@
Hotspot
{elseif $_user.service_type == 'PPPoE'}
PPPoE
+ {elseif $_user.service_type == 'VPN'}
+ VPN
{elseif $_user.service_type == 'Others' || $_user.service_type == null}
Others
{/if}
@@ -165,8 +167,10 @@
{if $_bill['type'] == 'Hotspot'}
{if $_c['hotspot_plan']==''}Hotspot Plan{else}{$_c['hotspot_plan']}{/if}
- {else}
+ {else if $_bill['type'] == 'PPPOE'}
{if $_c['pppoe_plan']==''}PPPOE Plan{else}{$_c['pppoe_plan']}{/if}
+ {else if $_bill['type'] == 'VPN'}
+ {if $_c['pppoe_plan']==''}VPN Plan{else}{$_c['vpn_plan']}{/if}
{/if}
@@ -215,6 +219,25 @@
{$_bill['plan_type']}
+ {if $_bill['type'] == 'VPN' && $_bill['routers'] == $vpn['routers']}
+
+ {Lang::T('Public IP')} |
+ {$vpn['public_ip']} / {$vpn['port_name']} |
+
+
+ {Lang::T('Private IP')} |
+ {$_user['pppoe_ip']} |
+
+ {foreach $cf as $tcf}
+
+ {if $tcf['field_name'] == 'Winbox' or $tcf['field_name'] == 'Api' or $tcf['field_name'] == 'Web'}
+ {$tcf['field_name']} - Port |
+ {$tcf['field_value']} |
+
+ {/if}
+ {/foreach}
+ {/if}
+
{if $nux_ip neq ''}
{Lang::T('Current IP')} |
diff --git a/ui/ui/user-ui/header.tpl b/ui/ui/user-ui/header.tpl
index a2be9f04..4298c03a 100644
--- a/ui/ui/user-ui/header.tpl
+++ b/ui/ui/user-ui/header.tpl
@@ -333,9 +333,6 @@
.toggle-container {
- position: absolute;
- top: 17px;
- right: 15px;
cursor: pointer;
}
@@ -346,10 +343,6 @@
}
@media (max-width: 600px) {
- .toggle-container {
- top: 15px;
- right: 200px;
- }
.toggle-container .toggle-icon {
font-size: 20px;
@@ -781,13 +774,13 @@
@@ -29,10 +29,11 @@
{Lang::T("Next")}
{/if}
+ {Lang::T("Back")}
{Lang::T("Delete")}
- {Lang::T("Share")}
+ {Lang::T("Share")}
diff --git a/ui/ui/user-ui/orderPlan.tpl b/ui/ui/user-ui/orderPlan.tpl
index f79ed4dd..9db514ff 100644
--- a/ui/ui/user-ui/orderPlan.tpl
+++ b/ui/ui/user-ui/orderPlan.tpl
@@ -226,6 +226,7 @@
{/if}
{foreach $routers as $router}
{if Validator::isRouterHasPlan($plans_hotspot, $router['name']) || Validator::isRouterHasPlan($plans_pppoe,
+ $router['name']) || Validator::isRouterHasPlan($plans_vpn,
$router['name'])}
@@ -338,9 +339,62 @@
{/if}
{/foreach}
+ {/if}
+ {if $_user['service_type'] == 'VPN' && Validator::countRouterPlan($plans_vpn,$router['name'])>0}
+
+
+ {foreach $plans_vpn as $plan}
+ {if $router['name'] eq $plan['routers']}
+
+
+
+
+
+
+
+
+ {Lang::T('Type')} |
+ {$plan['type']} |
+
+
+ {Lang::T('Price')} |
+ {Lang::moneyFormat($plan['price'])} |
+
+
+ {Lang::T('Validity')} |
+ {$plan['validity']} {$plan['validity_unit']} |
+
+
+
+
+
+
+
+ {if $_c['enable_balance'] == 'yes' && $_c['allow_balance_transfer'] == 'yes' &&
+ $_user['balance']>=$plan['price']}
+
{Lang::T('Buy for friend')}
+ {/if}
+
+
+
+ {/if}
+ {/foreach}
+
{/if}
{if $_user['service_type'] == 'Others' || $_user['service_type'] == '' &&
(Validator::countRouterPlan($plans_hotspot, $router['name'])>0 || Validator::countRouterPlan($plans_pppoe,
+ $router['name'])>0 || Validator::countRouterPlan($plans_vpn,
$router['name'])>0)}
@@ -443,6 +497,56 @@
{/if}
{/foreach}
+
+
+ {foreach $plans_vpn as $plan}
+ {if $router['name'] eq $plan['routers']}
+
+
+
+
+
+
+
+
+ {Lang::T('Type')} |
+ {$plan['type']} |
+
+
+ {Lang::T('Price')} |
+ {Lang::moneyFormat($plan['price'])} |
+
+
+ {Lang::T('Validity')} |
+ {$plan['validity']} {$plan['validity_unit']} |
+
+
+
+
+
+
+
+ {if $_c['enable_balance'] == 'yes' && $_c['allow_balance_transfer'] == 'yes' &&
+ $_user['balance']>=$plan['price']}
+
{Lang::T('Buy for friend')}
+ {/if}
+
+
+
+ {/if}
+ {/foreach}
+
{/if}
{/if}
diff --git a/ui/ui/voucher.tpl b/ui/ui/voucher.tpl
index 6f357b48..abf652f0 100644
--- a/ui/ui/voucher.tpl
+++ b/ui/ui/voucher.tpl
@@ -17,11 +17,12 @@
{if in_array($_admin['user_type'],['SuperAdmin','Admin'])}
-
+
{/if}
@@ -41,8 +42,8 @@
@@ -50,7 +51,7 @@
@@ -65,7 +66,7 @@
@@ -76,8 +77,8 @@
class="fa fa-search">
@@ -85,24 +86,25 @@
-
-
-
- ID |
- {Lang::T('Type')} |
- {Lang::T('Routers')} |
- {Lang::T('Plan Name')} |
- {Lang::T('Code Voucher')} |
- {Lang::T('Status Voucher')} |
- {Lang::T('Customer')} |
- {Lang::T('Used Date')} |
- {Lang::T('Generated By')} |
- {Lang::T('Manage')} |
-
-
-
- {foreach $d as $ds}
-
+
+
+
+
+ ID |
+ {Lang::T('Type')} |
+ {Lang::T('Routers')} |
+ {Lang::T('Plan Name')} |
+ {Lang::T('Code Voucher')} |
+ {Lang::T('Status Voucher')} |
+ {Lang::T('Customer')} |
+ {Lang::T('Used Date')} |
+ {Lang::T('Generated By')} |
+ {Lang::T('Manage')} |
+
+
+
+ {foreach $d as $ds}
+
{$ds['id']} |
{$ds['type']} |
{$ds['routers']} |
@@ -112,33 +114,35 @@
onmouseenter="this.style.backgroundColor = 'white';">
{$ds['code']}
{if $ds['status'] eq '0'} {else}
+ {else}
{/if} |
{if $ds['user'] eq '0'} -
{else}{$ds['user']}
{/if} |
{if $ds['used_date']}{Lang::dateTimeFormat($ds['used_date'])}{/if} |
{if $ds['generated_by']}
- {$admins[$ds['generated_by']]}
+ {$admins[$ds['generated_by']]}
{else} -
{/if}
|
{if $ds['status'] neq '1'}
- {Lang::T('View')}
+ {Lang::T('View')}
{/if}
{if in_array($_admin['user_type'],['SuperAdmin','Admin'])}
-
+
{/if}
|
- {/foreach}
-
-
+ {/foreach}
+
+
+
{include file="pagination.tpl"}
-{include file="sections/footer.tpl"}
+{include file="sections/footer.tpl"}
\ No newline at end of file
diff --git a/ui/ui/vpn-add.tpl b/ui/ui/vpn-add.tpl
new file mode 100644
index 00000000..ae26a8ac
--- /dev/null
+++ b/ui/ui/vpn-add.tpl
@@ -0,0 +1,188 @@
+{include file="sections/header.tpl"}
+
+
+
+
+
{Lang::T('Add Service Plan')}
+
+
+
+
+
+{if $_c['radius_enable']}
+ {literal}
+
+ {/literal}
+{/if}
+{include file="sections/footer.tpl"}
diff --git a/ui/ui/vpn-edit.tpl b/ui/ui/vpn-edit.tpl
new file mode 100644
index 00000000..53428605
--- /dev/null
+++ b/ui/ui/vpn-edit.tpl
@@ -0,0 +1,255 @@
+{include file="sections/header.tpl"}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+{include file="sections/footer.tpl"}
diff --git a/ui/ui/vpn.tpl b/ui/ui/vpn.tpl
new file mode 100644
index 00000000..6047e446
--- /dev/null
+++ b/ui/ui/vpn.tpl
@@ -0,0 +1,179 @@
+{include file="sections/header.tpl"}
+
+
+
+
+
+
{Lang::T('VPN Package')}
+
+
+
+
+
+
+ |
+ {Lang::T('Internet Plan')} |
+ |
+
+ {Lang::T('Expired')} |
+ |
+
+
+ {Lang::T('Name')} |
+ {Lang::T('Type')} |
+ {Lang::T('Bandwidth')} |
+ {Lang::T('Price')} |
+ {Lang::T('Validity')} |
+ {Lang::T('IP Pool')} |
+ {Lang::T('Internet Plan')} |
+ {Lang::T('Date')} |
+ {Lang::T('Location')} |
+ {Lang::T('Device')} |
+ {Lang::T('Manage')} |
+ ID |
+
+
+
+ {foreach $d as $ds}
+
+ {$ds['name_plan']} |
+ {$ds['plan_type']} {if $ds['prepaid'] != 'yes'}{Lang::T('Postpaid')}{else}{Lang::T('Prepaid')}{/if} |
+ {$ds['name_bw']} |
+ {Lang::moneyFormat($ds['price'])} |
+ {$ds['validity']} {$ds['validity_unit']} |
+ {$ds['pool']} |
+ {if $ds['plan_expired']}{Lang::T('Yes')}{else}{Lang::T('No')}
+ {/if} |
+ {if $ds['prepaid'] == no}{$ds['expired_date']}{/if} |
+
+ {if $ds['is_radius']}
+ RADIUS
+ {else}
+ {if $ds['routers']!=''}
+ {$ds['routers']}
+ {/if}
+ {/if}
+ |
+ {$ds['device']} |
+
+ {Lang::T('Edit')}
+
+ |
+ {$ds['id']} |
+
+ {/foreach}
+
+
+
+
+
+
+
+
+{include file="sections/footer.tpl"}
diff --git a/version.json b/version.json
index 1bd146b2..13f748ef 100644
--- a/version.json
+++ b/version.json
@@ -1,3 +1,3 @@
{
- "version": "2024.9.6"
+ "version": "2024.9.13"
}
\ No newline at end of file