Compare commits

...

42 Commits

Author SHA1 Message Date
ca4a290a72 Upload files to "system/uploads/paymentgateway"
Signed-off-by: nestict <icttechnest@gmail.com>
2025-05-24 12:41:35 +02:00
837df68e89 Upload files to "system/uploads/system"
Signed-off-by: nestict <icttechnest@gmail.com>
2025-05-24 12:40:47 +02:00
f16b4caeef Upload files to "system/uploads/sms"
Signed-off-by: nestict <icttechnest@gmail.com>
2025-05-24 12:40:15 +02:00
5ed64ed548 Upload files to "system/uploads/_sysfrm_tmp_"
Signed-off-by: nestict <icttechnest@gmail.com>
2025-05-24 12:39:46 +02:00
a70827a6b6 Upload files to "system/uploads"
Signed-off-by: nestict <icttechnest@gmail.com>
2025-05-24 12:39:02 +02:00
143f2b2402 Upload files to "system/uploads"
Signed-off-by: nestict <icttechnest@gmail.com>
2025-05-24 12:38:43 +02:00
717cb65516 Upload files to "system/plugin/ui"
Signed-off-by: nestict <icttechnest@gmail.com>
2025-05-24 12:37:49 +02:00
3a49bf9afa Upload files to "system/plugin/ui"
Signed-off-by: nestict <icttechnest@gmail.com>
2025-05-24 12:37:17 +02:00
21a5212120 Upload files to "system/plugin/ui"
Signed-off-by: nestict <icttechnest@gmail.com>
2025-05-24 12:36:52 +02:00
f5ec9276bd Upload files to "system/plugin"
Signed-off-by: nestict <icttechnest@gmail.com>
2025-05-24 12:35:37 +02:00
b71d6aa44d Upload files to "system/plugin"
Signed-off-by: nestict <icttechnest@gmail.com>
2025-05-24 12:35:14 +02:00
015b57ddfd Upload files to "system/plugin"
Signed-off-by: nestict <icttechnest@gmail.com>
2025-05-24 12:34:29 +02:00
2a327ddd3d Upload files to "system/plugin"
Signed-off-by: nestict <icttechnest@gmail.com>
2025-05-24 12:34:11 +02:00
fd6a88f443 Upload files to "system/mpesa"
Signed-off-by: nestict <icttechnest@gmail.com>
2025-05-24 12:32:45 +02:00
1524ae841f Upload files to "ui/ui/sections" 2025-05-24 12:30:59 +02:00
427ad9d085 Upload files to "ui/ui/sections"
Signed-off-by: nestict <icttechnest@gmail.com>
2025-05-24 12:30:41 +02:00
eba008c916 Upload files to "ui/ui/Ass/icons"
Signed-off-by: nestict <icttechnest@gmail.com>
2025-05-24 12:29:29 +02:00
3d75acc1be Upload files to "ui/ui/Ass/css"
Signed-off-by: nestict <icttechnest@gmail.com>
2025-05-24 12:28:12 +02:00
cec3f6b007 Upload files to "ui/ui/Ass"
Signed-off-by: nestict <icttechnest@gmail.com>
2025-05-24 12:27:28 +02:00
479b1d55e4 Upload files to "ui/ui"
Signed-off-by: nestict <icttechnest@gmail.com>
2025-05-24 12:26:21 +02:00
9ba7a1c003 Upload files to "ui/ui"
Signed-off-by: nestict <icttechnest@gmail.com>
2025-05-24 12:26:02 +02:00
dc570270a5 Upload files to "ui/ui"
Signed-off-by: nestict <icttechnest@gmail.com>
2025-05-24 12:24:38 +02:00
5242f5a262 Upload files to "ui/ui"
Signed-off-by: nestict <icttechnest@gmail.com>
2025-05-24 12:24:03 +02:00
b2eb51fff8 Upload files to "ui/ui"
Signed-off-by: nestict <icttechnest@gmail.com>
2025-05-24 12:23:06 +02:00
aa8c6c0b34 Upload files to "ui/ui"
Signed-off-by: nestict <icttechnest@gmail.com>
2025-05-24 12:22:33 +02:00
909c867ba5 Upload files to "ui/ui"
Signed-off-by: nestict <icttechnest@gmail.com>
2025-05-24 12:22:03 +02:00
4ea01347b2 Upload files to "ui/ui"
Signed-off-by: nestict <icttechnest@gmail.com>
2025-05-24 12:21:18 +02:00
f57c730219 Upload files to "ui/ui"
Signed-off-by: nestict <icttechnest@gmail.com>
2025-05-24 12:20:42 +02:00
6d4e964bf1 Upload files to "ui/ui"
Signed-off-by: nestict <icttechnest@gmail.com>
2025-05-24 12:20:12 +02:00
712224be37 Upload files to "ui/ui"
Signed-off-by: nestict <icttechnest@gmail.com>
2025-05-24 12:18:49 +02:00
ce6b63f3a3 Upload files to "ui/ui"
Signed-off-by: nestict <icttechnest@gmail.com>
2025-05-24 12:17:47 +02:00
c954394f5c Upload files to "ui/ui"
Signed-off-by: nestict <icttechnest@gmail.com>
2025-05-24 12:17:15 +02:00
64e8157c1c Upload files to "ui/ui"
Signed-off-by: nestict <icttechnest@gmail.com>
2025-05-24 12:16:20 +02:00
e5db5b605f Upload files to "ui/ui"
Signed-off-by: nestict <icttechnest@gmail.com>
2025-05-24 12:15:47 +02:00
ce74640689 Upload files to "ui/ui"
Signed-off-by: nestict <icttechnest@gmail.com>
2025-05-24 12:13:20 +02:00
fdadc713af Upload files to "ui/ui"
Signed-off-by: nestict <icttechnest@gmail.com>
2025-05-24 12:11:28 +02:00
04693a88e5 Upload files to "ui/ui"
Signed-off-by: nestict <icttechnest@gmail.com>
2025-05-24 12:10:37 +02:00
77bd3ded5b Upload files to "ui/ui"
Signed-off-by: nestict <icttechnest@gmail.com>
2025-05-24 12:09:20 +02:00
e79b6ffc00 Upload files to "ui/ui"
Signed-off-by: nestict <icttechnest@gmail.com>
2025-05-24 12:08:53 +02:00
4e9a4d12d4 Upload files to "ui/ui"
Signed-off-by: nestict <icttechnest@gmail.com>
2025-05-24 12:08:03 +02:00
2b04174ed7 Upload files to "ui"
Signed-off-by: nestict <icttechnest@gmail.com>
2025-05-24 12:07:18 +02:00
e279c52952 Upload files to "ui"
Signed-off-by: nestict <icttechnest@gmail.com>
2025-05-24 12:06:55 +02:00
156 changed files with 49916 additions and 0 deletions

View File

@ -0,0 +1,86 @@
<?php
function stkQuery()
{
header('Content-Type: application/json');
$rawData = file_get_contents('php://input');
$postData = json_decode($rawData, true);
// Check if CheckoutRequestID is set
if (!isset($postData['CheckoutRequestID'])) {
echo json_encode(['status' => 'error', 'code' => 400, 'message' => 'missing CheckoutRequestID fields']);
return;
}
$CheckoutRequestID = $postData['CheckoutRequestID'];
$consumerKey = '3AmVP1WFDQn7GrDH8GcSSKxcAvnJdZGC'; // Fill with your app Consumer Key
$consumerSecret = '71Lybl6jUtxM0F35'; // Fill with your app Secret
$headers = ['Content-Type:application/json; charset=utf8'];
$access_token_url = 'https://api.safaricom.co.ke/oauth/v1/generate?grant_type=client_credentials';
$curl = curl_init($access_token_url);
curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, TRUE);
curl_setopt($curl, CURLOPT_HEADER, FALSE);
curl_setopt($curl, CURLOPT_USERPWD, $consumerKey.':'.$consumerSecret);
$result = curl_exec($curl);
$status = curl_getinfo($curl, CURLINFO_HTTP_CODE);
$result = json_decode($result);
$access_token = $result->access_token;
date_default_timezone_set('Africa/Nairobi');
$query_url = 'https://api.safaricom.co.ke/mpesa/stkpushquery/v1/query';
$BusinessShortCode = '4122323';
$Passkey = 'aaebecea73082fa56af852606106b1316d5b4dfa2f12d0088800b0b88e4bb6e3';
$Timestamp = date('YmdHis');
// ENCRYPT DATA TO GET PASSWORD
$Password = base64_encode($BusinessShortCode . $Passkey . $Timestamp);
// THIS IS THE UNIQUE ID THAT WAS GENERATED WHEN STK REQUEST INITIATED SUCCESSFULLY
$queryheader = ['Content-Type:application/json', 'Authorization:Bearer ' . $access_token];
// Initiating the transaction
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, $query_url);
curl_setopt($curl, CURLOPT_HTTPHEADER, $queryheader); // Setting custom header
$curl_post_data = array(
'BusinessShortCode' => $BusinessShortCode,
'Password' => $Password,
'Timestamp' => $Timestamp,
'CheckoutRequestID' => $CheckoutRequestID
);
$data_string = json_encode($curl_post_data);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_POST, true);
curl_setopt($curl, CURLOPT_POSTFIELDS, $data_string);
$curl_response = curl_exec($curl);
$data_to = json_decode($curl_response, true);
// Handle response
if (isset($data_to['ResultCode'])) {
$ResultCode = $data_to['ResultCode'];
if ($ResultCode == '1037') {
$message = "1037 Timeout in completing transaction";
} elseif ($ResultCode == '1032') {
$message = "1032 Transaction has been cancelled by user";
} elseif ($ResultCode == '1') {
$message = "1 The balance is insufficient for the transaction";
} elseif ($ResultCode == '0') {
$message = "0 The transaction is successful";
} else {
$message = "Unknown Result Code: $ResultCode";
}
} else {
$message = "Error in the response received from the M-Pesa API";
}
// Sending the response back
echo json_encode([
'message' => $message,
'result' => $data_to
]);
}
?>

BIN
system/plugin/.DS_Store vendored Normal file

Binary file not shown.

2
system/plugin/.gitattributes vendored Normal file
View File

@ -0,0 +1,2 @@
# Auto detect text files and perform LF normalization
* text=auto

View File

@ -0,0 +1,420 @@
<?php
function Alloworigins()
{
header("Access-Control-Allow-Origin: *");
header("Access-Control-Allow-Methods: GET, POST, OPTIONS");
header("Access-Control-Allow-Headers: Content-Type");
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
exit;
}
$requestUri = $_SERVER['REQUEST_URI'];
$queryString = parse_url($requestUri, PHP_URL_QUERY);
$type = null;
if ($queryString) {
parse_str($queryString, $queryParameters);
if (isset($queryParameters['type'])) {
$type = $queryParameters['type'];
if ($type === "grant") {
CreateHostspotUser();
exit;
} elseif ($type === "verify") {
VerifyHotspot();
exit;
} elseif ($type === "reconnect") {
ReconnectUser();
exit;
} elseif ($type === "voucher") {
ReconnectVoucher();
exit;
} else {
echo json_encode(['status' => 'error', 'code' => 400, 'message' => 'The parameter is not present in the URL.']);
}
}
}
}
function ReconnectVoucher() {
header('Content-Type: application/json');
$rawData = file_get_contents('php://input');
$postData = json_decode($rawData, true);
if (!isset($postData['voucher_code'], $postData['account_id'])) {
echo json_encode(['status' => 'error', 'code' => 400, 'message' => 'Missing accountId or voucherCode field']);
return;
}
$accountId = $postData['account_id'];
$voucherCode = $postData['voucher_code'];
$voucher = ORM::for_table('tbl_voucher')
->where('code', $voucherCode)
->where('status', '0')
->find_one();
if (!$voucher) {
echo json_encode([
'status' => 'error',
'Resultcode' => '1',
'voucher' => 'Not Found',
'message' => 'Invalid Voucher code'
]);
exit();
}
if ($voucher['status'] == '1') {
echo json_encode([
'status' => 'error',
'Resultcode' => '3',
'voucher' => 'Used',
'message' => 'Voucher code is already used'
]);
exit();
}
$planId = $voucher['id_plan'];
$routername = $voucher['routers'];
$router = ORM::for_table('tbl_routers')
->where('name', $routername)
->find_one();
if (!$router) {
echo json_encode([
'status' => 'error',
'message' => 'Router not found'
]);
exit();
}
$routerId = $router['id'];
if (!ORM::for_table('tbl_plans')->where('id', $planId)->count() || !ORM::for_table('tbl_routers')->where('id', $routerId)->count()) {
echo json_encode([
'status' => 'error',
'message' => 'Unable to process your request, please refresh the page'
]);
exit();
}
$user = ORM::for_table('tbl_customers')->where('username', $accountId)->find_one();
if (!$user) {
// Create a new user if not exists
$user = ORM::for_table('tbl_customers')->create();
$user->username = $accountId;
$user->password = '1234';
$user->fullname = $accountId;
$user->email = $accountId . '@gmail.com';
$user->phonenumber = $accountId;
$user->pppoe_password = '1234';
$user->address = '';
$user->service_type = 'Hotspot';
}
$user->router_id = $routerId;
$user->save();
// Update the voucher with the user ID
$voucher->user = $user->id;
$voucher->status = '1'; // Mark as used
$voucher->save();
if (Package::rechargeUser($user->id, $routername, $planId, 'Voucher', $voucherCode)) {
echo json_encode([
'status' => 'success',
'Resultcode' => '2',
'voucher' => 'activated',
'message' => 'Voucher code has been activated',
'username' => $user->username
]);
} else {
echo json_encode([
'status' => 'error',
'message' => 'Failed to recharge user package'
]);
}
}
function ReconnectUser()
{
header('Content-Type: application/json');
$rawData = file_get_contents('php://input');
$postData = json_decode($rawData, true);
if (!$postData) {
echo json_encode(['status' => 'error', 'code' => 400, 'message' => 'Invalid JSON DATA']);
exit();
}
if (!isset($postData['mpesa_code'])) {
echo json_encode(['status' => 'error', 'code' => 400, 'message' => 'missing required fields']);
exit();
}
$mpesaCode = $postData['mpesa_code'];
// Query the payment gateway table
$payment = ORM::for_table('tbl_payment_gateway')
->where('gateway_trx_id', $mpesaCode)
->find_one();
if (!$payment) {
$data = array(['status' => 'error', "Resultcode" => "1", 'user' => "Not Found", 'message' => 'Invalid Mpesa Transaction code']);
echo json_encode($data);
exit();
}
$username = $payment['username'];
// Query the user recharges table
$recharge = ORM::for_table('tbl_user_recharges')
->where('username', $username)
->order_by_desc('id')
->find_one();
if ($recharge) {
$status = $recharge['status'];
if ($status == 'on') {
$data = array(
"Resultcode" => "2",
"user" => "Active User",
"username" => $username,
"tyhK" => "1234", // Replace with the actual password or token
"Message" => "We have verified your transaction under the Mpesa Transaction $mpesaCode. Please don't leave this page as we are redirecting you.",
"Status" => "success"
);
} elseif ($status == "off") {
$data = array(
"Resultcode" => "3",
"user" => "Expired User",
"Message" => "We have verified your transaction under the Mpesa Transaction $mpesaCode. But your Package is already Expired. Please buy a new Package.",
"Status" => "danger"
);
} else {
$data = array(
"Message" => "Unexpected status value",
"Status" => "error"
);
}
} else {
$data = array(
"Message" => "Recharge information not found",
"Status" => "error"
);
}
echo json_encode($data);
exit();
}
function VerifyHotspot() {
header('Content-Type: application/json');
$rawData = file_get_contents('php://input');
$postData = json_decode($rawData, true);
if (!$postData) {
echo json_encode(['Resultcode' => 'error', 'Message' => 'Invalid JSON data']);
return;
}
if (!isset($postData['account_id'])) {
echo json_encode(['Resultcode' => 'error', 'Message' => 'Missing required fields']);
return;
}
$accountId = $postData['account_id'];
$user = ORM::for_table('tbl_payment_gateway')
->where('username', $accountId)
->order_by_desc('id')
->find_one();
if ($user) {
$status = $user->status;
$mpesacode = $user->gateway_trx_id;
$res = $user->pg_paid_response;
if ($status == 2 && !empty($mpesacode)) {
echo json_encode([
"Resultcode" => "3",
"Message" => "We have received your transaction under the Mpesa Transaction $mpesacode. Please do not leave this page as we are redirecting you.",
"Status" => "success"
]);
} elseif ($res == "Not enough balance") {
echo json_encode([
"Resultcode" => "2",
"Message" => "Insufficient Balance for the transaction",
"Status" => "danger"
]);
} elseif ($res == "Wrong Mpesa pin") {
echo json_encode([
"Resultcode" => "2",
"Message" => "You entered Wrong Mpesa pin, please resubmit",
"Status" => "danger"
]);
} elseif ($status == 4) {
echo json_encode([
"Resultcode" => "2",
"Message" => "You cancelled the transaction, you can enter phone number again to activate",
"Status" => "info"
]);
} elseif (empty($mpesacode)) {
echo json_encode([
"Resultcode" => "1",
"Message" => "A payment pop up has been sent to your phone. Please enter PIN to continue (Please do not leave or reload the page until redirected).",
"Status" => "primary"
]);
}
} else {
echo json_encode([
"Resultcode" => "error",
"Message" => "User not found"
]);
}
}
function CreateHostspotUser()
{
header('Content-Type: application/json');
$rawData = file_get_contents('php://input');
$postData = json_decode($rawData, true);
if (!$postData) {
echo json_encode(['status' => 'error', 'code' => 400, 'message' => 'Invalid JSON DATA' . $postData . ' n tes ']);
} else {
$phone = $postData['phone_number'];
$planId = $postData['plan_id'];
$routerId = $postData['router_id'];
$accountId = $postData['account_id'];
if (!isset( $postData['phone_number'], $postData['plan_id'], $postData['router_id'], $postData['account_id'])) {
echo json_encode(['status' => 'error', 'code' => 400, 'message' => 'missing required fields' . $postData, 'phone' => $phone, 'planId' => $planId, 'routerId' => $routerId, 'accountId' => $accountId]);
} else {
$phone = (substr($phone, 0, 1) == '+') ? str_replace('+', '', $phone) : $phone;
$phone = (substr($phone, 0, 1) == '0') ? preg_replace('/^0/', '254', $phone) : $phone;
$phone = (substr($phone, 0, 1) == '7') ? preg_replace('/^7/', '2547', $phone) : $phone; //cater for phone number prefix 2547XXXX
$phone = (substr($phone, 0, 1) == '1') ? preg_replace('/^1/', '2541', $phone) : $phone; //cater for phone number prefix 2541XXXX
$phone = (substr($phone, 0, 1) == '0') ? preg_replace('/^01/', '2541', $phone) : $phone;
$phone = (substr($phone, 0, 1) == '0') ? preg_replace('/^07/', '2547', $phone) : $phone;
if (strlen($phone) !== 12) {
echo json_encode(['status' => 'error', 'code' => 1, 'message' => 'Phone number ' . $phone . ' is invalid. Please confirm.']);
}
if (strlen($phone) == 12 && !empty($planId) && !empty($routerId)) {
$PlanExist = ORM::for_table('tbl_plans')->where('id', $planId)->count() > 0;
$RouterExist = ORM::for_table('tbl_routers')->where('id', $routerId)->count() > 0;
if (!$PlanExist || !$RouterExist)
echo json_encode(["status" => "error", "message" => "Unable to process your request, please refresh the page."]);
}
$Userexist = ORM::for_table('tbl_customers')->where('username', $accountId)->find_one();
if ($Userexist) {
$Userexist->router_id = $routerId;
$Userexist->save();
InitiateStkpush($phone, $planId, $accountId, $routerId);
} else {
try {
$defpass = '1234';
$defaddr = 'netXtreme';
$defmail = $phone . '@gmail.com';
$createUser = ORM::for_table('tbl_customers')->create();
$createUser->username = $accountId;
$createUser->password = $defpass;
$createUser->fullname = $phone;
$createUser->router_id = $routerId;
$createUser->phonenumber = $phone;
$createUser->pppoe_password = $defpass;
$createUser->address = $defaddr;
$createUser->email = $defmail;
$createUser->service_type = 'Hotspot';
if ($createUser->save()) {
InitiateStkpush($phone, $planId, $accountId, $routerId);
} else {
echo json_encode(["status" => "error", "message" => "There was a system error when registering user, please contact support."]);
}
} catch (Exception $e) {
echo json_encode(["status" => "error", "message" => "Error creating user: " . $e->getMessage()]);
}
}
}
}
}
function InitiateStkpush($phone, $planId, $accountId, $routerId)
{
$gateway = ORM::for_table('tbl_appconfig')
->where('setting', 'payment_gateway')
->find_one();
$gateway = ($gateway) ? $gateway->value : null;
if ($gateway == "MpesatillStk") {
$url = U . "plugin/initiatetillstk";
} elseif ($gateway == "BankStkPush") {
$url = U . "plugin/initiatebankstk";
} elseif ($gateway == "mpesa") {
$url = U . "plugin/initiatempesa";
} else {
$url = null; // or handle the default case appropriately
}
$Planname = ORM::for_table('tbl_plans')
->where('id', $planId)
->order_by_desc('id')
->find_one();
$Findrouter = ORM::for_table('tbl_routers')
->where('id', $routerId)
->order_by_desc('id')
->find_one();
$rname = $Findrouter->name;
$price = $Planname->price;
$Planname = $Planname->name_plan;
$Checkorders = ORM::for_table('tbl_payment_gateway')
->where('username', $accountId)
->where('status', 1)
->order_by_desc('id')
->find_many();
if ($Checkorders) {
foreach ($Checkorders as $Dorder) {
$Dorder->delete();
}
}
try {
$d = ORM::for_table('tbl_payment_gateway')->create();
$d->username = $accountId;
$d->gateway = $gateway;
$d->plan_id = $planId;
$d->plan_name = $Planname;
$d->routers_id = $routerId;
$d->routers = $rname;
$d->price = $price;
$d->payment_method = $gateway;
$d->payment_channel = $gateway;
$d->created_date = date('Y-m-d H:i:s');
$d->paid_date = date('Y-m-d H:i:s');
$d->expired_date = date('Y-m-d H:i:s');
$d->pg_url_payment = $url;
$d->status = 1;
$d->save();
} catch (Exception $e) {
error_log('Error saving payment gateway record: ' . $e->getMessage());
throw $e;
}
SendSTKcred($phone, $url, $accountId);
}
function SendSTKcred($phone, $url, $accountId )
{
$link = $url;
$fields = array(
'username' => $accountId,
'phone' => $phone,
'channel' => 'Yes',
);
$postvars = http_build_query($fields);
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $link);
curl_setopt($ch, CURLOPT_POST, count($fields));
curl_setopt($ch, CURLOPT_POSTFIELDS, $postvars);
$result = curl_exec($ch);
}
Alloworigins();

636
system/plugin/c2b.php Normal file
View File

@ -0,0 +1,636 @@
<?php
register_menu("Mpesa C2B Settings", true, "c2b_settings", 'SETTINGS', '', '', "");
register_menu("Mpesa Transactions", true, "c2b_overview", 'AFTER_MESSAGE', 'fa fa-paypal', '', "");
try {
$db = ORM::get_db();
$tableCheckQuery = "CREATE TABLE IF NOT EXISTS tbl_mpesa_transactions (
id INT AUTO_INCREMENT PRIMARY KEY,
TransID VARCHAR(255) NOT NULL,
TransactionType VARCHAR(255) NOT NULL,
TransTime VARCHAR(255) NOT NULL,
TransAmount DECIMAL(10, 2) NOT NULL,
BusinessShortCode VARCHAR(255) NOT NULL,
BillRefNumber VARCHAR(255) NOT NULL,
OrgAccountBalance DECIMAL(10, 2) NOT NULL,
MSISDN VARCHAR(255) NOT NULL,
FirstName VARCHAR(255) NOT NULL,
CustomerID VARCHAR(255) NOT NULL,
PackageName VARCHAR(255) NOT NULL,
PackagePrice VARCHAR(255) NOT NULL,
TransactionStatus VARCHAR(255) NOT NULL,
CreatedAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
)";
$db->exec($tableCheckQuery);
} catch (PDOException $e) {
echo "Error creating the table: " . $e->getMessage();
} catch (Exception $e) {
echo "An unexpected error occurred: " . $e->getMessage();
}
function c2b_overview()
{
global $ui, $config;
_admin();
$ui->assign('_title', 'Mpesa C2B Payment Overview');
$ui->assign('_system_menu', '');
$admin = Admin::_info();
$ui->assign('_admin', $admin);
// Check user type for access
if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin', 'Sales'])) {
_alert(Lang::T('You do not have permission to access this page'), 'danger', "dashboard");
exit;
}
$query = ORM::for_table('tbl_mpesa_transactions')->order_by_desc('TransTime');
$payments = $query->find_many();
if (
(empty($config['mpesa_c2b_consumer_key']) || empty($config['mpesa_c2b_consumer_secret']) || empty($config['mpesa_c2b_business_code']))
&& !$config['c2b_registered']
) {
$ui->assign('message', '<em>' . Lang::T("You haven't registered your validation and verification URLs. Please register URLs by clicking ") . ' <a href="' . APP_URL . '/index.php?_route=plugin/c2b_settings"> Register URL </a>' . '</em>');
}
$ui->assign('payments', $payments);
$ui->assign('xheader', '<link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/1.11.5/css/jquery.dataTables.css">');
$ui->display('c2b_overview.tpl');
}
function c2b_settings()
{
global $ui, $admin, $config;
$ui->assign('_title', Lang::T("Mpesa C2B Settings [Offline Payment]"));
$ui->assign('_system_menu', 'settings');
$admin = Admin::_info();
$ui->assign('_admin', $admin);
if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin'])) {
_alert(Lang::T('You do not have permission to access this page'), 'danger', "dashboard");
}
if (_post('save') == 'save') {
$mpesa_c2b_consumer_key = _post('mpesa_c2b_consumer_key');
$mpesa_c2b_consumer_secret = _post('mpesa_c2b_consumer_secret');
$mpesa_c2b_business_code = _post('mpesa_c2b_business_code');
$mpesa_c2b_env = _post('mpesa_c2b_env');
$mpesa_c2b_api = _post('mpesa_c2b_api');
$mpesa_c2b_low_fee = _post('mpesa_c2b_low_fee') ? 1 : 0;
$mpesa_c2b_bill_ref = _post('mpesa_c2b_bill_ref');
$errors = [];
if (empty($mpesa_c2b_consumer_key)) {
$errors[] = Lang::T('Mpesa C2B Consumer Key is required.');
}
if (empty($mpesa_c2b_consumer_secret)) {
$errors[] = Lang::T('Mpesa C2B Consumer Secret is required.');
}
if (empty($mpesa_c2b_business_code)) {
$errors[] = Lang::T('Mpesa C2B Business Code is required.');
}
if (empty($mpesa_c2b_env)) {
$errors[] = Lang::T('Mpesa C2B Environment is required.');
}
if (empty($mpesa_c2b_api)) {
$errors[] = Lang::T('Mpesa C2B API URL is required.');
}
if (empty($mpesa_c2b_bill_ref)) {
$errors[] = Lang::T('Mpesa Bill Ref Number Type is required.');
}
if (!empty($errors)) {
$ui->assign('message', implode('<br>', $errors));
$ui->display('c2b_settings.tpl');
return;
}
$settings = [
'mpesa_c2b_consumer_key' => $mpesa_c2b_consumer_key,
'mpesa_c2b_consumer_secret' => $mpesa_c2b_consumer_secret,
'mpesa_c2b_business_code' => $mpesa_c2b_business_code,
'mpesa_c2b_env' => $mpesa_c2b_env,
'mpesa_c2b_api' => $mpesa_c2b_api,
'mpesa_c2b_low_fee' => $mpesa_c2b_low_fee,
'mpesa_c2b_bill_ref' => $mpesa_c2b_bill_ref,
];
// Update or insert settings in the database
foreach ($settings as $key => $value) {
$d = ORM::for_table('tbl_appconfig')->where('setting', $key)->find_one();
if ($d) {
$d->value = $value;
$d->save();
} else {
$d = ORM::for_table('tbl_appconfig')->create();
$d->setting = $key;
$d->value = $value;
$d->save();
}
}
if ($admin) {
_log('[' . $admin['username'] . ']: ' . Lang::T('Settings Saved Successfully'));
}
r2(U . 'plugin/c2b_settings', 's', Lang::T('Settings Saved Successfully'));
}
if (!empty($config['mpesa_c2b_consumer_key'] && $config['mpesa_c2b_consumer_secret'] && $config['mpesa_c2b_business_code']) && !$config['c2b_registered']) {
$ui->assign('message', '<em>' . Lang::T("You haven't registered your validation and verification URLs, Please register URLs by clicking ") . ' <a href="' . APP_URL . '/index.php?_route=plugin/c2b_settings"> Register URL </a>' . '</em>');
}
$ui->assign('_c', $config);
$ui->assign('companyName', $config['CompanyName']);
$ui->display('c2b_settings.tpl');
}
function c2b_generateAccessToken()
{
global $config;
$mpesa_c2b_env = $config['mpesa_c2b_env'] ?? null;
$mpesa_c2b_consumer_key = $config['mpesa_c2b_consumer_key'] ?? null;
$mpesa_c2b_consumer_secret = $config['mpesa_c2b_consumer_secret'] ?? null;
$access_token_url = match ($mpesa_c2b_env) {
"live" => 'https://api.safaricom.co.ke/oauth/v1/generate?grant_type=client_credentials',
"sandbox" => 'https://sandbox.safaricom.co.ke/oauth/v1/generate?grant_type=client_credentials',
};
$headers = ['Content-Type:application/json; charset=utf8'];
$curl = curl_init($access_token_url);
curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, TRUE);
curl_setopt($curl, CURLOPT_HEADER, FALSE);
curl_setopt($curl, CURLOPT_USERPWD, "$mpesa_c2b_consumer_key:$mpesa_c2b_consumer_secret");
$result = curl_exec($curl);
$result = json_decode($result);
if (isset($result->access_token)) {
return $result->access_token;
} else {
return null;
}
}
function c2b_registerUrl()
{
global $config;
if (
(empty($config['mpesa_c2b_consumer_key']) || empty($config['mpesa_c2b_consumer_secret']) || empty($config['mpesa_c2b_business_code']))
&& !$config['c2b_registered']
) {
r2(U . 'plugin/c2b_settings', 'e', Lang::T('Please setup your M-Pesa C2B settings first'));
exit;
}
$access_token = c2b_generateAccessToken();
switch ($access_token) {
case null:
r2(U . 'plugin/c2b_settings', 'e', Lang::T('Failed to generate access token'));
exit;
default:
$BusinessShortCode = $config['mpesa_c2b_business_code'] ?? null;
$mpesa_c2b_env = $config['mpesa_c2b_env'] ?? null;
$confirmationUrl = U . 'plugin/c2b_confirmation';
$validationUrl = U . 'plugin/c2b_validation';
$mpesa_c2b_api = $config['mpesa_c2b_api'] ?? null;
$registerurl = match ($mpesa_c2b_env) {
"live" => match ($mpesa_c2b_api) {
"v1" => 'https://api.safaricom.co.ke/mpesa/c2b/v1/registerurl',
"v2" => 'https://api.safaricom.co.ke/mpesa/c2b/v2/registerurl',
},
"sandbox" => 'https://sandbox.safaricom.co.ke/mpesa/c2b/v1/registerurl',
};
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, $registerurl);
curl_setopt($curl, CURLOPT_HTTPHEADER, [
'Content-Type:application/json',
"Authorization:Bearer $access_token"
]);
$data = [
'ShortCode' => $BusinessShortCode,
'ResponseType' => 'Completed',
'ConfirmationURL' => $confirmationUrl,
'ValidationURL' => $validationUrl
];
$data_string = json_encode($data);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_POST, true);
curl_setopt($curl, CURLOPT_POSTFIELDS, $data_string);
$curl_response = curl_exec($curl);
$data = json_decode($curl_response);
if (isset($data->ResponseCode) && $data->ResponseCode == 0) {
try {
$d = ORM::for_table('tbl_appconfig')->create();
$d->setting = 'c2b_registered';
$d->value = '1';
$d->save();
} catch (Exception $e) {
_log("Failed to save M-Pesa C2B URL to database.\n\n" . $e->getMessage());
sendTelegram("Failed to save M-Pesa C2B URL to database.\n\n" . $e->getMessage());
}
sendTelegram("M-Pesa C2B URL registered successfully");
r2(U . 'plugin/c2b_settings', 's', "M-Pesa C2B URL registered successfully");
} else {
$errorMessage = $data->errorMessage;
sendTelegram("Resister M-Pesa C2B URL Failed\n\n" . json_encode($curl_response, JSON_PRETTY_PRINT));
r2(U . 'plugin/c2b_settings', 'e', "Failed to register M-Pesa C2B URL Error $errorMessage");
}
break;
}
}
function c2b_webhook_log($data)
{
$logFile = 'pages/mpesa-webhook.html';
$logEntry = date('Y-m-d H:i:s') . "<pre>" . htmlspecialchars($data, ENT_QUOTES, 'UTF-8') . "</pre>\n";
if (file_put_contents($logFile, $logEntry, FILE_APPEND) === false) {
sendTelegram("Failed to write to log file: $logFile");
}
}
function c2b_isValidSafaricomIP($ip)
{
$config = c2b_config();
$safaricomIPs = [
'196.201.214.0/24',
'196.201.213.0/24',
'196.201.212.0/24',
'172.69.79.0/24',
'172.69.0.0/24',
'0.0.0.0/0',
];
if ($config['mpesa_c2b_env'] == 'sandbox') {
$safaricomIPs[] = '::1';
}
foreach ($safaricomIPs as $range) {
if (c2b_ipInRange($ip, $range)) {
return true;
}
}
return false;
}
function c2b_ipInRange($ip, $range)
{
list($subnet, $bits) = explode('/', $range);
$ip = ip2long($ip);
$subnet = ip2long($subnet);
$mask = -1 << (32 - $bits);
$subnet &= $mask;
return ($ip & $mask) == $subnet;
}
function c2b_confirmation()
{
global $config;
header("Content-Type: application/json");
$clientIP = $_SERVER['REMOTE_ADDR'];
if (!c2b_isValidSafaricomIP($clientIP)) {
c2b_logAndNotify("Unauthorized request from IP: {$clientIP}");
http_response_code(403);
echo json_encode(["ResultCode" => 1, "ResultDesc" => "Unauthorized"]);
return;
}
$mpesaResponse = file_get_contents('php://input');
if ($mpesaResponse === false) {
c2b_logAndNotify("Failed to get input stream.");
return;
}
c2b_webhook_log('Received webhook request');
c2b_webhook_log($mpesaResponse);
$content = json_decode($mpesaResponse);
if (json_last_error() !== JSON_ERROR_NONE) {
c2b_logAndNotify("Failed to decode JSON response: " . json_last_error_msg());
return;
}
c2b_webhook_log('Decoded JSON data successfully');
if (!class_exists('Package')) {
c2b_logAndNotify("Error: Package class does not exist.");
return;
}
if (isset($config['mpesa_c2b_bill_ref'])) {
switch ($config['mpesa_c2b_bill_ref']) {
case 'phone':
$customer = ORM::for_table('tbl_customers')
->where('phonenumber', $content->BillRefNumber)
->find_one();
break;
case 'username':
$customer = ORM::for_table('tbl_customers')
->where('username', $content->BillRefNumber)
->find_one();
break;
case 'id':
$customer = ORM::for_table('tbl_customers')
->where('id', $content->BillRefNumber)
->find_one();
break;
default:
$customer = null;
break;
}
if (!$customer) {
sendTelegram("Validation failed: No account found for BillRefNumber: $content->BillRefNumber");
_log("Validation failed: No account found for BillRefNumber: $content->BillRefNumber");
echo json_encode(["ResultCode" => "C2B00012", "ResultDesc" => "Invalid Account Number"]);
return;
}
} else {
_log("Configuration error: mpesa_c2b_bill_ref not set.");
sendTelegram("Configuration error: mpesa_c2b_bill_ref not set.");
}
$bills = c2b_billing($customer->id);
if (!$bills) {
c2b_logAndNotify("No matching bill found for BillRefNumber: {$content->BillRefNumber}");
return;
}
foreach ($bills as $bill) {
c2b_handleBillPayment($content, $customer, $bill);
}
echo json_encode(["ResultCode" => 0, "ResultDesc" => "Accepted"]);
}
function c2b_handleBillPayment($content, $customer, $bill)
{
$amountToPay = $bill['price'];
$amountPaid = $content->TransAmount;
$channel_mode = "Mpesa C2B - {$content->TransID}";
$customerBalance = $customer->balance;
$currentBalance = $customerBalance + $amountPaid;
$customerID = $customer->id;
try {
$transaction = c2b_storeTransaction($content, $bill['namebp'], $amountToPay, $customerID);
} catch (Exception $e) {
c2b_handleException("Failed to save transaction", $e);
exit;
}
if ($currentBalance >= $amountToPay) {
$excessAmount = $currentBalance - $amountToPay;
try {
$result = Package::rechargeUser($customer->id, $bill['routers'], $bill['plan_id'], 'mpesa', $channel_mode);
if (!$result) {
c2b_logAndNotify("Mpesa Payment Successful, but failed to activate the package for customer {$customer->username}.");
} else {
if ($excessAmount > 0) {
$customer->balance = $excessAmount;
$customer->save();
} else {
$customer->balance = 0;
$customer->save();
}
c2b_sendPaymentSuccessMessage($customer, $amountPaid, $bill['namebp']);
$transaction->transactionStatus = 'Completed';
$transaction->save();
}
} catch (Exception $e) {
c2b_handleException("Error during package activation", $e);
}
} else {
c2b_updateCustomerBalance($customer, $currentBalance, $amountPaid);
$neededToActivate = $amountToPay - $currentBalance;
c2b_sendBalanceUpdateMessage($customer, $amountPaid, $currentBalance, $neededToActivate);
$transaction->transactionStatus = 'Completed';
$transaction->save();
}
}
function c2b_storeTransaction($content, $packageName, $packagePrice, $customerID)
{
ORM::get_db()->beginTransaction();
try {
$transaction = ORM::for_table('tbl_mpesa_transactions')
->where('TransID', $content->TransID)
->find_one();
if ($transaction) {
// Update existing transaction
$transaction->TransactionType = $content->TransactionType;
$transaction->TransTime = $content->TransTime;
$transaction->TransAmount = $content->TransAmount;
$transaction->BusinessShortCode = $content->BusinessShortCode;
$transaction->BillRefNumber = $content->BillRefNumber;
$transaction->OrgAccountBalance = $content->OrgAccountBalance;
$transaction->MSISDN = $content->MSISDN;
$transaction->FirstName = $content->FirstName;
$transaction->PackageName = $packageName;
$transaction->PackagePrice = $packagePrice;
$transaction->customerID = $customerID;
$transaction->transactionStatus = 'Pending';
} else {
// Create new transaction
$transaction = ORM::for_table('tbl_mpesa_transactions')->create();
$transaction->TransID = $content->TransID;
$transaction->TransactionType = $content->TransactionType;
$transaction->TransTime = $content->TransTime;
$transaction->TransAmount = $content->TransAmount;
$transaction->BusinessShortCode = $content->BusinessShortCode;
$transaction->BillRefNumber = $content->BillRefNumber;
$transaction->OrgAccountBalance = $content->OrgAccountBalance;
$transaction->MSISDN = $content->MSISDN;
$transaction->FirstName = $content->FirstName;
$transaction->PackageName = $packageName;
$transaction->PackagePrice = $packagePrice;
$transaction->customerID = $customerID;
$transaction->transactionStatus = 'Pending';
}
$transaction->save();
ORM::get_db()->commit();
return $transaction;
} catch (Exception $e) {
ORM::get_db()->rollBack();
throw $e;
}
}
function c2b_logAndNotify($message)
{
_log($message);
sendTelegram($message);
}
function c2b_handleException($message, $e)
{
$fullMessage = "$message: " . $e->getMessage() . " in " . $e->getFile() . " on line " . $e->getLine();
c2b_logAndNotify($fullMessage);
}
function c2b_updateCustomerBalance($customer, $newBalance, $amountPaid)
{
try {
$customer->balance = $newBalance;
$customer->save();
c2b_logAndNotify("Payment of KES {$amountPaid} has been added to the balance of customer {$customer->username}.");
} catch (Exception $e) {
c2b_handleException("Failed to update customer balance", $e);
}
}
function c2b_sendPaymentSuccessMessage($customer, $amountPaid, $packageName)
{
$config = c2b_config();
$message = "Dear {$customer->fullname}, your payment of KES {$amountPaid} has been received and your plan {$packageName} has been successfully activated. Thank you for choosing {$config['CompanyName']}.";
c2b_sendNotification($customer, $message);
}
function c2b_sendBalanceUpdateMessage($customer, $amountPaid, $currentBalance, $neededToActivate)
{
$config = c2b_config();
$message = "Dear {$customer->fullname}, your payment of KES {$amountPaid} has been received and added to your account balance. Your current balance is KES {$currentBalance}.";
if ($neededToActivate > 0) {
$message .= " To activate your package, you need to add KES {$neededToActivate} more to your account.";
} else {
$message .= " Your current balance is sufficient to activate your package.";
}
$message .= "\n" . $config['CompanyName'];
c2b_sendNotification($customer, $message);
}
function c2b_sendNotification($customer, $message)
{
try {
Message::sendSMS($customer->phonenumber, $message);
Message::sendWhatsapp($customer->phonenumber, $message);
} catch (Exception $e) {
c2b_handleException("Failed to send SMS/WhatsApp message", $e);
}
}
function c2b_validation()
{
header("Content-Type: application/json");
$mpesaResponse = file_get_contents('php://input');
$config = c2b_config();
$content = json_decode($mpesaResponse);
if (json_last_error() !== JSON_ERROR_NONE) {
sendTelegram("Failed to decode JSON response.");
_log("Failed to decode JSON response.");
echo json_encode(["ResultCode" => "C2B00016", "ResultDesc" => "Invalid JSON format"]);
return;
}
$BillRefNumber = $content->BillRefNumber;
$TransAmount = $content->TransAmount;
if (isset($config['mpesa_c2b_bill_ref'])) {
switch ($config['mpesa_c2b_bill_ref']) {
case 'phone':
$customer = ORM::for_table('tbl_customers')
->where('phonenumber', $content->BillRefNumber)
->find_one();
break;
case 'username':
$customer = ORM::for_table('tbl_customers')
->where('username', $content->BillRefNumber)
->find_one();
break;
case 'id':
$customer = ORM::for_table('tbl_customers')
->where('id', $content->BillRefNumber)
->find_one();
break;
default:
$customer = null;
break;
}
if (!$customer) {
sendTelegram("Validation failed: No account found for BillRefNumber: $BillRefNumber");
_log("Validation failed: No account found for BillRefNumber: $BillRefNumber");
echo json_encode(["ResultCode" => "C2B00012", "ResultDesc" => "Invalid Account Number"]);
return;
}
} else {
_log("Configuration error: mpesa_c2b_bill_ref not set.");
sendTelegram("Configuration error: mpesa_c2b_bill_ref not set.");
}
$bills = c2b_billing($customer->id);
if (!$bills) {
sendTelegram("Validation failed: No bill found for BillRefNumber: $BillRefNumber");
_log("Validation failed: No bill found for BillRefNumber: $BillRefNumber");
echo json_encode(["ResultCode" => "C2B00012", "ResultDesc" => "Invalid Bill Reference"]);
return;
}
foreach ($bills as $bill) {
}
$billAmount = $bill['price'];
if (!$config['mpesa_c2b_low_fee']) {
if ($TransAmount < $billAmount) {
sendTelegram("Validation failed: Insufficient amount. Transferred: $TransAmount, Required: $billAmount");
_log("Validation failed: Insufficient amount. Transferred: $TransAmount, Required: $billAmount");
echo json_encode(["ResultCode" => "C2B00013", "ResultDesc" => "Invalid or Insufficient Amount"]);
return;
}
}
sendTelegram("Validation successful for BillRefNumber: $BillRefNumber with amount: $TransAmount");
_log("Validation successful for BillRefNumber: $BillRefNumber with amount: $TransAmount");
echo json_encode(["ResultCode" => 0, "ResultDesc" => "Accepted"]);
}
function c2b_billing($id)
{
$d = ORM::for_table('tbl_user_recharges')
->selects([
'customer_id',
'username',
'plan_id',
'namebp',
'recharged_on',
'recharged_time',
'expiration',
'time',
'status',
'method',
'plan_type',
['tbl_user_recharges.routers', 'routers'],
['tbl_user_recharges.type', 'type'],
'admin_id',
'prepaid'
])
->select('tbl_plans.price', 'price')
->left_outer_join('tbl_plans', array('tbl_plans.id', '=', 'tbl_user_recharges.plan_id'))
->where('customer_id', $id)
->find_many();
return $d;
}
function c2b_config()
{
$result = ORM::for_table('tbl_appconfig')->find_many();
foreach ($result as $value) {
$config[$value['setting']] = $value['value'];
}
return $config;
}

View File

@ -0,0 +1,48 @@
<?php
register_menu("Clear System Cache", true, "clear_cache", 'SETTINGS', '');
function clear_cache()
{
global $ui;
_admin();
$ui->assign('_title', 'Clear Cache');
$ui->assign('_system_menu', 'settings');
$admin = Admin::_info();
$ui->assign('_admin', $admin);
// Check user type for access
if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin'])) {
_alert(Lang::T('You do not have permission to access this page'), 'danger', "dashboard");
exit;
}
$compiledCacheDir = 'ui/compiled';
$templateCacheDir = 'system/cache';
try {
// Clear the compiled cache
$files = scandir($compiledCacheDir);
foreach ($files as $file) {
if ($file !== '.' && $file !== '..' && is_file($compiledCacheDir . '/' . $file)) {
unlink($compiledCacheDir . '/' . $file);
}
}
// Clear the template cache
$templateCacheFiles = glob($templateCacheDir . '/*.{json,temp}', GLOB_BRACE);
foreach ($templateCacheFiles as $file) {
if (is_file($file)) {
unlink($file);
}
}
// Cache cleared successfully
_log('[' . ($admin['fullname'] ?? 'Unknown Admin') . ']: ' . Lang::T(' Cleared the system cache '), $admin['user_type']);
r2(U . 'dashboard', 's', Lang::T("Cache cleared successfully!"));
} catch (Exception $e) {
// Error occurred while clearing the cache
_log('[' . ($admin['fullname'] ?? 'Unknown Admin') . ']: ' . Lang::T(' Error occurred while clearing the cache: ' . $e->getMessage()), $admin['user_type']);
r2(U . 'dashboard', 'e', Lang::T("Error occurred while clearing the cache: ") . $e->getMessage());
}
}

755
system/plugin/download.php Normal file
View File

@ -0,0 +1,755 @@
<?php
include '../../config.php';
$mysqli = new mysqli($db_host, $db_user, $db_password, $db_name);
if ($mysqli->connect_error) {
die("Connection failed: " . $mysqli->connect_error);
}
// Function to get a setting value
function getSettingValue($mysqli, $setting) {
$query = $mysqli->prepare("SELECT value FROM tbl_appconfig WHERE setting = ?");
$query->bind_param("s", $setting);
$query->execute();
$result = $query->get_result();
if ($row = $result->fetch_assoc()) {
return $row['value'];
}
return '';
}
// Fetch hotspot title and description from tbl_appconfig
$hotspotTitle = getSettingValue($mysqli, 'hotspot_title');
$description = getSettingValue($mysqli, 'description');
$phone = getSettingValue($mysqli, 'phone');
$company = getSettingValue($mysqli, 'CompanyName');
// Fetch router name and router ID from tbl_appconfig
$routerName = getSettingValue($mysqli, 'router_name');
$routerId = getSettingValue($mysqli, 'router_id');
// Fetch available plans
$planQuery = "SELECT id, type, name_plan, price, validity, validity_unit FROM tbl_plans WHERE routers = ? AND type = 'Hotspot'";
$planStmt = $mysqli->prepare($planQuery);
$planStmt->bind_param("s", $routerName);
$planStmt->execute();
$planResult = $planStmt->get_result();
$htmlContent = "";
$htmlContent .= "<!DOCTYPE html>\n";
$htmlContent .= "<html lang=\"en\">\n";
$htmlContent .= "<head>\n";
$htmlContent .= " <meta charset=\"UTF-8\">\n";
$htmlContent .= " <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n";
$htmlContent .= " <title>$company</title>\n";
$htmlContent .= " <script src=\"https://cdn.tailwindcss.com\"></script>\n";
$htmlContent .= " <link rel=\"stylesheet\" href=\"https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/css/all.min.css\">\n";
$htmlContent .= " <link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/glider-js@1.7.7/glider.min.css\" />\n";
$htmlContent .= " <script src=\"https://cdn.jsdelivr.net/npm/glider-js@1.7.7/glider.min.js\"></script>\n";
$htmlContent .= " <link rel=\"preconnect\" href=\"https://cdn.jsdelivr.net\">\n";
$htmlContent .= " <link rel=\"preconnect\" href=\"https://cdnjs.cloudflare.com\" crossorigin>\n";
$htmlContent .= " <link rel=\"stylesheet\" href=\"https://rsms.me/inter/inter.css\">\n";
$htmlContent .= " <!-- <link rel=\"stylesheet\" type=\"text/css\" href=\"styles.css\"> -->\n";
$htmlContent .= "</head>\n";
$htmlContent .= "<body class=\"font-sans antialiased text-gray-900 bg-gray-900 font-inter\">\n";
$htmlContent .= " <!-- Main Content -->\n";
$htmlContent .= " <div class=\"mx-auto max-w-screen-2xl px-4 md:px-4\">\n";
$htmlContent .= " <div class=\"max-h-34 relative mx-auto mt-4 flex max-w-lg flex-1 shrink-0 items-center justify-center overflow-hidden shadow-lg rounded-lg bg-blue-100\">\n";
$htmlContent .= " <!-- overlay - start -->\n";
$htmlContent .= " <!-- <div class=\"absolute inset-0 mix-blend-multiply\"></div> -->\n";
$htmlContent .= " <!-- overlay - end -->\n";
$htmlContent .= " <!-- text start -->\n";
$htmlContent .= " <div class=\"relative flex flex-col items-center p-4 sm:max-w-xl\">\n";
$htmlContent .= " <p class=\"mb-4 text-center text-2xl font-bold text-gray-800 sm:text-xl md:mb-2 \">$company HOTSPOT LOGIN </p>\n";
$htmlContent .= " <ol class=\"text-base text-left text-gray-800 mb-1 list-decimal pl-6\">\n";
$htmlContent .= " <li>Click on your preferred package</li>\n";
$htmlContent .= " <li>Enter Mpesa No.</li>\n";
$htmlContent .= " <li>Enter pin</li>\n";
$htmlContent .= " <li>Wait to be connected</li>\n";
$htmlContent .= " </ol>\n";
$htmlContent .= " <p class=\"mb-4 text-center text-lg font-medium text-gray-700 sm:text-1xl md:mb-1 md:text-xl\"> For any enquiries contact : $phone</p>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " <!-- text end -->\n";
$htmlContent .= " </div>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " <div class=\"py-2 sm:py-4 lg:py-4\">\n";
$htmlContent .= " <div class=\"mx-auto max-w-screen-2xl px-4 md:px-4\">\n";
$htmlContent .= " <div class=\"mx-auto max-w-lg\">\n";
$htmlContent .= " <div class=\"flex flex-col gap-4\">\n";
$htmlContent .= " <button type=\"button\" class=\"flex items-center justify-center gap-2 rounded-lg bg-blue-500 px-8 py-3 text-center text-sm font-semibold text-white outline-none ring-blue-300 transition duration-100 hover:bg-blue-600 focus-visible:ring active:bg-blue-700 md:text-base\" onclick=\"redeemVoucher()\">\n";
$htmlContent .= " <svg class=\"w-5 h-5 mr-2\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\n";
$htmlContent .= " <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M12 8v13m0-13V6a2 2 0 112 2h-2zm0 0V5.5A2.5 2.5 0 109.5 8H12zm-7 4h14M5 12a2 2 0 110-4h14a2 2 0 110 4M5 12v7a2 2 0 002 2h10a2 2 0 002-2v-7\"></path>\n";
$htmlContent .= " </svg>\n";
$htmlContent .= " Click here to Redeem Voucher\n";
$htmlContent .= " </button>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " <div class=\"py-2 sm:py-4 lg:py-6\">\n";
$htmlContent .= " <div class=\"mx-auto max-w-screen-2xl px-4 md:px-8\">\n";
$htmlContent .= " <div class=\"mx-auto max-w-lg grid grid-cols-2 sm:grid-cols-3 gap-1 p-1\" id=\"cards-container\">\n";
$htmlContent .= " </div>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " </div>\n";
// New HTML content added
$htmlContent .= " <div class=\"container mx-auto px-4 mb-2\">\n";
$htmlContent .= " <div class=\"max-w-md mx-auto bg-white rounded-lg overflow-hidden md:max-w-lg\">\n";
$htmlContent .= " <div class=\"p-3\">\n";
$htmlContent .= " <h3 class=\"text-2xl font-semibold text-gray-900 mb-3 text-center\">Enter code to reconnect</h3>\n";
$htmlContent .= " <div class=\"mb-6\">\n";
$htmlContent .= " <label for=\"mpesaCodeInput\" class=\"block text-gray-700 text-sm font-bold mb-2\">Code or
message:</label>\n";
$htmlContent .= " <input type=\"text\" id=\"mpesaCodeInput\" name=\"mpesa_code\" placeholder=\"Enter Code or Full Message\" class=\"w-full rounded-lg border bg-gray-50 px-3 py-2 text-gray-800 outline-none ring-indigo-300 transition duration-100 focus:ring\">\n";
$htmlContent .= " <button id=\"reconnectBtn\" class=\"w-full mt-3 rounded-lg bg-blue-500 px-4 py-2 text-white font-semibold hover:bg-red-600 transition duration-100\">Reconnect</button>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " <div class=\"p-1\">\n";
$htmlContent .= " <div class=\"w-full p-3\">\n";
$htmlContent .= " <div class=\"text-center\">\n";
$htmlContent .= " <h3 class=\"text-2xl text-gray-900\">Already Have an Active Package?</h3>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " <form id=\"loginForm\" class=\"form\" name=\"login\" action=\"$(link-login-only)\" method=\"post\" $(if chap-id)onSubmit=\"return doLogin()\" $(endif)>\n";
$htmlContent .= " <input type=\"hidden\" name=\"dst\" value=\"$(link-orig)\" />\n";
$htmlContent .= " <input type=\"hidden\" name=\"popup\" value=\"true\" />\n";
$htmlContent .= " <div class=\"mb-4\">\n";
$htmlContent .= " <label class=\"block text-gray-700 text-sm font-bold mb-2\" for=\"username\">enter username or account number.</label>\n";
$htmlContent .= " <div>\n";
$htmlContent .= " <input id=\"usernameInput\" name=\"username\" type=\"text\" value=\"\" placeholder=\"eg. ACC123456\" class=\"w-full rounded-lg border bg-gray-50 px-3 py-2 text-gray-800 outline-none ring-indigo-300 transition duration-100 focus:ring\" />\n";
$htmlContent .= " <button id=\"submitBtn\" class=\"w-full mt-3 flex items-center justify-center gap-2 rounded-lg bg-blue-500 px-8 py-3 text-center text-sm font-semibold text-white outline-none ring-red-300 transition duration-100 hover:bg-red-600 focus-visible:ring active:bg-red-700 md:text-base\" type=\"button\" onclick=\"submitLogin()\">\n";
$htmlContent .= " Connect\n";
$htmlContent .= " </button>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " <input type=\"hidden\" name=\"password\" value=\"1234\">\n";
$htmlContent .= " </form>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " <div class=\"mx-auto max-w-screen-2xl px-4 md:px-8\">\n";
$htmlContent .= " <div class=\"mx-auto mb-4 max-w-lg\">\n";
$htmlContent .= " <div class=\"border-t py-4\">\n";
$htmlContent .= " <p class=\"text-xs text-red-700 text-center\">&copy; " . date("Y") . ". Powered by NestICT</p>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " </div>\n";
$htmlContent .= "</body>\n";
// Add the closing script section as well, if necessary
$htmlContent .= "<script>\n";
// Add any required JavaScript here
$htmlContent .= "</script>\n";
$htmlContent .= "<script>\n";
$htmlContent .= "function fetchData() {\n";
$htmlContent .= " let domain = '" . APP_URL . "/';\n";
$htmlContent .= " let siteUrl = domain + \"/index.php?_route=plugin/hotspot_plan\";\n";
$htmlContent .= " let request = new XMLHttpRequest();\n";
$htmlContent .= " const routerName = encodeURIComponent(\"$routerName\");\n";
$htmlContent .= " const dataparams = `routername=\${routerName}`;\n";
$htmlContent .= " request.open(\"POST\", siteUrl, true);\n";
$htmlContent .= " request.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');\n";
$htmlContent .= " request.onload = () => {\n";
$htmlContent .= " if (request.readyState === XMLHttpRequest.DONE) {\n";
$htmlContent .= " if (request.status === 200) {\n";
$htmlContent .= " let fetchedData = JSON.parse(request.responseText);\n";
$htmlContent .= " populateCards(fetchedData);\n";
$htmlContent .= " } else {\n";
$htmlContent .= " console.log(`Error \${request.status}: \${request.statusText}`);\n";
$htmlContent .= " }\n";
$htmlContent .= " }\n";
$htmlContent .= " };\n";
$htmlContent .= " request.onerror = () => {\n";
$htmlContent .= " console.error(\"Network error\");\n";
$htmlContent .= " };\n";
$htmlContent .= " request.send(dataparams);\n";
$htmlContent .= "}\n";
$htmlContent .= "function populateCards(data) {\n";
$htmlContent .= " var cardsContainer = document.getElementById('cards-container');\n";
$htmlContent .= " cardsContainer.innerHTML = ''; // Clear existing content\n";
$htmlContent .= " // Sort the plans by price in ascending order\n";
$htmlContent .= " data.data.forEach(router => {\n";
$htmlContent .= " // Sort hotspot plans by price\n";
$htmlContent .= " router.plans_hotspot.sort((a, b) => parseFloat(a.price) - parseFloat(b.price));\n";
$htmlContent .= " router.plans_hotspot.forEach(item => {\n";
$htmlContent .= " var cardDiv = document.createElement('div');\n";
$htmlContent .= " cardDiv.className = 'bg-white border border-black rounded-lg shadow-md overflow-hidden transition duration-300 hover:shadow-lg flex flex-col items-center justify-between mx-auto mb-4 w-40';\n";
$htmlContent .= " cardDiv.innerHTML = `\n";
$htmlContent .= " <div class=\"bg-red-500 text-white w-full py-1\">\n";
$htmlContent .= " <h2 class=\"text-sm font-medium uppercase text-center\" style=\"font-size: clamp(0.75rem, 1.5vw, 1rem); white-space: nowrap; overflow: hidden; text-overflow: ellipsis;\">\n";
$htmlContent .= " \${item.planname}\n";
$htmlContent .= " </h2>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " <div class=\"px-4 py-2 flex-grow\">\n";
$htmlContent .= " <p class=\"text-2xl font-bold text-red-600 mb-1\">\n";
$htmlContent .= " <span class=\"text-lg font-medium text-black\">\${item.currency}</span>\n";
$htmlContent .= " \${item.price}\n";
$htmlContent .= " </p>\n";
$htmlContent .= " <p class=\"text-sm text-black mb-2\">\n";
$htmlContent .= " Valid for \${item.validity} \${item.timelimit}\n";
$htmlContent .= " </p>\n";
$htmlContent .= " <hr class=\"border-black mb-2\">\n";
$htmlContent .= " </div>\n";
$htmlContent .= " <div class=\"px-4 py-2 flex-shrink-0\">\n";
$htmlContent .= " <a href=\"#\" class=\"inline-block bg-gray-900 text-white hover:bg-red-600 font-semibold py-1 px-4 rounded-lg transition duration-300 text-md\"\n";
$htmlContent .= " onclick=\"handlePhoneNumberSubmission('\${item.planId}', '\${item.routerId}'); return false;\"\n";
$htmlContent .= " data-plan-id=\"\${item.planId}\"\n";
$htmlContent .= " data-router-id=\"\${item.routerId}\">\n";
$htmlContent .= " Buy\n";
$htmlContent .= " </a>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " `;\n";
$htmlContent .= " cardsContainer.appendChild(cardDiv);\n";
$htmlContent .= " });\n";
$htmlContent .= " });\n";
$htmlContent .= "}\n";
$htmlContent .= "fetchData();\n";
$htmlContent .= "</script>\n";
$htmlContent .= "<script src=\"https://cdn.jsdelivr.net/npm/sweetalert2@11\"></script>\n";
$htmlContent .= "<script>\n";
$htmlContent .= " function formatPhoneNumber(phoneNumber) {\n";
$htmlContent .= " if (phoneNumber.startsWith('+')) {\n";
$htmlContent .= " phoneNumber = phoneNumber.substring(1);\n";
$htmlContent .= " }\n";
$htmlContent .= " if (phoneNumber.startsWith('0')) {\n";
$htmlContent .= " phoneNumber = '254' + phoneNumber.substring(1);\n";
$htmlContent .= " }\n";
$htmlContent .= " if (phoneNumber.match(/^(7|1)/)) {\n";
$htmlContent .= " phoneNumber = '254' + phoneNumber;\n";
$htmlContent .= " }\n";
$htmlContent .= " return phoneNumber;\n";
$htmlContent .= " }\n";
$htmlContent .= "\n";
$htmlContent .= " function setCookie(name, value, days) {\n";
$htmlContent .= " var expires = \"\";\n";
$htmlContent .= " if (days) {\n";
$htmlContent .= " var date = new Date();\n";
$htmlContent .= " date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));\n";
$htmlContent .= " expires = \"; expires=\" + date.toUTCString();\n";
$htmlContent .= " }\n";
$htmlContent .= " document.cookie = name + \"=\" + (value || \"\") + expires + \"; path=/\";\n";
$htmlContent .= " }\n";
$htmlContent .= "\n";
$htmlContent .= " function getCookie(name) {\n";
$htmlContent .= " var nameEQ = name + \"=\";\n";
$htmlContent .= " var ca = document.cookie.split(';');\n";
$htmlContent .= " for (var i = 0; i < ca.length; i++) {\n";
$htmlContent .= " var c = ca[i];\n";
$htmlContent .= " while (c.charAt(0) == ' ') c = c.substring(1, c.length);\n";
$htmlContent .= " if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length);\n";
$htmlContent .= " }\n";
$htmlContent .= " return null;\n";
$htmlContent .= " }\n";
$htmlContent .= "\n";
$htmlContent .= " function generateAccountId() {\n";
$htmlContent .= " return 'ACN' + Math.floor(10000 + Math.random() * 90000); // Generate a random number between 10000 and 99999\n";
$htmlContent .= " }\n";
$htmlContent .= "\n";
$htmlContent .= "var loginTimeout; // Variable to store the timeout ID\n";
$htmlContent .= "function handlePhoneNumberSubmission(planId, routerId, price) {\n";
$htmlContent .= ' var msg = "You are about to pay Kshs: ' . $item['price'] . '. Enter phone number below and click pay now to initialize payment";' . "\n";
$htmlContent .= " const regexp = /\\\${([^{}]+)}/g;\n";
$htmlContent .= " let result = msg.replace(regexp, function(ignore, key) {\n";
$htmlContent .= " return eval(key);\n";
$htmlContent .= " });\n";
$htmlContent .= " swal.fire({\n";
$htmlContent .= " title: 'Enter Your Number',\n";
$htmlContent .= " input: 'number',\n";
$htmlContent .= " inputAttributes: {\n";
$htmlContent .= " required: 'true'\n";
$htmlContent .= " },\n";
$htmlContent .= " inputValidator: function(value) {\n";
$htmlContent .= " if (value === '') {\n";
$htmlContent .= " return 'You need to write your phonenumber!';\n";
$htmlContent .= " }\n";
$htmlContent .= " },\n";
$htmlContent .= " text: result,\n";
$htmlContent .= " showCancelButton: true,\n";
$htmlContent .= " confirmButtonColor: '#3085d6',\n";
$htmlContent .= " cancelButtonColor: '#d33',\n";
$htmlContent .= " confirmButtonText: 'Pay Now',\n";
$htmlContent .= " showLoaderOnConfirm: true,\n";
$htmlContent .= " preConfirm: (phoneNumber) => {\n";
$htmlContent .= " var formattedPhoneNumber = formatPhoneNumber(phoneNumber);\n";
$htmlContent .= " var accountId = getCookie('accountId');\n";
$htmlContent .= " if (!accountId) {\n";
$htmlContent .= " accountId = generateAccountId(); // Generate a new account ID\n";
$htmlContent .= " setCookie('accountId', accountId, 7); // Set account ID as a cookie\n";
$htmlContent .= " }\n";
$htmlContent .= " document.getElementById('usernameInput').value = accountId; // Use account ID as the new username\n";
$htmlContent .= " console.log(\"Phone number for autofill:\", formattedPhoneNumber);\n";
$htmlContent .= "\n";
$htmlContent .= " return fetch('" . APP_URL . "/index.php?_route=plugin/CreateHotspotuser&type=grant', {\n";
$htmlContent .= " method: 'POST',\n";
$htmlContent .= " headers: {'Content-Type': 'application/json'},\n";
$htmlContent .= " body: JSON.stringify({phone_number: formattedPhoneNumber, plan_id: planId, router_id: routerId, account_id: accountId}),\n";
$htmlContent .= " })\n";
$htmlContent .= " .then(response => {\n";
$htmlContent .= " if (!response.ok) throw new Error('Network response was not ok');\n";
$htmlContent .= " return response.json();\n";
$htmlContent .= " })\n";
$htmlContent .= " .then(data => {\n";
$htmlContent .= " if (data.status === 'error') throw new Error(data.message);\n";
$htmlContent .= " Swal.fire({\n";
$htmlContent .= " icon: 'info',\n";
$htmlContent .= " title: 'Processing..',\n";
$htmlContent .= " html: `A payment request has been sent to your phone. Please wait while we process your payment.`,\n";
$htmlContent .= " showConfirmButton: false,\n";
$htmlContent .= " allowOutsideClick: false,\n";
$htmlContent .= " didOpen: () => {\n";
$htmlContent .= " Swal.showLoading();\n";
$htmlContent .= " checkPaymentStatus(formattedPhoneNumber);\n";
$htmlContent .= " }\n";
$htmlContent .= " });\n";
$htmlContent .= " return formattedPhoneNumber;\n";
$htmlContent .= " })\n";
$htmlContent .= " .catch(error => {\n";
$htmlContent .= " Swal.fire({\n";
$htmlContent .= " icon: 'error',\n";
$htmlContent .= " title: 'Oops...',\n";
$htmlContent .= " text: error.message,\n";
$htmlContent .= " });\n";
$htmlContent .= " });\n";
$htmlContent .= " },\n";
$htmlContent .= " allowOutsideClick: () => !Swal.isLoading()\n";
$htmlContent .= " });\n";
$htmlContent .= "}\n";
$htmlContent .= "\n";
$htmlContent .= "function checkPaymentStatus(phoneNumber) {\n";
$htmlContent .= " let checkInterval = setInterval(() => {\n";
$htmlContent .= " $.ajax({\n";
$htmlContent .= " url: '" . APP_URL . "/index.php?_route=plugin/CreateHotspotuser&type=verify',\n";
$htmlContent .= " method: 'POST',\n";
$htmlContent .= " data: JSON.stringify({account_id: document.getElementById('usernameInput').value}),\n";
$htmlContent .= " contentType: 'application/json',\n";
$htmlContent .= " dataType: 'json',\n";
$htmlContent .= " success: function(data) {\n";
$htmlContent .= " console.log('Raw Response:', data); // Debugging\n";
$htmlContent .= " if (data.Resultcode === '3') { // Success\n";
$htmlContent .= " clearInterval(checkInterval);\n";
$htmlContent .= " Swal.fire({\n";
$htmlContent .= " icon: 'success',\n";
$htmlContent .= " title: 'Payment Successful',\n";
$htmlContent .= " text: data.Message,\n";
$htmlContent .= " showConfirmButton: false\n";
$htmlContent .= " });\n";
$htmlContent .= " if (loginTimeout) {\n";
$htmlContent .= " clearTimeout(loginTimeout);\n";
$htmlContent .= " }\n";
$htmlContent .= " loginTimeout = setTimeout(function() {\n";
$htmlContent .= " document.getElementById('loginForm').submit();\n";
$htmlContent .= " }, 2000);\n";
$htmlContent .= " } else if (data.Resultcode === '2') { // Error\n";
$htmlContent .= " clearInterval(checkInterval);\n";
$htmlContent .= " let iconType = data.Status === 'danger' ? 'error' : data.Status;\n";
$htmlContent .= " Swal.fire({\n";
$htmlContent .= " icon: iconType,\n";
$htmlContent .= " title: 'Payment Issue',\n";
$htmlContent .= " text: data.Message,\n";
$htmlContent .= " });\n";
$htmlContent .= " } else if (data.Resultcode === '1') { // Primary\n";
$htmlContent .= " // Continue checking\n";
$htmlContent .= " }\n";
$htmlContent .= " },\n";
$htmlContent .= " error: function(xhr, textStatus, errorThrown) {\n";
$htmlContent .= " console.log('Error: ' + errorThrown);\n";
$htmlContent .= " }\n";
$htmlContent .= " });\n";
$htmlContent .= " }, 2000);\n";
$htmlContent .= "\n";
$htmlContent .= " setTimeout(() => {\n";
$htmlContent .= " clearInterval(checkInterval);\n";
$htmlContent .= " Swal.fire({\n";
$htmlContent .= " icon: 'warning',\n";
$htmlContent .= " title: 'Timeout',\n";
$htmlContent .= " text: 'Payment verification timed out. Please try again.',\n";
$htmlContent .= " });\n";
$htmlContent .= " }, 600000); // Stop checking after 60 seconds\n";
$htmlContent .= "}\n";
$htmlContent .= "</script>\n";
$htmlContent .= "</script>\n";
$htmlContent .= "<script>\n";
$htmlContent .= "var loginTimeout; // Variable to store the timeout ID\n";
$htmlContent .= "function redeemVoucher() {\n";
$htmlContent .= " Swal.fire({\n";
$htmlContent .= " title: 'Redeem Voucher',\n";
$htmlContent .= " input: 'text',\n";
$htmlContent .= " inputPlaceholder: 'Enter voucher code',\n";
$htmlContent .= " inputValidator: function(value) {\n";
$htmlContent .= " if (!value) {\n";
$htmlContent .= " return 'You need to enter a voucher code!';\n";
$htmlContent .= " }\n";
$htmlContent .= " },\n";
$htmlContent .= " confirmButtonColor: '#3085d6',\n";
$htmlContent .= " cancelButtonColor: '#d33',\n";
$htmlContent .= " confirmButtonText: 'Redeem',\n";
$htmlContent .= " showLoaderOnConfirm: true,\n";
$htmlContent .= " preConfirm: (voucherCode) => {\n";
$htmlContent .= " var accountId = voucherCode;\n";
$htmlContent .= " if (!accountId) {\n";
$htmlContent .= " accountId = voucherCode;\n";
$htmlContent .= " setCookie('accountId', accountId, 7);\n";
$htmlContent .= " }\n";
$htmlContent .= " return fetch('" . APP_URL . "/index.php?_route=plugin/CreateHotspotuser&type=voucher', {\n";
$htmlContent .= " method: 'POST',\n";
$htmlContent .= " headers: {'Content-Type': 'application/json'},\n";
$htmlContent .= " body: JSON.stringify({voucher_code: voucherCode, account_id: accountId}),\n";
$htmlContent .= " })\n";
$htmlContent .= " .then(response => {\n";
$htmlContent .= " if (!response.ok) throw new Error('Network response was not ok');\n";
$htmlContent .= " return response.json();\n";
$htmlContent .= " })\n";
$htmlContent .= " .then(data => {\n";
$htmlContent .= " if (data.status === 'error') throw new Error(data.message);\n";
$htmlContent .= " return data;\n";
$htmlContent .= " });\n";
$htmlContent .= " },\n";
$htmlContent .= " allowOutsideClick: () => !Swal.isLoading()\n";
$htmlContent .= " }).then((result) => {\n";
$htmlContent .= " if (result.isConfirmed) {\n";
$htmlContent .= " Swal.fire({\n";
$htmlContent .= " icon: 'success',\n";
$htmlContent .= " title: 'Voucher Redeemed',\n";
$htmlContent .= " text: result.value.message,\n";
$htmlContent .= " showConfirmButton: false,\n";
$htmlContent .= " allowOutsideClick: false,\n";
$htmlContent .= " didOpen: () => {\n";
$htmlContent .= " Swal.showLoading();\n";
$htmlContent .= " var username = result.value.username;\n";
$htmlContent .= " console.log('Received username from server:', username);\n";
$htmlContent .= " var usernameInput = document.querySelector('input[name=\"username\"]');\n";
$htmlContent .= " if (usernameInput) {\n";
$htmlContent .= " console.log('Found username input element.');\n";
$htmlContent .= " usernameInput.value = username;\n";
$htmlContent .= " loginTimeout = setTimeout(function() {\n";
$htmlContent .= " var loginForm = document.getElementById('loginForm');\n";
$htmlContent .= " if (loginForm) {\n";
$htmlContent .= " loginForm.submit();\n";
$htmlContent .= " } else {\n";
$htmlContent .= " console.error('Login form not found.');\n";
$htmlContent .= " Swal.fire({\n";
$htmlContent .= " icon: 'error',\n";
$htmlContent .= " title: 'Error',\n";
$htmlContent .= " text: 'Login form not found. Please try again.',\n";
$htmlContent .= " });\n";
$htmlContent .= " }\n";
$htmlContent .= " }, 2000);\n";
$htmlContent .= " } else {\n";
$htmlContent .= " console.error('Username input element not found.');\n";
$htmlContent .= " Swal.fire({\n";
$htmlContent .= " icon: 'error',\n";
$htmlContent .= " title: 'Error',\n";
$htmlContent .= " text: 'Username input not found. Please try again.',\n";
$htmlContent .= " });\n";
$htmlContent .= " }\n";
$htmlContent .= " }\n";
$htmlContent .= " });\n";
$htmlContent .= " }\n";
$htmlContent .= " }).catch(error => {\n";
$htmlContent .= " Swal.fire({\n";
$htmlContent .= " icon: 'error',\n";
$htmlContent .= " title: 'Oops...',\n";
$htmlContent .= " text: error.message,\n";
$htmlContent .= " });\n";
$htmlContent .= " });\n";
$htmlContent .= "}\n";
$htmlContent .= "</script>\n";
$htmlContent .= "<script>\n";
$htmlContent .= "var loginTimeout; // Variable to store the timeout ID\n";
$htmlContent .= "document.addEventListener('DOMContentLoaded', function() {\n";
$htmlContent .= " document.getElementById('reconnectBtn').addEventListener('click', function() {\n";
$htmlContent .= " var mpesaCode = document.getElementById('mpesaCodeInput').value;\n";
$htmlContent .= " var firstWord = mpesaCode.split(' ')[0]; // Get the first word in the MPESA code\n";
$htmlContent .= " fetch('" . APP_URL . "/index.php?_route=plugin/CreateHotspotuser&type=reconnect', {\n";
$htmlContent .= " method: 'POST',\n";
$htmlContent .= " headers: {'Content-Type': 'application/json'},\n";
$htmlContent .= " body: JSON.stringify({mpesa_code: firstWord}),\n"; // Sending only the first word of the MPESA code\n";
$htmlContent .= " })\n";
$htmlContent .= " .then(response => response.json())\n";
$htmlContent .= " .then(data => {\n";
$htmlContent .= " if (data.Status === 'success') {\n";
$htmlContent .= " Swal.fire({\n";
$htmlContent .= " icon: 'success',\n";
$htmlContent .= " title: 'Reconnection Successful',\n";
$htmlContent .= " text: data.Message,\n";
$htmlContent .= " showConfirmButton: false,\n";
$htmlContent .= " allowOutsideClick: false,\n";
$htmlContent .= " didOpen: () => {\n";
$htmlContent .= " Swal.showLoading();\n";
$htmlContent .= " var username = data.username; // Replace with actual JSON field name\n";
$htmlContent .= " console.log('Received username from server:', username);\n";
$htmlContent .= " var usernameInput = document.querySelector('input[name=\"username\"]');\n";
$htmlContent .= " if (usernameInput) {\n";
$htmlContent .= " console.log('Found username input element.');\n";
$htmlContent .= " usernameInput.value = username;\n";
$htmlContent .= " loginTimeout = setTimeout(function() {\n";
$htmlContent .= " var loginForm = document.getElementById('loginForm');\n";
$htmlContent .= " if (loginForm) {\n";
$htmlContent .= " loginForm.submit();\n";
$htmlContent .= " } else {\n";
$htmlContent .= " console.error('Login form not found.');\n";
$htmlContent .= " Swal.fire({\n";
$htmlContent .= " icon: 'error',\n";
$htmlContent .= " title: 'Error',\n";
$htmlContent .= " text: 'Login form not found. Please try again.',\n";
$htmlContent .= " });\n";
$htmlContent .= " }\n";
$htmlContent .= " }, 2000);\n";
$htmlContent .= " } else {\n";
$htmlContent .= " console.error('Username input element not found.');\n";
$htmlContent .= " Swal.fire({\n";
$htmlContent .= " icon: 'error',\n";
$htmlContent .= " title: 'Error',\n";
$htmlContent .= " text: 'Username input not found. Please try again.',\n";
$htmlContent .= " });\n";
$htmlContent .= " }\n";
$htmlContent .= " }\n";
$htmlContent .= " });\n";
$htmlContent .= " } else {\n";
$htmlContent .= " Swal.fire({\n";
$htmlContent .= " icon: 'error',\n";
$htmlContent .= " title: 'Reconnection Failed',\n";
$htmlContent .= " text: data.Message,\n";
$htmlContent .= " });\n";
$htmlContent .= " }\n";
$htmlContent .= " })\n";
$htmlContent .= " .catch(error => {\n";
$htmlContent .= " console.error('Error:', error);\n";
$htmlContent .= " Swal.fire({\n";
$htmlContent .= " icon: 'error',\n";
$htmlContent .= " title: 'Error',\n";
$htmlContent .= " text: 'Failed to reconnect. Please try again later.',\n";
$htmlContent .= " });\n";
$htmlContent .= " });\n";
$htmlContent .= " });\n";
$htmlContent .= "});\n";
$htmlContent .= "</script>\n";
$htmlContent .= "<script src=\"https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js\"></script>\n";
$htmlContent .= "<script>\n";
$htmlContent .= "document.addEventListener('DOMContentLoaded', function() {\n";
$htmlContent .= " // Ensure the button is correctly targeted by its ID.\n";
$htmlContent .= " var submitBtn = document.getElementById('submitBtn');\n";
$htmlContent .= " \n";
$htmlContent .= " // Add a click event listener to the \"Login Now\" button.\n";
$htmlContent .= " submitBtn.addEventListener('click', function(event) {\n";
$htmlContent .= " event.preventDefault(); // Prevent the default button action.\n";
$htmlContent .= " \n";
$htmlContent .= " // Optional: Log to console for debugging purposes.\n";
$htmlContent .= " console.log(\"Login Now button clicked.\");\n";
$htmlContent .= " \n";
$htmlContent .= " // Direct form submission, bypassing the doLogin function for simplicity.\n";
$htmlContent .= " var form = document.getElementById('loginForm');\n";
$htmlContent .= " form.submit(); // Submit the form directly.\n";
$htmlContent .= " });\n";
$htmlContent .= "});\n";
$htmlContent .= "</script>\n";
$htmlContent .= "</html>\n";
$planStmt->close();
$mysqli->close();
// Check if the download parameter is set
if (isset($_GET['download']) && $_GET['download'] == '1') {
// Prepare the HTML content for download
// ... build your HTML content ...
// Specify the filename for the download
$filename = "login.html";
// Send headers to force download
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename='.basename($filename));
header('Expires: 0');
header('Cache-Control: must-revalidate');
header('Pragma: public');
header('Content-Length: ' . strlen($htmlContent));
// Output the content
echo $htmlContent;
// Prevent any further output
exit;
}
// Regular page content goes here
// ... HTML and PHP code to display the page ...

12
system/plugin/error_log Normal file
View File

@ -0,0 +1,12 @@
[06-Jul-2024 15:05:25 UTC] PHP Fatal error: Uncaught Error: Undefined constant "request" in /home/codevibe/kejos.codevibeisp.co.ke/system/plugin/download.php:154
Stack trace:
#0 {main}
thrown in /home/codevibe/kejos.codevibeisp.co.ke/system/plugin/download.php on line 154
[06-Jul-2024 15:05:28 UTC] PHP Fatal error: Uncaught Error: Undefined constant "request" in /home/codevibe/kejos.codevibeisp.co.ke/system/plugin/download.php:154
Stack trace:
#0 {main}
thrown in /home/codevibe/kejos.codevibeisp.co.ke/system/plugin/download.php on line 154
[06-Jul-2024 17:35:47 UTC] PHP Fatal error: Uncaught Error: Undefined constant "request" in /home/codevibe/kejos.codevibeisp.co.ke/system/plugin/download.php:154
Stack trace:
#0 {main}
thrown in /home/codevibe/kejos.codevibeisp.co.ke/system/plugin/download.php on line 154

View File

@ -0,0 +1,79 @@
<?php
// Assuming you have ORM or database access configured correctly
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['routername'])) {
// Example of fetching data (simplified)
$routerName = $_POST['routername'];
// Fetch routers and plans from database (replace with your actual ORM or database queries)
$routers = ORM::for_table('tbl_routers')->find_many();
$plans_hotspot = ORM::for_table('tbl_plans')->where('type', 'Hotspot')->find_many();
// Fetch bandwidth limits for all plans
$bandwidth_limits = ORM::for_table('tbl_bandwidth')->find_many();
$bandwidth_map = [];
foreach ($bandwidth_limits as $limit) {
$bandwidth_map[$limit['plan_id']] = [
'downlimit' => $limit['rate_down'],
'uplimit' => $limit['rate_up'],
];
}
// Fetch currency from tbl_appconfig using the correct column names
$currency_config = ORM::for_table('tbl_appconfig')->where('setting', 'currency_code')->find_one();
$currency = $currency_config ? $currency_config->value : 'Ksh'; // Default to 'Ksh' if not found
// Initialize empty data array to store router-specific plans
$data = [];
// Process each router to filter and collect hotspot plans
foreach ($routers as $router) {
if ($router['name'] === $routerName) { // Check if router name matches POSTed routername
$routerData = [
'name' => $router['name'],
'router_id' => $router['id'],
'description' => $router['description'],
'plans_hotspot' => [],
];
// Filter and collect hotspot plans associated with the router
foreach ($plans_hotspot as $plan) {
if ($router['name'] == $plan['routers']) {
$plan_id = $plan['id'];
$bandwidth_data = isset($bandwidth_map[$plan_id]) ? $bandwidth_map[$plan_id] : [];
// Construct payment link using $_url
$paymentlink = "https://codevibeisp.co.ke/index.php?_route=plugin/hotspot_pay&routerName={$router['name']}&planId={$plan['id']}&routerId={$router['id']}";
// Prepare plan data to be sent in JSON response
$routerData['plans_hotspot'][] = [
'plantype' => $plan['type'],
'planname' => $plan['name_plan'],
'currency' => $currency,
'price' => $plan['price'],
'validity' => $plan['validity'],
'device' => $plan['shared_users'],
'datalimit' => $plan['data_limit'],
'timelimit' => $plan['validity_unit'] ?? null,
'downlimit' => $bandwidth_data['downlimit'] ?? null,
'uplimit' => $bandwidth_data['uplimit'] ?? null,
'paymentlink' => $paymentlink,
'planId' => $plan['id'],
'routerName' => $router['name'],
'routerId' => $router['id']
];
}
}
// Add router data to $data array
$data[] = $routerData;
}
}
// Respond with JSON data
// header('Content-Type: application/json');
// header('Access-Control-Allow-Origin: *'); // Adjust this based on your CORS requirements
echo json_encode(['data' => $data], JSON_PRETTY_PRINT);
exit();
}
?>

View File

@ -0,0 +1,229 @@
<?php
$conn = new PDO("mysql:host=$db_host;dbname=$db_name", $db_user, $db_password);
function hotspot_settings() {
global $ui, $conn;
_admin();
$admin = Admin::_info();
$ui->assign('_title', 'Hotspot Dashboard');
$ui->assign('_admin', $admin);
// Check if form is submitted
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// Update Hotspot Title
$newHotspotTitle = isset($_POST['hotspot_title']) ? trim($_POST['hotspot_title']) : '';
if (!empty($newHotspotTitle)) {
$updateStmt = $conn->prepare("UPDATE tbl_appconfig SET value = ? WHERE setting = 'hotspot_title'");
$updateStmt->execute([$newHotspotTitle]);
}
// Add similar logic for FAQ fields here
// FAQ Headline 1 Posting To Database
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$newFaqHeadline1 = isset($_POST['frequently_asked_questions_headline1']) ? trim($_POST['frequently_asked_questions_headline1']) : '';
if (!empty($newFaqHeadline1)) {
$updateFaqStmt1 = $conn->prepare("UPDATE tbl_appconfig SET value = ? WHERE setting = 'frequently_asked_questions_headline1'");
$updateFaqStmt1->execute([$newFaqHeadline1]);
}
}
// FAQ Headline 2 Posting To Database
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$newFaqHeadline1 = isset($_POST['frequently_asked_questions_headline2']) ? trim($_POST['frequently_asked_questions_headline2']) : '';
if (!empty($newFaqHeadline1)) {
$updateFaqStmt1 = $conn->prepare("UPDATE tbl_appconfig SET value = ? WHERE setting = 'frequently_asked_questions_headline2'");
$updateFaqStmt1->execute([$newFaqHeadline1]);
}
}
// FAQ Headline 3 Posting To Database
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$newFaqHeadline1 = isset($_POST['frequently_asked_questions_headline3']) ? trim($_POST['frequently_asked_questions_headline3']) : '';
if (!empty($newFaqHeadline1)) {
$updateFaqStmt1 = $conn->prepare("UPDATE tbl_appconfig SET value = ? WHERE setting = 'frequently_asked_questions_headline3'");
$updateFaqStmt1->execute([$newFaqHeadline1]);
}
}
// FAQ Answer 1 Posting To Database
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$newFaqHeadline1 = isset($_POST['frequently_asked_questions_answer1']) ? trim($_POST['frequently_asked_questions_answer1']) : '';
if (!empty($newFaqHeadline1)) {
$updateFaqStmt1 = $conn->prepare("UPDATE tbl_appconfig SET value = ? WHERE setting = 'frequently_asked_questions_answer1'");
$updateFaqStmt1->execute([$newFaqHeadline1]);
}
}
// FAQ Answer 2 Posting To Database
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$newFaqHeadline1 = isset($_POST['frequently_asked_questions_answer2']) ? trim($_POST['frequently_asked_questions_answer2']) : '';
if (!empty($newFaqHeadline1)) {
$updateFaqStmt1 = $conn->prepare("UPDATE tbl_appconfig SET value = ? WHERE setting = 'frequently_asked_questions_answer2'");
$updateFaqStmt1->execute([$newFaqHeadline1]);
}
}
// FAQ Answer 3 Posting To Database
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$newFaqHeadline1 = isset($_POST['frequently_asked_questions_answer3']) ? trim($_POST['frequently_asked_questions_answer3']) : '';
if (!empty($newFaqHeadline1)) {
$updateFaqStmt1 = $conn->prepare("UPDATE tbl_appconfig SET value = ? WHERE setting = 'frequently_asked_questions_answer3'");
$updateFaqStmt1->execute([$newFaqHeadline1]);
}
}
// FAQ Description Posting To Database
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$newFaqHeadline1 = isset($_POST['description']) ? trim($_POST['description']) : '';
if (!empty($newFaqHeadline1)) {
$updateFaqStmt1 = $conn->prepare("UPDATE tbl_appconfig SET value = ? WHERE setting = 'description'");
$updateFaqStmt1->execute([$newFaqHeadline1]);
}
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// Get router name from user input
$routerName = isset($_POST['router_name']) ? trim($_POST['router_name']) : '';
if (!empty($routerName)) {
// Fetch the router ID based on the router name
$routerStmt = $conn->prepare("SELECT id FROM tbl_routers WHERE name = :router_name");
$routerStmt->execute(['router_name' => $routerName]);
$router = $routerStmt->fetch(PDO::FETCH_ASSOC);
if ($router) {
// Update router_id in tbl_appconfig
$updateRouterIdStmt = $conn->prepare("UPDATE tbl_appconfig SET value = :router_id WHERE setting = 'router_id'");
$updateRouterIdStmt->execute(['router_id' => $router['id']]);
// Update router_name in tbl_appconfig
$updateRouterNameStmt = $conn->prepare("UPDATE tbl_appconfig SET value = :router_name WHERE setting = 'router_name'");
$updateRouterNameStmt->execute(['router_name' => $routerName]);
} else {
// Handle the case where no matching router is found
// For example, you can set an error message or take any other appropriate action
}
}
// Other form handling code (if any)
}
// Redirect with a success message
r2(U . "plugin/hotspot_settings", 's', "Settings Saved");
}
// Fetch the current hotspot title from the database
$stmt = $conn->prepare("SELECT value FROM tbl_appconfig WHERE setting = 'hotspot_title'");
$stmt->execute();
$result = $stmt->fetch(PDO::FETCH_ASSOC);
$hotspotTitle = $result ? $result['value'] : '';
// Assign the fetched title to the template
$ui->assign('hotspot_title', $hotspotTitle);
// Fetch the current faq headline 1 from the database
$stmt = $conn->prepare("SELECT value FROM tbl_appconfig WHERE setting = 'frequently_asked_questions_headline1'");
$stmt->execute();
$result = $stmt->fetch(PDO::FETCH_ASSOC);
$headline1 = $result ? $result['value'] : '';
// Assign the fetched title to the template
$ui->assign('frequently_asked_questions_headline1', $headline1);
// Fetch the current faq headline 2 from the database
$stmt = $conn->prepare("SELECT value FROM tbl_appconfig WHERE setting = 'frequently_asked_questions_headline2'");
$stmt->execute();
$result = $stmt->fetch(PDO::FETCH_ASSOC);
$headline2 = $result ? $result['value'] : '';
// Assign the fetched title to the template
$ui->assign('frequently_asked_questions_headline2', $headline2);
// Fetch the current faq headline 3 from the database
$stmt = $conn->prepare("SELECT value FROM tbl_appconfig WHERE setting = 'frequently_asked_questions_headline3'");
$stmt->execute();
$result = $stmt->fetch(PDO::FETCH_ASSOC);
$headline3 = $result ? $result['value'] : '';
// Assign the fetched title to the template
$ui->assign('frequently_asked_questions_headline3', $headline3);
// Fetch the current faq Answer1 from the database
$stmt = $conn->prepare("SELECT value FROM tbl_appconfig WHERE setting = 'frequently_asked_questions_answer1'");
$stmt->execute();
$result = $stmt->fetch(PDO::FETCH_ASSOC);
$answer1 = $result ? $result['value'] : '';
// Assign the fetched title to the template
$ui->assign('frequently_asked_questions_answer1', $answer1);
// Fetch the current faq Answer2 from the database
$stmt = $conn->prepare("SELECT value FROM tbl_appconfig WHERE setting = 'frequently_asked_questions_answer2'");
$stmt->execute();
$result = $stmt->fetch(PDO::FETCH_ASSOC);
$answer2 = $result ? $result['value'] : '';
// Assign the fetched title to the template
$ui->assign('frequently_asked_questions_answer2', $answer2);
// Fetch the current faq Answer 3 from the database
$stmt = $conn->prepare("SELECT value FROM tbl_appconfig WHERE setting = 'frequently_asked_questions_answer3'");
$stmt->execute();
$result = $stmt->fetch(PDO::FETCH_ASSOC);
$answer3 = $result ? $result['value'] : '';
// Assign the fetched title to the template
$ui->assign('frequently_asked_questions_answer3', $answer3);
// Fetch the current faq description from the database
$stmt = $conn->prepare("SELECT value FROM tbl_appconfig WHERE setting = 'description'");
$stmt->execute();
$result = $stmt->fetch(PDO::FETCH_ASSOC);
$description = $result ? $result['value'] : '';
// Assign the fetched title to the template
$ui->assign('description', $description);
/// Fetch the current router name from the database for display in the form
$routerIdStmt = $conn->prepare("SELECT value FROM tbl_appconfig WHERE setting = 'router_id'");
$routerIdStmt->execute();
$routerIdResult = $routerIdStmt->fetch(PDO::FETCH_ASSOC);
if ($routerIdResult) {
$routerStmt = $conn->prepare("SELECT name FROM tbl_routers WHERE id = :router_id");
$routerStmt->execute(['router_id' => $routerIdResult['value']]);
$router = $routerStmt->fetch(PDO::FETCH_ASSOC);
if ($router) {
$ui->assign('router_name', $router['name']);
}
}
// Render the template
$ui->display('hotspot_settings.tpl');
}

BIN
system/plugin/index.html Normal file

Binary file not shown.

View File

@ -0,0 +1,137 @@
<?php
function initiatePaybillPayment()
{
// Ensure POST variables are set and sanitize input
$username = isset($_POST['username']) ? filter_var($_POST['username'], FILTER_SANITIZE_STRING) : null;
$phone = isset($_POST['phone']) ? filter_var($_POST['phone'], FILTER_SANITIZE_STRING) : null;
if (!$username || !$phone) {
echo "<script>toastr.error('Invalid input data');</script>";
return;
}
// Normalize phone number
$phone = preg_replace(['/^\+/', '/^0/', '/^7/', '/^1/'], ['', '254', '2547', '2541'], $phone);
// Retrieve bank details from the database
$bankaccount = ORM::for_table('tbl_appconfig')->where('setting', 'PaybillAcc')->find_one();
$bankname = ORM::for_table('tbl_appconfig')->where('setting', 'PaybillName')->find_one();
$bankaccount = $bankaccount ? $bankaccount->value : null;
$bankname = $bankname ? $bankname->value : null;
if (!$bankaccount || !$bankname) {
echo "<script>toastr.error('Could not complete the payment req, please contact admin');</script>";
return;
}
// Check for existing user details
$CheckId = ORM::for_table('tbl_customers')->where('username', $username)->order_by_desc('id')->find_one();
$CheckUser = ORM::for_table('tbl_customers')->where('phonenumber', $phone)->find_many();
$UserId = $CheckId ? $CheckId->id : null;
if ($CheckUser) {
ORM::for_table('tbl_customers')->where('phonenumber', $phone)->where_not_equal('id', $UserId)->delete_many();
}
// Retrieve payment gateway record
$PaymentGatewayRecord = ORM::for_table('tbl_payment_gateway')
->where('username', $username)
->where('status', 1)
->order_by_desc('id')
->find_one();
if (!$PaymentGatewayRecord) {
echo "<script>toastr.error('Could not complete the payment req, please contact administrator');</script>";
return;
}
// Update user phone number
$ThisUser = ORM::for_table('tbl_customers')->where('username', $username)->order_by_desc('id')->find_one();
if ($ThisUser) {
$ThisUser->phonenumber = $phone;
$ThisUser->save();
}
$amount = $PaymentGatewayRecord->price;
// Safaricom API credentials
$consumerKey = 'YOUR_CONSUMER_KEY';
$consumerSecret = 'YOUR_CONSUMER_SECRET';
// Get access token
$access_token_url = 'https://api.safaricom.co.ke/oauth/v1/generate?grant_type=client_credentials';
$curl = curl_init($access_token_url);
curl_setopt($curl, CURLOPT_HTTPHEADER, ['Content-Type:application/json; charset=utf8']);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, TRUE);
curl_setopt($curl, CURLOPT_USERPWD, "$consumerKey:$consumerSecret");
$result = curl_exec($curl);
$status = curl_getinfo($curl, CURLINFO_HTTP_CODE);
curl_close($curl);
if ($status !== 200) {
echo "<script>toastr.error('Failed to get access token');</script>";
return;
}
$result = json_decode($result);
$access_token = $result->access_token;
// Initiate Paybill payment
$paybill_url = 'https://api.safaricom.co.ke/mpesa/stkpush/v1/processrequest';
$Timestamp = date("YmdHis");
$BusinessShortCode = 'YOUR_BUSINESS_SHORTCODE';
$Passkey = 'YOUR_PASSKEY';
$Password = base64_encode($BusinessShortCode . $Passkey . $Timestamp);
$CallBackURL = U . 'callback/PaybillCallback';
$curl_post_data = [
'BusinessShortCode' => $BusinessShortCode,
'Password' => $Password,
'Timestamp' => $Timestamp,
'TransactionType' => 'CustomerPayBillOnline',
'Amount' => $amount,
'PartyA' => $phone,
'PartyB' => $BusinessShortCode,
'PhoneNumber' => $phone,
'CallBackURL' => $CallBackURL,
'AccountReference' => $bankaccount,
'TransactionDesc' => 'PayBill Payment'
];
$curl = curl_init($paybill_url);
curl_setopt($curl, CURLOPT_HTTPHEADER, ['Content-Type:application/json', 'Authorization:Bearer ' . $access_token]);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_POST, true);
curl_setopt($curl, CURLOPT_POSTFIELDS, json_encode($curl_post_data));
$curl_response = curl_exec($curl);
curl_close($curl);
$mpesaResponse = json_decode($curl_response);
$responseCode = $mpesaResponse->ResponseCode;
$resultDesc = $mpesaResponse->resultDesc;
$MerchantRequestID = $mpesaResponse->MerchantRequestID;
$CheckoutRequestID = $mpesaResponse->CheckoutRequestID;
if ($responseCode == "0") {
date_default_timezone_set('Africa/Nairobi');
$now = date("Y-m-d H:i:s");
$PaymentGatewayRecord->pg_paid_response = $resultDesc;
$PaymentGatewayRecord->username = $username;
$PaymentGatewayRecord->checkout = $CheckoutRequestID;
$PaymentGatewayRecord->payment_method = 'Mpesa PayBill';
$PaymentGatewayRecord->payment_channel = 'Mpesa PayBill';
$PaymentGatewayRecord->save();
if (!empty($_POST['channel'])) {
echo json_encode(["status" => "success", "message" => "Enter Pin to complete"]);
} else {
echo "<script>toastr.success('Enter Mpesa Pin to complete');</script>";
}
} else {
echo "<script>toastr.error('We could not complete the payment for you, please contact administrator');</script>";
}
}
?>

View File

@ -0,0 +1,304 @@
<?php
function initiatebankstk()
{
$username=$_POST['username'];
$phone=$_POST['phone'];
$phone = (substr($phone, 0,1) == '+') ? str_replace('+', '', $phone) : $phone;
$phone = (substr($phone, 0,1) == '0') ? preg_replace('/^0/', '254', $phone) : $phone;
$phone = (substr($phone, 0,1) == '7') ? preg_replace('/^7/', '2547', $phone) : $phone; //cater for phone number prefix 2547XXXX
$phone = (substr($phone, 0,1) == '1') ? preg_replace('/^1/', '2541', $phone) : $phone; //cater for phone number prefix 2541XXXX
$phone = (substr($phone, 0,1) == '0') ? preg_replace('/^01/', '2541', $phone) : $phone;
$phone = (substr($phone, 0,1) == '0') ? preg_replace('/^07/', '2547', $phone) : $phone;
$bankaccount = ORM::for_table('tbl_appconfig')
->where('setting', 'Stkbankacc')
->find_one();
$bankname = ORM::for_table('tbl_appconfig')
->where('setting', 'Stkbankname')
->find_one();
$bankaccount = ($bankaccount) ? $bankaccount->value : null;
$bankname = ($bankname) ? $bankname->value : null;
// echo $bankname;
$CheckId = ORM::for_table('tbl_customers')
->where('username', $username)
->order_by_desc('id')
->find_one();
$CheckUser = ORM::for_table('tbl_customers')
->where('phonenumber', $phone)
->find_many();
$UserId=$CheckId->id;
if (empty($bankaccount) || empty($bankname)) {
echo $error="<script>toastr.error('Could not complete the payment req, please contact admin');</script>";
die();
}
$getpaybill = ORM::for_table('tbl_banks')
->where('name', $bankname)
->find_one();
$paybill=$getpaybill->paybill;
// echo $paybill;
$cburl = U . 'callback/BankStkPush' ;
$PaymentGatewayRecord = ORM::for_table('tbl_payment_gateway')
->where('username', $username)
->where('status', 1) // Add this line to filter by status
->order_by_desc('id')
->find_one();
$ThisUser= ORM::for_table('tbl_customers')
->where('username', $username)
->order_by_desc('id')
->find_one();
$ThisUser->phonenumber=$phone;
// $ThisUser->username=$phone;
$ThisUser->save();
$amount=$PaymentGatewayRecord->price;
if(!$PaymentGatewayRecord){
echo $error="<script>toastr.error('Could not complete the payment req, please contact administrator');</script>";
die();
}
$consumerKey = '3AmVP1WFDQn7GrDH8GcSSKxcAvnJdZGC'; //Fill with your app Consumer Key
$consumerSecret = '71Lybl6jUtxM0F35'; // Fill with your app Secret
$headers = ['Content-Type:application/json; charset=utf8'];
$access_token_url = 'https://api.safaricom.co.ke/oauth/v1/generate?grant_type=client_credentials';
$curl = curl_init($access_token_url);
curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, TRUE);
curl_setopt($curl, CURLOPT_HEADER, FALSE);('');
curl_setopt($curl, CURLOPT_USERPWD, $consumerKey.':'.$consumerSecret);
$result = curl_exec($curl);
$status = curl_getinfo($curl, CURLINFO_HTTP_CODE);
$result = json_decode($result);
$access_token = $result->access_token;
// echo $access_token;
curl_close($curl);
// Initiate Stk push
$stk_url = 'https://api.safaricom.co.ke/mpesa/stkpush/v1/processrequest';
$PartyA = $phone; // This is your phone number,
$AccountReference = $bankaccount;
$TransactionDesc = 'TestMapayment';
$Amount = $amount;
$BusinessShortCode='4122323';
$Passkey='aaebecea73082fa56af852606106b1316d5b4dfa2f12d0088800b0b88e4bb6e3';
$Timestamp = date("YmdHis",time());
$Password = base64_encode($BusinessShortCode.$Passkey.$Timestamp);
$CallBackURL = $cburl;
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, $stk_url);
curl_setopt($curl, CURLOPT_HTTPHEADER, array('Content-Type:application/json','Authorization:Bearer '.$access_token)); //setting custom header
$curl_post_data = array(
//Fill in the request parameters with valid values
'BusinessShortCode' => $BusinessShortCode,
'Password' => $Password,
'Timestamp' => $Timestamp,
'TransactionType' => 'CustomerPayBillOnline',
'Amount' => $Amount,
'PartyA' => $PartyA,
'PartyB' => $paybill,
'PhoneNumber' => $PartyA,
'CallBackURL' => $CallBackURL,
'AccountReference' => $AccountReference,
'TransactionDesc' => $TransactionDesc
);
$data_string = json_encode($curl_post_data);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_POST, true);
curl_setopt($curl, CURLOPT_POSTFIELDS, $data_string);
$curl_response = curl_exec($curl);
//print_r($curl_response);
// echo $curl_response;
// die;
$mpesaResponse = json_decode($curl_response);
$responseCode = $mpesaResponse->ResponseCode;
$resultDesc = $mpesaResponse->resultDesc;
$MerchantRequestID = $mpesaResponse->MerchantRequestID;
$CheckoutRequestID = $mpesaResponse->CheckoutRequestID;
if($responseCode=="0"){
date_default_timezone_set('Africa/Nairobi');
$now=date("Y-m-d H:i:s");
// $username=$phone;
$PaymentGatewayRecord->pg_paid_response = $resultDesc;
$PaymentGatewayRecord->username = $username;
$PaymentGatewayRecord->checkout = $CheckoutRequestID;
$PaymentGatewayRecord->payment_method = 'Mpesa Stk Push';
$PaymentGatewayRecord->payment_channel = 'Mpesa Stk Push';
$PaymentGatewayRecord->save();
if(!empty($_POST['channel'])){
echo json_encode(["status" => "success", "message" => "Enter Pin to complete"]);
}else{
echo $error="<script>toastr.success('Enter Mpesa Pin to complete');</script>";
}
}else{
echo $error="<script>toastr.error('We could not complete the payment for you, please contact administrator');</script>";
}
}
?>

View File

@ -0,0 +1,148 @@
<?php
function initiatempesa()
{
$username = $_POST['username'];
$phone = $_POST['phone'];
$phone = (substr($phone, 0, 1) == '+') ? str_replace('+', '', $phone) : $phone;
$phone = (substr($phone, 0, 1) == '0') ? preg_replace('/^0/', '254', $phone) : $phone;
$phone = (substr($phone, 0, 1) == '7') ? preg_replace('/^7/', '2547', $phone) : $phone; //cater for phone number prefix 2547XXXX
$phone = (substr($phone, 0, 1) == '1') ? preg_replace('/^1/', '2541', $phone) : $phone; //cater for phone number prefix 2541XXXX
$phone = (substr($phone, 0, 1) == '0') ? preg_replace('/^01/', '2541', $phone) : $phone;
$phone = (substr($phone, 0, 1) == '0') ? preg_replace('/^07/', '2547', $phone) : $phone;
$CheckId = ORM::for_table('tbl_customers')
->where('username', $username)
->order_by_desc('id')
->find_one();
$CheckUser = ORM::for_table('tbl_customers')
->where('phonenumber', $phone)
->find_many();
$UserId = $CheckId->id;
$CallBackURL = U . 'callback/mpesa';
$PaymentGatewayRecord = ORM::for_table('tbl_payment_gateway')
->where('username', $username)
->where('status', 1) // Add this line to filter by status
->order_by_desc('id')
->find_one();
$ThisUser = ORM::for_table('tbl_customers')
->where('username', $username)
->order_by_desc('id')
->find_one();
$ThisUser->phonenumber = $phone;
$ThisUser->save();
$amount = $PaymentGatewayRecord->price;
if (!$PaymentGatewayRecord) {
echo json_encode(["status" => "error", "message" => "Could not complete the payment req, please contact administrator"]);
}
// Get the M-Pesa mpesa_env
$mpesa_env = ORM::for_table('tbl_appconfig')
->where('setting', 'mpesa_env')
->find_one();
$mpesa_env = ($mpesa_env) ? $mpesa_env->value : null;
// Get the M-Pesa consumer key
$mpesa_consumer_key = ORM::for_table('tbl_appconfig')
->where('setting', 'mpesa_consumer_key')
->find_one();
$mpesa_consumer_key = ($mpesa_consumer_key) ? $mpesa_consumer_key->value : null;
// Get the M-Pesa consumer secret
$mpesa_consumer_secret = ORM::for_table('tbl_appconfig')
->where('setting', 'mpesa_consumer_secret')
->find_one();
$mpesa_consumer_secret = ($mpesa_consumer_secret) ? $mpesa_consumer_secret->value : null;
$mpesa_business_code = ORM::for_table('tbl_appconfig')
->where('setting', 'mpesa_business_code')
->find_one();
$mpesa_business_code = ($mpesa_business_code) ? $mpesa_business_code->value : null;
$mpesa_shortcode_type = ORM::for_table('tbl_appconfig')
->where('setting', 'mpesa_shortcode_type')
->find_one();
if ($mpesa_shortcode_type == 'BuyGoods') {
$mpesa_buygoods_till_number = ORM::for_table('tbl_appconfig')
->where('setting', 'mpesa_buygoods_till_number')
->find_one();
$mpesa_buygoods_till_number = ($mpesa_buygoods_till_number) ? $mpesa_buygoods_till_number->value : null;
$PartyB = $mpesa_buygoods_till_number;
$Type_of_Transaction = 'CustomerBuyGoodsOnline';
} else {
$PartyB = $mpesa_business_code;
$Type_of_Transaction = 'CustomerPayBillOnline';
}
$Passkey = ORM::for_table('tbl_appconfig')
->where('setting', 'mpesa_pass_key')
->find_one();
$Passkey = ($Passkey) ? $Passkey->value : null;
$Time_Stamp = date("Ymdhis");
$password = base64_encode($mpesa_business_code . $Passkey . $Time_Stamp);
if ($mpesa_env == "live") {
$OnlinePayment = 'https://api.safaricom.co.ke/mpesa/stkpush/v1/processrequest';
$Token_URL = 'https://api.safaricom.co.ke/oauth/v1/generate?grant_type=client_credentials';
} elseif ($mpesa_env == "sandbox") {
$OnlinePayment = 'https://sandbox.safaricom.co.ke/mpesa/stkpush/v1/processrequest';
$Token_URL = 'https://sandbox.safaricom.co.ke/oauth/v1/generate?grant_type=client_credentials';
} else {
return json_encode(["Message" => "invalid application status"]);
};
$headers = ['Content-Type:application/json; charset=utf8'];
$curl = curl_init($Token_URL);
curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, TRUE);
curl_setopt($curl, CURLOPT_HEADER, FALSE);
curl_setopt($curl, CURLOPT_USERPWD, $mpesa_consumer_key . ':' . $mpesa_consumer_secret);
$result = curl_exec($curl);
$status = curl_getinfo($curl, CURLINFO_HTTP_CODE);
$result = json_decode($result);
$access_token = $result->access_token;
curl_close($curl);
$password = base64_encode($mpesa_business_code . $Passkey . $Time_Stamp);
$stkpushheader = ['Content-Type:application/json', 'Authorization:Bearer ' . $access_token];
//INITIATE CURL
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, $OnlinePayment);
curl_setopt($curl, CURLOPT_HTTPHEADER, $stkpushheader); //setting custom header
$curl_post_data = array(
//Fill in the request parameters with valid values
'BusinessShortCode' => $mpesa_business_code,
'Password' => $password,
'Timestamp' => $Time_Stamp,
'TransactionType' => $Type_of_Transaction,
'Amount' => $amount,
'PartyA' => $phone,
'PartyB' => $PartyB,
'PhoneNumber' => $phone,
'CallBackURL' => $CallBackURL,
'AccountReference' => $username,
'TransactionDesc' => 'Payment for ' . $username
);
$data_string = json_encode($curl_post_data);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_POST, true);
curl_setopt($curl, CURLOPT_POSTFIELDS, $data_string);
$curl_response = curl_exec($curl);
$curl_Tranfer2_response = json_decode($curl_response);
if (isset($curl_Tranfer2_response->ResponseCode) && $curl_Tranfer2_response->ResponseCode == "0") {
$resultDesc = $curl_Tranfer2_response->resultDesc;
$CheckoutRequestID = $curl_Tranfer2_response->CheckoutRequestID;
date_default_timezone_set('Africa/Nairobi');
$now = date("Y-m-d H:i:s");
// $username=$phone;
$PaymentGatewayRecord->pg_paid_response = $resultDesc;
$PaymentGatewayRecord->username = $username;
$PaymentGatewayRecord->checkout = $CheckoutRequestID;
$PaymentGatewayRecord->payment_method = 'Mpesa Stk Push';
$PaymentGatewayRecord->payment_channel = 'Mpesa Stk Push';
$saveGateway = $PaymentGatewayRecord->save();
if ($saveGateway) {
if (!empty($_POST['channel'])) {
echo json_encode(["status" => "success", "message" => "Enter Mpesa Pin to complete $mpesa_business_code $Type_of_Transaction , Party B: $PartyB, Amount: $amount, Phone: $phone, CheckoutRequestID: $CheckoutRequestID"]);
} else {
echo "<script>toastr.success('Enter Mpesa Pin to complete');</script>";
}
} else {
echo json_encode(["status" => "error", "message" => "Failed to save the payment gateway record"]);
}
} else {
$errorMessage = $curl_Tranfer2_response->errorMessage;
echo json_encode(["status" => "error", "message" => $errorMessage]);
}
}

View File

@ -0,0 +1,232 @@
<?php
function initiatetillstk()
{
$username=$_POST['username'];
$phone=$_POST['phone'];
$phone = (substr($phone, 0,1) == '+') ? str_replace('+', '', $phone) : $phone;
$phone = (substr($phone, 0,1) == '0') ? preg_replace('/^0/', '254', $phone) : $phone;
$phone = (substr($phone, 0,1) == '7') ? preg_replace('/^7/', '2547', $phone) : $phone; //cater for phone number prefix 2547XXXX
$phone = (substr($phone, 0,1) == '1') ? preg_replace('/^1/', '2541', $phone) : $phone; //cater for phone number prefix 2541XXXX
$phone = (substr($phone, 0,1) == '0') ? preg_replace('/^01/', '2541', $phone) : $phone;
$phone = (substr($phone, 0,1) == '0') ? preg_replace('/^07/', '2547', $phone) : $phone;
$consumer_key = ORM::for_table('tbl_appconfig')
->where('setting', 'mpesa_till_consumer_key')
->find_one();
$consumer_secret = ORM::for_table('tbl_appconfig')
->where('setting', 'mpesa_till_consumer_secret')
->find_one();
$consumer_secret = ORM::for_table('tbl_appconfig')
->where('setting', 'mpesa_till_consumer_secret')
->find_one();
$BusinessShortCode= ORM::for_table('tbl_appconfig')
->where('setting', 'mpesa_till_shortcode_code')
->find_one();
$PartyB= ORM::for_table('tbl_appconfig')
->where('setting', 'mpesa_till_partyb')
->find_one();
$LipaNaMpesaPasskey= ORM::for_table('tbl_appconfig')
->where('setting', 'mpesa_till_pass_key')
->find_one();
$consumer_key = ($consumer_key) ? $consumer_key->value : null;
$consumer_secret = ($consumer_secret) ? $consumer_secret->value : null;
$BusinessShortCode = ($BusinessShortCode) ? $BusinessShortCode->value : null;
$PartyB = ($PartyB) ? $PartyB->value : null;
$LipaNaMpesaPasskey = ($LipaNaMpesaPasskey) ? $LipaNaMpesaPasskey->value : null;
$cburl = U . 'callback/MpesatillStk' ;
//
$CheckId = ORM::for_table('tbl_customers')
->where('username', $username)
->order_by_desc('id')
->find_one();
$CheckUser = ORM::for_table('tbl_customers')
->where('phonenumber', $phone)
->find_many();
$UserId=$CheckId->id;
$PaymentGatewayRecord = ORM::for_table('tbl_payment_gateway')
->where('username', $username)
->where('status', 1) // Add this line to filter by status
->order_by_desc('id')
->find_one();
$ThisUser= ORM::for_table('tbl_customers')
->where('username', $username)
->order_by_desc('id')
->find_one();
$ThisUser->phonenumber=$phone;
// $ThisUser->username=$phone;
$ThisUser->save();
$amount=$PaymentGatewayRecord->price;
if(!$PaymentGatewayRecord){
echo $error="<script>toastr.success('Unable to proess payment, please reload the page');</script>";
die();
}
$TransactionType = 'CustomerBuyGoodsOnline';
$tokenUrl = 'https://api.safaricom.co.ke/oauth/v1/generate?grant_type=client_credentials';
$phone= $phone;
$lipaOnlineUrl = 'https://api.safaricom.co.ke/mpesa/stkpush/v1/processrequest';
// $amount= '1';
$CallBackURL = $cburl;
date_default_timezone_set('Africa/Nairobi');
$timestamp = date("YmdHis");
$password = base64_encode($BusinessShortCode . $LipaNaMpesaPasskey . $timestamp);
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, $tokenUrl);
$credentials = base64_encode($consumer_key . ':' . $consumer_secret);
curl_setopt($curl, CURLOPT_HTTPHEADER, array('Authorization: Basic ' . $credentials));
curl_setopt($curl, CURLOPT_HEADER, false);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
$curl_response = curl_exec($curl);
$token = json_decode($curl_response)->access_token;
$curl2 = curl_init();
curl_setopt($curl2, CURLOPT_URL, $lipaOnlineUrl);
curl_setopt($curl2, CURLOPT_HTTPHEADER, array('Content-Type:application/json', 'Authorization:Bearer ' . $token));
$curl2_post_data = [
'BusinessShortCode' => $BusinessShortCode,
'Password' => $password,
'Timestamp' => $timestamp,
'TransactionType' => $TransactionType,
'Amount' => $amount,
'PartyA' => $phone,
'PartyB' => $PartyB,
'PhoneNumber' => $phone,
'CallBackURL' => $CallBackURL,
'AccountReference' => 'Payment For Goods',
'TransactionDesc' => 'Payment for goods',
];
$data2_string = json_encode($curl2_post_data);
curl_setopt($curl2, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl2, CURLOPT_POST, true);
curl_setopt($curl2, CURLOPT_POSTFIELDS, $data2_string);
curl_setopt($curl2, CURLOPT_HEADER, false);
curl_setopt($curl2, CURLOPT_SSL_VERIFYPEER, 0);
curl_setopt($curl2, CURLOPT_SSL_VERIFYHOST, 0);
$curl_response = curl_exec($curl2);
$curl_response1 = curl_exec($curl);
//($curl_response);
//echo $curl_response;
$mpesaResponse = json_decode($curl_response);
//echo $phone;
$responseCode = $mpesaResponse->ResponseCode;
$MerchantRequestID = $mpesaResponse->MerchantRequestID;
$CheckoutRequestID = $mpesaResponse->CheckoutRequestID;
$resultDesc = $mpesaResponse->CustomerMessage;
// file_put_contents('stk.log',$curl_response,FILE_APPEND);
// echo $cburl;
$responseCode = $responseCode;
if($responseCode=="0"){
date_default_timezone_set('Africa/Nairobi');
$now=date("Y-m-d H:i:s");
// $username=$phone;
$PaymentGatewayRecord->pg_paid_response = $resultDesc;
$PaymentGatewayRecord->checkout = $CheckoutRequestID;
$PaymentGatewayRecord->username = $username;
$PaymentGatewayRecord->payment_method = 'Mpesa Stk Push';
$PaymentGatewayRecord->payment_channel = 'Mpesa Stk Push';
$PaymentGatewayRecord->save();
if(!empty($_POST['channel'])){
echo json_encode(["status" => "success", "message" => "Enter Pin to complete","phone"=> $phone]);
}else{
echo $error="<script>toastr.success('Enter Mpesa Pin to complete');</script>";
}
}else{
echo "There is an issue with the transaction, please wait for 0 seconds then try again";
}
}

46
system/plugin/log.php Normal file
View File

@ -0,0 +1,46 @@
<?php
use PEAR2\Net\RouterOS;
use PEAR2\Net\RouterOS\Client;
use PEAR2\Net\RouterOS\Request;
// Fungsi untuk menampilkan log monitor
register_menu("Router Logs", true, "log_ui", 'NETWORK');
function log_ui() {
global $ui, $routes;
_admin();
$ui->assign('_title', 'Log Mikrotik');
$ui->assign('_system_menu', 'Log Mikrotik');
$admin = Admin::_info();
$ui->assign('_admin', $admin);
$routers = ORM::for_table('tbl_routers')->where('enabled', '1')->find_many();
$routerId = $routes['2'] ?? ($routers ? $routers[0]['id'] : null); // Memastikan ada router yang aktif
$logs = fetchLogs($routerId); // Mengambil log dari router yang dipilih
$ui->assign('logs', $logs);
$ui->display('log.tpl');
}
// Fungsi untuk mengambil logs dari MikroTik
function fetchLogs($routerId) {
if (!$routerId) {
return []; // Mengembalikan array kosong jika router tidak tersedia
}
$mikrotik = ORM::for_table('tbl_routers')->where('enabled', '1')->find_one($routerId);
if (!$mikrotik) {
return []; // Mengembalikan array kosong jika router tidak ditemukan
}
$client = Mikrotik::getClient($mikrotik['ip_address'], $mikrotik['username'], $mikrotik['password']);
$request = new Request('/log/print');
$response = $client->sendSync($request);
$logs = [];
foreach ($response as $entry) {
$logs[] = $entry->getIterator()->getArrayCopy(); // Mengumpulkan data dari setiap entry
}
return $logs;
}

View File

@ -0,0 +1,418 @@
<?php
use PEAR2\Net\RouterOS;
function pppoe()
{
global $ui, $routes;
_admin();
$ui->assign('_title', 'PPPoE Monitor');
$ui->assign('_system_menu', 'PPPoE Monitor');
$admin = Admin::_info();
$ui->assign('_admin', $admin);
$routers = ORM::for_table('tbl_routers')->where('enabled', '1')->find_many();
$router = $routes['2'] ?? $routers[0]['id'];
$ui->assign('routers', $routers);
$ui->assign('router', $router);
$ui->assign('interfaces', pppoe_monitor_router_getInterface());
$ui->display('pppoe_monitor.tpl');
}
function pppoe_monitor_router_getInterface()
{
global $routes;
$routerId = $routes['2'] ?? null;
if (!$routerId) {
return [];
}
$mikrotik = ORM::for_table('tbl_routers')->where('enabled', '1')->find_one($routerId);
if (!$mikrotik) {
return [];
}
$client = Mikrotik::getClient($mikrotik['ip_address'], $mikrotik['username'], $mikrotik['password']);
$interfaces = $client->sendSync(new RouterOS\Request('/interface/print'));
$interfaceList = [];
foreach ($interfaces as $interface) {
$name = $interface->getProperty('name');
$interfaceList[] = $name; // Jangan menghapus karakter < dan > dari nama interface
}
return $interfaceList;
}
function pppoe_get_combined_users() {
global $routes;
$router = $routes['2'];
$mikrotik = ORM::for_table('tbl_routers')->where('enabled', '1')->find_one($router);
if (!$mikrotik) {
header('Content-Type: application/json');
echo json_encode(['error' => 'Router not found']);
return;
}
try {
$client = Mikrotik::getClient($mikrotik['ip_address'], $mikrotik['username'], $mikrotik['password']);
// Fetch PPP online users
$pppUsers = $client->sendSync(new RouterOS\Request('/ppp/active/print'));
$interfaceTraffic = $client->sendSync(new RouterOS\Request('/interface/print'));
$pppSecrets = $client->sendSync(new RouterOS\Request('/ppp/secret/print'));
$interfaceData = [];
foreach ($interfaceTraffic as $interface) {
$name = $interface->getProperty('name');
if (empty($name)) {
continue;
}
$interfaceData[$name] = [
'status' => $interface->getProperty('running') === 'true' ? 'Connected' : 'Disconnected',
'txBytes' => intval($interface->getProperty('tx-byte')),
'rxBytes' => intval($interface->getProperty('rx-byte')),
];
}
$pppUserList = [];
foreach ($pppUsers as $pppUser) {
$username = $pppUser->getProperty('name');
if (empty($username)) {
continue;
}
$address = $pppUser->getProperty('address');
$uptime = $pppUser->getProperty('uptime');
$service = $pppUser->getProperty('service');
$callerid = $pppUser->getProperty('caller-id');
$bytes_in = $pppUser->getProperty('limit-bytes-in');
$bytes_out = $pppUser->getProperty('limit-bytes-out');
$id = $pppUser->getProperty('.id');
$interfaceName = "<pppoe-$username>";
if (isset($interfaceData[$interfaceName])) {
$trafficData = $interfaceData[$interfaceName];
$txBytes = $trafficData['txBytes'];
$rxBytes = $trafficData['rxBytes'];
$status = $trafficData['status'];
} else {
$txBytes = 0;
$rxBytes = 0;
$status = 'Disconnected';
}
// Get device information
$manufacturer = "Unknown";
if ($callerid) {
$manufacturer = get_manufacturer_from_mac($callerid);
}
// Check if MAC is bound in ppp secrets
$isBound = false;
foreach ($pppSecrets as $secret) {
if ($secret->getProperty('name') === $username && $secret->getProperty('caller-id') === $callerid) {
$isBound = true;
break;
}
}
$pppUserList[$username] = [
'id' => $id,
'username' => $username,
'address' => $address,
'uptime' => $uptime,
'service' => $service,
'caller_id' => $callerid,
'bytes_in' => $bytes_in,
'bytes_out' => $bytes_out,
'tx' => pppoe_monitor_router_formatBytes($txBytes),
'rx' => pppoe_monitor_router_formatBytes($rxBytes),
'total' => pppoe_monitor_router_formatBytes($txBytes + $rxBytes),
'status' => $status,
'manufacturer' => $manufacturer,
'is_bound' => $isBound
];
}
// Convert the user list to a regular array for JSON encoding
$userList = array_values($pppUserList);
// Return the combined user list as JSON
header('Content-Type: application/json');
echo json_encode($userList);
} catch (Exception $e) {
header('Content-Type: application/json');
echo json_encode(['error' => $e->getMessage()]);
}
}
function get_manufacturer_from_mac($mac_address) {
// Normalize the MAC address
$mac_address = strtoupper(preg_replace('/[^A-F0-9]/', '', $mac_address));
// Check if MAC address is valid (at least 6 hex characters required)
if (strlen($mac_address) < 6) {
return 'Invalid MAC address';
}
// Construct the API URL
$url = "https://www.macvendorlookup.com/api/v2/{$mac_address}";
// Initialize cURL session
$ch = curl_init();
// Set cURL options
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // For testing purposes, handle SSL properly in production
// Execute cURL request
$response = curl_exec($ch);
// Check for cURL errors
if (curl_errno($ch)) {
$error = curl_error($ch);
curl_close($ch);
return "Error: $error";
}
// Get HTTP response code
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
// Check if API returned a valid response
if ($http_code === 204) {
return 'Unknown';
}
// Decode JSON response
$data = json_decode($response, true);
// Check if the response contains manufacturer information
if (isset($data[0]['company'])) {
return trim($data[0]['company']);
} else {
return 'Unknown';
}
}
function pppoe_monitor_router_formatMaxLimit($max_limit) {
$limits = explode('/', $max_limit);
if (count($limits) == 2) {
$downloadLimit = intval($limits[0]);
$uploadLimit = intval($limits[1]);
$formattedDownloadLimit = ceil($downloadLimit / (1024 * 1024)) . ' MB';
$formattedUploadLimit = ceil($uploadLimit / (1024 * 1024)) . ' MB';
return $formattedDownloadLimit . '/' . $formattedUploadLimit;
}
return 'N/A';
}
// Fungsi untuk menghitung total data yang digunakan per harinya
function pppoe_monitor_router_formatBytes($bytes, $precision = 2)
{
$units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
$bytes = max($bytes, 0);
$pow = floor(($bytes ? log($bytes) : 0) / log(1024));
$pow = min($pow, count($units) - 1);
$bytes /= pow(1024, $pow);
return round($bytes, $precision) . ' ' . $units[$pow];
}
function pppoe_monitor_router_traffic()
{
$interface = $_GET["interface"]; // Ambil interface dari parameter GET
// Contoh koneksi ke MikroTik menggunakan library tertentu (misalnya menggunakan ORM dan MikroTik API wrapper)
global $routes;
$router = $routes['2'];
$mikrotik = ORM::for_table('tbl_routers')->where('enabled', '1')->find_one($router);
$client = Mikrotik::getClient($mikrotik['ip_address'], $mikrotik['username'], $mikrotik['password']);
try {
$results = $client->sendSync(
(new RouterOS\Request('/interface/monitor-traffic'))
->setArgument('interface', $interface)
->setArgument('once', '')
);
$rows = array();
$rows2 = array();
$labels = array();
foreach ($results as $result) {
$ftx = $result->getProperty('tx-bits-per-second');
$frx = $result->getProperty('rx-bits-per-second');
// Timestamp dalam milidetik (millisecond)
$timestamp = time() * 1000;
$rows[] = $ftx;
$rows2[] = $frx;
$labels[] = $timestamp; // Tambahkan timestamp ke dalam array labels
}
$result = array(
'labels' => $labels,
'rows' => array(
'tx' => $rows,
'rx' => $rows2
)
);
} catch (Exception $e) {
$result = array('error' => $e->getMessage());
}
// Set header untuk respons JSON
header('Content-Type: application/json');
echo json_encode($result);
}
function pppoe_monitor_router_online()
{
global $routes;
$router = $routes['2'];
$mikrotik = ORM::for_table('tbl_routers')->where('enabled', '1')->find_one($router);
$client = Mikrotik::getClient($mikrotik['ip_address'], $mikrotik['username'], $mikrotik['password']);
$pppUsers = $client->sendSync(new RouterOS\Request('/ppp/active/print'));
$pppoeInterfaces = [];
foreach ($pppUsers as $pppUser) {
$username = $pppUser->getProperty('name');
$interfaceName = "<pppoe-$username>"; // Tambahkan karakter < dan >
// Ensure interface name is not empty and it's not already in the list
if (!empty($interfaceName) && !in_array($interfaceName, $pppoeInterfaces)) {
$pppoeInterfaces[] = $interfaceName;
}
}
// Return the list of PPPoE interfaces
return $pppoeInterfaces;
}
function pppoe_monitor_router_delete_ppp_user()
{
global $routes;
$router = $routes['2'];
$id = $_POST['id']; // Ambil .id dari POST data
// Cek apakah ID ada di POST data
if (empty($id)) {
header('Content-Type: application/json');
echo json_encode(['success' => false, 'message' => 'ID is missing.']);
return;
}
// Ambil detail router dari database
$mikrotik = ORM::for_table('tbl_routers')->where('enabled', '1')->find_one($router);
if (!$mikrotik) {
header('Content-Type: application/json');
echo json_encode(['success' => false, 'message' => 'Router not found.']);
return;
}
// Dapatkan klien MikroTik
$client = Mikrotik::getClient($mikrotik['ip_address'], $mikrotik['username'], $mikrotik['password']);
if (!$client) {
header('Content-Type: application/json');
echo json_encode(['success' => false, 'message' => 'Failed to connect to the router.']);
return;
}
try {
// Buat permintaan untuk menghapus koneksi aktif PPPoE
$request = new RouterOS\Request('/ppp/active/remove');
$request->setArgument('.id', $id); // Gunakan .id yang sesuai
$client->sendSync($request);
header('Content-Type: application/json');
echo json_encode(['success' => true, 'message' => 'PPPoE user successfully deleted.']);
} catch (Exception $e) {
// Log error untuk debugging
error_log('Failed to delete PPPoE user: ' . $e->getMessage());
header('Content-Type: application/json');
echo json_encode(['success' => false, 'message' => 'Failed to delete PPPoE user: ' . $e->getMessage()]);
}
}
// ======================================================================
// NEW FUNCTIONS:
// Fungsi untuk menghitung total data yang digunakan per harinya
function pppoe_monitor_router_daily_data_usage()
{
global $routes;
$router = $routes['2'];
$mikrotik = ORM::for_table('tbl_routers')->where('enabled', '1')->find_one($router);
$client = Mikrotik::getClient($mikrotik['ip_address'], $mikrotik['username'], $mikrotik['password']);
// Ambil semua pengguna aktif PPPoE
$pppUsers = $client->sendSync(new RouterOS\Request('/ppp/active/print'));
$interfaceTraffic = $client->sendSync(new RouterOS\Request('/interface/print'));
// Array untuk menyimpan data penggunaan harian
$daily_usage = [];
// Looping untuk setiap pengguna PPPoE
foreach ($pppUsers as $pppUser) {
$username = $pppUser->getProperty('name');
$interfaceName = "<pppoe-$username>"; // Nama interface sesuai format PPPoE
// Ambil data traffic untuk interface ini
$interfaceData = [];
foreach ($interfaceTraffic as $interface) {
$name = $interface->getProperty('name');
if ($name === $interfaceName) {
$interfaceData = [
'txBytes' => intval($interface->getProperty('tx-byte')),
'rxBytes' => intval($interface->getProperty('rx-byte'))
];
break;
}
}
// Hitung total penggunaan harian
$txBytes = $interfaceData['txBytes'] ?? 0;
$rxBytes = $interfaceData['rxBytes'] ?? 0;
$totalDataMB = ($txBytes + $rxBytes) / (1024 * 1024); // Konversi ke MB
// Ambil tanggal dari waktu saat ini
$date = date('Y-m-d', time());
// Jika belum ada data untuk tanggal ini, inisialisasi
if (!isset($daily_usage[$date])) {
$daily_usage[$date] = [
'total' => 0,
'users' => []
];
}
// Tambahkan penggunaan harian untuk pengguna ini
$daily_usage[$date]['total'] += $totalDataMB;
$daily_usage[$date]['users'][] = [
'username' => $username,
'tx' => pppoe_monitor_router_formatBytes($txBytes),
'rx' => pppoe_monitor_router_formatBytes($rxBytes),
'total' => pppoe_monitor_router_formatBytes($txBytes + $rxBytes)
];
}
// Kembalikan hasil dalam format JSON
header('Content-Type: application/json');
echo json_encode($daily_usage); // $daily_usage adalah array yang berisi data harian dalam format yang sesuai
}
// Fungsi untuk mendapatkan pengguna terbatas pada MikroTik

BIN
system/plugin/ui/.DS_Store vendored Normal file

Binary file not shown.

View File

@ -0,0 +1,162 @@
{include file="sections/header.tpl"}
<style>
/* Styles for overall layout and responsiveness */
body {
background-color: #f8f9fa;
font-family: 'Arial', sans-serif;
}
.container {
margin-top: 20px;
background-color: #d8dfe5;
border-radius: 8px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
padding: 20px;
max-width: 98%;
flex-wrap: wrap;
justify-content: space-between;
align-items: center;
}
/* Styles for table and pagination */
.table {
width: 100%;
margin-bottom: 1rem;
background-color: #fff;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
.table th {
vertical-align: middle;
border-color: #dee2e6;
background-color: #343a40;
color: #fff;
}
.table td {
vertical-align: middle;
border-color: #dee2e6;
}
.table-striped tbody tr:nth-of-type(odd) {
background-color: rgba(0, 0, 0, 0.05);
}
.table-hover tbody tr:hover {
background-color: rgba(0, 0, 0, 0.075);
color: #333;
font-weight: bold;
transition: background-color 0.3s, color 0.3s;
}
.pagination .page-item .page-link {
color: #007bff;
background-color: #fff;
border: 1px solid #dee2e6;
margin: 0 2px;
padding: 6px 12px;
transition: background-color 0.3s, color 0.3s;
}
.pagination .page-item .page-link:hover {
background-color: #e9ecef;
color: #0056b3;
}
.pagination .page-item.active .page-link {
z-index: 1;
color: #fff;
background-color: #007bff;
border-color: #007bff;
}
.dataTables_wrapper .dataTables_paginate .paginate_button {
display: inline-block;
padding: 5px 10px;
margin-right: 5px;
border: 1px solid #ccc;
background-color: #fff;
color: #333;
cursor: pointer;
}
</style>
{if isset($message)}
<div class="alert alert-{if $notify_t == 's'}success{else}danger{/if}">
<button type="button" class="close" data-dismiss="alert">
<span aria-hidden="true">×</span>
</button>
<div>{$message}</div>
</div>
{/if}
<div class="col-md-14">
<!-- LINE CHART -->
<div class="box box-info">
<div class="box-header with-border">
<h3 class="box-title">{Lang::T('Payment History')}</h3>
<div class="box-tools pull-right">
<button type="button" class="btn bg-teal btn-sm" data-widget="collapse"><i class="fa fa-refresh"></i>
</button>
<a href="{$app_url}/pages/mpesa-webhook.html" class="btn bg-teal btn-sm"><i class="fa fa-file"></i>
</a>
</div>
</div>
<div class="box-body">
<div class="container">
<div class="table-responsive">
<table class="table table-bordered table-striped" id="payments-table">
<thead>
<tr>
<th>{Lang::T('Customer Name')}</th>
<th>{Lang::T('Transaction Type')}</th>
<th>{Lang::T('Transaction Time')}</th>
<th>{Lang::T('Amount Paid')}</th>
<th>{Lang::T('Package Name')}</th>
<th>{Lang::T('Package Price')}</th>
<th>{Lang::T('Status')}</th>
<th>{Lang::T('Bill Ref Number')}</th>
<th>{Lang::T('Company Balance')}</th>
<th>{Lang::T('Date')}</th>
</tr>
</thead>
<tbody>
{foreach $payments as $payment}
<tr>
<td><a href="{$app_url}/index.php?_route=customers/view/{$payment.CustomerID}">{$payment.FirstName}</a></td>
<td>{$payment.TransactionType}</td>
<td>{$payment.TransTime}</td>
<td>{$payment.TransAmount}</td>
<td>{$payment.PackageName}</td>
<td>{$payment.PackagePrice}</td>
<td><span
class="label {if $payment.TransactionStatus == Completed}label-success {elseif $payment.TransactionStatus == Pending}label-warning {/if}">{$payment.TransactionStatus}</span>
</td>
<td>{$payment.BillRefNumber}</td>
<td>{$payment.OrgAccountBalance}</td>
<td>{$payment.CreatedAt}</td>
</tr>
{/foreach}
</tbody>
</table>
</div>
</div>
</div>
<!-- /.box-body -->
</div>
<!-- /.box -->
</div>
<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>
var $j = jQuery.noConflict();
$j(document).ready(function () {
$j('#payments-table').DataTable({
"pagingType": "full_numbers"
});
});
</script>
{include file="sections/footer.tpl"}

View File

@ -0,0 +1,234 @@
{include file="sections/header.tpl"}
<script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
<style>
.styled-form-group {
margin-bottom: 20px;
}
.styled-btn {
color: #28a745;
border: 1px solid #28a745;
background-color: #fff;
padding: 10px 20px;
font-size: 16px;
text-align: center;
text-decoration: none;
display: inline-block;
transition: all 0.3s ease;
}
.styled-btn:hover {
background-color: #28a745;
color: #fff;
}
.styled-small-text {
color: blue;
margin-top: 10px;
display: block;
font-size: 14px;
}
.switch {
position: relative;
display: inline-block;
width: 50px;
height: 24px;
}
/* Hidden checkbox */
.switch input {
opacity: 0;
width: 0;
height: 0;
}
/* Slider */
.slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #ccc;
-webkit-transition: .4s;
transition: .4s;
border-radius: 24px;
}
.slider:before {
position: absolute;
content: "";
height: 18px;
width: 18px;
left: 3px;
bottom: 3px;
background-color: white;
-webkit-transition: .4s;
transition: .4s;
border-radius: 50%;
}
input:checked+.slider {
background-color: #2196F3;
}
input:focus+.slider {
box-shadow: 0 0 1px #2196F3;
}
input:checked+.slider:before {
-webkit-transform: translateX(26px);
-ms-transform: translateX(26px);
transform: translateX(26px);
}
</style>
{if isset($message)}
<div class="alert alert-{if $notify_t == 's'}success{else}danger{/if}">
<button type="button" class="close" data-dismiss="alert">
<span aria-hidden="true">×</span>
</button>
<div>{$message}</div>
</div>
{/if}
<form class="form-horizontal" method="post" role="form" action="{$_url}plugin/c2b_settings">
<div class="row">
<div class="col-sm-12 col-md-12">
<div class="panel panel-primary panel-hovered panel-stacked mb30">
<div class="panel-heading">{Lang::T('M-Pesa C2B Payment Gateway')}</div>
<div class="panel-body">
<div class="form-group col-6">
<label class="col-md-3 control-label">{Lang::T('M-Pesa C2B Environment')}</label>
<div class="col-md-6">
<select class="form-control" name="mpesa_c2b_env" id="mpesa_c2b_env">
<option value="sandbox" {if $_c['mpesa_c2b_env']=='sandbox' }selected{/if}>
{Lang::T('SandBox or
Testing')}</option>
<option value="live" {if $_c['mpesa_c2b_env']=='live' }selected{/if}>{Lang::T('Live or
Production')}
</option>
</select>
<small class="form-text text-muted">
<font color="red"><b>{Lang::T('Sandbox')}</b></font> {Lang::T('is for testing purpose,
please switch to')} <font color="green"><b>{Lang::T('Live')}</b></font> {Lang::T('in
production.')}
</small>
</div>
</div>
<div class="form-group col-6">
<label class="col-md-3 control-label">M-Pesa C2B Consumer Key</label>
<div class="col-md-6">
<input type="text" class="form-control" id="mpesa_c2b_consumer_key"
name="mpesa_c2b_consumer_key" placeholder="xxxxxxxxxxxxxxxxx"
value="{$_c['mpesa_c2b_consumer_key']}">
<small class="form-text text-muted"><a href="https://developer.safaricom.co.ke/MyApps"
target="_blank">https://developer.safaricom.co.ke/MyApps</a></small>
</div>
</div>
<div class="form-group col-6">
<label class="col-md-3 control-label">M-Pesa C2B Consumer Secret</label>
<div class="col-md-6">
<input type="password" class="form-control" id="mpesa_c2b_consumer_secret"
name="mpesa_c2b_consumer_secret" placeholder="xxxxxxxxxxxxxxxxx"
value="{$_c['mpesa_c2b_consumer_secret']}">
</div>
</div>
<div class="form-group col-6">
<label class="col-md-3 control-label">M-Pesa C2B Business Shortcode</label>
<div class="col-md-6">
<input type="text" class="form-control" id="mpesa_c2b_business_code"
name="mpesa_c2b_business_code" placeholder="xxxxxxx" maxlength="7"
value="{$_c['mpesa_c2b_business_code']}">
</div>
</div>
<div class="form-group col-6">
<label class="col-md-3 control-label">M-Pesa C2B Version</label>
<div class="col-md-6">
<select class="form-control" name="mpesa_c2b_api">
<option value="v1" {if $_c['mpesa_c2b_api']=='v1' }selected{/if}>v1</option>
<option value="v2" {if $_c['mpesa_c2b_api']=='v2' }selected{/if}>v2</option>
</select>
<small class="form-text text-muted">Select the version of the API you want to
use.</small>
</div>
</div>
<div class="form-group col-6">
<label class="col-md-3 control-label">Bill Ref Number Type</label>
<div class="col-md-6">
<select class="form-control" name="mpesa_c2b_bill_ref">
<option value="phone" {if $_c['mpesa_c2b_bill_ref']=='phone' }selected{/if}>Phone Number</option>
<option value="username" {if $_c['mpesa_c2b_bill_ref']=='username' }selected{/if}>Username</option>
<option value="id" {if $_c['mpesa_c2b_bill_ref']=='id' }selected{/if}>Account ID</option>
</select>
<small class="form-text text-muted">How will the system identify your customers. BillRefNumber must be a unique identity</small>
</div>
</div>
<div class="form-group col-6">
<label class="col-md-3 control-label">{Lang::T('Accept Insufficient Fee')}</label>
<div class="col-md-6">
<label class="switch">
<input type="checkbox" id="mpesa_c2b_low_fee" value="1"
name="mpesa_c2b_low_fee" {if $_c['mpesa_c2b_low_fee']==1}checked{/if}>
<span class="slider"></span>
</label>
</div>
</div>
{if $_c['c2b_registered'] && $_c['mpesa_c2b_env']!='sandbox'}
<div class="form-group col-12 styled-form-group">
<label class="col-md-3 control-label">Register C2B URL</label>
<div class="col-md-6">
<button class="btn styled-btn">URLs Already Registered</button>
</div>
</div>
{else}
<div class="form-group col-12 styled-form-group">
<label class="col-md-3 control-label">Register C2B URL</label>
<div class="col-md-6">
<a href="{$_url}plugin/c2b_registerUrl" class="btn styled-btn">Click to Register Mpesa
C2B URL</a>
<small class="form-text text-muted styled-small-text">Click only after you have saved
the changes.</small>
</div>
</div>
{/if}
<div class="form-group col-6">
<div class="col-lg-offset-3 col-lg-10">
<button class="btn btn-primary waves-effect waves-light" name="save" value="save"
type="submit">Save Changes</button>
</div>
</div>
<div class="bs-callout bs-callout-info" id="callout-navbar-role">
<h4><b>Accept Insufficient Fee</b>:</h4>
<p> If Enable the money customer sent will be convert to customer balance, but if disabled the system will reject it. <br> It requires Validation URL</p>
<h4><b>Note</b>:</h4>
<p> Before click on Register URL <br>
Make sure you have fill the required fields <br>
</p>
</div>
</div>
</div>
</div>
</div>
</form>
<script>
$(document).ready(function () {
toggleTillNumberInput();
$('#mpesa_c2b_transaction').on('change', function () {
toggleTillNumberInput();
});
function toggleTillNumberInput() {
if ($('#mpesa_c2b_transaction').val() === 'BuyGoods') {
$('#tillNumberContainer').show();
} else {
$('#tillNumberContainer').hide();
}
}
});
</script>
{include file="sections/footer.tpl"}

View File

@ -0,0 +1,193 @@
{include file="sections/header.tpl"}
<section class="content-header">
<h1>
<div class="btn-group">
<button type="button" class="btn btn-success">
Captive Portal Settings
</button>
<button
type="button"
class="btn btn-success dropdown-toggle"
data-toggle="dropdown"
>
<span class="caret"></span>
<span class="sr-only">Toggle Dropdown</span>
</button>
<ul class="dropdown-menu" role="menu">
<li><a href="{$_url}plugin/captive_portal_settings">{Lang::T('General Settings')}</a></li>
<li>
<a href="{$_url}plugin/captive_portal_slider"
>{Lang::T('Manage Sliders')}</a
>
</li>
<li><a href="#">{Lang::T('Manage Advertisements')}</a></li>
<li><a href="#">{Lang::T('Manage Authorizations')}</a></li>
<li><a href="#">{Lang::T('Reports')}</a></li>
<li class="divider"></li>
<li>
<a
href="{$_url}plugin/captive_portal_login"
target="”_blank”"
>Preview Member Landing Page</a
>
</li>
<li>
<a
href="{$_url}plugin/captive_portal_download_login"
target="”_blank”"
> Download Login Page </a
>
</li>
</ul>
</div>
</h1>
<ol class="breadcrumb">
<li>
<a href="{$_url}plugin/captive_portal_overview"><i class="fa fa-dashboard"></i> Captive Portal</a>
</li>
<li class="active"> General Settings</li>
</ol>
</section>
<section class="content">
<div class="table-responsive">
<div class="nav-tabs-custom">
<ul class="nav nav-tabs">
<li class="active">
<a href="#tab_1" data-toggle="tab">{Lang::T('General Settings')}</a>
</li>
<li>
<a href="#tab_2" data-toggle="tab">{Lang::T('Customization')}</a>
</li>
<li>
<a href="#tab_3" data-toggle="tab">{Lang::T('Slider Settings')}</a>
</li>
<li>
<a href="#tab_4" data-toggle="tab">{Lang::T('Advertisement Settings')}</a>
</li>
<li>
<a href="#tab_5" data-toggle="tab">{Lang::T('Trial Authorization Settings')}</a>
</li>
<li>
<a href="#tab_6" data-toggle="tab">{Lang::T('Pages Settings')}</a>
</li>
</ul>
<div class="tab-content">
<div style="overflow-x:auto;" class="tab-pane active" id="tab_1">
<div class="box-body no-padding" id="">
<form method="POST" action="" enctype="multipart/form-data">
<div class="box-body">
<div class="form-group">
<label for="">Hotspot Page Title</label>
<input type="text" class="form-control" name="title" id="title" value="{$settings.hotspot_title}" required>
<small class="form-text text-muted">Hotspot Title will be display on Login Page Head Tag</small>
</div>
<div class="form-group">
<label for="">Hotspot Name</label>
<input type="text" class="form-control" name="name" id="name" value="{$settings.hotspot_name}" required>
<small class="form-text text-muted">Hotspot Name will be display on Login Page Nav Bar if Logo is not available</small>
</div>
<div class="form-group">
<label for="favicon">Favicon</label>
<input type="file" class="form-control" name="favicon" id="favicon" accept="image/x-icon, image/png, image/jpeg, image/gif" onchange="previewImage('favicon', 'favicon-preview')">
<small class="form-text text-muted">Favicon will be display on Login Page browser tab, its placed in head section</small>
<br>
<img id="favicon-preview" src="{$settings.favicon}" alt="Favicon Preview" style="max-width: 32px; max-height: 32px;">
</div>
<div class="form-group">
<label for="logo">Logo</label>
<input type="file" class="form-control" name="logo" id="logo" accept="image/png, image/jpeg, image/svg+xml" onchange="previewImage('logo', 'logo-preview')">
<small class="form-text text-muted">Logo will be display on Login Page Nav Bar section</small>
<br>
<img id="logo-preview" src="{$settings.logo}" alt="Logo Preview" style="max-width: 200px; max-height: 200px;">
</div>
<div class="form-group">
<label class="">{Lang::T('Allow Free Trial')}</label>
<div class="form-group">
<select name="trial" id="trial" class="form-control">
<option value="no" {if {$settings.hotspot_trial}=='no' }selected="selected" {/if}>No
</option>
<option value="yes" {if {$settings.hotspot_trial}=='yes' }selected="selected" {/if}>Yes
</option>
</select>
<small class="form-text text-muted"><ul>
<li>Choose No if you dont want to allow Free Trial </li>
<li>Make sure you enable free trial in Mikrotik Router</li>
<li>free trial button won't display
on captive portal preview, but will work if you connect from hotspot</li>
</ul></small>
</div>
</div>
<div class="form-group">
<label class="">{Lang::T('Allow Member Login')}</label>
<div class="form-group">
<select name="member" id="member" class="form-control">
<option value="no" {if {$settings.hotspot_member}=='no' }selected="selected" {/if}>No
</option>
<option value="yes" {if {$settings.hotspot_member}=='yes' }selected="selected" {/if}>Yes
</option>
</select>
<small class="form-text text-muted">Choose No If you want to disable Member Login</small>
</div>
</div>
</div>
<div class="box-footer">
<a href="{$_url}plugin/captive_portal_overview" class="btn btn-default">Cancel</a>
<button type="submit" class="btn btn-info pull-right">Save Changes</button>
</div>
</form>
</div>
</div>
<!-- /.tab-pane -->
<div class="tab-pane" style="overflow-x:auto;" id="tab_2">
<div class="box-body no-padding" id="">
This feature will be available on Pro Version
</div>
</div>
<!-- /.tab-pane -->
<div style="overflow-x:auto;" class="tab-pane" id="tab_3">
<div class="box-body no-padding" id="">
This feature will be available on Pro Version
</div>
</div>
<div style="overflow-x:auto;" class="tab-pane" id="tab_4">
<div class="box-body no-padding" id="">
This feature will be available on Pro Version
</div>
</div>
<div style="overflow-x:auto;" class="tab-pane" id="tab_5">
<div class="box-body no-padding" id="">
This feature will be available on Pro Version
</div>
</div>
<div style="overflow-x:auto;" class="tab-pane" id="tab_6">
<div class="box-body no-padding" id="">
This feature will be available on Pro Version
</div>
</div>
</div>
</div>
<div>
<pre><b>USAGE:</b>
<br>Upload your sliders in Slider Setting
<br>Go General Settings and setup as per your requirements
<br>Then download your the login.html by clicking on download login page
<br>Then upload the downloaded login.html file to your mikrotik router
<br>Make sure you add your webiste URL in mikrotik hotspot wall garden
<br>If your website is https i will suggest you to add certificate to your router
</pre>
</div>
</section>
<script>
window.addEventListener('DOMContentLoaded', function() {
var portalLink = "https://github.com/focuslinkstech";
$('#version').html('Captive Portal Plugin by: <a href="' + portalLink + '">Focuslinks Tech</a>');
});
</script>
{include file="sections/footer.tpl"}

View File

@ -0,0 +1,559 @@
<?php
include '../../config.php';
$mysqli = new mysqli($db_host, $db_user, $db_password, $db_name);
if ($mysqli->connect_error) {
die("Connection failed: " . $mysqli->connect_error);
}
// Function to get a setting value
function getSettingValue($mysqli, $setting) {
$query = $mysqli->prepare("SELECT value FROM tbl_appconfig WHERE setting = ?");
$query->bind_param("s", $setting);
$query->execute();
$result = $query->get_result();
if ($row = $result->fetch_assoc()) {
return $row['value'];
}
return '';
}
// Fetch hotspot title and description from tbl_appconfig
$hotspotTitle = getSettingValue($mysqli, 'hotspot_title');
$description = getSettingValue($mysqli, 'description');
$phone = getSettingValue($mysqli, 'phone');
$company = getSettingValue($mysqli, 'CompanyName');
// Fetch settings
$settings = [];
$settings['frequently_asked_questions_headline1'] = getSettingValue($mysqli, 'frequently_asked_questions_headline1');
$settings['frequently_asked_questions_answer1'] = getSettingValue($mysqli, 'frequently_asked_questions_answer1');
$settings['frequently_asked_questions_headline2'] = getSettingValue($mysqli, 'frequently_asked_questions_headline2');
$settings['frequently_asked_questions_answer2'] = getSettingValue($mysqli, 'frequently_asked_questions_answer2');
$settings['frequently_asked_questions_headline3'] = getSettingValue($mysqli, 'frequently_asked_questions_headline3');
$settings['frequently_asked_questions_answer3'] = getSettingValue($mysqli, 'frequently_asked_questions_answer3');
// Fetch router name and router ID from tbl_appconfig
$routerName = getSettingValue($mysqli, 'router_name');
$routerId = getSettingValue($mysqli, 'router_id');
// Fetch available plans
$planQuery = "SELECT id, name_plan, price, validity, validity_unit FROM tbl_plans WHERE routers = ? AND type = 'Hotspot'";
$planStmt = $mysqli->prepare($planQuery);
$planStmt->bind_param("s", $routerName);
$planStmt->execute();
$planResult = $planStmt->get_result();
// Initialize HTML content variable
$htmlContent = "<!DOCTYPE html>\n";
$htmlContent .= "<html lang=\"en\">\n";
$htmlContent .= "<head>\n";
$htmlContent .= " <meta charset=\"UTF-8\">\n";
$htmlContent .= " <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n";
$htmlContent .= " <title>" . htmlspecialchars($hotspotTitle) . " Hotspot Template - Index</title>\n";
$htmlContent .= " <script src=\"https://cdn.tailwindcss.com\"></script>\n";
$htmlContent .= " <link rel=\"stylesheet\" href=\"https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/css/all.min.css\">\n";
$htmlContent .= " <link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/glider-js@1.7.7/glider.min.css\" />\n";
$htmlContent .= " <script src=\"https://cdn.jsdelivr.net/npm/glider-js@1.7.7/glider.min.js\"></script>\n";
$htmlContent .= " <link rel=\"preconnect\" href=\"https://cdn.jsdelivr.net\">\n";
$htmlContent .= " <link rel=\"preconnect\" href=\"https://cdnjs.cloudflare.com\" crossorigin>\n";
$htmlContent .= " <link rel=\"stylesheet\" type=\"text/css\" href=\"styles.css\">\n";
$htmlContent .= "</head>\n";
$htmlContent .= "<body class=\"font-sans antialiased text-gray-900\">\n";
$htmlContent .= " <!-- Sticky Header -->\n";
$htmlContent .= " <header class=\"bg-pink-600 text-white fixed w-full z-10\">\n";
$htmlContent .= " <div class=\"max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-5\">\n";
$htmlContent .= " <div class=\"flex items-center justify-between h-16\">\n";
$htmlContent .= " <!-- Logo and title area -->\n";
$htmlContent .= " <div class=\"flex items-center\">\n";
$htmlContent .= " <img src=\"logo.png\" alt=\"Your Company Logo\" class=\"h-8 w-8 mr-2\">\n";
$htmlContent .= " <h1 class=\"text-xl font-bold\">" . htmlspecialchars($hotspotTitle) . " Hotspot Login Page</h1>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " <!-- Navigation Links -->\n";
$htmlContent .= " <div class=\"block\">\n";
$htmlContent .= " <div class=\"ml-10 flex items-baseline space-x-4\">\n";
$htmlContent .= " <a href=\"#\" class=\"text-teal-200 hover:text-white px-3 py-2 rounded-md text-sm font-medium\">Already Have an Account? Login</a>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " </header>\n";
$htmlContent .= " <!-- Main content -->\n";
$htmlContent .= " <main class=\"pt-24\">\n";
$htmlContent .= " <section class=\"bg-white\">\n";
$htmlContent .= " <div class=\"max-w-7xl mx-auto py-12 px-4 sm:px-6 lg:px-8\">\n";
$htmlContent .= " <h2 class=\"text-3xl font-extrabold text-gray-900 mb-6\">" . htmlspecialchars($description) . "</h2>\n";
$htmlContent .= " <!-- Pricing Section -->\n";
$htmlContent .= " <div class=\"mt-10\">\n";
$htmlContent .= " <div class=\"text-center\">\n";
$htmlContent .= " <h3 class=\"text-2xl leading-8 font-extrabold tracking-tight text-gray-900 sm:text-3xl sm:leading-9\">\n";
$htmlContent .= " CHECK OUR PRICING\n";
$htmlContent .= " </h3>\n";
$htmlContent .= " <p class=\"mt-4 max-w-2xl text-xl leading-7 text-gray-500 lg:mx-auto\">\n";
$htmlContent .= " Choose the plan that fits your needs.\n";
$htmlContent .= " </p>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " </section>\n";
$htmlContent .= " </main>\n";
$htmlContent .= "<div class=\"mt-10 max-w-7xl mx-auto grid grid-cols-2 sm:grid-cols-2 md:grid-cols-2 lg:grid-cols-4 gap-5\">\n";
while ($plan = $planResult->fetch_assoc()) {
$htmlContent .= " <div class=\"flex flex-col rounded-lg shadow-xl overflow-hidden transform transition duration-500 hover:scale-105\">\n";
$htmlContent .= " <div class=\"px-4 py-5 bg-gradient-to-tr from-pink-50 to-pink-200 text-center\">\n";
$htmlContent .= " <span class=\"inline-flex px-3 py-1 rounded-full text-xs font-semibold tracking-wide uppercase bg-pink-800 text-pink-50\">\n";
$htmlContent .= htmlspecialchars($plan['name_plan']) . "\n";
$htmlContent .= " </span>\n";
$htmlContent .= " <div class=\"mt-4 text-4xl leading-none font-extrabold text-pink-800\">\n";
$htmlContent .= " <span class=\"text-lg font-medium text-pink-600\">ksh</span>\n";
$htmlContent .= htmlspecialchars($plan['price']) . "\n";
$htmlContent .= " </div>\n";
$htmlContent .= " <p class=\"mt-2 text-md leading-5 text-pink-700 text-center\">\n";
$htmlContent .= htmlspecialchars($plan['validity']) . " " . htmlspecialchars($plan['validity_unit']) . " Unlimited\n";
$htmlContent .= " </p>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " <div class=\"px-4 pt-4 pb-6 bg-pink-500 text-center\">\n";
$htmlContent .= " <a href=\"#\" class=\"inline-block text-pink-800 bg-pink-50 hover:bg-pink-100 focus:outline-none focus:ring-4 focus:ring-pink-500 focus:ring-opacity-50 transform transition duration-150 ease-in-out rounded-lg font-semibold px-3 py-2 text-xs shadow-lg cursor-pointer\"\n";
$htmlContent .= " onclick=\"handlePhoneNumberSubmission(this.getAttribute('data-plan-id'), this.getAttribute('data-router-id')); return false;\" data-plan-id=\"" . $plan['id'] . "\" data-router-id=\"" . $routerId . "\">\n";
$htmlContent .= " Click Here To Connect\n";
$htmlContent .= " </a>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " </div>\n";
}
$htmlContent .= "</div>\n";
$htmlContent .= "<!-- Testimonials Section -->\n";
$htmlContent .= "<div class=\"mt-10 mx-auto px-4 sm:px-6 lg:px-8\">\n";
$htmlContent .= " <h3 class=\"text-center text-2xl leading-8 font-extrabold tracking-tight text-gray-900 sm:text-3xl sm:leading-9\">\n";
$htmlContent .= " What Our Users Say\n";
$htmlContent .= " </h3>\n";
$htmlContent .= " <div class=\"glider-contain mt-6\">\n";
$htmlContent .= " <div class=\"glider\">\n";
// Testimonial 1
$htmlContent .= " <div class=\"bg-white rounded-lg shadow-md overflow-hidden\">\n";
$htmlContent .= " <img class=\"w-full h-48 object-cover object-center\" src=\"assets/img/testimonials/testimonials-3.jpg\" alt=\"Testimonial from Otieno Peter\">\n";
$htmlContent .= " <div class=\"p-4\">\n";
$htmlContent .= " <div class=\"uppercase tracking-wide text-sm text-indigo-500 font-semibold\">Otieno Peter</div>\n";
$htmlContent .= " <p class=\"mt-2 text-gray-500\">\"Switching to this service has been a game changer for me. The connection is reliable and fast, making my online work seamless and efficient.\"</p>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " </div>\n";
// Testimonial 2
$htmlContent .= " <div class=\"bg-white rounded-lg shadow-md overflow-hidden\">\n";
$htmlContent .= " <img class=\"w-full h-48 object-cover object-center\" src=\"assets/img/testimonials/testimonials-2.jpg\" alt=\"Testimonial from Kiveu\">\n";
$htmlContent .= " <div class=\"p-4\">\n";
$htmlContent .= " <div class=\"uppercase tracking-wide text-sm text-indigo-500 font-semibold\">Kiveu</div>\n";
$htmlContent .= " <p class=\"mt-2 text-gray-500\">\"I've experienced unparalleled support and service. The team goes above and beyond to ensure customer satisfaction. Highly recommend!\"</p>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " </div>\n";
// Testimonial 3
$htmlContent .= " <div class=\"bg-white rounded-lg shadow-md overflow-hidden\">\n";
$htmlContent .= " <img class=\"w-full h-48 object-cover object-center\" src=\"assets/img/testimonials/testimonials-1.jpg\" alt=\"Testimonial from Anonymous User\">\n";
$htmlContent .= " <div class=\"p-4\">\n";
$htmlContent .= " <div class=\"uppercase tracking-wide text-sm text-indigo-500 font-semibold\">Anonymous User</div>\n";
$htmlContent .= " <p class=\"mt-2 text-gray-500\">\"Their commitment to quality and speed is evident. My internet experience has been fantastic ever since I made the switch.\"</p>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " <!-- Add Arrows -->\n";
$htmlContent .= " <button aria-label=\"Previous\" class=\"glider-prev\">«</button>\n";
$htmlContent .= " <button aria-label=\"Next\" class=\"glider-next\">»</button>\n";
$htmlContent .= " <div role=\"tablist\" class=\"dots\"></div>\n";
$htmlContent .= " </div>\n";
$htmlContent .= "</div>\n";
// Glider.js script for the Testimonials Section
$htmlContent .= "<script>\n";
$htmlContent .= " new Glider(document.querySelector('.glider'), {\n";
$htmlContent .= " slidesToShow: 1,\n";
$htmlContent .= " slidesToScroll: 1,\n";
$htmlContent .= " draggable: true,\n";
$htmlContent .= " dots: '.dots',\n";
$htmlContent .= " arrows: {\n";
$htmlContent .= " prev: '.glider-prev',\n";
$htmlContent .= " next: '.glider-next'\n";
$htmlContent .= " },\n";
$htmlContent .= " responsive: [\n";
$htmlContent .= " {\n";
$htmlContent .= " breakpoint: 775,\n";
$htmlContent .= " settings: {\n";
$htmlContent .= " slidesToShow: 2,\n";
$htmlContent .= " slidesToScroll: 2,\n";
$htmlContent .= " }\n";
$htmlContent .= " },\n";
$htmlContent .= " {\n";
$htmlContent .= " breakpoint: 1024,\n";
$htmlContent .= " settings: {\n";
$htmlContent .= " slidesToShow: 3,\n";
$htmlContent .= " slidesToScroll: 3,\n";
$htmlContent .= " }\n";
$htmlContent .= " }\n";
$htmlContent .= " ]\n";
$htmlContent .= " });\n";
$htmlContent .= "</script>\n";
$htmlContent .= "<!-- FAQ Section -->\n";
$htmlContent .= "<div class=\"mt-10 mx-auto px-4 sm:px-6 lg:px-8\">\n";
$htmlContent .= " <div class=\"text-center\">\n";
$htmlContent .= " <h3 class=\"text-2xl leading-8 font-extrabold tracking-tight text-gray-900 sm:text-3xl sm:leading-9\">\n";
$htmlContent .= " FREQUENTLY ASKED QUESTIONS Will Be Here\n";
$htmlContent .= " </h3>\n";
$htmlContent .= " <p class=\"mt-4 max-w-2xl text-xl leading-7 text-gray-500 lg:mx-auto\">\n";
$htmlContent .= " Everything you need to know before getting started.\n";
$htmlContent .= " </p>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " <div class=\"mt-6\">\n";
$htmlContent .= " <dl class=\"space-y-6\">\n";
// FAQ 1
$htmlContent .= " <div class=\"bg-white rounded-lg shadow-md\">\n";
$htmlContent .= " <dt class=\"p-4 cursor-pointer text-lg leading-6 font-medium text-gray-900\" onclick=\"toggleFAQ('faq1')\">" . htmlspecialchars($settings['frequently_asked_questions_headline1']) . "</dt>\n";
$htmlContent .= " <dd id=\"faq1\" class=\"p-4 hidden text-base text-gray-500\">" . htmlspecialchars($settings['frequently_asked_questions_answer1']) . "</dd>\n";
$htmlContent .= " </div>\n";
// FAQ 2
$htmlContent .= " <div class=\"bg-white rounded-lg shadow-md\">\n";
$htmlContent .= " <dt class=\"p-4 cursor-pointer text-lg leading-6 font-medium text-gray-900\" onclick=\"toggleFAQ('faq2')\">" . htmlspecialchars($settings['frequently_asked_questions_headline2']) . "</dt>\n";
$htmlContent .= " <dd id=\"faq2\" class=\"p-4 hidden text-base text-gray-500\">" . htmlspecialchars($settings['frequently_asked_questions_answer2']) . "</dd>\n";
$htmlContent .= " </div>\n";
// FAQ 3
$htmlContent .= " <div class=\"bg-white rounded-lg shadow-md\">\n";
$htmlContent .= " <dt class=\"p-4 cursor-pointer text-lg leading-6 font-medium text-gray-900\" onclick=\"toggleFAQ('faq3')\">" . htmlspecialchars($settings['frequently_asked_questions_headline3']) . "</dt>\n";
$htmlContent .= " <dd id=\"faq3\" class=\"p-4 hidden text-base text-gray-500\">" . htmlspecialchars($settings['frequently_asked_questions_answer3']) . "</dd>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " </dl>\n";
$htmlContent .= " </div>\n";
$htmlContent .= "</div>\n";
$htmlContent .= "<div class=\"container mx-auto px-4\">\n";
$htmlContent .= " <div class=\"max-w-md mx-auto bg-white rounded-lg overflow-hidden md:max-w-lg\">\n";
$htmlContent .= " <div class=\"md:flex\">\n";
$htmlContent .= " <div class=\"w-full p-5\">\n";
$htmlContent .= " <div class=\"text-center\">\n";
$htmlContent .= " <h3 class=\"text-2xl text-gray-900\">Already Have an Active Package?</h3>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " <form id=\"loginForm\" class=\"form\" name=\"login\" action=\"$(link-login-only)\" method=\"post\" $(if chap-id)onSubmit=\"return doLogin()\"$(endif)>\n";
$htmlContent .= " <input type=\"hidden\" name=\"dst\" value=\"$(link-orig)\" />\n";
$htmlContent .= " <input type=\"hidden\" name=\"popup\" value=\"true\" />\n";
$htmlContent .= " <div class=\"mb-4\">\n";
$htmlContent .= " <label class=\"block text-gray-700 text-sm font-bold mb-2\" for=\"username\">Username</label>\n";
$htmlContent .= " <input id=\"usernameInput\" class=\"shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline\" name=\"username\" type=\"text\" value=\"\" placeholder=\"Username\">\n";
$htmlContent .= " </div>\n";
$htmlContent .= " <div class=\"mb-6\">\n";
$htmlContent .= " <label class=\"block text-gray-700 text-sm font-bold mb-2\" for=\"password\">Password</label>\n";
$htmlContent .= " <input id=\"passwordInput\" class=\"shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 mb-3 leading-tight focus:outline-none focus:shadow-outline\" name=\"password\" type=\"password\" placeholder=\"******************\">\n";
$htmlContent .= " </div>\n";
$htmlContent .= " <div class=\"flex items-center justify-between\">\n";
$htmlContent .= " <button id=\"submitBtn\" class=\"bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline\" type=\"button\">\n";
$htmlContent .= " Click Here To Connect\n";
$htmlContent .= " </button>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " </form>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " </div>\n";
$htmlContent .= "</div>\n";
$htmlContent .= "<script>\n";
$htmlContent .= "document.addEventListener('DOMContentLoaded', function() {\n";
$htmlContent .= " function autofillLogin() {\n";
$htmlContent .= " var phoneNumber = '2547xxxxxxx';\n";
$htmlContent .= " var password = '1234';\n";
$htmlContent .= " document.querySelector('input[name=\"username\"]').value = phoneNumber;\n";
$htmlContent .= " document.querySelector('input[name=\"password\"]').value = password;\n";
$htmlContent .= " setTimeout(function() {\n";
$htmlContent .= " document.querySelector('button[type=\"submit\"]').click();\n";
$htmlContent .= " }, 15000);\n";
$htmlContent .= " }\n";
$htmlContent .= " autofillLogin();\n";
$htmlContent .= "});\n";
$htmlContent .= "</script>\n";
$htmlContent .= "<script>\n";
$htmlContent .= "function toggleFAQ(faqId) {\n";
$htmlContent .= " var element = document.getElementById(faqId);\n";
$htmlContent .= " if (element.style.display === \"block\") {\n";
$htmlContent .= " element.style.display = \"none\";\n";
$htmlContent .= " } else {\n";
$htmlContent .= " element.style.display = \"block\";\n";
$htmlContent .= " }\n";
$htmlContent .= "}\n";
$htmlContent .= "</script>\n";
$htmlContent .= "</section>\n";
$htmlContent .= "</main>\n";
$htmlContent .= "<!-- Footer -->\n";
$htmlContent .= "<footer class=\"bg-blue-900 text-white\">\n";
$htmlContent .= " <div class=\"max-w-7xl mx-auto px-4 py-12 sm:px-6 lg:px-8\">\n";
$htmlContent .= " <div class=\"lg:grid lg:grid-cols-3 lg:gap-8\">\n";
$htmlContent .= " <div class=\"lg:col-span-1\">\n";
$htmlContent .= " <h2 class=\"text-sm font-semibold uppercase tracking-wider\">\n";
$htmlContent .= " Contact Us\n";
$htmlContent .= " </h2>\n";
$htmlContent .= " <ul class=\"mt-4 space-y-4\">\n";
$htmlContent .= " <li>\n";
$htmlContent .= " <span class=\"block\">Address</span>\n";
$htmlContent .= " </li>\n";
$htmlContent .= " <li>\n";
$htmlContent .= " <span class=\"block\">Email: contact@" . htmlspecialchars($company) . "</span>\n";
$htmlContent .= " </li>\n";
$htmlContent .= " <li>\n";
$htmlContent .= " <span class=\"block\">Phone: " . htmlspecialchars($phone) . "</span>\n";
$htmlContent .= " </li>\n";
$htmlContent .= " </ul>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " <div class=\"lg:col-span-1\">\n";
$htmlContent .= " <h2 class=\"text-sm font-semibold uppercase tracking-wider\">\n";
$htmlContent .= " Quick Links\n";
$htmlContent .= " </h2>\n";
$htmlContent .= " <ul class=\"mt-4 space-y-4\">\n";
$htmlContent .= " <li><a href=\"#\" class=\"hover:underline\">About Us</a></li>\n";
$htmlContent .= " <li><a href=\"#\" class=\"hover:underline\">Our Services</a></li>\n";
$htmlContent .= " <li><a href=\"#\" class=\"hover:underline\">FAQ</a></li>\n";
$htmlContent .= " <li><a href=\"#\" class=\"hover:underline\">Support</a></li>\n";
$htmlContent .= " </ul>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " <div class=\"lg:col-span-1\">\n";
$htmlContent .= " <h2 class=\"text-sm font-semibold uppercase tracking-wider\">\n";
$htmlContent .= " Follow Us\n";
$htmlContent .= " </h2>\n";
$htmlContent .= " <div class=\"mt-4 space-x-4\">\n";
$htmlContent .= " <a href=\"#\" class=\"hover:text-gray-400\"><i class=\"fab fa-facebook-f\"></i></a>\n";
$htmlContent .= " <a href=\"#\" class=\"hover:text-gray-400\"><i class=\"fab fa-twitter\"></i></a>\n";
$htmlContent .= " <a href=\"#\" class=\"hover:text-gray-400\"><i class=\"fab fa-instagram\"></i></a>\n";
$htmlContent .= " <a href=\"#\" class=\"hover:text-gray-400\"><i class=\"fab fa-linkedin-in\"></i></a>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " <div class=\"mt-8 border-t border-gray-700 pt-8 md:flex md:items-center md:justify-between\">\n";
$htmlContent .= " <div class=\"flex space-x-6 md:order-2\">\n";
$htmlContent .= " <a href=\"#\" class=\"text-gray-400 hover:text-gray-300\"><span class=\"sr-only\">Facebook</span><i class=\"fab fa-facebook-f\"></i></a>\n";
$htmlContent .= " <a href=\"#\" class=\"text-gray-400 hover:text-gray-300\"><span class=\"sr-only\">Instagram</span><i class=\"fab fa-instagram\"></i></a>\n";
$htmlContent .= " <a href=\"#\" class=\"text-gray-400 hover:text-gray-300\"><span class=\"sr-only\">Twitter</span><i class=\"fab fa-twitter\"></i></a>\n";
$htmlContent .= " <a href=\"#\" class=\"text-gray-400 hover:text-gray-300\"><span class=\"sr-only\">LinkedIn</span><i class=\"fab fa-linkedin-in\"></i></a>\n";
$htmlContent .= " </div>\n";
$htmlContent .= "<p class=\"mt-8 text-base leading-6 text-gray-400 md:mt-0 md:order-1\">\n";
$htmlContent .= " &copy; 2024 " . htmlspecialchars($company) . " All rights reserved.\n";
$htmlContent .= " </p>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " </div>\n";
$htmlContent .= "</footer>\n";
$htmlContent .= "<script src=\"https://cdn.jsdelivr.net/npm/sweetalert2@11\"></script>\n";
$htmlContent .= "<script>\n";
$htmlContent .= " function formatPhoneNumber(phoneNumber) {\n";
$htmlContent .= " if (phoneNumber.startsWith('+')) {\n";
$htmlContent .= " phoneNumber = phoneNumber.substring(1);\n";
$htmlContent .= " }\n";
$htmlContent .= " if (phoneNumber.startsWith('0')) {\n";
$htmlContent .= " phoneNumber = '254' + phoneNumber.substring(1);\n";
$htmlContent .= " }\n";
$htmlContent .= " if (phoneNumber.match(/^(7|1)/)) {\n";
$htmlContent .= " phoneNumber = '254' + phoneNumber;\n";
$htmlContent .= " }\n";
$htmlContent .= " return phoneNumber;\n";
$htmlContent .= " }\n";
$htmlContent .= "\n";
$htmlContent .= " function handlePhoneNumberSubmission(planId, routerId) {\n";
$htmlContent .= " Swal.fire({\n";
$htmlContent .= " title: 'Enter Your Phone Number',\n";
$htmlContent .= " input: 'text',\n";
$htmlContent .= " inputPlaceholder: 'Your phone number here',\n";
$htmlContent .= " inputAttributes: {\n";
$htmlContent .= " autocapitalize: 'off'\n";
$htmlContent .= " },\n";
$htmlContent .= " showCancelButton: true,\n";
$htmlContent .= " confirmButtonColor: '#3085d6',\n";
$htmlContent .= " cancelButtonColor: '#d33',\n";
$htmlContent .= " confirmButtonText: 'Submit',\n";
$htmlContent .= " showLoaderOnConfirm: true,\n";
$htmlContent .= " backdrop: `\n";
$htmlContent .= " rgba(0,0,123,0.4)\n";
$htmlContent .= " url(\"https://sweetalert2.github.io/images/nyan-cat.gif\")\n";
$htmlContent .= " center left\n";
$htmlContent .= " no-repeat\n";
$htmlContent .= " `,\n";
$htmlContent .= " preConfirm: (phoneNumber) => {\n";
$htmlContent .= " var formattedPhoneNumber = formatPhoneNumber(phoneNumber);\n";
$htmlContent .= " document.getElementById('usernameInput').value = formattedPhoneNumber;\n";
$htmlContent .= " console.log(\"Phone number for autofill:\", formattedPhoneNumber);\n";
$htmlContent .= "\n";
$htmlContent .= " return fetch('" . APP_URL . "/index.php?_route=plugin/CreateHotspotuser&type=grant', {\n";
$htmlContent .= " method: 'POST',\n";
$htmlContent .= " headers: {'Content-Type': 'application/json'},\n";
$htmlContent .= " body: JSON.stringify({phone_number: formattedPhoneNumber, plan_id: planId, router_id: routerId}),\n";
$htmlContent .= " })\n";
$htmlContent .= " .then(response => {\n";
$htmlContent .= " if (!response.ok) throw new Error('Network response was not ok');\n";
$htmlContent .= " return response.json();\n";
$htmlContent .= " })\n";
$htmlContent .= " .then(data => {\n";
$htmlContent .= " if (data.status === 'error') throw new Error(data.message);\n";
$htmlContent .= " Swal.fire({\n";
$htmlContent .= " title: 'Connecting in 35 Secs...',\n";
$htmlContent .= " html: `Remaining time is <b>\${formattedPhoneNumber}</b>.<br>A payment request has been sent to <b>\${formattedPhoneNumber}</b>. Dont click anything until you are connected. Still on this page after the timer ended? Scroll down and Click Login Now`,\n";
$htmlContent .= " timer: 35000, // Adjusted for 35 seconds\n";
$htmlContent .= " timerProgressBar: true,\n";
$htmlContent .= " didOpen: () => {\n";
$htmlContent .= " Swal.showLoading();\n";
$htmlContent .= " const timer = Swal.getPopup().querySelector(\"b\");\n";
$htmlContent .= " timerInterval = setInterval(() => {\n";
$htmlContent .= " timer.textContent = `\${Swal.getTimerLeft()}`;\n";
$htmlContent .= " }, 100);\n";
$htmlContent .= " },\n";
$htmlContent .= " willClose: () => {\n";
$htmlContent .= " clearInterval(timerInterval);\n";
$htmlContent .= " }\n";
$htmlContent .= " }).then((result) => {\n";
$htmlContent .= " if (result.dismiss === Swal.DismissReason.timer) {\n";
$htmlContent .= " console.log('I was closed by the timer');\n";
$htmlContent .= " document.getElementById('submitBtn').click();\n";
$htmlContent .= " }\n";
$htmlContent .= " });\n";
$htmlContent .= " return formattedPhoneNumber; \n";
$htmlContent .= " })\n";
$htmlContent .= " .catch(error => {\n";
$htmlContent .= " Swal.fire({\n";
$htmlContent .= " icon: 'error',\n";
$htmlContent .= " title: 'Oops...',\n";
$htmlContent .= " text: error.message,\n";
$htmlContent .= " });\n";
$htmlContent .= " });\n";
$htmlContent .= " },\n";
$htmlContent .= " allowOutsideClick: () => !Swal.isLoading()\n";
$htmlContent .= " });\n";
$htmlContent .= " }\n";
$htmlContent .= "\n";
$htmlContent .= " function FetchAjax(phoneNumber) {\n";
$htmlContent .= " refreshData();\n";
$htmlContent .= " }\n";
$htmlContent .= "\n";
$htmlContent .= " function refreshData() {\n";
$htmlContent .= " function refreshDataInternal() {\n";
$htmlContent .= " $.ajax({\n";
$htmlContent .= " url: '" . APP_URL . "/index.php?_route=plugin/CreateHotspotuser&type=verify',\n";
$htmlContent .= " method: \"POST\",\n";
$htmlContent .= " data: {phone_number: document.getElementById('usernameInput').value},\n";
$htmlContent .= " dataType: \"json\",\n";
$htmlContent .= " success: function(data) {\n";
$htmlContent .= " // Response handling code\n";
$htmlContent .= " },\n";
$htmlContent .= " error: function(xhr, textStatus, errorThrown) {\n";
$htmlContent .= " console.log(\"Error: \" + errorThrown);\n";
$htmlContent .= " }\n";
$htmlContent .= " });\n";
$htmlContent .= " }\n";
$htmlContent .= " var refreshInterval = setInterval(refreshDataInternal, 2000);\n";
$htmlContent .= " }\n";
$htmlContent .= "\n";
$htmlContent .= " document.addEventListener('DOMContentLoaded', function() {\n";
$htmlContent .= " var submitBtn = document.getElementById('submitBtn');\n";
$htmlContent .= " if (submitBtn) {\n";
$htmlContent .= " submitBtn.addEventListener('click', function(event) {\n";
$htmlContent .= " event.preventDefault();\n";
$htmlContent .= " document.getElementById('loginForm').submit();\n";
$htmlContent .= " });\n";
$htmlContent .= " }\n";
$htmlContent .= " });\n";
$htmlContent .= "</script>\n";
$htmlContent .= "<script src=\"https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js\"></script>\n";
$htmlContent .= "<script>\n";
$htmlContent .= "document.addEventListener('DOMContentLoaded', function() {\n";
$htmlContent .= " // Ensure the button is correctly targeted by its ID.\n";
$htmlContent .= " var submitBtn = document.getElementById('submitBtn');\n";
$htmlContent .= " \n";
$htmlContent .= " // Add a click event listener to the \"Login Now\" button.\n";
$htmlContent .= " submitBtn.addEventListener('click', function(event) {\n";
$htmlContent .= " event.preventDefault(); // Prevent the default button action.\n";
$htmlContent .= " \n";
$htmlContent .= " // Optional: Log to console for debugging purposes.\n";
$htmlContent .= " console.log(\"Login Now button clicked.\");\n";
$htmlContent .= " \n";
$htmlContent .= " // Direct form submission, bypassing the doLogin function for simplicity.\n";
$htmlContent .= " var form = document.getElementById('loginForm');\n";
$htmlContent .= " form.submit(); // Submit the form directly.\n";
$htmlContent .= " });\n";
$htmlContent .= "});\n";
$htmlContent .= "</script>\n";
$htmlContent .= "</html>\n";
$planStmt->close();
$mysqli->close();
// Check if the download parameter is set
if (isset($_GET['download']) && $_GET['download'] == '1') {
// Prepare the HTML content for download
// ... build your HTML content ...
// Specify the filename for the download
$filename = "login.html";
// Send headers to force download
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename='.basename($filename));
header('Expires: 0');
header('Cache-Control: must-revalidate');
header('Pragma: public');
header('Content-Length: ' . strlen($htmlContent));
// Output the content
echo $htmlContent;
// Prevent any further output
exit;
}
// Regular page content goes here
// ... HTML and PHP code to display the page ...

View File

@ -0,0 +1,117 @@
{include file="sections/header.tpl"}
<section class="content-header">
<h1>
<div class="btn-group">
<button type="button" class="btn btn-success">
Hotspot Settings
</button>
<button type="button" class="btn btn-success dropdown-toggle" data-toggle="dropdown">
<span class="caret"></span>
<span class="sr-only">Toggle Dropdown</span>
</button>
<ul class="dropdown-menu" role="menu">
<li><a href="{$_url}plugin/hotspot_settings">{Lang::T('General Settings')}</a></li>
<li class="divider"></li>
<li><a href="{$_url}plugin/captive_portal_login" target="_blank">Preview Hotspot Login Page</a></li>
<li><a href="{$app_url}/system/plugin/download.php?download=1" target="_blank">Download Login Page</a></li>
</ul>
</div>
</h1>
<ol class="breadcrumb">
<li><a href="{$app_url}/system/plugin/download.php?download=1"><i class="fa fa-dashboard"></i> Click Here To Download Login Page</a></li>
<li class="active">Hotspot Settings</li>
</ol>
</section>
<section class="content">
<div class="table-responsive">
<div class="nav-tabs-custom">
<ul class="nav nav-tabs">
<li class="active"><a href="#tab_1" data-toggle="tab">{Lang::T('General Settings')}</a></li>
</ul>
<div class="tab-content">
<div class="tab-pane active" id="tab_1" style="overflow-x:auto;">
<div class="box-body no-padding" id="">
<form method="POST" action="" enctype="multipart/form-data">
<div class="box-body">
<div class="form-group">
<label for="hotspot_title">Hotspot Page Title</label>
<input type="text" class="form-control" name="hotspot_title" id="hotspot_title" value="{$hotspot_title}" required>
<small class="form-text text-muted">In this field, you can enter the name of your ISP company. It will appear as the main title on the hotspot page.</small>
</div>
<div class="form-group">
<label for="description">Brief Description Of Company/Tagline</label>
<input type="text" class="form-control" name="description" id="description" value="{$description}" required>
</div>
<div class="form-group">
<label for="router_name">Router Name:</label>
<input type="text" class="form-control" name="router_name" id="router_name" value="{$router_name}" required>
<small class="form-text text-muted">This is the most important part of the form. Go to Network and then Routers, and copy the exact router name.</small>
</div>
<!-- FAQ fields -->
<div class="form-group">
<label for="frequently_asked_questions_headline1">FAQ Headline 1</label>
<input type="text" class="form-control" name="frequently_asked_questions_headline1" id="frequently_asked_questions_headline1" value="{$frequently_asked_questions_headline1}" required>
</div>
<div class="form-group">
<label for="frequently_asked_questions_answer1">FAQ Answer 1</label>
<textarea class="form-control" id="frequently_asked_questions_answer1" name="frequently_asked_questions_answer1" required>{$frequently_asked_questions_answer1}</textarea>
</div>
<div class="form-group">
<label for="frequently_asked_questions_headline2">FAQ Headline 2</label>
<input type="text" class="form-control" id="frequently_asked_questions_headline2" name="frequently_asked_questions_headline2" value="{$frequently_asked_questions_headline2}" required>
</div>
<div class="form-group">
<label for="frequently_asked_questions_answer2">FAQ Answer 2</label>
<textarea class="form-control" id="frequently_asked_questions_answer2" name="frequently_asked_questions_answer2" required>{$frequently_asked_questions_answer2}</textarea>
</div>
<div class="form-group">
<label for="frequently_asked_questions_headline3">FAQ Headline 3</label>
<input type="text" class="form-control" name="frequently_asked_questions_headline3" id="frequently_asked_questions_headline3" value="{$frequently_asked_questions_headline3}" required>
</div>
<div class="form-group">
<label for="frequently_asked_questions_answer3">FAQ Answer 3</label>
<textarea class="form-control" id="frequently_asked_questions_answer3" name="frequently_asked_questions_answer3" required>{$frequently_asked_questions_answer3}</textarea>
</div>
<!-- Save Changes button -->
<button type="submit" class="btn btn-info pull-right">Save Changes</button>
</form>
<div class="tab-pane" id="tab_6" style="overflow-x:auto;">
<div class="box-body no-padding" id="">
<!-- Content for Pages Settings tab -->
This feature will be Coming Soon
</div>
</div>
</div>
</div>
<div>
<pre><b>USAGE:</b>
<br>Make sure you change this custom Settings and personalize them.
<br>Then download the <strong style="color: black; background-color: yellow;">login.html</strong> by clicking on download login page.
<br>Then upload the downloaded <strong style="color: black; background-color: yellow;">login.html</strong> file to your Mikrotik router.
<br>Make sure you add your website URL in Mikrotik hotspot wall garden. <strong style="color: black; background-color: yellow;">login.html</strong>
</pre>
</div>
</section>
{include file="sections/footer.tpl"}

BIN
system/plugin/ui/index.html Normal file

Binary file not shown.

154
system/plugin/ui/log.tpl Normal file
View File

@ -0,0 +1,154 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Log UI</title>
<link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet">
</head>
{include file="sections/header.tpl"}
<body class="bg-gray-100 font-sans leading-normal tracking-normal">
<div class="container mx-auto mt-8 bg-white rounded-lg shadow-lg p-6">
<form class="mb-4" method="post" role="form" action="{$_url}plugin/log_ui">
<ul class="nav nav-tabs flex border-b">
{foreach $routers as $r}
<li class="mr-1" role="presentation" {if $r['id']==$router}class="active"{/if}>
<a class="bg-white inline-block py-2 px-4 text-blue-500 hover:text-blue-800 font-semibold" href="{$_url}plugin/log_ui/{$r['id']}">{$r['name']}</a>
</li>
{/foreach}
</ul>
</form>
<div class="flex flex-wrap mb-4">
<div class="w-full md:w-8/12">
<label class="block">
Show entries
<select name="data_length" aria-controls="data" class="form-control form-control-sm" onchange="updatePerPage(this.value)">
<option value="5" {if $per_page == 5}selected{/if}>5</option>
<option value="10" {if $per_page == 10}selected{/if}>10</option>
<option value="25" {if $per_page == 25}selected{/if}>25</option>
<option value="50" {if $per_page == 50}selected{/if}>50</option>
<option value="100" {if $per_page == 100}selected{/if}>100</option>
</select>
</label>
</div>
<div class="w-full md:w-4/12">
<label class="block">
Search:
<input type="search" id="logSearch" class="form-control form-control-sm" placeholder="Search logs" aria-controls="data" onkeyup="filterLogs()">
</label>
</div>
</div>
<table class="min-w-full bg-white table-auto">
<thead>
<tr class="bg-gray-200">
<th class="w-1/3 py-2 px-4">Time</th>
<th class="w-1/3 py-2 px-4">Topic</th>
<th class="w-1/3 py-2 px-4">Message</th>
</tr>
</thead>
<tbody id="logTableBody">
{assign var=current_page value=$smarty.get.page|default:1}
{assign var=per_page value=$smarty.get.per_page|default:10}
{assign var=start_index value=($current_page - 1) * $per_page}
{foreach from=$logs|array_reverse item=log name=logLoop}
{if $smarty.foreach.logLoop.index >= $start_index && $smarty.foreach.logLoop.index < ($start_index + $per_page)}
<tr class="log-entry">
<td class="border px-4 py-2">{$log.time}</td>
<td class="border px-4 py-2">{$log.topics}</td>
<td class="border px-4 py-2 log-message">
{if $log.message|lower|strpos:'failed' !== false}
<span class="text-red-700 text-bold">{$log.message}</span>
{elseif $log.message|lower|strpos:'trying' !== false}
<span class="text-yellow-700">{$log.message}</span>
{elseif $log.message|lower|strpos:'logged in' !== false}
<span class="text-green-700">{$log.message}</span>
{elseif $log.message|lower|strpos:'login failed' !== false}
<span class="text-blue-700">{$log.message}</span>
{else}
<span class="text-gray-700">{$log.message}</span>
{/if}
</td>
</tr>
{/if}
{/foreach}
</tbody>
</table>
{assign var=total_logs value=$logs|@count}
{assign var=last_page value=ceil($total_logs / $per_page)}
<nav class="mt-4">
<ul class="pagination flex justify-center">
{if $current_page > 1}
<li>
<a class="page-link border py-2 px-4 mx-1" href="index.php?_route=plugin/log_ui&page=1&per_page={$per_page}" aria-label="First">&laquo;&laquo;</a>
</li>
<li>
<a class="page-link border py-2 px-4 mx-1" href="index.php?_route=plugin/log_ui&page={$current_page-1}&per_page={$per_page}" aria-label="Previous">&laquo;</a>
</li>
{/if}
{assign var=max_links value=5}
{assign var=start_page value=max(1, $current_page - floor($max_links / 2))}
{assign var=end_page value=min($last_page, $start_page + $max_links - 1)}
{if $start_page > 1}
<li>
<a class="page-link border py-2 px-4 mx-1" href="index.php?_route=plugin/log_ui&page={$start_page-1}&per_page={$per_page}" aria-label="Previous">&hellip;</a>
</li>
{/if}
{foreach from=range($start_page, $end_page) item=page}
<li>
<a class="page-link border py-2 px-4 mx-1 {if $page == $current_page}bg-blue-500 text-white{/if}" href="index.php?_route=plugin/log_ui&page={$page}&per_page={$per_page}">{$page}</a>
</li>
{/foreach}
{if $end_page < $last_page}
<li>
<a class="page-link border py-2 px-4 mx-1" href="index.php?_route=plugin/log_ui&page={$end_page+1}&per_page={$per_page}" aria-label="Next">&hellip;</a>
</li>
{/if}
{if $current_page < $last_page}
<li>
<a class="page-link border py-2 px-4 mx-1" href="index.php?_route=plugin/log_ui&page={$current_page+1}&per_page={$per_page}" aria-label="Next">&raquo;</a>
</li>
<li>
<a class="page-link border py-2 px-4 mx-1" href="index.php?_route=plugin/log_ui&page={$last_page}&per_page={$per_page}" aria-label="Last">&raquo;&raquo;</a>
</li>
{/if}
</ul>
</nav>
</div>
<script>
function updatePerPage(value) {
var urlParams = new URLSearchParams(window.location.search);
urlParams.set('per_page', value);
urlParams.set('page', 1); // Reset to first page
window.location.search = urlParams.toString();
}
function filterLogs() {
var input = document.getElementById('logSearch').value.toLowerCase();
var table = document.getElementById('logTableBody');
var tr = table.getElementsByClassName('log-entry');
for (var i = 0; i < tr.length; i++) {
var logMessage = tr[i].getElementsByClassName('log-message')[0].textContent || tr[i].getElementsByClassName('log-message')[0].innerText;
if (logMessage.toLowerCase().indexOf(input) > -1) {
tr[i].style.display = '';
} else {
tr[i].style.display = 'none';
}
}
}
</script>
{include file="sections/footer.tpl"}
</body>
</html>

View File

@ -0,0 +1,34 @@
{include file="sections/header.tpl"}
<form class="form-horizontal" method="post" role="form" action="{$_url}plugin/port_tester">
<div class="row">
<div class="col-sm-12 col-md-12">
<div class="panel panel-primary panel-hovered panel-stacked mb30">
<div class="panel-heading">Testing port external</div>
<div class="panel-body">
<div class="form-group">
<label class="col-md-2 control-label">Port</label>
<div class="col-md-6">
<input type="text" id="port" name="port" value="{$port}" placeholder="8728" class="form-control">
</div>
</div>
<div class="form-group">
<div class="col-lg-offset-2 col-lg-10">
<button class="btn btn-success waves-effect waves-light" type="submit">Test It</button>
</div>
</div>
</div>
</div>
{if $result != ''}
<div class="panel panel-primary panel-hovered panel-stacked mb30">
<div class="panel-heading">Result</div>
<div class="panel-body">
{Lang::nl2br($result)}
</div>
</div>
{/if}
</div>
</div>
</form>
{include file="sections/footer.tpl"}

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"}

BIN
system/plugin/user.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 281 KiB

View File

@ -0,0 +1,8 @@
<html>
<head>
<title>403 Forbidden</title>
</head>
<body>
<p>Directory access is forbidden.</p>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@ -0,0 +1,8 @@
<html>
<head>
<title>403 Forbidden</title>
</head>
<body>
<p>Directory access is forbidden.</p>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

BIN
system/uploads/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

View File

@ -0,0 +1,10 @@
{
"expired": "Hello [[name]], your internet package [[package]] has been expired.",
"balance_send": "You sent [[balance]] to [[name]].",
"balance_received": "You have received [[balance]] from [[name]].",
"reminder_7_day": "Hello *[[name]]*, \r\nyour internet package *[[package]]* will be expired in 7 days.",
"reminder_3_day": "Hello *[[name]]*, \r\nyour internet package *[[package]]* will be expired in 3 days.",
"reminder_1_day": "Hello *[[name]]*,\r\n your internet package *[[package]]* will be expired tomorrow.",
"invoice_paid": "*[[company_name]]*\r\n[[address]]\r\n[[phone]]\r\n\r\n\r\nINVOICE: *[[invoice]]*\r\nDate : [[date]]\r\n[[payment_gateway]] [[payment_channel]]\r\n\r\n\r\nType : *[[type]]*\r\nPackage : *[[plan_name]]*\r\nPrice : *[[plan_price]]*\r\n\r\nUsername : *[[user_name]]*\r\nPassword : ***********\r\n\r\nExpired : *[[expired_date]]*\r\n\r\n====================\r\n[[footer]]",
"invoice_balance": "*[[company_name]]*\r\n[[address]]\r\n[[phone]]\r\n\r\n\r\nINVOICE: *[[invoice]]*\r\nDate : [[date]]\r\n[[payment_gateway]] [[payment_channel]]\r\n\r\n\r\nType : *[[type]]*\r\nPackage : *[[plan_name]]*\r\nPrice : *[[plan_price]]*\r\n\r\n====================\r\n[[footer]]"
}

View File

@ -0,0 +1 @@
{"expired":"Dear Customer, your subscription of [[package]] has expired.\r\nONLY Home\/Office Users pay using:\r\nPay Bill:4355580\r\nAcc. No:[[username]]\r\nPrice:[[price]]","reminder_7_day":"","reminder_3_day":"","reminder_1_day":"Hello, Your Internet will Expire in 1 Day, Kindly make subscribe to continue using our Services. \r\n","invoice_paid":"Successfully Purchased [[plan_name]]. Expiry: [[expired_date]]. \r\nIf not connected use MPESA message or Account No.:[[user_name]] on the login page.","invoice_balance":""}

View File

@ -0,0 +1,205 @@
<?php
function BankStkPush_validate_config()
{
global $config;
if (empty($config['Stkbankacc']) || empty($config['Stkbankname']) ) {
sendTelegram("Bank Stk payment gateway not configured");
r2(U . 'order/balance', 'w', Lang::T("Admin has not yet setup the payment gateway, please tell admin"));
}
}
function BankStkPush_show_config()
{
global $ui, $config;
$ui->assign('_title', 'Bank Stk Push - ' . $config['CompanyName']);
$ui->display('bankstkpush.tpl');
}
function BankStkPush_save_config()
{
global $admin, $_L;
$bankacc = _post('account');
$bankname = _post('bankname');
$d = ORM::for_table('tbl_appconfig')->where('setting', 'Stkbankacc')->find_one();
if ($d) {
$d->value = $bankacc;
$d->save();
} else {
$d = ORM::for_table('tbl_appconfig')->create();
$d->setting = 'Stkbankacc';
$d->value = $bankacc;
$d->save();
}
$d = ORM::for_table('tbl_appconfig')->where('setting', 'Stkbankname')->find_one();
if ($d) {
$d->value = $bankname;
$d->save();
} else {
$d = ORM::for_table('tbl_appconfig')->create();
$d->setting = 'Stkbankname';
$d->value = $bankname;
$d->save();
}
_log('[' . $admin['username'] . ']: Stk Bank details ' . $_L['Settings_Saved_Successfully'], 'Admin', $admin['id']);
r2(U . 'paymentgateway/BankStkPush', 's', $_L['Settings_Saved_Successfully']);
}
function BankStkPush_create_transaction($trx, $user )
{
$url=(U. "plugin/initiatebankstk");
$d = ORM::for_table('tbl_payment_gateway')
->where('username', $user['username'])
->where('status', 1)
->find_one();
$d->gateway_trx_id = '';
$d->payment_method = 'Bank Stk Push';
$d->pg_url_payment = $url;
$d->pg_request = '';
$d->expired_date = date('Y-m-d H:i:s', strtotime("+5 minutes"));
$d->save();
r2(U . "order/view/" . $d['id'], 's', Lang::T("Create Transaction Success, Please click pay now to process payment"));
die();
}
function BankStkPush_payment_notification()
{
$captureLogs = file_get_contents("php://input");
$analizzare = json_decode($captureLogs);
/// sleep(10);
file_put_contents('back.log',$captureLogs,FILE_APPEND);
$response_code = $analizzare->Body->stkCallback->ResultCode;
$resultDesc = ($analizzare->Body->stkCallback->ResultDesc);
$merchant_req_id = ($analizzare->Body->stkCallback->MerchantRequestID);
$checkout_req_id = ($analizzare->Body->stkCallback->CheckoutRequestID);
$amount_paid = ($analizzare->Body->stkCallback->CallbackMetadata->Item['0']->Value);//get the amount value
$mpesa_code = ($analizzare->Body->stkCallback->CallbackMetadata->Item['1']->Value);//mpesa transaction code..
$sender_phone = ($analizzare->Body->stkCallback->CallbackMetadata->Item['4']->Value);//Telephone Number
$PaymentGatewayRecord = ORM::for_table('tbl_payment_gateway')
->where('checkout', $checkout_req_id)
->where('status', 1) // Add this line to filter by status
->order_by_desc('id')
->find_one();
$uname=$PaymentGatewayRecord->username;
$plan_id=$PaymentGatewayRecord->plan_id;
$mac_address=$PaymentGatewayRecord->mac_address;
$user=$PaymentGatewayRecord;
$userid = ORM::for_table('tbl_customers')
->where('username', $uname)
->order_by_desc('id')
->find_one();
$userid->username=$uname;
$userid->save();
$plans = ORM::for_table('tbl_plans')
->where('id', $plan_id)
->order_by_desc('id')
->find_one();
if ($response_code=="1032")
{
$now = date('Y-m-d H:i:s');
$PaymentGatewayRecord->paid_date = $now;
$PaymentGatewayRecord->status = 4;
$PaymentGatewayRecord->save();
exit();
}
if($response_code=="1037"){
$PaymentGatewayRecord->status = 1;
$PaymentGatewayRecord->pg_paid_response = 'User failed to enter pin';
$PaymentGatewayRecord->save();
exit();
}
if($response_code=="1"){
$PaymentGatewayRecord->status = 1;
$PaymentGatewayRecord->pg_paid_response = 'Not enough balance';
$PaymentGatewayRecord->save();
exit();
}
if($response_code=="2001"){
$PaymentGatewayRecord->status = 1;
$PaymentGatewayRecord->pg_paid_response = 'Wrong Mpesa pin';
$PaymentGatewayRecord->save();
exit();
}
if($response_code=="0"){
$now = date('Y-m-d H:i:s');
$date = date('Y-m-d');
$time= date('H:i:s');
$check_mpesa = ORM::for_table('tbl_payment_gateway')
->where('gateway_trx_id', $mpesa_code)
->find_one();
if($check_mpesa){
echo "double callback, ignore one";
die;
}
$plan_type=$plans->type;
$UserId=$userid->id;
if (!Package::rechargeUser($UserId, $user['routers'], $user['plan_id'], $user['gateway'], $mpesa_code)){
$PaymentGatewayRecord->status = 2;
$PaymentGatewayRecord->paid_date = $now;
$PaymentGatewayRecord->gateway_trx_id = $mpesa_code;
$PaymentGatewayRecord->save();
$username = $PaymentGatewayRecord->username;
// Check if a transaction with the same gateway_trx_id already exists
$existingTransaction = ORM::for_table('tbl_transactions')
->where('mpesacode', $mpesa_code)
->find_one();
if (!$existingTransaction) {
// Save transaction data to tbl_transactions
$transaction = ORM::for_table('tbl_transactions')->create();
$transaction->invoice = $PaymentGatewayRecord->gateway_trx_id; // Set invoice to gateway_trx_id value
$transaction->username = $PaymentGatewayRecord->username;
$transaction->plan_name = $PaymentGatewayRecord->plan_name;
$transaction->price = $amount_paid;
$transaction->recharged_on = $date;
$transaction->recharged_time = $time;
$transaction->expiration = $now;
$transaction->time = $now;
$transaction->method = $PaymentGatewayRecord->payment_method;
$transaction->routers = 0;
$transaction->Type = 'Balance';
$transaction->mpesacode = $mpesa_code;
$transaction->save();
} else {
error_log("Duplicate transaction entry detected for gateway_trx_id: " . $PaymentGatewayRecord->gateway_trx_id);
}
} else {
// Update tbl_recharges
$PaymentGatewayRecord->status = 2;
$PaymentGatewayRecord->paid_date = $now;
$PaymentGatewayRecord->gateway_trx_id = $mpesa_code;
$PaymentGatewayRecord->save();
}
}
}

View File

@ -0,0 +1,234 @@
<?php
/**
* PHP Mikrotik Billing (https://github.com/hotspotbilling/phpnuxbill/)
*
* Payment Gateway flutterwave.com
*
* created by @foculinkstech
*
**/
function flutterwave_validate_config()
{
global $config;
if (empty($config['flutterwave_secret_key'])) {
Message::sendTelegram("flutterwave payment gateway not configured");
r2(U . 'order/package', 'w', Lang::T("Admin has not yet setup flutterwave payment gateway, please tell admin"));
}
}
function flutterwave_show_config()
{
global $ui;
$ui->assign('_title', 'Flutterwave - Payment Gateway');
$ui->assign('cur', json_decode(file_get_contents('system/paymentgateway/flutterwave_currency.json'), true));
$ui->assign('channel', json_decode(file_get_contents('system/paymentgateway/flutterwave_channel.json'), true));
$ui->display('flutterwave.tpl');
}
function flutterwave_save_config()
{
global $admin, $_L;
$flutterwave_secret_key = _post('flutterwave_secret_key');
$flutterwave_currency = _post('flutterwave_currency');
$d = ORM::for_table('tbl_appconfig')->where('setting', 'flutterwave_secret_key')->find_one();
if ($d) {
$d->value = $flutterwave_secret_key;
$d->save();
} else {
$d = ORM::for_table('tbl_appconfig')->create();
$d->setting = 'flutterwave_secret_key';
$d->value = $flutterwave_secret_key;
$d->save();
}
$d = ORM::for_table('tbl_appconfig')->where('setting', 'flutterwave_currency')->find_one();
if ($d) {
$d->value = $flutterwave_currency;
$d->save();
} else {
$d = ORM::for_table('tbl_appconfig')->create();
$d->setting = 'flutterwave_currency';
$d->value = $flutterwave_currency;
$d->save();
}
$d = ORM::for_table('tbl_appconfig')->where('setting', 'flutterwave_channel')->find_one();
if ($d) {
$d->value = implode(',', $_POST['flutterwave_channel']);
$d->save();
} else {
$d = ORM::for_table('tbl_appconfig')->create();
$d->setting = 'flutterwave_channel';
$d->value = implode(',', $_POST['flutterwave_channel']);
$d->save();
}
_log('[' . $admin['username'] . ']: Flutterwave ' . $_L['Settings_Saved_Successfully'], 'Admin', $admin['id']);
r2(U . 'paymentgateway/flutterwave', 's', $_L['Settings_Saved_Successfully']);
}
function flutterwave_create_transaction($trx, $user)
{
global $config;
$txref = uniqid('trx');
$json = [
'tx_ref' => $txref,
'amount' => $trx['price'],
'currency' => $config['flutterwave_currency'],
'payment_options' => explode(',', $config['flutterwave_channel']),
'customer' => [
'email' => (empty($user['email'])) ? $user['username'] . '@' . $_SERVER['HTTP_HOST'] : $user['email'],
'name' => $user['fullname'],
'phonenumber' => $user['phonenumber']
],
'meta' => [
'price' => $trx['price'],
'username' => $user['username'],
'trxid' => $trx['id']
],
'customizations' => [
'title' => $trx['plan_name'],
'description' => $trx['plan_name'],
],
'redirect_url' => U . 'callback/flutterwave'
];
// die(json_encode($json,JSON_PRETTY_PRINT));
$result = json_decode(Http::postJsonData(flutterwave_get_server() . 'payments', $json,[
'Authorization: Bearer ' . $config['flutterwave_secret_key'],
'Cache-Control: no-cahe'
],
),
true);
//die(json_encode($result,JSON_PRETTY_PRINT));
if ($result['status'] == 'error') {
Message::sendTelegram("Flutterwave payment failed\n\n" . json_encode($result, JSON_PRETTY_PRINT));
r2(U . 'order/package', 'e', Lang::T("Failed to create transaction.\n".$result['message']));
}
$d = ORM::for_table('tbl_payment_gateway')
->where('username', $user['username'])
->where('status', 1)
->find_one();
$d->gateway_trx_id = $txref;
$d->pg_url_payment = $result['data']['link'];
$d->pg_request = json_encode($result);
$d->expired_date = date('Y-m-d H:i:s', strtotime("+ 6 HOUR"));
$d->save();
header('Location: ' . $result['data']['link']);
exit();
r2(U . "order/view/" . $d['id'], 's', Lang::T("Create Transaction Success"));
}
function flutterwave_payment_notification()
{
global $config;
if(isset($_GET['status']))
{
//* check payment status
if($_GET['status'] == 'cancelled')
{
// die(json_encode($txref,JSON_PRETTY_PRINT));
Message::sendTelegram("Flutterwave Payment Cancelled: \n\n");
r2(U . 'order/package', 'e', Lang::T("Flutterwave Payment Cancelled."));
}
elseif($_GET['status'] == 'successful')
{
$txid = $_GET['transaction_id'];
$result = json_decode(Http::getData(flutterwave_get_server() . 'transactions/' . $txid. '/verify', [
'Authorization: Bearer ' . $config['flutterwave_secret_key'],
'Cache-Control: no-cahe'
]), true);
//die(json_encode($result,JSON_PRETTY_PRINT));
{
$id = $result['data']['id'];
$amountPaid = $result['data']['charged_amount'];
$amountToPay = $result['data']['meta']['price'];
$username = $result['data']['meta']['username'];
$trxid = $result['data']['meta']['trxid'];
if($amountPaid >= $amountToPay)
{
// die(json_encode($trxid,JSON_PRETTY_PRINT));
// echo 'Payment successful';
$d = ORM::for_table('tbl_payment_gateway')
->where('username', $username)
->where('status', 1)
->find_one();
$d->gateway_trx_id = $id;
$d->save();
r2(U . 'order/view/'.$trxid.'/check');
// r2(U . 'order/package', 's', Lang::T("Flutterwave Payment Completed."));
exit();
//* Continue to give item to the user
}
else
{
// echo 'Fraud transactio detected';
r2(U . 'order/package', 'e', Lang::T("Fraud transactions detected."));
exit();
}
}
}
}
}
function flutterwave_get_status($trx, $user)
{
global $config;
$trans_id = $trx['gateway_trx_id'];
$result = json_decode(Http::getData(flutterwave_get_server() . 'transactions/' . $trx['gateway_trx_id']. '/verify', [
'Authorization: Bearer ' . $config['flutterwave_secret_key'],
'Cache-Control: no-cahe'
]), true);
//die(json_encode($result,JSON_PRETTY_PRINT));
if ($result['status'] == 'error') {
r2(U . "order/view/" . $trx['id'], 'w', Lang::T("Transaction still unpaid."));
} else if (in_array($result['status'], ['success']) && $trx['status'] != 2) {
if (!Package::rechargeUser($user['id'], $trx['routers'], $trx['plan_id'], $trx['gateway'], 'Flutterwave')) {
r2(U . "order/view/" . $trx['id'], 'd', Lang::T("Failed to activate your Package, please try again later."));
}
$trx->pg_paid_response = json_encode($result);
$trx->payment_method = 'Flutterwave';
$trx->payment_channel = $result['data']['payment_type'];
$trx->paid_date = date('Y-m-d H:i:s', strtotime( $result['data']['created_at']));
$trx->status = 2;
$trx->save();
r2(U . "order/view/" . $trx['id'], 's', Lang::T("Transaction successful."));
} else if ($result['status'] == 'EXPIRED') {
$trx->pg_paid_response = json_encode($result);
$trx->status = 3;
$trx->save();
r2(U . "order/view/" . $trx['id'], 'd', Lang::T("Transaction expired."));
} else if ($trx['status'] == 2) {
r2(U . "order/view/" . $trx['id'], 'd', Lang::T("Transaction has been paid.."));
}else{
Message::sendTelegram("flutterwave_get_status: unknown result\n\n".json_encode($result, JSON_PRETTY_PRINT));
r2(U . "order/view/" . $trx['id'], 'd', Lang::T("Unknown Command."));
}
}
function flutterwave_get_server()
{
global $_app_stage;
if ($_app_stage == 'Live') {
return 'https://api.flutterwave.com/v3/';
} else {
return 'https://api.flutterwave.com/v3/';
}
}

View File

@ -0,0 +1,35 @@
[
{
"id": "card",
"name": "Card Payment"
},
{
"id": "ussd",
"name": "USSD"
},
{
"id": "account",
"name": "Bank Account"
},
{
"id": "banktransfer",
"name": "Bank Transfer"
},
{
"id": "nqr",
"name": "QR payment"
},
{
"id": "mpesa",
"name": "M-Pesa"
},
{
"id": "mobilemoneyghana",
"name": "Mobile money Ghana"
},
{
"id": "credit",
"name": "Credit payment"
}
]

View File

@ -0,0 +1,30 @@
[
{
"id": "NGN",
"name": "Nigerian Naira"
},
{
"id": "GHC",
"name": "Ghana Cedis"
},
{
"id": "KES",
"name": "Kenyan Shilling"
},
{
"id": "ZAR",
"name": "South African Rand"
},
{
"id": "GBP",
"name": "British Pound Sterling"
},
{
"id": "USD",
"name": "United States Dollar"
},
{
"id": "TZS",
"name": "Tanzanian Shilling"
}
]

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,8 @@
<html>
<head>
<title>403 Forbidden</title>
</head>
<body>
<p>Directory access is forbidden.</p>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
ui/.DS_Store vendored Normal file

Binary file not shown.

8
ui/index.html Normal file
View File

@ -0,0 +1,8 @@
<html>
<head>
<title>403 Forbidden</title>
</head>
<body>
<p>Directory access is forbidden.</p>
</body>
</html>

BIN
ui/ui/.DS_Store vendored Normal file

Binary file not shown.

17
ui/ui/404.tpl Normal file
View File

@ -0,0 +1,17 @@
{include file="sections/user-header.tpl"}
<div class="container-fluid">
<div class="col-xl-12 col-xxl-12">
<div class="col-md-12">
<div class="card" style="display: grid; align-content: center;">
<div class="card-body text-center ai-icon text-primary">
<i class="flaticon-381-error"></i>
<h4 class="my-2">404</h4>
<a href="{$_url}home" title="Go to Dashboard" class="btn my-2 btn-primary btn-lg px-4"><i
class="fa fa-usd"></i> Go to Dashboard</a>
</div>
</div>
</div>
</div>
</div>
</div>
{include file="sections/user-footer.tpl"}

BIN
ui/ui/Ass/.DS_Store vendored Normal file

Binary file not shown.

30929
ui/ui/Ass/css/style.css Normal file

File diff suppressed because it is too large Load Diff

BIN
ui/ui/Ass/icons/.DS_Store vendored Normal file

Binary file not shown.

49
ui/ui/a404.tpl Normal file
View File

@ -0,0 +1,49 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="shortcut icon" href="https://laravel.com/img/favicon/favicon-16x16.png" type='image/x-icon'>
<title>Admin Dashboard</title>
<link rel="stylesheet" href="assets/vendor/chartist/css/chartist.min.css">
<link href="assets/vendor/bootstrap-select/dist/css/bootstrap-select.min.css" rel="stylesheet">
<link href="assets/css/style.css" rel="stylesheet">
</head>
<body style="background-color:#e9ecef;">
<div class="" style="margin-top: 32px;">
<div class="container-fluid">
<div class="col-xl-12 col-xxl-12">
<div class="col-md-12">
<div class="card" style="display: grid; align-content: center;">
<div class="card-body text-center ai-icon text-primary">
<i class="flaticon-381-error"></i>
<h4 class="my-2">404</h4>
<a href="{$_url}dashboard" title="Go to Dashboard" class="btn my-2 btn-primary btn-lg px-4"><i
class="fa fa-usd"></i> Go to Dashboard</a>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script src="assets/vendor/global/global.min.js"></script>
<script src="assets/vendor/bootstrap-select/dist/js/bootstrap-select.min.js"></script>
<script src="assets/vendor/chart.js/Chart.bundle.min.js"></script>
<!-- Chart piety plugin files -->
<script src="assets/vendor/peity/jquery.peity.min.js"></script>
<!-- Apex Chart -->
<script src="assets/vendor/apexchart/apexchart.js"></script>
<!-- Dashboard 1 -->
<script src="assets/js/dashboard/dashboard-1.js"></script>
<script src="assets/js/custom.min.js"></script>
<script src="assets/js/deznav-init.js"></script>
<script src="assets/js/demo.js"></script>
</body>
</html>

53
ui/ui/admin-login.tpl Normal file
View File

@ -0,0 +1,53 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="shortcut icon" href="https://laravel.com/img/favicon/favicon-16x16.png" type='image/x-icon'>
<title>{Lang::T('Login')} - {$_c['CompanyName']}</title>
<link rel="stylesheet" href="ui/ui/Css/lineicons.min.css">
<link rel="stylesheet" href="ui/ui/Css/Style.css">
<meta http-equiv="refresh" content="{$time}; url={$url}">
</head>
<body>
<div class="login-div">
<div class="logo"></div>
<div class="title">{$_c['CompanyName']} - Admin</div>
{if isset($notify)}
{$notify}
{/if}
<form action="{$_url}admin/post" method="post">
<div class="login-in">
<div class="username">
<i class="lni lni-user"></i>
<input type="text" required class="form-control" name="username" placeholder="{Lang::T('Username')}">
</div>
<div class="password">
<i class="lni lni-lock"></i>
<input type="password" required class="form-control" name="password" placeholder="{Lang::T('Password')}">
</div>
</div>
<button type="submit" class="buttons" value="{Lang::T('Login')}">login</button>
</form>
</div>
<script src="assets/vendor/global/global.min.js"></script>
<script src="assets/vendor/bootstrap-select/dist/js/bootstrap-select.min.js"></script>
<script src="assets/vendor/chart.js/Chart.bundle.min.js"></script>
<!-- Chart piety plugin files -->
<script src="assets/vendor/peity/jquery.peity.min.js"></script>
<!-- Apex Chart -->
<script src="assets/vendor/apexchart/apexchart.js"></script>
<!-- Dashboard 1 -->
<script src="assets/js/dashboard/dashboard-1.js"></script>
<script src="assets/js/custom.min.js"></script>
<script src="assets/js/deznav-init.js"></script>
<script src="assets/js/demo.js"></script>
</body>
</html>

70
ui/ui/alert.tpl Normal file
View File

@ -0,0 +1,70 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="shortcut icon" href="https://laravel.com/img/favicon/favicon-16x16.png" type='image/x-icon'>
<title>{Lang::T('Login')} - {$_c['CompanyName']}</title>
<link rel="stylesheet" href="ui/ui/assets/vendor/chartist/css/chartist.min.css">
<link href="ui/ui/assets/vendor/bootstrap-select/dist/css/bootstrap-select.min.css" rel="stylesheet">
<link href="ui/ui/assets/css/style.css" rel="stylesheet">
<link rel="stylesheet" href="ui/ui/styles/sweetalert2.min.css" />
<link rel="stylesheet" href="ui/ui/styles/plugins/pace.css" />
<script src="ui/ui/scripts/sweetalert2.all.min.js"></script>
<meta http-equiv="refresh" content="{$time}; url={$url}">
</head>
<body style="background-color:#e9ecef;">
<div class="" style="margin-top: 72px;">
<div class="container-fluid">
<div class="col-xl-12 col-xxl-12">
<div class="col-md-12">
<div class="card" style="display: grid; align-content: center;">
<div class="card-body text-center ai-icon text-primary">
<h4 class="my-2">{ucwords(Lang::T($type))}</h4>
<div class="my-2">{$text}</div>
<a href="{$url}" id="button" class="btn my-2 btn-primary btn-lg px-4"><i
class="fa fa-usd"></i> {Lang::T('Click Here')} ({$time})</a>
<div class="lockscreen-footer text-center">
{$_c['CompanyName']}
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
var time = {$time};
timer();
function timer() {
setTimeout(() => {
time--;
if (time > -1) {
document.getElementById("button").innerHTML = "{Lang::T('Click Here')} (" + time + ")";
timer();
}
}, 1000);
}
</script>
<script src="assets/vendor/global/global.min.js"></script>
<script src="assets/vendor/bootstrap-select/dist/js/bootstrap-select.min.js"></script>
<script src="assets/vendor/chart.js/Chart.bundle.min.js"></script>
<!-- Chart piety plugin files -->
<script src="assets/vendor/peity/jquery.peity.min.js"></script>
<!-- Apex Chart -->
<script src="assets/vendor/apexchart/apexchart.js"></script>
<!-- Dashboard 1 -->
<script src="assets/js/dashboard/dashboard-1.js"></script>
<script src="assets/js/custom.min.js"></script>
<script src="assets/js/deznav-init.js"></script>
<script src="assets/js/demo.js"></script>
</body>
</html>

159
ui/ui/app-localisation.tpl Normal file
View File

@ -0,0 +1,159 @@
{include file="sections/header.tpl"}
<div class="container-fluid">
<div class="row">
<div class="col-xxl-6 col-xxl-12">
<div class="col-md-12">
<div class="card">
<div class="card-header"
style="display: grid; align-content: center; justify-content: center;">
<h5 class="card-title">{Lang::T('Localisation')}</h5>
</div>
<div class="card-body">
<form class="form-horizontal" method="post" role="form"
action="{$_url}settings/localisation-post">
<div class="form-group row">
<label class="col-md-2 control-label">{Lang::T('Timezone')}</label>
<div class="col-md-6">
<select name="tzone" id="tzone" class="form-control">
{foreach $tlist as $value => $label}
<option value="{$value}" {if $_c['timezone'] eq
$value}selected="selected" {/if}>
{$label}</option>
{/foreach}
</select>
</div>
</div>
<div class="form-group row">
<label class="col-md-2 control-label">{Lang::T('Date Format')}</label>
<div class="col-md-6">
<select class="form-control" name="date_format" id="date_format">
<option value="d/m/Y" {if $_c['date_format'] eq 'd/m/Y' }
selected="selected" {/if}>
{date('d/m/Y')}</option>
<option value="d.m.Y" {if $_c['date_format'] eq 'd.m.Y' }
selected="selected" {/if}>
{date('d.m.Y')}</option>
<option value="d-m-Y" {if $_c['date_format'] eq 'd-m-Y' }
selected="selected" {/if}>
{date('d-m-Y')}</option>
<option value="m/d/Y" {if $_c['date_format'] eq 'm/d/Y' }
selected="selected" {/if}>
{date('m/d/Y')}</option>
<option value="Y/m/d" {if $_c['date_format'] eq 'Y/m/d' }
selected="selected" {/if}>
{date('Y/m/d')}</option>
<option value="Y-m-d" {if $_c['date_format'] eq 'Y-m-d' }
selected="selected" {/if}>
{date('Y-m-d')}</option>
<option value="M d Y" {if $_c['date_format'] eq 'M d Y' }
selected="selected" {/if}>
{date('M d Y')}</option>
<option value="d M Y" {if $_c['date_format'] eq 'd M Y' }
selected="selected" {/if}>
{date('d M Y')}</option>
<option value="jS M y" {if $_c['date_format'] eq 'jS M y' }
selected="selected" {/if}>
{date('jS M y')}</option>
</select>
</div>
</div>
<div class="form-group row">
<label class="col-md-2 control-label">{Lang::T('Default Language')}</label>
<div class="col-md-6">
<select class="form-control" name="lan" id="lan">
{foreach $lani as $lanis}
<option value="{$lanis@key}" {if $_c['language'] eq $lanis@key}
selected="selected" {/if}>
{$lanis@key}
</option>
{/foreach}
<option disabled>_________</option>
{foreach $lan as $lans}
<option value="{$lans@key}" {if $_c['language'] eq $lans@key}
selected="selected" {/if}>
{$lans@key}
</option>
{/foreach}
</select>
</div>
<div class="col-md-4 help-block">
<a href="{$_url}settings/language">{Lang::T('Language Editor')}</a>
</div>
</div>
<div class="form-group row">
<label class="col-md-2 control-label">{Lang::T('Decimal Point')}</label>
<div class="col-md-6">
<input type="text" class="form-control" id="dec_point" name="dec_point"
value="{$_c['dec_point']}">
</div>
</div>
<div class="form-group row">
<label class="col-md-2 control-label">{Lang::T('Thousands Separator')}</label>
<div class="col-md-6">
<input type="text" class="form-control" id="thousands_sep"
name="thousands_sep" value="{$_c['thousands_sep']}">
</div>
</div>
<div class="form-group row">
<label class="col-md-2 control-label">{Lang::T('Currency Code')}</label>
<div class="col-md-6">
<input type="text" class="form-control" id="currency_code"
name="currency_code" value="{$_c['currency_code']}">
</div>
<span class="help-block col-md-4">{Lang::T('Keep it blank if you do not want to
show currency code')}</span>
</div>
<div class="form-group row">
<label class="col-md-2 control-label">{Lang::T('Country Code Phone')}</label>
<div class="col-md-6">
<div class="input-group">
<span class="input-group-text" id="basic-addon1">+</span>
<input type="text" class="form-control" id="country_code_phone"
placeholder="62" name="country_code_phone"
value="{$_c['country_code_phone']}">
</div>
</div>
</div>
<hr>
<div class="form-group row">
<label class="col-md-2 control-label">Radius Plan</label>
<div class="col-md-6">
<input type="text" class="form-control" id="radius_plan" name="radius_plan"
value="{if $_c['radius_plan']==''}Radius Plan{else}{$_c['radius_plan']}{/if}">
</div>
<span class="help-block col-md-4">{Lang::T('Change title in user Plan
order')}</span>
</div>
<div class="form-group row">
<label class="col-md-2 control-label">Hotspot Plan</label>
<div class="col-md-6">
<input type="text" class="form-control" id="hotspot_plan"
name="hotspot_plan"
value="{if $_c['hotspot_plan']==''}Hotspot Plan{else}{$_c['hotspot_plan']}{/if}">
</div>
<span class="help-block col-md-4">{Lang::T('Change title in user Plan
order')}</span>
</div>
<div class="form-group row">
<label class="col-md-2 control-label">PPPOE Plan</label>
<div class="col-md-6">
<input type="text" class="form-control" id="pppoe_plan" name="pppoe_plan"
value="{if $_c['pppoe_plan']==''}PPPOE Plan{else}{$_c['pppoe_plan']}{/if}">
</div>
<span class="help-block col-md-4">{Lang::T('Change title in user Plan
order')}</span>
</div>
<div class="form-group">
<div class="col-lg-offset-2 col-lg-10">
<button class="btn btn-primary" type="submit">{Lang::T('Save
Changes')}</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
{include file="sections/footer.tpl"}

192
ui/ui/app-notifications.tpl Normal file
View File

@ -0,0 +1,192 @@
{include file="sections/header.tpl"}
<div class="container-fluid">
<form class="form-horizontal" method="post" role="form" action="{$_url}settings/notifications-post">
<div class="row">
<div class="col-xxl-6 col-xxl-12">
<div class="col-md-12">
<div class="card">
<div class="card-header"
style="display: grid; align-content: center; justify-content: center;">
<div class="panel panel-primary panel-hovered panel-stacked mb30">
<div class="card-header">
<h5 class="card-title">{Lang::T('User Notification')}</h5>
<div class="btn-group pull-right">
<button class="btn btn-primary" title="save" type="submit"><span
class="flaticon-381-save"
aria-hidden="true"></span></button>
</div>
</div>
</div>
<div class="card-body">
<div class="form-group">
<label class="control-label">{Lang::T('Expired Notification
Message')}</label>
<div class="">
<textarea style="overflow: hidden;" class="form-control"
id="expired" oninput="autoExpand(this)" name="expired"
placeholder="Hello [[name]], your internet package [[package]] has been expired"
rows="6">{if $_json['expired']!=''}{Lang::htmlspecialchars($_json['expired'])}{else}Hello [[name]], your internet package [[package]] has been expired.{/if}</textarea>
</div>
<script>
function autoExpand(element) {
element.style.height = 'auto';
element.style.height = (element.scrollHeight) + 'px';
}
</script>
<p class="help-block">
<b>[[name]]</b> will be replaced with Customer Name.
<b>[[username]]</b> will be replaced with Customer username.
<b>[[package]]</b> will be replaced with Package name.
<b>[[price]]</b> will be replaced with Package price.
<b>[[bills]]</b> aditional bills for customers
</p>
</div>
<div class="form-group row">
<label class="control-label">{Lang::T('Reminder 7 days')}</label>
<div class="">
<textarea class="form-control" id="reminder_7_day"
name="reminder_7_day"
rows="4">{Lang::htmlspecialchars($_json['reminder_7_day'])}</textarea>
</div>
<p class="help-block">
<b>[[name]]</b> will be replaced with Customer Name.
<b>[[username]]</b> will be replaced with Customer username.
<b>[[package]]</b> will be replaced with Package name.
<b>[[price]]</b> will be replaced with Package price.
<b>[[expired_date]]</b> will be replaced with Expiration date.
<b>[[bills]]</b> aditional bills for customers
</p>
</div>
<div class="form-group">
<label class="control-label">{Lang::T('Reminder 3 days')}</label>
<div class="">
<textarea class="form-control" id="reminder_3_day"
name="reminder_3_day"
rows="3">{Lang::htmlspecialchars($_json['reminder_3_day'])}</textarea>
</div>
<p class="help-block">
<b>[[name]]</b> will be replaced with Customer Name.
<b>[[username]]</b> will be replaced with Customer username.
<b>[[package]]</b> will be replaced with Package name.
<b>[[price]]</b> will be replaced with Package price.
<b>[[expired_date]]</b> will be replaced with Expiration date.
<b>[[bills]]</b> aditional bills for customers
</p>
</div>
<div class="form-group">
<label class="control-label">{Lang::T('Reminder 1 day')}</label>
<div class="">
<textarea class="form-control" id="reminder_1_day"
name="reminder_1_day"
rows="3">{Lang::htmlspecialchars($_json['reminder_1_day'])}</textarea>
</div>
<p class="help-block">
<b>[[name]]</b> will be replaced with Customer Name.
<b>[[username]]</b> will be replaced with Customer username.
<b>[[package]]</b> will be replaced with Package name.
<b>[[price]]</b> will be replaced with Package price.
<b>[[expired_date]]</b> will be replaced with Expiration date.
<b>[[bills]]</b> aditional bills for customers
</p>
</div>
<div class="form-group">
<label class="control-label">{Lang::T('Invoice Notification
Payment')}</label>
<div class="">
<textarea class="form-control" id="invoice_paid" name="invoice_paid"
placeholder="Hello [[name]], your internet package [[package]] has been expired"
rows="20">{Lang::htmlspecialchars($_json['invoice_paid'])}</textarea>
</div>
<p class="help-block">
<b>[[company_name]]</b> Your Company Name at Settings.
<b>[[address]]</b> Your Company Address at Settings.
<b>[[phone]]</b> Your Company Phone at Settings.
<b>[[invoice]]</b> invoice number.
<b>[[date]]</b> Date invoice created.
<b>[[payment_gateway]]</b> Payment gateway user paid from.
<b>[[payment_channel]]</b> Payment channel user paid from.
<b>[[type]]</b> is Hotspot/PPPOE.
<b>[[plan_name]]</b> Internet Package.
<b>[[plan_price]]</b> Internet Package Prices.
<b>[[name]]</b> Receiver name.
<b>[[user_name]]</b> Username internet.
<b>[[user_password]]</b> User password.
<b>[[expired_date]]</b> Expired datetime.
<b>[[footer]]</b> Invoice Footer.
<b>[[note]]</b> For Notes by admin.
</p>
</div>
<div class="form-group">
<label class="control-label">{Lang::T('Balance Notification
Payment')}</label>
<div class="">
<textarea class="form-control" id="invoice_balance"
name="invoice_balance"
placeholder="Hello [[name]], your internet package [[package]] has been expired"
rows="20">{Lang::htmlspecialchars($_json['invoice_balance'])}</textarea>
</div>
<p class="help-block">
<b>[[company_name]]</b> Your Company Name at Settings.
<b>[[address]]</b> Your Company Address at Settings.
<b>[[phone]]</b> Your Company Phone at Settings.
<b>[[invoice]]</b> invoice number.
<b>[[date]]</b> Date invoice created.
<b>[[payment_gateway]]</b> Payment gateway user paid from.
<b>[[payment_channel]]</b> Payment channel user paid from.
<b>[[type]]</b> is Hotspot/PPPOE.
<b>[[plan_name]]</b> Internet Package.
<b>[[plan_price]]</b> Internet Package Prices.
<b>[[name]]</b> Receiver name.
<b>[[user_name]]</b> Username internet.
<b>[[user_password]]</b> User password.
<b>[[trx_date]]</b> Transaction datetime.
<b>[[balance_before]]</b> Balance Before.
<b>[[balance]]</b> Balance After.
<b>[[footer]]</b> Invoice Footer.
</p>
</div>
{if $_c['enable_balance'] == 'yes'}
<div class="panel-body">
<div class="form-group">
<label class="control-label">{Lang::T('Send Balance')}</label>
<div class="">
<textarea class="form-control" id="balance_send"
name="balance_send"
rows="3">{if $_json['balance_send']}{Lang::htmlspecialchars($_json['balance_send'])}{else}{Lang::htmlspecialchars($_default['balance_send'])}{/if}</textarea>
</div>
<p class="help-block">
<b>[[name]]</b> Receiver name.
<b>[[balance]]</b> how much balance have been send.
<b>[[current_balance]]</b> Current Balance.
</p>
</div>
</div>
<div class="panel-body">
<div class="form-group">
<label class="control-label">{Lang::T('Received Balance')}</label>
<div class="">
<textarea class="form-control" id="balance_received"
name="balance_received"
rows="3">{if $_json['balance_received']}{Lang::htmlspecialchars($_json['balance_received'])}{else}{Lang::htmlspecialchars($_default['balance_received'])}{/if}</textarea>
</div>
<p class="help-block">
<b>[[name]]</b> Sender name.
<b>[[balance]]</b> how much balance have been received.
<b>[[current_balance]]</b> Current Balance.
</p>
</div>
</div>
{/if}
<hr>
<div class="form-group">
<button class="btn btn-success btn-block" type="submit">{Lang::T('Save
Changes')}</button>
</div>
</div>
</div>
</div>
</div>
</div>
</form>
</div>
{include file="sections/footer.tpl"}

831
ui/ui/app-settings.tpl Normal file
View File

@ -0,0 +1,831 @@
{include file="sections/header.tpl"}
<div class="container-fluid">
<form class="form-horizontal" method="post" role="form" action="{$_url}settings/app-post"
enctype="multipart/form-data">
<div class="row">
<div class="col-xxl-12">
<div class="col-md-12">
<div class="card">
<div class="card-header"
style="display: grid; align-content: center; justify-content: center;">
<div class="panel panel-primary panel-hovered panel-stacked mb30">
<div class="card-header">
<h5 class="card-title">{Lang::T('General Settings')}</h5>
<div class="btn-group pull-right">
<button class="btn btn-primary" title="save" type="submit"><span
class="flaticon-381-save"
aria-hidden="true"></span></button>
</div>
</div>
</div>
<div class="card-body">
<div class="form-group row">
<label class="col-md-2 control-label">{Lang::T('Application Name/
Company Name')}</label>
<div class="col-md-6">
<input type="text" required class="form-control" id="CompanyName"
name="CompanyName" value="{$_c['CompanyName']}">
</div>
<span class="help-block col-md-4">{Lang::T('This Name will be shown on
the Title')}</span>
</div>
<div class="form-group row">
<label class="col-md-2 control-label">{Lang::T('Company Logo')}</label>
<div class="col-md-6">
<input type="file" class="form-control" id="logo" name="logo"
accept="image/*">
<span class="help-block">For PDF Reports | Best size 1078 x 200 |
uploaded image will be
autosize</span>
</div>
<span class="help-block col-md-4">
<a href="./{$logo}" target="_blank"><img src="./{$logo}" height="48"
alt="logo for PDF"></a>
</span>
</div>
<div class="form-group row">
<label class="col-md-2 control-label">{Lang::T('Company
Footer')}</label>
<div class="col-md-6">
<input type="text" required class="form-control" id="CompanyFooter"
name="CompanyFooter" value="{$_c['CompanyFooter']}">
</div>
<span class="help-block col-md-4">{Lang::T('Will show below user
pages')}</span>
</div>
<div class="form-group row">
<label class="col-md-2 control-label">{Lang::T('Address')}</label>
<div class="col-md-6">
<textarea class="form-control" id="address" name="address"
rows="3">{Lang::htmlspecialchars($_c['address'])}</textarea>
</div>
<span class="help-block col-md-4">{Lang::T('You can use html
tag')}</span>
</div>
<div class="form-group row">
<label class="col-md-2 control-label">{Lang::T('Phone Number')}</label>
<div class="col-md-6">
<input type="text" class="form-control" id="phone" name="phone"
value="{$_c['phone']}">
</div>
</div>
<div class="form-group row">
<label class="col-md-2 control-label">{Lang::T('Invoice
Footer')}</label>
<div class="col-md-6">
<textarea class="form-control" id="note" name="note"
rows="3">{Lang::htmlspecialchars($_c['note'])}</textarea>
<span class="help-block">{Lang::T('You can use html tag')}</span>
</div>
</div>
<div class="form-group row">
<label class="col-md-2 control-label"><i
class="glyphicon glyphicon-print"></i> Print Max
Char</label>
<div class="col-md-6">
<input type="number" required class="form-control" id="printer_cols"
placeholder="37" name="printer_cols"
value="{$_c['printer_cols']}">
</div>
<span class="help-block col-md-4">For invoice print using Thermal
Printer</span>
</div>
<div class="form-group row">
<label class="col-md-2 control-label">Theme</label>
<div class="col-md-6">
<select name="theme" id="theme" class="form-select" style="height: 52px; background-color: white;">
<option value="default" {if $_c['theme'] eq 'default'
}selected="selected" {/if}>Default
</option>
{foreach $themes as $theme}
<option value="{$theme}" {if $_c['theme'] eq
$theme}selected="selected" {/if}>
{Lang::ucWords($theme)}</option>
{/foreach}
</select>
</div>
</div>
<div class="form-group row">
<label class="col-md-2 control-label">APP URL</label>
<div class="col-md-6">
<input type="text" readonly class="form-control" value="{$app_url}">
</div>
<p class="help-block col-md-4">edit at config.php</p>
</div>
</div>
</div>
<hr>
<div class="card-header"
style="display: grid; align-content: center; justify-content: center;">
<div class="panel panel-primary panel-hovered panel-stacked mb30">
<div class="card-header">
<h5 class="card-title">Hide Dashboard Content</h5>
<div class="btn-group pull-right">
<button class="btn btn-primary" title="save" type="submit"><span
class="flaticon-381-save"
aria-hidden="true"></span></button>
</div>
</div>
</div>
<div class="card-body">
<div class="form-group row">
<label class="col-md-3 control-label"><input type="checkbox"
name="hide_mrc" value="yes" {if $_c['hide_mrc'] eq 'yes'
}checked{/if}>
{Lang::T('Monthly Registered Customers')}</label>
<label class="col-md-2 control-label"><input type="checkbox"
name="hide_tms" value="yes" {if $_c['hide_tms'] eq 'yes'
}checked{/if}> {Lang::T('Total Monthly Sales')}</label>
<label class="col-md-2 control-label"><input type="checkbox"
name="hide_aui" value="yes" {if $_c['hide_aui'] eq 'yes'
}checked{/if}> {Lang::T('All Users Insights')}</label>
<label class="col-md-2 control-label"><input type="checkbox"
name="hide_al" value="yes" {if $_c['hide_al'] eq 'yes'
}checked{/if}> {Lang::T('Activity Log')}</label>
<label class="col-md-2 control-label"><input type="checkbox"
name="hide_uet" value="yes" {if $_c['hide_uet'] eq 'yes'
}checked{/if}> {Lang::T('User Expired, Today')}</label>
<label class="col-md-2 control-label"><input type="checkbox"
name="hide_vs" value="yes" {if $_c['hide_vs'] eq 'yes'
}checked{/if}> Vouchers Stock</label>
<label class="col-md-2 control-label"><input type="checkbox"
name="hide_pg" value="yes" {if $_c['hide_pg'] eq 'yes'
}checked{/if}> Payment Gateway</label>
</div>
</div>
</div>
<hr>
<div class="card-header"
style="display: grid; align-content: center; justify-content: center;">
<div class="panel panel-primary panel-hovered panel-stacked mb30">
<div class="card-header">
<h5 class="card-title">Voucher</h5>
<div class="btn-group pull-right">
<button class="btn btn-primary" title="save" type="submit"><span
class="flaticon-381-save"
aria-hidden="true"></span></button>
</div>
</div>
</div>
<div class="card-body">
<div class="form-group row">
<label class="col-md-2 control-label">{Lang::T('Disable
Voucher')}</label>
<div class="col-md-6">
<select name="disable_voucher" id="disable_voucher"
class="form-select" style="height: 52px; background-color: white;">
<option value="no" {if $_c['disable_voucher']=='no'
}selected="selected" {/if}>No
</option>
<option value="yes" {if $_c['disable_voucher']=='yes'
}selected="selected" {/if}>Yes
</option>
</select>
</div>
<p class="help-block col-md-4">{Lang::T('Voucher activation menu will be
hidden')}</p>
</div>
<div class="form-group row">
<label class="col-md-2 control-label">{Lang::T('Voucher
Format')}</label>
<div class="col-md-6">
<select name="voucher_format" id="voucher_format"
class="form-select" style="height: 52px; background-color: white;">
<option value="up" {if $_c['voucher_format']=='up'
}selected="selected" {/if}>UPPERCASE
</option>
<option value="low" {if $_c['voucher_format']=='low'
}selected="selected" {/if}>
lowercase
</option>
<option value="rand" {if $_c['voucher_format']=='rand'
}selected="selected" {/if}>
RaNdoM
</option>
</select>
</div>
<p class="help-block col-md-4">UPPERCASE lowercase RaNdoM</p>
</div>
{if $_c['disable_voucher'] != 'yes'}
<div class="form-group row">
<label class="col-md-2 control-label">{Lang::T('Disable
Registration')}</label>
<div class="col-md-6">
<select name="disable_registration" id="disable_registration"
class="form-select" style="height: 52px; background-color: white;">
<option value="no" {if $_c['disable_registration']=='no'
}selected="selected" {/if}>No
</option>
<option value="yes" {if $_c['disable_registration']=='yes'
}selected="selected" {/if}>
Yes
</option>
</select>
</div>
<p class="help-block col-md-4">
{Lang::T('Customer just Login with Phone number and Voucher Code,
Voucher will be
password')}
</p>
</div>
<div class="form-group row">
<label class="col-md-2 control-label">Redirect after Activation</label>
<div class="col-md-6">
<input type="text" class="form-control" id="voucher_redirect"
name="voucher_redirect"
placeholder="https://192.168.88.1/status"
value="{$voucher_redirect}">
</div>
<p class="help-block col-md-4">
{Lang::T('After Customer activate voucher or login, customer will be
redirected to this
url')}
</p>
</div>
{/if}
</div>
</div>
<hr>
<div class="card-header"
style="display: grid; align-content: center; justify-content: center;">
<div class="panel panel-primary panel-hovered panel-stacked mb30">
<div class="card-header">
<h5 class="card-title">FreeRadius</h5>
<div class="btn-group pull-right">
<button class="btn btn-primary" title="save" type="submit"><span
class="flaticon-381-save"
aria-hidden="true"></span></button>
</div>
</div>
</div>
<div class="card-body">
<div class="form-group row">
<label class="col-md-2 control-label">Enable Radius</label>
<div class="col-md-6">
<select name="radius_enable" id="radius_enable"
class="form-select" style="height: 52px; background-color: white;" text-muted">
<option value="0">No</option>
<option value="1" {if $_c['radius_enable']}selected="selected"
{/if}>Yes</option>
</select>
</div>
<p class="help-block col-md-4"><a
href="https://github.com/hotspotbilling/phpnuxbill/wiki/FreeRadius"
target="_blank">You can find Free Radius Setup & Configuration
Instructions here.</a></p>
</div>
<div class="form-group row">
<label class="col-md-2 control-label">Radius Client</label>
<div class="col-md-6">
<input type="text" class="form-control" name="radius_client"
value="{$_c['radius_client']}">
</div>
</div>
</div>
</div>
<hr>
<div class="card-header"
style="display: grid; align-content: center; justify-content: center;">
<div class="panel panel-primary panel-hovered panel-stacked mb30">
<div class="card-header">
<h5 class="card-title">{Lang::T('Balance System')}</h5>
<div class="btn-group pull-right">
<button class="btn btn-primary" title="save" type="submit"><span
class="flaticon-381-save"
aria-hidden="true"></span></button>
</div>
</div>
</div>
<div class="card-body">
<div class="form-group row">
<label class="col-md-2 control-label">{Lang::T('Enable System')}</label>
<div class="col-md-6">
<select name="enable_balance" id="enable_balance"
class="form-select" style="height: 52px; background-color: white;">
<option value="no" {if $_c['enable_balance']=='no'
}selected="selected" {/if}>No
</option>
<option value="yes" {if $_c['enable_balance']=='yes'
}selected="selected" {/if}>Yes
</option>
</select>
</div>
<p class="help-block col-md-4">{Lang::T('Customer can deposit money to
buy voucher')}</p>
</div>
<div class="form-group row">
<label class="col-md-2 control-label">{Lang::T('Allow
Transfer')}</label>
<div class="col-md-6">
<select name="allow_balance_transfer" id="allow_balance_transfer"
class="form-select" style="height: 52px; background-color: white;">
<option value="no" {if $_c['allow_balance_transfer']=='no'
}selected="selected" {/if}>
No</option>
<option value="yes" {if $_c['allow_balance_transfer']=='yes'
}selected="selected" {/if}>
Yes</option>
</select>
</div>
<p class="help-block col-md-4">{Lang::T('Allow balance transfer between
customers')}</p>
</div>
<div class="form-group row">
<label class="col-md-2 control-label">{Lang::T('Minimum Balance
Transfer')}</label>
<div class="col-md-6">
<input type="number" class="form-control" id="minimum_transfer"
name="minimum_transfer" value="{$_c['minimum_transfer']}">
</div>
</div>
</div>
</div>
<hr>
<div class="card-header"
style="display: grid; align-content: center; justify-content: center;">
<div class="panel panel-primary panel-hovered panel-stacked mb30">
<div class="card-header">
<h5 class="card-title">{Lang::T('Telegram Notification')}</h5>
<div class="btn-group pull-right">
<a class="btn btn-success btn-xs" href="javascript:testTg()">Test
TG</a>
<button class="btn btn-primary" title="save" type="submit"><span
class="flaticon-381-save"
aria-hidden="true"></span></button>
</div>
</div>
</div>
<div class="card-body">
<div class="form-group row">
<label class="col-md-2 control-label">Telegram Bot Token</label>
<div class="col-md-6">
<input type="password" class="form-control" id="telegram_bot"
name="telegram_bot" onmouseleave="this.type = 'password'"
onmouseenter="this.type = 'text'" value="{$_c['telegram_bot']}"
placeholder="123456:asdasgdkuasghddlashdashldhalskdklasd">
</div>
</div>
<div class="form-group row">
<label class="col-md-2 control-label">Telegram User/Channel/Group
ID</label>
<div class="col-md-6">
<input type="text" class="form-control" id="telegram_target_id"
name="telegram_target_id" value="{$_c['telegram_target_id']}"
placeholder="12345678">
</div>
<small id="emailHelp"
class="form-text text-muted help-block col-md-4">You will get
Payment and
Error
notification on you telegram</small>
</div>
</div>
</div>
<hr>
<div class="card-header"
style="display: grid; align-content: center; justify-content: center;">
<div class="panel panel-primary panel-hovered panel-stacked mb30">
<div class="card-header">
<h5 class="card-title">{Lang::T('SMS OTP Registration')}</h5>
<div class="btn-group pull-right">
<a class="btn btn-success" href="javascript:testSms()">Test SMS</a>
<button class="btn btn-primary" title="save" type="submit"><span
class="flaticon-381-save"
aria-hidden="true"></span></button>
</div>
</div>
</div>
<div class="card-body">
<div class="form-group row">
<label class="col-md-2 control-label">SMS Server URL</label>
<div class="col-md-6">
<input type="text" class="form-control" id="sms_url" name="sms_url"
value="{$_c['sms_url']}"
placeholder="https://domain/?param_number=[number]&param_text=[text]&secret=">
</div>
<p class="help-block col-md-4">Must include <b>[text]</b> &amp;
<b>[number]</b>, it will be
replaced.
</p>
</div>
<div class="form-group row">
<label class="col-md-2 control-label">Or use Mikrotik SMS</label>
<div class="col-md-6">
<select class="form-control"
onchange="document.getElementById('sms_url').value = this.value">
<option value="">Select Router</option>
{foreach $r as $rs}
<option value="{$rs['name']}" {if
$rs['name']==$_c['sms_url']}selected{/if}>
{$rs['name']}</option>
{/foreach}
</select>
</div>
<p class="help-block col-md-4">Must include <b>[text]</b> &amp;
<b>[number]</b>, it will be
replaced.
</p>
</div>
</div>
</div>
<hr>
<div class="card-header"
style="display: grid; align-content: center; justify-content: center;">
<div class="panel panel-primary panel-hovered panel-stacked mb30">
<div class="card-header">
<h5 class="card-title">{Lang::T('Whatsapp Notification')}</h5>
<div class="btn-group pull-right">
<a class="btn btn-success" href="javascript:testWa()">Test WA</a>
<button class="btn btn-primary" title="save" type="submit"><span
class="flaticon-381-save"
aria-hidden="true"></span></button>
</div>
</div>
</div>
<div class="card-body">
<div class="form-group row">
<label class="col-md-2 control-label">Whatsapp Server URL</label>
<div class="col-md-6">
<input type="text" class="form-control" id="wa_url" name="wa_url"
value="{$_c['wa_url']}"
placeholder="https://domain/?param_number=[number]&param_text=[text]&secret=">
</div>
<p class="help-block col-md-4">Must include <b>[text]</b> &amp;
<b>[number]</b>, it will be
replaced.
</p>
</div>
</div>
</div>
<hr>
<div class="card-header"
style="display: grid; align-content: center; justify-content: center;">
<div class="panel panel-primary panel-hovered panel-stacked mb30">
<div class="card-header">
<h5 class="card-title">{Lang::T('Email Notification')}</h5>
<div class="btn-group pull-right">
<a class="btn btn-success" href="javascript:testEmail()">Test
Email</a>
<button class="btn btn-primary" title="save" type="submit"><span
class="flaticon-381-save" aria-hidden="true"></span>
</button>
</div>
</div>
</div>
<div class="card-body">
<div class="form-group row">
<label class="col-md-2 control-label">SMTP Host : port</label>
<div class="col-md-4">
<input type="text" class="form-control" id="smtp_host"
name="smtp_host" value="{$_c['smtp_host']}"
placeholder="smtp.host.tld">
</div>
<div class="col-md-2">
<input type="number" class="form-control" id="smtp_port"
name="smtp_port" value="{$_c['smtp_port']}"
placeholder="465 587 port">
</div>
<p class="help-block col-md-4">Empty this to use internal mail() PHP</p>
</div>
<div class="form-group row">
<label class="col-md-2 control-label">SMTP username</label>
<div class="col-md-6">
<input type="text" class="form-control" id="smtp_user"
name="smtp_user" value="{$_c['smtp_user']}"
placeholder="user@host.tld">
</div>
</div>
<div class="form-group row">
<label class="col-md-2 control-label">SMTP Password</label>
<div class="col-md-6">
<input type="password" class="form-control" id="smtp_pass"
name="smtp_pass" value="{$_c['smtp_pass']}"
onmouseleave="this.type = 'password'"
onmouseenter="this.type = 'text'">
</div>
</div>
<div class="form-group row">
<label class="col-md-2 control-label">SMTP Security</label>
<div class="col-md-6">
<select name="smtp_ssltls" id="smtp_ssltls" class="form-select" style="height: 52px; background-color: white;">
<option value="" {if $_c['smtp_ssltls']=='' }selected="selected"
{/if}>Not Secure</option>
<option value="ssl" {if $_c['smtp_ssltls']=='ssl'
}selected="selected" {/if}>SSL</option>
<option value="tls" {if $_c['smtp_ssltls']=='tls'
}selected="selected" {/if}>TLS</option>
</select>
</div>
<p class="help-block col-md-4">UPPERCASE lowercase RaNdoM</p>
</div>
<div class="form-group row">
<label class="col-md-2 control-label">Mail From</label>
<div class="col-md-6">
<input type="text" class="form-control" id="mail_from"
name="mail_from" value="{$_c['mail_from']}"
placeholder="noreply@host.tld">
</div>
</div>
<div class="form-group row">
<label class="col-md-2 control-label">Mail Reply To</label>
<div class="col-md-6">
<input type="text" class="form-control" id="mail_reply_to"
name="mail_reply_to" value="{$_c['mail_reply_to']}"
placeholder="support@host.tld">
</div>
<p class="help-block col-md-4">Customer will reply email to this
address, empty if you want to use From Address</p>
</div>
</div>
</div>
<hr>
<div class="card-header"
style="display: grid; align-content: center; justify-content: center;">
<div class="panel panel-primary panel-hovered panel-stacked mb30">
<div class="card-header">
<h5 class="card-title">{Lang::T('User Notification')}</h5>
<div class="btn-group pull-right">
<button class="btn btn-primary" title="save" type="submit"><span
class="flaticon-381-save"
aria-hidden="true"></span></button>
</div>
</div>
</div>
<div class="card-body">
<div class="form-group row">
<label class="col-md-2 control-label">{Lang::T('Expired
Notification')}</label>
<div class="col-md-6">
<select name="user_notification_expired"
id="user_notification_expired" class="form-select" style="height: 52px; background-color: white;">
<option value="none">None</option>
<option value="wa" {if $_c['user_notification_expired']=='wa'
}selected="selected" {/if}>Whatsapp</option>
<option value="sms" {if $_c['user_notification_expired']=='sms'
}selected="selected" {/if}>SMS</option>
</select>
</div>
<p class="help-block col-md-4">{Lang::T('User will get notification when
package expired')}</p>
</div>
<div class="form-group row">
<label class="col-md-2 control-label">{Lang::T('Payment
Notification')}</label>
<div class="col-md-6">
<select name="user_notification_payment"
id="user_notification_payment" class="form-select" style="height: 52px; background-color: white;">
<option value="none">None</option>
<option value="wa" {if $_c['user_notification_payment']=='wa'
}selected="selected" {/if}>Whatsapp</option>
<option value="sms" {if $_c['user_notification_payment']=='sms'
}selected="selected" {/if}>SMS</option>
</select>
</div>
<p class="help-block col-md-4">
{Lang::T('User will get invoice notification when buy package or
package refilled')}</p>
</div>
<div class="form-group row">
<label class="col-md-2 control-label">{Lang::T('Reminder
Notification')}</label>
<div class="col-md-6">
<select name="user_notification_reminder"
id="user_notification_reminder" class="form-select" style="height: 52px; background-color: white;">
<option value="none">None</option>
<option value="wa" {if $_c['user_notification_reminder']=='wa'
}selected="selected" {/if}>Whatsapp</option>
<option value="sms" {if $_c['user_notification_reminder']=='sms'
}selected="selected" {/if}>SMS</option>
</select>
</div>
</div>
</div>
</div>
<hr>
<div class="card-header"
style="display: grid; align-content: center; justify-content: center;">
<div class="panel panel-primary panel-hovered panel-stacked mb30">
<div class="card-header">
<h5 class="card-title">{Lang::T('Tawk.to Chat Widget')}</h5>
<div class="btn-group pull-right">
<button class="btn btn-primary" title="save" type="submit"><span
class="flaticon-381-save"
aria-hidden="true"></span></button>
</div>
</div>
</div>
<div class="card-body">
<div class="form-group row">
<label class="col-md-2 control-label">https://tawk.to/chat/</label>
<div class="col-md-6">
<input type="text" class="form-control" id="tawkto" name="tawkto"
value="{$_c['tawkto']}"
placeholder="62f1ca7037898912e961f5/1ga07df">
</div>
<p class="help-block col-md-4">For Direct Chat Link with client from
their Dashboard</p>
</div>
<div class="col-md-12">
<div class="card kanbanPreview-bx">
<div class="card-body">
<div class="sub-card">
<span class="text-warning sub-title fs-14">Copy and paste in
Terminal</span>
<p class="font-w500">
<code>/ip hotspot walled-garden add dst-host=tawk.to add dst-host=*.tawk.to</code>
</p>
</div>
</div>
</div>
</div>
</div>
</div>
<hr>
<div class="card-header"
style="display: grid; align-content: center; justify-content: center;">
<div class="panel panel-primary panel-hovered panel-stacked mb30">
<div class="card-header">
<h5 class="card-title">Generate Your API Key</h5>
<div class="btn-group pull-right">
<button class="btn btn-primary" title="save" type="submit"><span
class="flaticon-381-save"
aria-hidden="true"></span></button>
</div>
</div>
</div>
<div class="card-body">
<div class="form-group row">
<label class="col-md-2 control-label">Access Token</label>
<div class="col-md-6">
<input type="password" class="form-control" id="api_key"
name="api_key" value="{$_c['api_key']}"
placeholder="Empty this to randomly created API key"
onmouseleave="this.type = 'password'"
onmouseenter="this.type = 'text'">
</div>
<p class="col-md-4 help-block">{Lang::T('This Token will act as
SuperAdmin/Admin.')}</p>
<p class="col-md-12 help-block">Empty this to randomly created API key
that you can use to link this application to other services</p>
</div>
</div>
</div>
<hr>
<div class="card-header"
style="display: grid; align-content: center; justify-content: center;">
<div class="panel panel-primary panel-hovered panel-stacked mb30">
<div class="card-header">
<h5 class="card-title">{Lang::T('Proxy')}</h5>
<div class="btn-group pull-right">
<button class="btn btn-primary" title="save" type="submit"><span
class="flaticon-381-save"
aria-hidden="true"></span></button>
</div>
</div>
</div>
<div class="card-body">
<div class="form-group row">
<label class="col-md-2 control-label">{Lang::T('Proxy
Server')}</label>
<div class="col-md-6">
<input type="text" class="form-control" id="http_proxy"
name="http_proxy" value="{$_c['http_proxy']}"
placeholder="127.0.0.1:3128">
</div>
<p class="col-md-4 help-block">Enter Your Proxy Server IP Address or DNS IP or URl</p>
</div>
<div class="form-group row">
<label class="col-md-2 control-label">{Lang::T('Proxy Server
Login')}</label>
<div class="col-md-6">
<input type="password" class="form-control" id="http_proxyauth"
name="http_proxyauth" autocomplete="off"
value="{$_c['http_proxyauth']}" placeholder="username:password"
onmouseleave="this.type = 'password'"
onmouseenter="this.type = 'text'">
</div>
<p class="col-md-4 help-block">Enter Your Proxy Server Login Password</p>
</div>
</div>
</div>
<hr>
<div class="card-header"
style="display: grid; align-content: center; justify-content: center;">
<div class="panel panel-primary panel-hovered panel-stacked mb30">
<div class="card-header">
<h5 class="card-title">{Lang::T('Miscellaneous')}</h5>
<div class="btn-group pull-right">
<button class="btn btn-primary" title="save" type="submit"><span
class="flaticon-381-save"
aria-hidden="true"></span></button>
</div>
</div>
</div>
<div class="card-body">
<div class="form-group row">
<label class="col-md-2 control-label">{Lang::T('OTP
Required')}</label>
<div class="col-md-6">
<select name="allow_phone_otp" id="allow_phone_otp"
class="form-select" style="height: 52px; background-color: white;">
<option value="no" {if $_c['allow_phone_otp']=='no'
}selected="selected" {/if}>
No</option>
<option value="yes" {if $_c['allow_phone_otp']=='yes'
}selected="selected" {/if}>Yes
</option>
</select>
</div>
<p class="help-block col-md-4">{Lang::T('OTP is required
when user want
to change phone
number')}</p>
</div>
<div class="form-group row">
<label class="col-md-2 control-label">{Lang::T('OTP Method')}</label>
<div class="col-md-6">
<select name="phone_otp_type" id="phone_otp_type"
class="form-select" style="height: 52px; background-color: white;">
<option value="sms" {if $_c['phone_otp_type']=='sms'
}selected="selected" {/if}>
{Lang::T('SMS')}
<option value="whatsapp" {if $_c['phone_otp_type']=='whatsapp'
}selected="selected" {/if}>
{Lang::T('WhatsApp')}
<option value="both" {if $_c['phone_otp_type']=='both'
}selected="selected" {/if}>
{Lang::T('SMS and WhatsApp')}
</option>
</select>
</div>
<p class="help-block col-md-4">{Lang::T('The method which
OTP will be
sent to user')}</p>
</div>
</div>
</div>
<hr>
<div class="card-body">
<div class="form-group">
<button class="btn btn-success btn-block" type="submit">{Lang::T('Save
Changes')}</button>
</div>
</div>
<div class="card-body">
<div class="card kanbanPreview-bx">
<div class="card-body">
<div class="sub-card">
<span class="text-warning sub-title fs-14">Copy and paste in
Terminal</span>
<code>/ip hotspot walled-garden add dst-host={$_domain} add dst-host=*.{$_domain}</code>
</div>
</div>
</div>
<div class="card kanbanPreview-bx">
<div class="card-body">
<div class="sub-card">
<span class="text-warning sub-title fs-14">Cron Jobs</span>
<p># Expired Cronjob Every 5 Minutes</p>
<pre>*/5 * * * * cd {$dir} && {$php} cron.php</pre>
<p># Expired Cronjob Every 1 Hour</p>
<pre>0 * * * * cd {$dir} && {$php} cron.php</pre>
<p># Reminder Cronjob Every 7 AM</p>
<pre>0 7 * * * cd {$dir} && {$php} cron_reminder.php</pre>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</form>
<script>
function testWa() {
var target = prompt("Phone number\nSave First before Test", "");
if (target != null) {
window.location.href = '{$_url}settings/app&testWa=' + target;
}
}
function testSms() {
var target = prompt("Phone number\nSave First before Test", "");
if (target != null) {
window.location.href = '{$_url}settings/app&testSms=' + target;
}
}
function testEmail() {
var target = prompt("Email\nSave First before Test", "");
if (target != null) {
window.location.href = '{$_url}settings/app&testEmail=' + target;
}
}
function testTg() {
window.location.href = '{$_url}settings/app&testTg=test';
}
</script>
</div>
{include file="sections/footer.tpl"}

4
ui/ui/autoload-pool.tpl Normal file
View File

@ -0,0 +1,4 @@
<option value=''>{Lang::T('Select Pool')}</option>
{foreach $d as $ds}
<option value="{$ds['pool_name']}">{$ds['pool_name']}{if $routers==''} - {$ds['routers']}{/if}</option>
{/foreach}

View File

@ -0,0 +1,7 @@
<option value=''>{Lang::T('Select Routers')}</option>
{if $_c['radius_enable']}
<option value="radius">Radius</option>
{/if}
{foreach $d as $ds}
<option value="{$ds['name']}">{$ds['name']}</option>
{/foreach}

9
ui/ui/autoload.tpl Normal file
View File

@ -0,0 +1,9 @@
<option value="">Select Plans</option>
{foreach $d as $ds}
<option value="{$ds['id']}">
{if $ds['enabled'] neq 1}DISABLED PLAN &bull; {/if}
{$ds['name_plan']} &bull;
{Lang::moneyFormat($ds['price'])}
{if $ds['prepaid'] neq 'yes'} &bull; POSTPAID {/if}
</option>
{/foreach}

51
ui/ui/balance-add.tpl Normal file
View File

@ -0,0 +1,51 @@
{include file="sections/header.tpl"}
<div class="container-fluid">
<div class="row">
<div class="col-xxl-6 col-xxl-12">
<div class="card card-primary card-hovered card-stacked mb30">
<div class="card-header" style="display: grid; align-content: center; justify-content: center;">
<h5 class="card-title">{Lang::T('Add Service Plan')}</h5>
</div>
<div class="card-body">
<form class="form-horizontal" method="post" role="form" action="{$_url}services/balance-add-post">
<div class="form-group row">
<label class="col-md-4 control-label">{Lang::T('Status')}</label>
<div class="col-md-8">
<label class="radio-inline warning">
<input type="radio" checked name="enabled" value="1"> Enable
</label>
<label class="radio-inline">
<input type="radio" name="enabled" value="0"> Disable
</label>
</div>
</div>
<div class="form-group row">
<label class="col-md-4 control-label">{Lang::T('Plan Name')}</label>
<div class="col-md-8">
<input type="text" required class="form-control" id="name" name="name" maxlength="40"
placeholder="Topup 100">
</div>
</div>
<div class="form-group row">
<label class="col-md-4 control-label">{Lang::T('Plan Price')}</label>
<div class="col-md-8">
<div class="input-group">
<span class="input-group-text" >{$_c['currency_code']}</span>
<input type="number" class="form-control" name="price" required>
</div>
</div>
</div>
<div class="form-group">
<div class="col-lg-offset-2 col-lg-10">
<button class="btn btn-success" type="submit">{Lang::T('Save Changes')}</button>
<a class="btn btn-warning" href="{$_url}services/balance">{Lang::T('Cancel')}</a>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
{include file="sections/footer.tpl"}

51
ui/ui/balance-edit.tpl Normal file
View File

@ -0,0 +1,51 @@
{include file="sections/header.tpl"}
<div class="container-fluid">
<div class="row">
<div class="col-xxl-6 col-xxl-12">
<div class="card card-primary card-hovered panel-stacked mb30">
<div class="card-header" style="display: grid; align-content: center; justify-content: center;">
<h5 class="card-title">{Lang::T('Edit Service Plan')}</h5>
</div>
<div class="card-body">
<form class="form-horizontal" method="post" role="form" action="{$_url}services/balance-edit-post">
<input type="hidden" name="id" value="{$d['id']}">
<div class="form-group row">
<label class="col-md-4 control-label">{Lang::T('Status')}</label>
<div class="col-md-8">
<label class="radio-inline warning">
<input type="radio" checked name="enabled" value="1"> Enable
</label>
<label class="radio-inline">
<input type="radio" name="enabled" value="0"> Disable
</label>
</div>
</div>
<div class="form-group row">
<label class="col-md-4 control-label">{Lang::T('Plan Name')}</label>
<div class="col-md-8">
<input type="text" required class="form-control" id="name" value="{$d['name_plan']}" name="name" maxlength="40" placeholder="Topup 100">
</div>
</div>
<div class="form-group row">
<label class="col-md-4 control-label">{Lang::T('Plan Price')}</label>
<div class="col-md-8">
<div class="input-group">
<span class="input-group-addon" style="background-color: white; align-items: center; display: flex; border-radius: 12px 0 0 12px; height: 50px;">{$_c['currency_code']}</span>
<input type="number" class="form-control" name="price" value="{$d['price']}" required>
</div>
</div>
</div>
<div class="form-group">
<div class="col-lg-offset-2 col-lg-10">
<button class="btn btn-success" type="submit">{Lang::T('Save Changes')}</button>
Or <a href="{$_url}services/balance">{Lang::T('Cancel')}</a>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
{include file="sections/footer.tpl"}

55
ui/ui/balance.tpl Normal file
View File

@ -0,0 +1,55 @@
{include file="sections/header.tpl"}
<div class="container-fluid">
<div class="row">
<div class="col-xxl-6 col-xxl-12">
<div class="card panel-hovered mb20 panel-primary">
<div class="card-header" style="display: grid; align-content: center; justify-content: center;">
<h5 class="card-title">{Lang::T('Balance Plans')}</h5>
</div>
<div class="card-body">
<div class="md-whiteframe-z1 mb20 text-center row" style="padding: 15px">
<div class="col-md-8">
<form id="site-search" method="post" action="{$_url}services/balance/">
<div class="input-group">
<!-- <div class="input-group-addon">
<span class="fa fa-search"></span>
</div> -->
<input type="text" name="name" class="form-control" placeholder="{Lang::T('Search by Name')}...">
<button class="btn btn-success" style="border-radius: 0 12px 12px 0; border: none;" type="submit"><i class="fa fa-search"></i>{Lang::T('Search')}</button>
</div>
</form>
</div>
<div class="col-md-4">
<a href="{$_url}services/balance-add" class="btn btn-primary btn-block"><i class="fa fa-plus"> </i> {Lang::T('New Service Plan')}</a>
</div>&nbsp;
</div>
<div class="table-responsive">
<table class="table table-bordered table-striped table-condensed">
<thead>
<tr>
<th>{Lang::T('Plan Name')}</th>
<th>{Lang::T('Plan Price')}</th>
<th>{Lang::T('Manage')}</th>
</tr>
</thead>
<tbody>
{foreach $d as $ds}
<tr {if $ds['enabled'] != 1}class="danger" title="disabled"{/if}>
<td>{$ds['name_plan']}</td>
<td>{Lang::moneyFormat($ds['price'])}</td>
<td>
<a href="{$_url}services/balance-edit/{$ds['id']}" class="btn btn-info btn-xs"><i class="fa fa-edit"></i>{Lang::T('Edit')}</a>
<a href="{$_url}services/balance-delete/{$ds['id']}" onclick="return confirm('{Lang::T('Delete')}?')" id="{$ds['id']}" class="btn btn-danger btn-xs"><i class="fa fa-trash" aria-hidden="true"></i></a>
</td>
</tr>
{/foreach}
</tbody>
</table>
</div>
{include file="pagination.tpl"}
</div>
</div>
</div>
</div>
</div>
{include file="sections/footer.tpl"}

84
ui/ui/bandwidth-add.tpl Normal file
View File

@ -0,0 +1,84 @@
{include file="sections/header.tpl"}
<div class="container-fluid">
<div class="row">
<div class="col-sm-12 col-md-12">
<div class="card card-primary card-hovered card-stacked mb30">
<div class="card-header" style="display: grid; align-content: center; justify-content: center;">
<h5 class="card-title">{Lang::T('Add New Bandwidth')}</h5></div>
<div class="card-body">
<form class="form-horizontal" method="post" role="form" action="{$_url}bandwidth/add-post">
<div class="form-group row">
<label class="col-md-4 control-label">{Lang::T('Bandwidth Name')}</label>
<div class="col-md-6">
<input type="text" class="form-control" id="name" name="name">
</div>
</div>
<div class="form-group row">
<label class="col-md-4 control-label">{Lang::T('Rate Download')}</label>
<div class="col-md-4">
<input type="text" class="form-control" id="rate_down" name="rate_down">
</div>
<div class="col-md-2">
<select class="form-select" style="height: 52px; background-color: white;" id="rate_down_unit" name="rate_down_unit">
<option value="Kbps">Kbps</option>
<option value="Mbps">Mbps</option>
</select>
</div>
</div>
<div class="form-group row">
<label class="col-md-4 control-label">{Lang::T('Rate Upload')}</label>
<div class="col-md-4">
<input type="text" class="form-control" id="rate_up" name="rate_up">
</div>
<div class="col-md-2">
<select class="form-select" style="height: 52px; background-color: white;" id="rate_up_unit" name="rate_up_unit">
<option value="Kbps">Kbps</option>
<option value="Mbps">Mbps</option>
</select>
</div>
</div>
<div class="form-group row">
<label class="col-md-4 control-label">Burst Limit</label>
<div class="col-md-6">
<input type="text" class="form-control" name="burst[]" placeholder="[Burst/Limit]">
</div>
</div>
<div class="form-group row">
<label class="col-md-4 control-label">Burst Threshold</label>
<div class="col-md-6">
<input type="text" class="form-control" name="burst[]" placeholder="[Burst/Threshold]">
</div>
</div>
<div class="form-group row">
<label class="col-md-4 control-label">Burst Time</label>
<div class="col-md-6">
<input type="text" class="form-control" name="burst[]" placeholder="[Burst/Time]">
</div>
</div>
<div class="form-group row">
<label class="col-md-4 control-label">Priority</label>
<div class="col-md-6">
<input type="number" class="form-control" name="burst[]" placeholder="[Priority]">
</div>
</div>
<div class="form-group row">
<label class="col-md-4 control-label">Limit At</label>
<div class="col-md-6">
<input type="text" class="form-control" name="burst[]" placeholder="[Limit/At]">
</div>
</div>
<div class="form-group row">
<div class="col-lg-offset-2 col-lg-10">
<button class="btn btn-primary"
type="submit">{Lang::T('Submit')}</button>
Or <a href="{$_url}bandwidth/list">{Lang::T('Cancel')}</a>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
{include file="sections/footer.tpl"}

97
ui/ui/bandwidth-edit.tpl Normal file
View File

@ -0,0 +1,97 @@
{include file="sections/header.tpl"}
<div class="container-fluid">
<div class="row">
<div class="col-sm-12 col-md-12">
<div class="card card-primary card-hovered card-stacked mb30">
<div class="card-heading" style="display: grid; align-content: center; justify-content: center;">
<h5 class="card-title">{Lang::T('Edit Bandwidth')}</h5>
</div>
<div class="card-body">
<form class="form-horizontal" method="post" role="form" action="{$_url}bandwidth/edit-post">
<input type="hidden" name="id" value="{$d['id']}">
<div class="form-group row">
<label class="col-md-4 control-label">{Lang::T('Bandwidth Name')}</label>
<div class="col-md-6">
<input type="text" class="form-control" id="name" name="name" value="{$d['name_bw']}">
</div>
</div>
<div class="form-group row">
<label class="col-md-4 control-label">{Lang::T('Rate Download')}</label>
<div class="col-md-4">
<input type="text" class="form-control" id="rate_down" name="rate_down"
value="{$d['rate_down']}">
</div>
<div class="col-md-2">
<select class="form-select" style="height: 52px; background-color: white;" id="rate_down_unit" name="rate_down_unit">
<option value="Kbps" {if $d['rate_down_unit'] eq 'Kbps'}selected="selected" {/if}>Kbps
</option>
<option value="Mbps" {if $d['rate_down_unit'] eq 'Mbps'}selected="selected" {/if}>Mbps
</option>
</select>
</div>
</div>
<div class="form-group row">
<label class="col-md-4 control-label">{Lang::T('Rate Upload')}</label>
<div class="col-md-4">
<input type="text" class="form-control" id="rate_up" name="rate_up" value="{$d['rate_up']}">
</div>
<div class="col-md-2">
<select class="form-select" style="height: 52px; background-color: white;" id="rate_up_unit" name="rate_up_unit">
<option value="Kbps" {if $d['rate_up_unit'] eq 'Kbps'}selected="selected" {/if}>Kbps
</option>
<option value="Mbps" {if $d['rate_up_unit'] eq 'Mbps'}selected="selected" {/if}>Mbps
</option>
</select>
</div>
</div>
<div class="form-group row">
<label class="col-md-4 control-label">Burst Limit</label>
<div class="col-md-6">
<input type="text" class="form-control" name="burst[]" placeholder="[Burst/Limit]" value="{$burst[0]}">
</div>
</div>
<div class="form-group row">
<label class="col-md-4 control-label">Burst Threshold</label>
<div class="col-md-6">
<input type="text" class="form-control" name="burst[]" placeholder="[Burst/Threshold]" value="{$burst[1]}">
</div>
</div>
<div class="form-group row">
<label class="col-md-4 control-label">Burst Time</label>
<div class="col-md-6">
<input type="text" class="form-control" name="burst[]" placeholder="[Burst/Time]" value="{$burst[2]}">
</div>
</div>
<div class="form-group row">
<label class="col-md-4 control-label">Priority</label>
<div class="col-md-6">
<input type="number" class="form-control" name="burst[]" placeholder="[Priority]" value="{$burst[3]}">
</div>
</div>
<div class="form-group row">
<label class="col-md-4 control-label">Limit At</label>
<div class="col-md-6">
<input type="text" class="form-control" name="burst[]" placeholder="[Limit/At]" value="{$burst[4]}">
</div>
</div>
<div class="form-group row">
<div class="col-lg-offset-2 col-lg-10">
<small>{Lang::T('Editing Bandwidth will not automatically update the plan, you need to edit the plan then save again')}</small>
</div>
</div>
<div class="form-group row">
<div class="col-lg-offset-2 col-lg-10">
<button class="btn btn-primary"
type="submit">{Lang::T('Submit')}</button>
Or <a href="{$_url}bandwidth/list">{Lang::T('Cancel')}</a>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
{include file="sections/footer.tpl"}

58
ui/ui/bandwidth.tpl Normal file
View File

@ -0,0 +1,58 @@
{include file="sections/header.tpl"}
<div class="container-fluid">
<div class="row">
<div class="col-sm-12">
<div class="card card-hovered mb20 card-primary">
<div class="card-header" style="display: grid; align-content: center; justify-content: center;">
<h5 class="card-title">{Lang::T('Bandwidth Plans')}</h5></div>
<div class="card-body">
<div class="md-whiteframe-z1 mb20 text-center row" style="padding: 15px">
<div class="col-md-8">
<form id="site-search" method="post" action="{$_url}bandwidth/list/">
<div class="input-group">
<!-- <div class="input-group-addon">
<span class="fa fa-search"></span>
</div> -->
<input type="text" name="name" class="form-control" placeholder="{Lang::T('Search by Name')}...">
<button class="btn btn-success" type="submit"><i class="fa fa-search"></i>{Lang::T('Search')}</button>
<!-- <div class="input-group-btn">
</div> -->
</div>
</form>
</div>
<div class="col-md-4">
<a href="{$_url}bandwidth/add" class="btn btn-primary btn-block"><i class="fa fa-plus"> </i> {Lang::T('New Bandwidth')}</a>
</div>&nbsp;
</div>
<div class="table-responsive">
<table class="table table-bordered table-condensed table-striped table_mobile">
<thead>
<tr>
<th>{Lang::T('Bandwidth Name')}</th>
<th>{Lang::T('Rate')}</th>
<th>{Lang::T('Burst')}</th>
<th>{Lang::T('Manage')}</th>
</tr>
</thead>
<tbody>
{foreach $d as $ds}
<tr>
<td>{$ds['name_bw']}</td>
<td>{$ds['rate_down']} {$ds['rate_down_unit']} / {$ds['rate_up']} {$ds['rate_up_unit']}</td>
<td>{$ds['burst']}</td>
<td>
<a href="{$_url}bandwidth/edit/{$ds['id']}" class="btn btn-sm btn-warning"><i class="fa fa-edit"></i>{Lang::T('Edit')}</a>
<a href="{$_url}bandwidth/delete/{$ds['id']}" id="{$ds['id']}" class="btn btn-danger btn-sm" onclick="return confirm('{Lang::T('Delete')}?')" ><i class="fa fa-trash"></i></a>
</td>
</tr>
{/foreach}
</tbody>
</table>
</div>
{include file="pagination.tpl"}
</div>
</div>
</div>
</div>
</div>
{include file="sections/footer.tpl"}

42
ui/ui/change-password.tpl Normal file
View File

@ -0,0 +1,42 @@
{include file="sections/header.tpl"}
<div class="container-fluid">
<div class="row">
<div class="col-sm-12 col-md-12">
<div class="card card-primary card-hovered card-stacked mb30">
<div class="card-header" style="display: grid; align-content: center; justify-content: center;">
<h5 class="card-title">{Lang::T('Change Password')}</h5></div>
<div class="card-body">
<form class="form-horizontal" method="post" role="form" action="{$_url}settings/change-password-post">
<div class="form-group row">
<label class="col-md-4 control-label">{Lang::T('Current Password')}</label>
<div class="col-md-6">
<input type="password" class="form-control" id="password" name="password">
</div>
</div>
<div class="form-group row">
<label class="col-md-4 control-label">{Lang::T('New Password')}</label>
<div class="col-md-6">
<input type="password" class="form-control" id="npass" name="npass">
</div>
</div>
<div class="form-group row">
<label class="col-md-4 control-label">{Lang::T('Confirm New Password')}</label>
<div class="col-md-6">
<input type="password" class="form-control" id="cnpass" name="cnpass">
</div>
</div>
<div class="form-group row">
<div class="col-lg-offset-2 col-lg-10">
<button class="btn btn-success" type="submit">{Lang::T('Save Changes')}</button>
Or <a href="{$_url}dashboard">{Lang::T('Cancel')}</a>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
{include file="sections/footer.tpl"}

42
ui/ui/community.tpl Normal file
View File

@ -0,0 +1,42 @@
{include file="sections/header.tpl"}
<div class="container-fluid">
<div class="row">
<div class="col-sm-12" id="update">
<div class="card card-primary card-hovered mb20 activities">
<div class="card-header">
<h3 class="card-title">MikroPulse</h3>
</div>
<div class="card-body">
<b>MikroPulse</b> is a Billing System for Hotspot and PPPOE in Mikrotik using PHP and Mikrotik API to comunicate with router.
<hr>
<div id="currentVersion">Current Version: </div>
<div id="latestVersion">Latest Version: </div>
</div>
<div class="card-body">
<div class="btn-group btn-group-justified" role="group" aria-label="...">
<a href="./update.php" class="btn btn-success btn-sm btn-block">Install Latest Version</a>
<a href="https://github.com/hotspotbilling/phpnuxbill/archive/refs/heads/master.zip" target="_blank"
class="btn btn-warning btn-sm btn-block">Download Latest Version</a>
</div>
</div>
<div class="card-body">
If you Download manual the update file, sometime update change database, after uploading, click this button to update database structure.
<a href="./update.php?step=4" class="btn btn-default btn-sm btn-block">Update Database</a>
</div>
</div>
</div>
</div>
<script>
window.addEventListener('DOMContentLoaded', function() {
$.getJSON("./version.json?" + Math.random(), function(data) {
$('#currentVersion').html('Current Version: ' + data.version);
});
$.getJSON("https://raw.githubusercontent.com/hotspotbilling/phpnuxbill/master/version.json?" + Math
.random(),
function(data) {
$('#latestVersion').html('Latest Version: ' + data.version);
});
});
</script>
</div>
{include file="sections/footer.tpl"}

215
ui/ui/customers-add.tpl Normal file
View File

@ -0,0 +1,215 @@
{include file="sections/header.tpl"}
{assign var="icmcode" value="ICM{rand(000000,999999)}"}
{assign var="itcode" value="IT{rand(500,999)}"}
<div class="container-fluid">
<form class="form-horizontal" method="post" role="form" action="{$_url}customers/add-post">
<div class="row">
<div class="col-md-12">
<div class="card card-primary card-hovered card-stacked mb30">
<div class="card-header" style="display: grid; align-content: center; justify-content: center;">
<h5 class="card-title">{Lang::T('Add New Contact')}</h5></div>
<div class="card-body">
<div class="form-group row">
<label class="col-md-3 control-label">{Lang::T('Full Name')}</label>
<div class="col-md-9">
<input type="text" required class="form-control" id="fullname" name="fullname">
</div>
</div>
<div class="form-group row">
<label class="col-md-3 control-label">{Lang::T('Username')}</label>
<div class="col-md-9">
<div class="input-group">
<span class="input-group-text" id="basic-addon1"><i
class="fa fa-user"></i></span>
<input type="text" class="form-control" name="username" value="{$icmcode}" id="username" required
placeholder="{if $_c['country_code_phone']!= ''}{$_c['country_code_phone']} {Lang::T('Phone Number')}{else}{Lang::T('Username')}{/if}">
</div>
</div>
</div>
<div class="form-group row">
<label class="col-md-3 control-label">{Lang::T('Account')}</label>
<div class="col-md-9">
<div class="input-group">
<span class="input-group-text" id="basic-addon1"><i
class="fa fa-user"></i></span>
<input type="text" class="form-control" name="account" value="{$itcode}" id="account" required>
</div>
</div>
</div>
<div class="form-group row">
<label class="col-md-3 control-label">{Lang::T('Email')}</label>
<div class="col-md-9">
<input type="email" class="form-control" id="email" name="email" value="icom@net.com">
</div>
</div>
<div class="form-group row">
<label class="col-md-3 control-label">{Lang::T('Phone Number')}</label>
<div class="col-md-9">
<div class="input-group">
{if $_c['country_code_phone']!= ''}
<span class="input-group-text" id="basic-addon1">+</span>
{else}
<span class="input-group-text" id="basic-addon1"><i
class="glyphicon glyphicon-phone-alt"></i></span>
{/if}
<input type="text" class="form-control" name="phonenumber" value="254"
placeholder="{if $_c['country_code_phone']!= ''}{$_c['country_code_phone']}{/if} {Lang::T('Phone Number')}">
</div>
</div>
</div>
<div class="form-group row">
<label class="col-md-3 control-label">{Lang::T('Password')}</label>
<div class="col-md-9">
<input type="password" class="form-control" autocomplete="off" required id="password"
value="{$icmcode}" name="password" onmouseleave="this.type = 'password'"
onmouseenter="this.type = 'text'">
</div>
</div>
<div class="form-group row">
<label class="col-md-3 control-label">{Lang::T('PPPOE Password')}</label>
<div class="col-md-9">
<input type="password" class="form-control" id="pppoe_password" name="pppoe_password"
value="{$icmcode}" onmouseleave="this.type = 'password'"
onmouseenter="this.type = 'text'">
<span class="help-block">
{Lang::T('User Cannot change this, only admin. if it Empty it will use user password')}
</span>
</div>
</div>
<div class="form-group row">
<label class="col-md-3 control-label">{Lang::T('Area')}</label>
<div class="col-md-9">
<textarea name="address" id="address" class="form-control"></textarea>
</div>
</div>
<div class="form-group row">
<label class="col-md-3 control-label">{Lang::T('Service Type')}</label>
<div class="col-md-9">
<select class="form-select" style="height: 52px; background-color: white;" id="service_type" name="service_type">
<option value="Hotspot">Hotspot</option>
<option value="PPPoE">PPPoE</option>
<option value="Static">Static</option>
</select>
</div>
</div>
<div class="form-group row">
<label class="col-md-3 control-label">{Lang::T('Account Type')}</label>
<div class="col-md-9">
<select class="form-select" style="height: 52px; background-color: white;" id="account_type" name="account_type">
<option value="Personal">Personal
</option>
<option value="Business">Business</option>
</select>
</div>
</div>
<div class="form-group row">
<label class="col-md-3 control-label">{Lang::T('Coordinates')}</label>
<div class="col-md-9">
<input name="coordinates" id="coordinates" class="form-control" value=""
placeholder="6.465422, 3.406448">
<div id="map" style="width: '100%'; height: 200px; min-height: 150px;"></div>
</div>
</div>
<div class="col-md-12">
<div class="card kanbanPreview-bx">
<div class="card-body">
<div class="sub-card">
<div class="card-header">
<h5 class="">{Lang::T('Attributes')}</div>
<div class="card-body" style="border: none;">
<!-- Customers Attributes add start -->
<div id="custom-fields-container">
</div>
<!-- Customers Attributes add end -->
</div>
<div class="card-footer">
<button class="btn btn-success btn-block" type="button"
id="add-custom-field">{Lang::T('Add')}</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<center>
<button class="btn btn-primary" type="submit">
{Lang::T('Save Changes')}
</button>
<br><a href="{$_url}customers/list" class="btn btn-link">{Lang::T('Cancel')}</a>
</center>
</form>
{literal}
<script type="text/javascript">
document.addEventListener("DOMContentLoaded", function() {
var customFieldsContainer = document.getElementById('custom-fields-container');
var addCustomFieldButton = document.getElementById('add-custom-field');
addCustomFieldButton.addEventListener('click', function() {
var fieldIndex = customFieldsContainer.children.length;
var newField = document.createElement('div');
newField.className = 'form-group row';
newField.innerHTML = `
<div class="col-md-4">
<input type="text" class="form-control" name="custom_field_name[]" placeholder="Name">
</div>
<div class="col-md-6">
<input type="text" class="form-control" name="custom_field_value[]" placeholder="Value">
</div>
<div class="col-md-2">
<button type="button" class="remove-custom-field btn btn-danger btn-sm">-</button>
</div>
`;
customFieldsContainer.appendChild(newField);
});
customFieldsContainer.addEventListener('click', function(event) {
if (event.target.classList.contains('remove-custom-field')) {
var fieldContainer = event.target.parentNode.parentNode;
fieldContainer.parentNode.removeChild(fieldContainer);
}
});
});
</script>
<script src="https://unpkg.com/leaflet@1.9.3/dist/leaflet.js"></script>
<script>
function getLocation() {
if (window.location.protocol == "https:" && navigator.geolocation) {
navigator.geolocation.getCurrentPosition(showPosition);
} else {
setupMap(51.505, -0.09);
}
}
function showPosition(position) {
setupMap(position.coords.latitude, position.coords.longitude);
}
function setupMap(lat, lon) {
var map = L.map('map').setView([lat, lon], 13);
L.tileLayer('https://{s}.basemaps.cartocdn.com/rastertiles/light_all/{z}/{x}/{y}.png', {
attribution:
'&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors &copy; <a href="https://carto.com/attributions">CARTO</a>',
subdomains: 'abcd',
maxZoom: 20
}).addTo(map);
var marker = L.marker([lat, lon]).addTo(map);
map.on('click', function(e){
var coord = e.latlng;
var lat = coord.lat;
var lng = coord.lng;
var newLatLng = new L.LatLng(lat, lng);
marker.setLatLng(newLatLng);
$('#coordinates').val(lat + ',' + lng);
});
}
window.onload = function() {
getLocation();
}
</script>
{/literal}
</div>
{include file="sections/footer.tpl"}

254
ui/ui/customers-edit.tpl Normal file
View File

@ -0,0 +1,254 @@
{include file="sections/header.tpl"}
<div class="container-fluid">
<form class="form-horizontal" method="post" role="form" action="{$_url}customers/edit-post">
<div class="row">
<div class="col-md-12">
<div class="card card-primary card-hovered card-stacked mb30">
<div class="card-header" style="display: grid; align-content: center; justify-content: center;">
<h5 class="card-title">{Lang::T('Edit Contact')}</h5>
</div>
<div class="card-body">
<input type="hidden" name="id" value="{$d['id']}">
<div class="form-group row">
<label class="col-md-3 control-label">{Lang::T('Full Name')}</label>
<div class="col-md-9">
<input type="text" class="form-control" id="fullname" name="fullname"
value="{$d['fullname']}">
</div>
</div>
<div class="form-group row">
<label class="col-md-3 control-label">{Lang::T('Username')}</label>
<div class="col-md-9">
<div class="input-group mb-3">
{if $_c['country_code_phone']!= ''}
<span class="input-group-text" id="basic-addon1"><i
class="fa fa-user"></i></span>
{else}
<span class="input-group-text" id="basic-addon1"><i
class="fa fa-user"></i></span>
{/if}
<input type="text" class="form-control" name="username" value="{$d['username']}"
required placeholder="{if $_c['country_code_phone']!= ''}{$_c['country_code_phone']} {Lang::T('Phone Number')}{else}{Lang::T('Username')}{/if}">
</div>
</div>
</div>
<div class="form-group row">
<label class="col-md-3 control-label">{Lang::T('Account')}</label>
<div class="col-md-9">
<div class="input-group mb-3">
{if $_c['country_code_phone']!= ''}
<span class="input-group-text" id="basic-addon1"><i
class="fa fa-user"></i></span>
{else}
<span class="input-group-text" id="basic-addon1"><i
class="fa fa-user"></i></span>
{/if}
<input type="text" class="form-control" name="username" value="{$d['account']}"
required>
</div>
</div>
</div>
<div class="form-group row">
<label class="col-md-3 control-label">{Lang::T('Email')}</label>
<div class="col-md-9">
<input type="email" class="form-control" id="email" name="email" value="{$d['email']}">
</div>
</div>
<div class="form-group row">
<label class="col-md-3 control-label">{Lang::T('Phone Number')}</label>
<div class="col-md-9">
<div class="input-group">
{if $_c['country_code_phone']!= ''}
<span class="input-group-text" id="basic-addon1">+</span>
{else}
<span class="input-group-text" id="basic-addon1"><i
class="fa fa-phone-alt"></i></span>
{/if}
<input type="text" class="form-control" name="phonenumber" value="{$d['phonenumber']}"
placeholder="{if $_c['country_code_phone']!= ''}{$_c['country_code_phone']}{/if} {Lang::T('Phone Number')}">
</div>
</div>
</div>
<div class="form-group row">
<label class="col-md-3 control-label">{Lang::T('Password')}</label>
<div class="col-md-9">
<input type="password" autocomplete="off" class="form-control" id="password" name="password"
onmouseleave="this.type = 'password'" onmouseenter="this.type = 'text'"
value="{$d['password']}">
<span class="help-block">{Lang::T('Keep Blank to do not change Password')}</span>
</div>
</div>
<div class="form-group row">
<label class="col-md-3 control-label">{Lang::T('PPPOE Password')}</label>
<div class="col-md-9">
<input type="password" autocomplete="off" class="form-control" id="pppoe_password"
name="pppoe_password" value="{$d['pppoe_password']}"
onmouseleave="this.type = 'password'" onmouseenter="this.type = 'text'">
<span class="help-block">
{Lang::T('User Cannot change this, only admin. if it Empty it will use user password')}
</span>
</div>
</div>
<div class="form-group row">
<label class="col-md-3 control-label">{Lang::T('Area')}</label>
<div class="col-md-9">
<textarea name="address" id="address" class="form-control">{$d['address']}</textarea>
</div>
</div>
<div class="form-group row">
<label class="col-md-3 control-label">{Lang::T('Service Type')}</label>
<div class="col-md-9">
<select class="form-select" style="height: 52px; background-color: white;" id="service_type" name="service_type">
<option value="Hotspot" {if $d['service_type'] eq 'Hotspot' }selected{/if}>Hotspot
</option>
<option value="PPPoE" {if $d['service_type'] eq 'PPPoE' }selected{/if}>PPPoE</option>
<option value="Static" {if $d['service_type'] eq 'Static' }selected{/if}>Static</option>
</select>
</div>
</div>
<div class="form-group row">
<label class="col-md-3 control-label">{Lang::T('Account Type')}</label>
<div class="col-md-9">
<select class="form-select" style="height: 52px; background-color: white;" id="account_type" name="account_type">
<option value="Personal" {if $d['account_type'] eq 'Personal' }selected{/if}>Personal
</option>
<option value="Business" {if $d['account_type'] eq 'Business' }selected{/if}>Business
</option>
</select>
</div>
</div>
<div class="form-group row">
<label class="col-md-3 control-label">{Lang::T('Coordinates')}</label>
<div class="col-md-9">
<input name="coordinates" id="coordinates" class="form-control" value="{$d['coordinates']}"
placeholder="6.465422, 3.406448">
<div id="map" style="width: '100%'; height: 200px; min-height: 150px;"></div>
</div>
</div>
<div class="col-md-12">
<div class="card kanbanPreview-bx">
<div class="card-body">
<div class="sub-card">
<div class="card-header">
<h5>{Lang::T('Attributes')}</h5></div>
<div class="card-body">
<!--Customers Attributes edit start -->
{if $customFields}
{foreach $customFields as $customField}
<div class="form-group row">
<label class="col-md-4 control-label"
for="{$customField.field_name}">{$customField.field_name}</label>
<div class="col-md-6">
<input class="form-control" type="text" name="custom_fields[{$customField.field_name}]"
id="{$customField.field_name}" value="{$customField.field_value}">
</div>
<label class="col-md-2">
<input type="checkbox" name="delete_custom_fields[]" value="{$customField.field_name}">
Delete
</label>
</div>
{/foreach}
{/if}
<!--Customers Attributes edit end -->
<!-- Customers Attributes add start -->
<div id="custom-fields-container">
</div>
<!-- Customers Attributes add end -->
</div>
<div class="card-footer">
<button class="btn btn-success btn-block" type="button"
id="add-custom-field">{Lang::T('Add')}</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<center>
<button class="btn btn-primary" type="submit">
{Lang::T('Save Changes')}
</button>
<br><a href="{$_url}customers/list" class="btn btn-link">{Lang::T('Cancel')}</a>
</center>
</form>
{literal}
<script type="text/javascript">
document.addEventListener("DOMContentLoaded", function() {
var customFieldsContainer = document.getElementById('custom-fields-container');
var addCustomFieldButton = document.getElementById('add-custom-field');
addCustomFieldButton.addEventListener('click', function() {
var fieldIndex = customFieldsContainer.children.length;
var newField = document.createElement('div');
newField.className = 'form-group row';
newField.innerHTML = `
<div class="col-md-4">
<input type="text" class="form-control" name="custom_field_name[]" placeholder="Name">
</div>
<div class="col-md-6">
<input type="text" class="form-control" name="custom_field_value[]" placeholder="Value">
</div>
<div class="col-md-2">
<button type="button" class="remove-custom-field btn btn-danger btn-sm">-</button>
</div>
`;
customFieldsContainer.appendChild(newField);
});
customFieldsContainer.addEventListener('click', function(event) {
if (event.target.classList.contains('remove-custom-field')) {
var fieldContainer = event.target.parentNode.parentNode;
fieldContainer.parentNode.removeChild(fieldContainer);
}
});
});
</script>
<script src="https://unpkg.com/leaflet@1.9.3/dist/leaflet.js"></script>
<script>
function getLocation() {
if (window.location.protocol == "https:" && navigator.geolocation) {
navigator.geolocation.getCurrentPosition(showPosition);
} else {
setupMap(51.505, -0.09);
}
}
function showPosition(position) {
setupMap(position.coords.latitude, position.coords.longitude);
}
function setupMap(lat, lon) {
var map = L.map('map').setView([lat, lon], 13);
L.tileLayer('https://{s}.basemaps.cartocdn.com/rastertiles/light_all/{z}/{x}/{y}.png', {
attribution:
'&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors &copy; <a href="https://carto.com/attributions">CARTO</a>',
subdomains: 'abcd',
maxZoom: 20
}).addTo(map);
var marker = L.marker([lat, lon]).addTo(map);
map.on('click', function(e) {
var coord = e.latlng;
var lat = coord.lat;
var lng = coord.lng;
var newLatLng = new L.LatLng(lat, lng);
marker.setLatLng(newLatLng);
$('#coordinates').val(lat + ',' + lng);
});
}
window.onload = function() {
{/literal}{if $d['coordinates']}
setupMap({$d['coordinates']});
{else}
getLocation();
{/if}{literal}
}
</script>
{/literal}
</div>
{include file="sections/footer.tpl"}

64
ui/ui/customers-map.tpl Normal file
View File

@ -0,0 +1,64 @@
{include file="sections/header.tpl"}
<div class="container-fluid">
<!-- Map container div -->
<div class="card">
<div id="map" class="well card-body" style="width: '100%'; height: 78vh; margin: 20px auto"></div>
</div>
{literal}
<script>
function getLocation() {
if (window.location.protocol == "https:" && navigator.geolocation) {
navigator.geolocation.getCurrentPosition(showPosition);
} else {
setupMap(51.505, -0.09);
}
}
function showPosition(position) {
setupMap(position.coords.latitude, position.coords.longitude);
}
function setupMap(lat, lon) {
var map = L.map('map').setView([lat, lon], 13);
var group = L.featureGroup().addTo(map);
var customers = {/literal}{$customers|json_encode}{literal};
L.tileLayer('https://{s}.basemaps.cartocdn.com/rastertiles/light_all/{z}/{x}/{y}.png', {
attribution:
'&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors &copy; <a href="https://carto.com/attributions">CARTO</a>',
subdomains: 'abcd',
maxZoom: 20
}).addTo(map);
customers.forEach(function(customer) {
var name = customer.id;
var name = customer.name;
var info = customer.info;
var direction = customer.direction;
var coordinates = customer.coordinates;
var balance = customer.balance;
var address = customer.address;
// Create a popup for the marker
var popupContent = "<strong>Name</strong>: " + name + "<br>" +
"<strong>Info</strong>: " + info + "<br>" +
"<strong>Balance</strong>: " + balance + "<br>" +
"<strong>Address</strong>: " + address + "<br>" +
"<a href='{/literal}{$_url}{literal}customers/view/"+ customer.id +"'>More Info</a> &bull; " +
"<a href='https://www.google.com/maps/dir//" + direction + "' target='maps'>Get Direction</a><br>";
// Add marker to map
var marker = L.marker(JSON.parse(coordinates)).addTo(group);
marker.bindTooltip(name, { permanent: true }).bindPopup(popupContent);
});
map.fitBounds(group.getBounds());
}
window.onload = function() {
getLocation();
}
</script>
{/literal}
</div>
{include file="sections/footer.tpl"}

263
ui/ui/customers-view.tpl Normal file
View File

@ -0,0 +1,263 @@
{include file="sections/header.tpl"}
<div class="container-fluid">
<div class="row">
<div class="col-sm-4 col-md-4">
<div class="card card-primary">
<div class="card-body card-profile">
<img class="user-img img-responsive img-circle"
src="https://robohash.org/{$d['id']}?set=set3&size=100x100&bgset=bg1"
onerror="this.src='{$UPLOAD_PATH}/user.default.jpg'" alt="avatar">
<h3 class="profile-username text-center">{$d['fullname']}</h3>
<ul class="list-group list-group-unbordered">
<li class="list-group-item">
<b>{Lang::T('Username')}</b> <span class="pull-right">{$d['username']}</span>
</li>
<li class="list-group-item">
<b>{Lang::T('Phone Number')}</b> <span class="pull-right">{$d['phonenumber']}</span>
</li>
<li class="list-group-item">
<b>{Lang::T('Email')}</b> <span class="pull-right">{$d['email']}</span>
</li>
</ul>
<!--<p class="text-muted">{Lang::nl2br($d['address'])}</p>-->
<ul class="list-group list-group-unbordered">
<li class="list-group-item">
<b>{Lang::T('Password')}</b> <input type="password" value="{$d['password']}"
style=" border: 0px; text-align: right;" class="pull-right"
onmouseleave="this.type = 'password'" onmouseenter="this.type = 'text'"
onclick="this.select()">
</li>
{if $d['pppoe_password'] != ''}
<li class="list-group-item">
<b>PPPOE {Lang::T('Password')}</b> <input type="password" value="{$d['pppoe_password']}"
style=" border: 0px; text-align: right;" class="pull-right"
onmouseleave="this.type = 'password'" onmouseenter="this.type = 'text'"
onclick="this.select()">
</li>
{/if}
<!--Customers Attributes view start -->
{if $customFields}
{foreach $customFields as $customField}
<li class="list-group-item">
<b>{$customField.field_name}</b> <span class="pull-right">
{if strpos($customField.field_value, ':0') === false}
{$customField.field_value}
{else}
<b>{Lang::T('Paid')}</b>
{/if}
</span>
</li>
{/foreach}
{/if}
<!--Customers Attributes view end -->
<li class="list-group-item">
<b>{Lang::T('Service Type')}</b> <span class="pull-right">{Lang::T($d['service_type'])}</span>
</li>
<li class="list-group-item">
<b>{Lang::T('Account')}</b> <span class="pull-right">{Lang::T($d['account'])}</span>
</li>
<li class="list-group-item">
<b>{Lang::T('Account Type')}</b> <span class="pull-right">{Lang::T($d['account_type'])}</span>
</li>
<li class="list-group-item">
<b>{Lang::T('Balance')}</b> <span class="pull-right">{Lang::moneyFormat($d['balance'])}</span>
</li>
<li class="list-group-item">
<b>{Lang::T('Auto Renewal')}</b> <span class="pull-right">{if
$d['auto_renewal']}yes{else}no
{/if}</span>
</li>
<li class="list-group-item">
<b>{Lang::T('Created On')}</b> <span
class="pull-right">{Lang::dateTimeFormat($d['created_at'])}</span>
</li>
<li class="list-group-item">
<b>{Lang::T('Last Login')}</b> <span
class="pull-right">{Lang::dateTimeFormat($d['last_login'])}</span>
</li>
{if $d['coordinates']}
<li class="list-group-item">
<b>{Lang::T('Coordinates')}</b> <span class="pull-right">
<i class="glyphicon glyphicon-road"></i> <a style="color: black;"
href="https://www.google.com/maps/dir//{$d['coordinates']}/" target="_blank">Get
Directions</a>
</span>
<div id="map" style="width: '100%'; height: 100px;"></div>
</li>
{/if}
</ul>
<div class="row">
<div class="col-xs-4 mb-3">
<a href="{$_url}customers/delete/{$d['id']}" id="{$d['id']}"
class="btn btn-danger btn-block btn-sm"
onclick="return confirm('{Lang::T('Delete')}?')"><span class="fa fa-trash"></span></a>
</div>
<div class="col-xs-8">
<a href="{$_url}customers/edit/{$d['id']}"
class="btn btn-warning btn-sm btn-block">{Lang::T('Edit')}</a>
</div>
</div>
</div>
</div>
{foreach $packages as $package}
<div class="card card-{if $package['status']=='on'}success{else}danger{/if}">
<div class="card-body card-profile">
<h4 class="text-center">{$package['type']} - {$package['namebp']}</h4>
<ul class="list-group list-group-unbordered">
<li class="list-group-item">
{Lang::T('Active')} <span class="pull-right">{if
$package['status']=='on'}yes{else}no
{/if}</span>
</li>
<li class="list-group-item">
{Lang::T('Type')} <span class="pull-right">
{if $package['prepaid'] eq yes}Prepaid{else}<b>Postpaid</b>{/if}</span>
</li>
<li class="list-group-item">
{Lang::T('Created On')} <span
class="pull-right">{Lang::dateAndTimeFormat($package['recharged_on'],$package['recharged_time'])}</span>
</li>
<li class="list-group-item">
{Lang::T('Expires On')} <span class="pull-right">{Lang::dateAndTimeFormat($package['expiration'],
$package['time'])}</span>
</li>
<li class="list-group-item">
{$package['routers']} <span class="pull-right">{$package['method']}</span>
</li>
</ul>
<div class="row">
<div class="col-xs-4">
<a href="{$_url}customers/deactivate/{$d['id']}/{$package['plan_id']}" id="{$d['id']}"
class="btn btn-danger btn-block btn-sm"
onclick="return confirm('This will deactivate Customer Plan, and make it expired')">{Lang::T('Deactivate')}</a>
</div>
<div class="col-xs-8">
<a href="{$_url}customers/recharge/{$d['id']}/{$package['plan_id']}"
class="btn btn-success btn-sm btn-block">{Lang::T('Recharge')}</a>
</div>
</div>
</div>
</div>
{/foreach}
<div class="row">
<div class="col-xs-4">
<a href="{$_url}customers/list" class="btn btn-primary btn-sm btn-block">{Lang::T('Back')}</a>
</div>
<div class="col-xs-4">
<a href="{$_url}customers/sync/{$d['id']}"
onclick="return confirm('This will sync Customer to Mikrotik?')"
class="btn btn-info btn-sm btn-block">{Lang::T('Sync')}</a>
</div>
<div class="col-xs-4">
<a href="{$_url}message/send/{$d['id']}" class="btn btn-success btn-sm btn-block">{Lang::T('Send
Message')}</a>
</div>
</div>
</div>
<div class="col-sm-8 col-md-8">
<div class="card">
<div class="card-body">
<ul class="nav nav-tabs">
<li role="presentation" {if $v=='order' }class="nav-item" {/if}><a class="nav-link active"
href="{$_url}customers/view/{$d['id']}/order">30 {Lang::T('Order History')}</a></li>
<li role="presentation" {if $v=='activation' }class="nav-item" {/if}><a class="nav-link active"
href="{$_url}customers/view/{$d['id']}/activation">30 {Lang::T('Activation History')}</a></li>
</ul>
<div class="table-responsive" style="background-color: white;">
<table id="datatable" class="table table-bordered table-striped">
{if Lang::arrayCount($activation)}
<thead>
<tr>
<th>{Lang::T('Invoice')}</th>
<th>{Lang::T('Username')}</th>
<th>{Lang::T('Plan Name')}</th>
<th>{Lang::T('Plan Price')}</th>
<th>{Lang::T('Type')}</th>
<th>{Lang::T('Created On')}</th>
<th>{Lang::T('Expires On')}</th>
<th>{Lang::T('Method')}</th>
</tr>
</thead>
<tbody>
{foreach $activation as $ds}
<tr onclick="window.location.href = '{$_url}plan/view/{$ds['id']}'" style="cursor:pointer;">
<td>{$ds['invoice']}</td>
<td>{$ds['username']}</td>
<td>{$ds['plan_name']}</td>
<td>{Lang::moneyFormat($ds['price'])}</td>
<td>{$ds['type']}</td>
<td class="text-success">{Lang::dateAndTimeFormat($ds['recharged_on'],$ds['recharged_time'])}
</td>
<td class="text-danger">{Lang::dateAndTimeFormat($ds['expiration'],$ds['time'])}</td>
<td>{$ds['method']}</td>
</tr>
{/foreach}
</tbody>
{/if}
{if Lang::arrayCount($order)}
<thead>
<tr>
<th>{Lang::T('Plan Name')}</th>
<th>{Lang::T('Gateway')}</th>
<th>{Lang::T('Routers')}</th>
<th>{Lang::T('Type')}</th>
<th>{Lang::T('Plan Price')}</th>
<th>{Lang::T('Created On')}</th>
<th>{Lang::T('Expires On')}</th>
<th>{Lang::T('Date Done')}</th>
<th>{Lang::T('Method')}</th>
</tr>
</thead>
<tbody>
{foreach $order as $ds}
<tr>
<td>{$ds['plan_name']}</td>
<td>{$ds['gateway']}</td>
<td>{$ds['routers']}</td>
<td>{$ds['payment_channel']}</td>
<td>{Lang::moneyFormat($ds['price'])}</td>
<td class="text-primary">{Lang::dateTimeFormat($ds['created_date'])}</td>
<td class="text-danger">{Lang::dateTimeFormat($ds['expired_date'])}</td>
<td class="text-success">{if $ds['status']!=1}{Lang::dateTimeFormat($ds['paid_date'])}{/if}</td>
<td>{if $ds['status']==1}{Lang::T('UNPAID')}
{elseif $ds['status']==2}{Lang::T('PAID')}
{elseif $ds['status']==3}{$_L['FAILED']}
{elseif $ds['status']==4}{Lang::T('CANCELED')}
{elseif $ds['status']==5}{Lang::T('UNKNOWN')}
{/if}</td>
</tr>
{/foreach}
</tbody>
{/if}
</table>
</div>
{include file="pagination.tpl"}
</div>
</div>
</div>
</div>
{if $d['coordinates']}
{literal}
<script src="https://unpkg.com/leaflet@1.9.3/dist/leaflet.js"></script>
<script>
function setupMap(lat, lon) {
var map = L.map('map').setView([lat, lon], 17);
L.tileLayer('https://{s}.basemaps.cartocdn.com/rastertiles/light_all/{z}/{x}/{y}.png', {
attribution:
'&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors &copy; <a href="https://carto.com/attributions">CARTO</a>',
subdomains: 'abcd',
maxZoom: 20
}).addTo(map);
var marker = L.marker([lat, lon]).addTo(map);
}
window.onload = function() {
{/literal}setupMap({$d['coordinates']});{literal}
}
</script>
{/literal}
{/if}
</div>
{include file="sections/footer.tpl"}

97
ui/ui/customers.tpl Normal file
View File

@ -0,0 +1,97 @@
{include file="sections/header.tpl"}
<div class="container-fluid">
<style>
.dataTables_wrapper .dataTables_paginate .paginate_button {
display: inline-block;
padding: 5px 10px;
margin-right: 5px;
border: 1px solid #ccc;
background-color: #fff;
color: #333;
cursor: pointer;
}
</style>
<div class="row">
<div class="col-sm-12">
<div class="card card-hovered mb20 card-primary">
<div class="card-header">
{if in_array($_admin['user_type'],['SuperAdmin','Admin'])}
<div class="btn-group pull-right">
<a class="btn btn-primary btn-xs" title="save" href="{$_url}customers/csv"
onclick="return confirm('This will export to CSV?')"><span class="fa fa-download"
aria-hidden="true"></span> CSV</a>
</div>
{/if}
{Lang::T('Manage Contact')}
</div>
<div class="card-body">
<div class="md-whiteframe-z1 mb20 text-center">
<div class="col-md-8">
</div>
<div class="col-md-4">
<a href="{$_url}customers/add" class="btn btn-primary btn-block"><i class="fa fa-plus">
</i> {Lang::T('Add New Client')}</a>
</div>&nbsp;
</div>
<div class="table-responsive table_mobile">
<table id="customerTable" class="table table-bordered table-striped table-condensed">
<thead>
<tr>
<th>{Lang::T('Full Name')}</th>
<th>{Lang::T('Username')}</th>
<th>{Lang::T('Account')}</th>
<th>{Lang::T('Acc Type')}</th>
<th>{Lang::T('Area')}</th>
<th>{Lang::T('Package')}</th>
<th>{Lang::T('Service')}</th>
<!--<th>{Lang::T('Created On')}</th>-->
<th>{Lang::T('Manage')}</th>
</tr>
</thead>
<tbody>
{foreach $d as $ds}
<tr {if $ds['status'] != 'on'}class="danger"{/if}>
<td onclick="window.location.href = '{$_url}customers/view/{$ds['id']}'"
style="cursor: pointer;">{$ds['fullname']}</td>
<td onclick="window.location.href = '{$_url}customers/view/{$ds['id']}'"
style="cursor:pointer;">{$ds['username']}</td>
<td>{$ds['account']}</td>
<td>{$ds['account_type']}</td>
<td>{$ds['address']}</td>
<td align="center" api-get-text="{$_url}autoload/customer_is_active/{$d['id']}">
<span class="label label-default">&bull;</span>
</td>
<td>{$ds['service_type']}</td>
<!--<td>{Lang::dateTimeFormat($ds['created_at'])}</td>-->
<td align="center">
<a href="{$_url}customers/view/{$ds['id']}" id="{$ds['id']}"
style="margin: 0px; color:black"
class="btn btn-success btn-xs">&nbsp;&nbsp;{Lang::T('View')}&nbsp;&nbsp;</a>
<a href="{$_url}customers/edit/{$ds['id']}" id="{$ds['id']}"
style="margin: 0px; color:black"
class="btn btn-info btn-xs">&nbsp;&nbsp;{Lang::T('Edit')}&nbsp;&nbsp;</a>
<a href="{$_url}plan/recharge/{$ds['id']}" id="{$ds['id']}" style="margin: 0px;color:black;"
class="btn btn-warning btn-xs">{Lang::T('Recharge')}</a>
</td>
</tr>
{/foreach}
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<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>
var $j = jQuery.noConflict();
$j(document).ready(function () {
$j('#customerTable').DataTable({
"pagingType": "full_numbers"
});
});
</script>
</div>
{include file="sections/footer.tpl"}

499
ui/ui/dashboard.tpl Normal file
View File

@ -0,0 +1,499 @@
{include file="sections/header.tpl"}
<div class="container-fluid">
<style>
.icon i{
font-size: 36px;
}
</style>
<div class="row">
<div class="col-xl-3 col-xxl-3 col-lg-6 col-sm-6">
<div class="card card-bd">
<div class="bg-secondary card-border" style="background:#3444d5 !important;"></div>
<div class="card-body box-style">
<div class="media align-items-center col">
<div class="inner media-body me-3">
<h4 class=" num-text text-black font-w200 fs-48">{$_c['currency_code']}{number_format($iday,0,$_c['dec_point'],$_c['thousands_sep'])}</h4>
<p class="fs-14">{Lang::T('Income Today')}</p>
</div>
<div class="icon" style="color: #864AD1;">
<i class="fas fa-dollar-sign"></i>
</div>
</div>
<div class="">
<a href="{$_url}reports/by-date" class="small-box-footer" style="color: #fc5130;">{Lang::T('View Reports')} <i
class="fa fa-arrow-circle-right"></i>
</a>
</div>
</div>
</div>
</div>
<div class="col-xl-3 col-xxl-3 col-lg-6 col-sm-6">
<div class="card card-bd">
<div class="bg-warning card-border"></div>
<div class="card-body">
<div class="media align-items-center col">
<div class="inner media-body me-3">
<h4 class="num-text text-black font-w200 fs-48">{$_c['currency_code']}{number_format($imonth,0,$_c['dec_point'],$_c['thousands_sep'])}</h4>
<p class="fs-14">{Lang::T('Income This Month')}</p>
</div>
<!--<div class="icon" style="color: #FFB930;">
<i class="fa fa-chart-bar"></i>
</div>-->
</div>
<div class="">
<a href="{$_url}reports/by-period" class="small-box-footer" style="color: #fc5130;">{Lang::T('View Reports')} <i
class="fa fa-arrow-circle-right"></i></a>
</div>
</div>
</div>
</div>
<div class="col-xl-3 col-xxl-3 col-lg-6 col-sm-6">
<div class="card card-bd">
<div class="bg-success card-border"></div>
<div class="card-body box-style">
<div class="media align-items-center col">
<div class="inner media-body me-3">
<h4 class="num-text text-black font-w200 fs-48">{$u_act}/{$u_all}</h4>
<p class="fs-14">{Lang::T('Users Active')}</p>
</div>
<div class="icon" style="color: #20F174;">
<i class="fa fa-users"></i>
</div>
</div>
<div class="">
<a href="{$_url}plan/list" class="small-box-footer" style="color: #fc5130;">{Lang::T('View All')} <i
class="fa fa-arrow-circle-right"></i></a>
</div>
</div>
</div>
</div>
<div class="col-xl-3 col-xxl-3 col-lg-6 col-sm-6">
<div class="card card-bd">
<div class="bg-info card-border"></div>
<div class="card-body box-style">
<div class="media align-items-center col">
<div class="inner media-body me-3">
<h4 class="num-text text-black font-w200 fs-48">{$c_all}</h4>
<p class="fs-14">{Lang::T('Total Users')}</p>
</div>
<div class="icon" style="color: #3ECDFF;">
<i class="fa fa-users"></i>
</div>
</div>
<div class="" >
<a href="{$_url}customers/list" class="small-box-footer" style="color: #fc5130;">{Lang::T('View All')} <i
class="fa fa-arrow-circle-right"></i></a>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-xl-3 col-xxl-3 col-lg-6 col-sm-6">
<div class="card card-bd">
<div class="bg-secondary card-border" style="background:#3444d5 !important;"></div>
<div class="card-body box-style">
<div class="media align-items-center col">
<div class="inner media-body me-3">
<h4 class=" num-text text-black font-w200 fs-48" id="total-online-users"></h4>
<p class="fs-14">{Lang::T('Online Users')}</p>
</div>
<div class="icon" style="color: #864AD1;">
<i class="fas fa-users"></i>
</div>
</div>
</div>
</div>
</div>
<div class="col-xl-3 col-xxl-3 col-lg-6 col-sm-6">
<div class="card card-bd">
<div class="bg-warning card-border"></div>
<div class="card-body">
<div class="media align-items-center col">
<div class="inner media-body me-3">
<h4 class="num-text text-black font-w200 fs-48" id="total_data"></h4>
<p class="fs-14">{Lang::T('Data Usage')}</p>
</div>
<div class="icon" style="color: #FFB930;">
<i class="fas fa-signal"></i>
</div>
</div>
</div>
</div>
</div>
<div class="col-xl-3 col-xxl-3 col-lg-6 col-sm-6">
<div class="card card-bd">
<div class="bg-success card-border"></div>
<div class="card-body box-style">
<div class="media align-items-center col">
<div class="inner media-body me-3">
<h4 class="num-text text-black font-w200 fs-48" id="online-hotspot-users"></h4>
<p class="fs-14">{Lang::T('Online Hotspot Users')}</p>
</div>
<div class="icon" style="color: #20F174;">
<i class="fas fa-wifi"></i>
</div>
</div>
</div>
</div>
</div>
<div class="col-xl-3 col-xxl-3 col-lg-6 col-sm-6">
<div class="card card-bd">
<div class="bg-info card-border"></div>
<div class="card-body box-style">
<div class="media align-items-center col">
<div class="inner media-body me-3">
<h4 class="num-text text-black font-w200 fs-48" id="online-pppoe-users"></h4>
<p class="fs-14">{Lang::T('Online PPPoE Users')}</p>
</div>
<div class="icon" style="color: #3ECDFF;">
<i class="fas fa-network-wired"></i>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<!-- solid sales graph -->
{if $_c['hide_mrc'] != 'yes'}
<div class="card">
<div class="card-header">
<i class="fa fa-th"></i>
<h3 class="card-title">{Lang::T('Monthly Customers')}</h3>
<div class="card-tools pull-right">
<a href="{$_url}dashboard&refresh" class="btn bg-teal btn-xsm"><i
class="fa fa-refresh"></i>
</a>
</div>
</div>
<!-- <div class="card-body text-center pb-0 px-2 pt-2">
<div id="widgetChart1" class="widgetChart1 dashboard-chart"></div>
</div> -->
<div class="card-body border-radius-none">
<canvas class="chart" id="chart" style="height: 200px;"></canvas>
</div>
</div>
{/if}
</div>
<div class="col-md-6">
<!-- solid sales graph -->
{if $_c['hide_tms'] != 'yes'}
<div class="card card-solid ">
<div class="card-header">
<i class="fa fa-inbox"></i>
<h3 class="card-title">{Lang::T('Total Monthly Sales')}</h3>
<div class="card-tools pull-right">
<a href="{$_url}dashboard&refresh" class="btn bg-teal btn-sm"><i
class="fa fa-refresh"></i>
</a>
</div>
</div>
<!-- <div class="card-body pb-0 px-2 pt-2">
<div id="chartTimeline" class="timeline-chart"></div>
</div> -->
<div class="card-body border-radius-none">
<canvas id="salesChart" class="monthly-project-chart" style="height: 200px;"></canvas>
</div>
</div>
{/if}
</div>
<div class="col-md-12">
{if $_c['hide_uet'] != 'yes'}
<div class="card card-warning card-hovered project-stats table-responsive">
<div class="card-header">
<h3 class="card-title">{Lang::T('Expired Users')}</h3></div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-striped table-condensed">
<thead>
<tr>
<th>{Lang::T('Fullname')}</th>
<th>{Lang::T('Username')}</th>
<th>{Lang::T('Account')}</th>
<th>{Lang::T('Created On')}</th>
<th>{Lang::T('Expires On')}</th>
<th>{Lang::T('Router')}</th>
<th>{Lang::T('Package')}</th>
<th>{Lang::T('Action')}</th>
</tr>
</thead>
<tbody>
{foreach $expire as $expired}
<tr>
<td><a href="{$_url}customers/viewu/{$expired['username']}">{$expired['fullname']}</a></td>
<td><a href="{$_url}customers/viewu/{$expired['username']}">{$expired['username']}</a></td>
<td><a href="{$_url}customers/viewu/{$expired['username']}">{$expired['account']}</a></td>
<td>{Lang::dateAndTimeFormat($expired['recharged_on'],$expired['recharged_time'])}
</td>
<td>{Lang::dateAndTimeFormat($expired['expiration'],$expired['time'])}
</td>
<td>{$expired['routers']}</td>
<td>{$expired['namebp']}</td>
<td>
<a href="{$_url}plan/recharge/{$expired['customer_id']}" id="{$expired['customer_id']}" style="margin: 0px;color:black;"
class="btn btn-warning btn-xs">{Lang::T('Recharge')}</a>
</td>
</tr>
</tbody>
{/foreach}
</table>
</div>
&nbsp; {include file="pagination.tpl"}
</div>
</div>
{/if}
</div>
<div class="col-md-6">
{if $_c['disable_voucher'] != 'yes' && $stocks['unused']>0 || $stocks['used']>0}
{if $_c['hide_vs'] != 'yes'}
<div class="card card-primary card-hovered project-stats">
<div class="card-header">Vouchers Stock</div>
<div class="table-responsive">
<table class="table table-striped table-condensed">
<thead>
<tr>
<th>{Lang::T('Plan 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}
</div>
</div>
<div class="row">
<div class="col-md-6">
{if $_c['hide_aui'] != 'yes'}
<div class="card card-hovered activities">
<div class="card-header"><h3 class="card-title">{Lang::T('All Users Insights')}</h3></div>
<div class="card-body">
<canvas id="userRechargesChart" style="height: 600px;"></canvas>
</div>
</div>
{/if}
</div>
<div class="col-md-6">
{if $_c['hide_al'] != 'yes'}
<div class="card card-info card-hovered activities" style="height: 600px;">
<div class="card-header"><a class="card-title" href="{$_url}logs">{Lang::T('Activity Log')}</a></div>
<div class="card-body">
<div id="DZ_W_TimeLine" class="widget-timeline dz-scroll ps ps--active-y" style="height: 460px;">
<ul class="timeline">
{foreach $dlog as $dlogs}
<li class="primary">
<div class="timeline-badge primary"></div>
<div class="timeline-panel text-muted">
<span class="point"></span>
<span class="time small text-muted">{Lang::timeElapsed($dlogs['date'],true)}</span>
<p>{$dlogs['description']}</p>
</div>
</li>
{/foreach}
</ul>
</div>
</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: '#fc5130',
borderColor: '#fc5130',
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',
//type: 'radialBar',
data: {
labels: labels,
datasets: [{
label: 'Monthly Sales',
data: data,
backgroundColor: '#fc5130', // Customize the background color
borderColor: '#fc5130', // Customize the border color
borderWidth: 2
}]
},
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;
// 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>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script>
$(document).ready(function() {
$.ajax({
url: "{$_url}onlineusers/summary", // Adjust this URL to your actual endpoint
type: 'GET',
dataType: 'json', // Ensure the expected response is JSON
success: function(data) {
console.log('Data fetched successfully:', data);
// Check if data is null or missing properties and set defaults to 0
$('#total-online-users').text(data.total_users || 0);
$('#online-hotspot-users').text(data.hotspot_users || 0);
$('#online-pppoe-users').text(data.ppp_users || 0);
$('#total_data').text(data.total_bytes || 0);
},
error: function(error) {
console.log('Error fetching data:', error);
// Set all values to 0 in case of an error
$('#total-online-users').text(0);
$('#online-hotspot-users').text(0);
$('#online-pppoe-users').text(0);
$('#total_data').text(0);
}
});
});
</script>
{include file="sections/footer.tpl"}

61
ui/ui/dbstatus.tpl Normal file
View File

@ -0,0 +1,61 @@
{include file="sections/header.tpl"}
<div class="container-fluid">
<div class="row">
<div class="col-sm-7">
<div class="card card-primary">
<div class="card-header">Backup Database</div>
<form method="post" action="{$_url}settings/dbbackup">
<div class="table-responsive">
<table class="table table-bordered table-striped">
<thead>
<tr>
<th width="50%">{Lang::T('Table Name')}</th>
<th>{Lang::T('Rows')}</th>
<th>Select</th>
</tr>
</thead>
<tbody>
{foreach $tables as $tbl}
<tr>
<td>{$tbl['name']}</td>
<td>{$tbl['rows']}</td>
<td><input type="checkbox" checked name="tables[]" value="{$tbl['name']}"></td>
</tr>
{/foreach}
</tbody>
</table>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-6">Dont select logs if it failed</div>
<div class="col-md-4 text-right">
<button type="submit" class="btn btn-primary btn-xs btn-block"><i
class="fa fa-download"></i>
{Lang::T('Download Database Backup')}</button>
</div>
</div>
</div>
</form>
</div>
</div>
<div class="col-sm-5">
<div class="card card-primary">
<div class="card-header">Restore Database</div>
<form method="post" action="{$_url}settings/dbrestore" enctype="multipart/form-data">
<div class="card-body">
<div class="row">
<div class="col-md-7"><input type="file" name="json" accept="application/json"></div>
<div class="col-md-5 text-right">
<button type="submit" class="btn btn-primary btn-block btn-xs"><i class="fa fa-upload"></i>
Restore Dabase</button>
</div>
</div>
</div>
</form>
<div class="card-footer">Restoring database will clean up data and then restore all the data</div>
</div>
</div>
</div>
</div>
{include file="sections/footer.tpl"}

44
ui/ui/deposit.tpl Normal file
View File

@ -0,0 +1,44 @@
{include file="sections/header.tpl"}
<div class="container-fluid">
<div class="row">
<div class="col-sm-12 col-md-12">
<div class="card card-primary card-hovered card-stacked mb30">
<div class="card-header">{Lang::T('Refill Balance')}</div>
<div class="card-body">
<form class="form-horizontal" method="post" role="form" action="{$_url}plan/deposit-post">
<div class="form-group row">
<label class="col-md-4 control-label">{Lang::T('Select Account')}</label>
<div class="col-md-6">
<select id="personSelect" class="form-control select2" name="id_customer" style="width: 100%"
data-placeholder="{Lang::T('Select a customer')}...">
</select>
</div>
</div>
<div class="form-group row">
<label class="col-md-4 control-label"><a href="{$_url}services/balance">{Lang::T('Balance Plans')}</a></label>
<div class="col-md-6">
<select id="planSelect" class="form-control select2" name="id_plan" style="width: 100%"
data-placeholder="{Lang::T('Select Plans')}...">
<option></option>
{foreach $p as $pl}
<option value="{$pl['id']}">{if $pl['enabled'] neq 1}DISABLED PLAN &bull; {/if}{$pl['name_plan']} - {Lang::moneyFormat($pl['price'])}</option>
{/foreach}
</select>
</div>
</div>
<center>
<div class="form-group row">
<div class="col-lg-offset-2 col-lg-10">
<button class="btn btn-success"
type="submit">{Lang::T('Recharge')}</button>
Or <a href="{$_url}customers/list">{Lang::T('Cancel')}</a>
</div>
</div>
</center>
</form>
</div>
</div>
</div>
</div>
</div>
{include file="sections/footer.tpl"}

222
ui/ui/hotspot-add.tpl Normal file
View File

@ -0,0 +1,222 @@
{include file="sections/header.tpl"}
<div class="container-fluid">
<div class="row">
<div class="col-sm-12 col-md-12">
<div class="card card-primary card-hovered card-stacked mb30">
<div class="card-header">{Lang::T('Add Service Plan')}</div>
<div class="card-body">
<form class="form-horizontal" method="post" role="form" action="{$_url}services/add-post">
<div class="form-group row">
<label class="col-md-2 control-label">{Lang::T('Status')}</label>
<div class="col-md-10">
<input type="radio" name="enabled" value="1" checked> Enable
<input type="radio" name="enabled" value="0"> Disable
</div>
</div>
<div class="form-group row">
<label class="col-md-2 control-label">{Lang::T('Type')}</label>
<div class="col-md-10">
<input type="radio" name="prepaid" onclick="prePaid()" value="yes" checked> Prepaid
<input type="radio" name="prepaid" onclick="postPaid()" value="no"> Postpaid
</div>
</div>
<div class="form-group row">
<label class="col-md-2 control-label">{Lang::T('Plan Type')}</label>
<div class="col-md-10">
<input type="radio" name="plan_type" value="Personal" checked> Personal
<input type="radio" name="plan_type" value="Business"> Business
</div>
</div>
{if $_c['radius_enable']}
<div class="form-group row">
<label class="col-md-2 control-label">Radius</label>
<div class="col-md-6">
<label class="radio-inline">
<input type="checkbox" name="radius" onclick="isRadius(this)" value="1"> Radius Plan
</label>
</div>
<p class="help-block col-md-4">{Lang::T('Cannot be change after saved')}</p>
</div>
{/if}
<div class="form-group row">
<label class="col-md-2 control-label">{Lang::T('Plan Name')}</label>
<div class="col-md-6">
<input type="text" class="form-control" id="name" name="name" maxlength="40">
</div>
</div>
<div class="form-group row">
<label class="col-md-2 control-label">{Lang::T('Plan Type')}</label>
<div class="col-md-10">
<input type="radio" id="Unlimited" name="typebp" value="Unlimited" checked>
{Lang::T('Unlimited')}
<input type="radio" id="Limited" name="typebp" value="Limited"> {Lang::T('Limited')}
</div>
</div>
<div style="display:none;" id="Type">
<div class="form-group row">
<label class="col-md-2 control-label">{Lang::T('Limit Type')}</label>
<div class="col-md-10">
<input type="radio" id="Time_Limit" name="limit_type" value="Time_Limit" checked>
{Lang::T('Time Limit')}
<input type="radio" id="Data_Limit" name="limit_type" value="Data_Limit">
{Lang::T('Data Limit')}
<input type="radio" id="Both_Limit" name="limit_type" value="Both_Limit">
{Lang::T('Both Limit')}
</div>
</div>
</div>
<div style="display:none;" id="TimeLimit">
<div class="form-group row">
<label class="col-md-2 control-label">{Lang::T('Time Limit')}</label>
<div class="col-md-4">
<input type="text" class="form-control" id="time_limit" name="time_limit" value="0">
</div>
<div class="col-md-2">
<select class="form-select select2" style="height: 52px; background-color: white;" id="time_unit" name="time_unit">
<option value="Hrs">{Lang::T('Hrs')}</option>
<option value="Mins">{Lang::T('Mins')}</option>
</select>
</div>
</div>
</div>
<div style="display:none;" id="DataLimit">
<div class="form-group row">
<label class="col-md-2 control-label">{Lang::T('Data Limit')}</label>
<div class="col-md-4">
<input type="text" class="form-control" id="data_limit" name="data_limit" value="0">
</div>
<div class="col-md-2">
<select class="form-select" style="height: 52px; background-color: white;" id="data_unit" name="data_unit">
<option value="MB">MBs</option>
<option value="GB">GBs</option>
</select>
</div>
</div>
</div>
<div class="form-group row">
<label class="col-md-2 control-label"><a
href="{$_url}bandwidth/add">{Lang::T('Bandwidth Name')}</a></label>
<div class="col-md-6">
<select id="id_bw" name="id_bw" class="form-select select2" style="height: 52px; background-color: white;">
<option value="">{Lang::T('Select Bandwidth')}...</option>
{foreach $d as $ds}
<option value="{$ds['id']}">{$ds['name_bw']}</option>
{/foreach}
</select>
</div>
</div>
<div class="form-group row">
<label class="col-md-2 control-label">{Lang::T('Plan Price')}</label>
<div class="col-md-6">
<div class="input-group">
<span class="input-group-text" id="basic-addon1">{$_c['currency_code']}</span>
<input type="number" class="form-control" name="price" required>
</div>
</div>
</div>
<div class="form-group row">
<label class="col-md-2 control-label">{Lang::T('Shared Users')}</label>
<div class="col-md-6">
<input type="text" class="form-control" id="sharedusers" name="sharedusers" value="1">
<p class="help-block">{Lang::T('1 user can be used for many devices?')}</p>
</div>
</div>
<div class="form-group row">
<label class="col-md-2 control-label">{Lang::T('Plan Validity')}</label>
<div class="col-md-4">
<input type="text" class="form-control" id="validity" name="validity">
</div>
<div class="col-md-2">
<select class="form-select" style="height: 52px; background-color: white;" id="validity_unit" name="validity_unit">
</select>
</div>
<p class="help-block col-md-4">{Lang::T('1 Period = 1 Month, Expires the 20th of each month')}</p>
</div>
<span id="routerChoose" class="">
<div class="form-group row">
<label class="col-md-2 control-label"><a
href="{$_url}routers/add">{Lang::T('Router Name')}</a></label>
<div class="col-md-6">
<select id="routers" name="routers" required class="form-select" style="height: 52px; background-color: white;">
<option value=''>{Lang::T('Select Routers')}</option>
{foreach $r as $rs}
<option value="{$rs['name']}">{$rs['name']}</option>
{/foreach}
</select>
<p class="help-block">{Lang::T('Cannot be change after saved')}</p>
</div>
</div>
</span>
<legend>{Lang::T('Expired Action')}</legend>
<div class="form-group row" id="ipPool">
<label class="col-md-2 control-label"><a
href="{$_url}pool/add">{Lang::T('Expired IP Pool')}</a></label>
<div class="col-md-6">
<select id="pool_expired" name="pool_expired" class="form-select select2" style="height: 52px; background-color: white;">
<option value=''>{Lang::T('Select Pool')}</option>
</select>
</div>
</div>
{* <div class="form-group row" id="AddressList">
<label class="col-md-2 control-label">{Lang::T('Address List')}</label>
<div class="col-md-6">
<input type="text" class="form-control" name="list_expired" id="list_expired">
</div>
</div> *}
<center>
<div class="form-group row">
<div class="col-md-offset-2 col-md-10">
<button class="btn btn-success" type="submit">{Lang::T('Save Changes')}</button>
Or <a href="{$_url}services/hotspot">{Lang::T('Cancel')}</a>
</div>
</div>
</center>
</form>
</div>
</div>
</div>
</div>
<script>
var preOpt = `<option value="Mins">{Lang::T('Mins')}</option>
<option value="Hrs">{Lang::T('Hrs')}</option>
<option value="Days">{Lang::T('Days')}</option>
<option value="Months">{Lang::T('Months')}</option>`;
var postOpt = `<option value="Period">{Lang::T('Period')}</option>`;
function prePaid() {
$("#validity_unit").html(preOpt);
}
function postPaid() {
$("#validity_unit").html(postOpt);
}
document.addEventListener("DOMContentLoaded", function(event) {
prePaid()
})
</script>
{if $_c['radius_enable']}
{literal}
<script>
function isRadius(cek) {
if (cek.checked) {
$("#routerChoose").addClass('hidden');
document.getElementById("routers").required = false;
$("#pool_expired").html('');
$.ajax({
url: "index.php?_route=autoload/pool",
data: "routers=radius",
cache: false,
success: function(msg) {
$("#pool_expired").html(msg);
}
});
} else {
document.getElementById("routers").required = true;
$("#routerChoose").removeClass('hidden');
}
}
</script>
{/literal}
{/if}
</div>
{include file="sections/footer.tpl"}

258
ui/ui/hotspot-edit.tpl Normal file
View File

@ -0,0 +1,258 @@
{include file="sections/header.tpl"}
<div class="container-fluid">
<div class="row">
<div class="col-sm-12 col-md-12">
<div class="card card-primary card-hovered card-stacked mb30">
<div class="card-header">{Lang::T('Edit Service Plan')} || {$d['name_plan']}</div>
<div class="card-body">
<form class="form-horizontal" method="post" role="form" action="{$_url}services/edit-post">
<input type="hidden" name="id" value="{$d['id']}">
<div class="form-group row">
<label class="col-md-2 control-label">{Lang::T('Status')}</label>
<div class="col-md-10">
<input type="radio" name="enabled" value="1" {if $d['enabled'] == 1}checked{/if}> Enable
<input type="radio" name="enabled" value="0" {if $d['enabled'] == 0}checked{/if}> Disable
</div>
</div>
<div class="form-group row">
<label class="col-md-2 control-label">{Lang::T('Type')}</label>
<div class="col-md-10">
<input type="radio" name="prepaid" onclick="prePaid()" value="yes"
{if $d['prepaid'] == yes}checked{/if}>
Prepaid
<input type="radio" name="prepaid" onclick="postPaid()" value="no"
{if $d['prepaid'] == no}checked{/if}> Postpaid
</div>
</div>
<div class="form-group row">
<label class="col-md-2 control-label">{Lang::T('Plan Type')}</label>
<div class="col-md-10">
<input type="radio" name="plan_type" value="Personal"
{if $d['plan_type'] == 'Personal'}checked{/if}>
Personal
<input type="radio" name="plan_type" value="Business"
{if $d['plan_type'] == 'Business'}checked{/if}> Business
</div>
</div>
{if $_c['radius_enable'] and $d['is_radius']}
<div class="form-group row">
<label class="col-md-2 control-label">Radius</label>
<div class="col-md-10">
<label class="label label-primary">RADIUS</label>
</div>
</div>
{/if}
<div class="form-group row">
<label class="col-md-2 control-label">{Lang::T('Plan Name')}</label>
<div class="col-md-6">
<input type="text" class="form-control" id="name" name="name" maxlength="40"
value="{$d['name_plan']}">
</div>
</div>
<div class="form-group row">
<label class="col-md-2 control-label">{Lang::T('Plan Type')}</label>
<div class="col-md-10">
<input type="radio" id="Unlimited" name="typebp" value="Unlimited"
{if $d['typebp'] eq 'Unlimited'} checked {/if}> {Lang::T('Unlimited')}
<input type="radio" id="Limited" {if $_c['radius_enable'] and $d['is_radius']}disabled{/if}
name="typebp" value="Limited" {if $d['typebp'] eq 'Limited'} checked {/if}>
{Lang::T('Limited')}
</div>
</div>
<div {if $d['typebp'] eq 'Unlimited'} style="display:none;" {/if} id="Type">
<div class="form-group row">
<label class="col-md-2 control-label">{Lang::T('Limit Type')}</label>
<div class="col-md-10">
<input type="radio" id="Time_Limit" name="limit_type" value="Time_Limit"
{if $d['limit_type'] eq 'Time_Limit'} checked {/if}> {Lang::T('Time Limit')}
<input type="radio" id="Data_Limit" name="limit_type" value="Data_Limit"
{if $d['limit_type'] eq 'Data_Limit'} checked {/if}> {Lang::T('Data Limit')}
<input type="radio" id="Both_Limit" name="limit_type" value="Both_Limit"
{if $d['limit_type'] eq 'Both_Limit'} checked {/if}> {Lang::T('Both Limit')}
</div>
</div>
</div>
<div {if $d['typebp'] eq 'Unlimited'} style="display:none;"
{elseif ($d['time_limit']) eq '0'}
style="display:none;" {/if} id="TimeLimit">
<div class="form-group row">
<label class="col-md-2 control-label">{Lang::T('Time Limit')}</label>
<div class="col-md-4">
<input type="text" class="form-control" id="time_limit" name="time_limit"
value="{$d['time_limit']}">
</div>
<div class="col-md-2">
<select class="form-select" style="height: 52px; background-color: white;" id="time_unit" name="time_unit">
<option value="Hrs" {if $d['time_unit'] eq 'Hrs'} selected {/if}>{Lang::T('Hrs')}
</option>
<option value="Mins" {if $d['time_unit'] eq 'Mins'} selected {/if}>{Lang::T('Mins')}
</option>
</select>
</div>
</div>
</div>
<div {if $d['typebp'] eq 'Unlimited'} style="display:none;"
{elseif ($d['data_limit']) eq '0'}
style="display:none;" {/if} id="DataLimit">
<div class="form-group row">
<label class="col-md-2 control-label">{Lang::T('Data Limit')}</label>
<div class="col-md-4">
<input type="text" class="form-control" id="data_limit" name="data_limit"
value="{$d['data_limit']}">
</div>
<div class="col-md-2">
<select class="form-select" style="height: 52px; background-color: white;" id="data_unit" name="data_unit">
<option value="MB" {if $d['data_unit'] eq 'MB'} selected {/if}>MBs</option>
<option value="GB" {if $d['data_unit'] eq 'GB'} selected {/if}>GBs</option>
</select>
</div>
</div>
</div>
<div class="form-group row">
<label class="col-md-2 control-label"><a
href="{$_url}bandwidth/add">{Lang::T('Bandwidth Name')}</a></label>
<div class="col-md-6">
<select id="id_bw" name="id_bw" class="form-select" style="height: 52px; background-color: white;">
{foreach $b as $bs}
<option value="{$bs['id']}" {if $d['id_bw'] eq $bs['id']} selected {/if}>
{$bs['name_bw']}</option>
{/foreach}
</select>
</div>
</div>
<div class="form-group row">
<label class="col-md-2 control-label">{Lang::T('Plan Price')}</label>
<div class="col-md-6">
<div class="input-group">
<span class="input-group-text" id="basic-addon1">{$_c['currency_code']}</span>
<input type="number" class="form-control" name="price" value="{$d['price']}" required>
</div>
</div>
</div>
<div class="form-group row">
<label class="col-md-2 control-label">{Lang::T('Shared Users')}</label>
<div class="col-md-6">
<input type="text" class="form-control" id="sharedusers" name="sharedusers"
value="{$d['shared_users']}">
</div>
</div>
<div class="form-group row">
<label class="col-md-2 control-label">{Lang::T('Plan Validity')}</label>
<div class="col-md-4">
<input type="text" class="form-control" id="validity" name="validity"
value="{$d['validity']}">
</div>
<div class="col-md-2">
<select class="form-select" style="height: 52px; background-color: white;" id="validity_unit" name="validity_unit">
{if $d['prepaid'] == yes}
<option value="Mins" {if $d['validity_unit'] eq 'Mins'} selected {/if}>{Lang::T('Mins')}
</option>
<option value="Hrs" {if $d['validity_unit'] eq 'Hrs'} selected {/if}>{Lang::T('Hrs')}
</option>
<option value="Days" {if $d['validity_unit'] eq 'Days'} selected {/if}>{Lang::T('Days')}
</option>
<option value="Months" {if $d['validity_unit'] eq 'Months'} selected {/if}>
{Lang::T('Months')}</option>
{else}
<option value="Period" {if $d['validity_unit'] eq 'Period'} selected {/if}>
{Lang::T('Period')}</option>
{/if}
</select>
</div>
<p class="help-block col-md-4">{Lang::T('1 Period = 1 Month, Expires the 20th of each month')}
</p>
</div>
<span id="routerChoose" class="{if $d['is_radius']}hidden{/if}">
<div class="form-group row">
<label class="col-md-2 control-label"><a
href="{$_url}routers/add">{Lang::T('Router Name')}</a></label>
<div class="col-md-6">
<input type="text" class="form-control" id="routers" name="routers"
value="{$d['routers']}" readonly>
</div>
</div>
</span>
<legend>{Lang::T('Expired Action')} <sub>{Lang::T('Optional')}</sub></legend>
<div class="form-group row">
<label class="col-md-2 control-label"><a
href="{$_url}pool/add">{Lang::T('Expired IP Pool')}</a></label>
<div class="col-md-6">
<select id="pool_expired" name="pool_expired" class="form-select" style="height: 52px; background-color: white;">
<option value=''>{Lang::T('Select Pool')}</option>
{foreach $p as $ps}
<option value="{$ps['pool_name']}" {if $d['pool_expired'] eq $ps['pool_name']} selected
{/if}>{$ps['pool_name']}</option>
{/foreach}
</select>
</div>
</div>
{* <div class="form-group row" id="AddressList">
<label class="col-md-2 control-label">{Lang::T('Address List')}</label>
<div class="col-md-6">
<input type="text" class="form-control" name="list_expired" id="list_expired" value="{$d['list_expired']}">
</div>
</div> *}
<center>
<div class="form-group row">
<div class="col-md-offset-2 col-md-10">
<button class="btn btn-success" type="submit">{Lang::T('Save Changes')}</button>
Or <a href="{$_url}services/hotspot">{Lang::T('Cancel')}</a>
</div>
</div>
</center>
</form>
</div>
</div>
</div>
</div>
<script>
var preOpt = `<option value="Mins">{Lang::T('Mins')}</option>
<option value="Hrs">{Lang::T('Hrs')}</option>
<option value="Days">{Lang::T('Days')}</option>
<option value="Months">{Lang::T('Months')}</option>`;
var postOpt = `<option value="Period">{Lang::T('Period')}</option>`;
function prePaid() {
$("#validity_unit").html(preOpt);
}
function postPaid() {
$("#validity_unit").html(postOpt);
}
</script>
{if $_c['radius_enable'] && $d['is_radius']}
{literal}
<script>
function isRadius(cek) {
if (cek.checked) {
$("#routerChoose").addClass('hidden');
document.getElementById("routers").required = false;
document.getElementById("Limited").disabled = true;
} else {
document.getElementById("Limited").disabled = false;
document.getElementById("routers").required = true;
$("#routerChoose").removeClass('hidden');
}
}
setTimeout(() => {
$.ajax({
url: "index.php?_route=autoload/pool",
data: "routers=radius",
cache: false,
success: function(msg) {
$("#pool_expired").html(msg);
}
});
}, 2000);
</script>
{/literal}
{/if}
</div>
{include file="sections/footer.tpl"}

95
ui/ui/hotspot.tpl Normal file
View File

@ -0,0 +1,95 @@
{include file="sections/header.tpl"}
<div class="container-fluid">
<div class="row">
<div class="col-sm-12">
<div class="card card-hovered mb20 card-primary">
<div class="card-header">
{Lang::T('Hotspot Plans')}
<div class="btn-group pull-right">
<a class="btn btn-primary btn-xs" title="save" href="{$_url}services/sync/hotspot"
onclick="return confirm('This will sync/send hotspot plan to Mikrotik?')"><span
class="fa fa-refresh" aria-hidden="true"></span> sync</a>
</div>
</div>
<div class="card-body">
<div class="md-whiteframe-z1 mb20 text-center row" style="padding: 15px">
<div class="col-md-8">
<form id="site-search" method="post" action="{$_url}services/hotspot/">
<div class="input-group">
<div class="input-group-text" id="basic-addon1">
<span class="fa fa-search"></span>
</div>
<input type="text" name="name" class="form-control"
placeholder="{Lang::T('Search by Name')}...">
<div class="input-group-btn">
<button class="btn btn-success" id="button-addon2" style="height: 52px;" type="submit">{Lang::T('Search')}</button>
</div>
</div>
</form>
</div>
<div class="col-md-4">
<a href="{$_url}services/add" class="btn btn-primary btn-block"><i class="fa fa-plus">
</i> {Lang::T('New Service Plan')}</a>
</div>&nbsp;
</div>
<div class="table-responsive">
<table class="table table-bordered table-striped table-condensed">
<thead>
<tr>
<th>{Lang::T('Plan Name')}</th>
<th>{Lang::T('Plan Type')}</th>
<th>{Lang::T('Bandwidth Plans')}</th>
<th>{Lang::T('Plan Category')}</th>
<th>{Lang::T('Plan Price')}</th>
<th>{Lang::T('Time Limit')}</th>
<th>{Lang::T('Data Limit')}</th>
<th>{Lang::T('Plan Validity')}</th>
<th>{Lang::T('Routers')}</th>
<th>{Lang::T('Expired IP Pool')}</th>
<th>{Lang::T('ID')}</th>
<th>{Lang::T('Manage')}</th>
</tr>
</thead>
<tbody>
{foreach $d as $ds}
<tr {if $ds['enabled'] !=1}class="danger" title="disabled" {elseif $ds['prepaid'] !='yes'
}class="warning" title="Postpaid" {/if}>
<td class="headcol">{$ds['name_plan']}</td>
<td>{$ds['plan_type']}</td>
<td>{$ds['name_bw']}</td>
<td>{$ds['typebp']}</td>
<td>{Lang::moneyFormat($ds['price'])}</td>
<td>{$ds['time_limit']} {$ds['time_unit']}</td>
<td>{$ds['data_limit']} {$ds['data_unit']}</td>
<td>{$ds['validity']} {$ds['validity_unit']}</td>
<td>
{if $ds['is_radius']}
<span class="label label-primary">RADIUS</span>
{else}
{if $ds['routers']!=''}
<a href="{$_url}routers/edit/0&name={$ds['routers']}">{$ds['routers']}</a>
{/if}
{/if}
</td>
<td>{$ds['pool_expired']}{if $ds['list_expired']}{if $ds['pool_expired']} |
{/if}{$ds['list_expired']}{/if}</td>
<td>{$ds['id']}</td>
<td>
<a href="{$_url}services/edit/{$ds['id']}"
class="btn btn-info btn-xs">{Lang::T('Edit')}</a>
<a href="{$_url}services/delete/{$ds['id']}" id="{$ds['id']}"
onclick="return confirm('{Lang::T('Delete')}?')"
class="btn btn-danger btn-xs"><i class="fa fa-trash"></i></a>
</td>
</tr>
{/foreach}
</tbody>
</table>
</div>
{include file="pagination.tpl"}
</div>
</div>
</div>
</div>
</div>
{include file="sections/footer.tpl"}

69
ui/ui/hotspot_users.tpl Normal file
View File

@ -0,0 +1,69 @@
{include file="sections/header.tpl"}
<div class="container-fluid">
<div class="row">
<div class="col-sm-12">
<div class="card card-hovered mb20 card">
<div class="card-header">{Lang::T('Hotspot Users')}</div>
<div class="card-body">
<div class="table-responsive table_mobile">
<table id="hotspot_users_table" class="table table-bordered table-striped table-condensed">
<thead>
<tr>
<th>{Lang::T('Username')}</th>
<th>{Lang::T('Address')}</th>
<th>{Lang::T('Uptime')}</th>
<th>{Lang::T('Server')}</th>
<th>{Lang::T('MAC Address')}</th>
<th>{Lang::T('Session Time')}</th>
<th style="color: red;">{Lang::T('Upload')}</th>
<th style="color: green;">{Lang::T('Download')}</th>
<th>{Lang::T('Total')}</th>
<th>{Lang::T('Action')}</th>
</tr>
</thead>
<tbody>
<!-- DataTables will populate the table body dynamically -->
</tbody>
</table>
</div>
</div>
</div>
{include file="pagination.tpl"}
</div>
</div>
</div>
{include file="sections/footer.tpl"}
<!-- Include jQuery and DataTables JS CDN -->
<!--<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>-->
<script src="https://cdn.datatables.net/1.11.5/js/jquery.dataTables.min.js"></script>
<script>
$(document).ready(function() {
$('#hotspot_users_table').DataTable({
"ajax": {
"url": "{$_url}onlineusers/hotspot_users",
"dataSrc": ""
},
"columns": [
{ "data": "username", "render": function(data, type, row) {
return '<span style="color: blue;">' + data + '</span>';
}
},
{ "data": "address" },
{ "data": "uptime" },
{ "data": "server" },
{ "data": "mac" },
{ "data": "session_time" },
{ "data": "rx_bytes" },
{ "data": "tx_bytes" },
{ "data": "total" },
{ "data": null, "render": function(data, type, row) {
return '<form method="post" action="{$_url}onlineusers/disconnect/{$routerId}/' + row.username + '/hotspot">' +
'<button type="submit" class="btn btn-danger btn-sm" title="Disconnect"><i class="fa fa-times"></i></button>' +
'</form>';
}
}
]
});
});
</script>

8
ui/ui/index.html Normal file
View File

@ -0,0 +1,8 @@
<html>
<head>
<title>403 Forbidden</title>
</head>
<body>
<p>Directory access is forbidden.</p>
</body>
</html>

815
ui/ui/indexmain.tpl Normal file
View File

@ -0,0 +1,815 @@
{include file="sections/header.tpl"}
<div class="container-fluid">
<!-- Add Project -->
<div class="modal fade" id="addProjectSidebar">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Create Project</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<form>
<div class="form-group">
<label class="text-black font-w500">Project Name</label>
<input type="text" class="form-control">
</div>
<div class="form-group">
<label class="text-black font-w500">Dadeline</label>
<div class="cal-icon"><input type="date" class="form-control"><i
class="far fa-calendar-alt"></i></div>
</div>
<div class="form-group">
<label class="text-black font-w500">Client Name</label>
<input type="text" class="form-control">
</div>
<div class="form-group">
<button type="button" class="btn btn-primary">CREATE</button>
</div>
</form>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-xl-3 col-xxl-3 col-lg-6 col-sm-6">
<div class="card card-bd">
<div class="bg-secondary card-border" style="background:#3444d5 !important;"></div>
<div class="card-body box-style">
<div class="media align-items-center">
<div class="media-body me-3">
<h2 class="count num-text text-black font-w700">78</h2>
<span class="fs-14">Total Project Handled</span>
</div>
<svg width="36" height="36" viewBox="0 0 36 36" fill="none"
xmlns="http://www.w3.org/2000/svg">
<path
d="M34.422 13.9831C34.3341 13.721 34.1756 13.4884 33.9638 13.3108C33.7521 13.1332 33.4954 13.0175 33.222 12.9766L23.649 11.5141L19.353 2.36408C19.2319 2.10638 19.0399 1.88849 18.7995 1.73587C18.5591 1.58325 18.2803 1.5022 17.9955 1.5022C17.7108 1.5022 17.4319 1.58325 17.1915 1.73587C16.9511 1.88849 16.7592 2.10638 16.638 2.36408L12.342 11.5141L2.76902 12.9766C2.49635 13.0181 2.24042 13.1341 2.02937 13.3117C1.81831 13.4892 1.6603 13.7215 1.57271 13.9831C1.48511 14.2446 1.47133 14.5253 1.53287 14.7941C1.59441 15.063 1.72889 15.3097 1.92152 15.5071L8.89802 22.6501L7.24802 32.7571C7.20299 33.0345 7.23679 33.3189 7.34555 33.578C7.45431 33.8371 7.63367 34.0605 7.86319 34.2226C8.09271 34.3847 8.36315 34.4791 8.64371 34.495C8.92426 34.5109 9.20365 34.4477 9.45002 34.3126L18 29.5906L26.55 34.3126C26.7964 34.4489 27.0761 34.5131 27.3573 34.4978C27.6384 34.4826 27.9096 34.3885 28.1398 34.2264C28.37 34.0643 28.5499 33.8406 28.659 33.5811C28.768 33.3215 28.8018 33.0365 28.7565 32.7586L27.1065 22.6516L34.0785 15.5071C34.2703 15.3091 34.4037 15.0622 34.4643 14.7933C34.5249 14.5245 34.5103 14.2441 34.422 13.9831Z"
fill="#864AD1" />
</svg>
</div>
</div>
</div>
</div>
<div class="col-xl-3 col-xxl-3 col-lg-6 col-sm-6">
<div class="card card-bd">
<div class="bg-warning card-border"></div>
<div class="card-body box-style">
<div class="media align-items-center">
<div class="media-body me-3">
<h2 class="count num-text text-black font-w700">214</h2>
<span class="fs-14">Contacts You Have</span>
</div>
<svg width="36" height="36" viewBox="0 0 36 36" fill="none"
xmlns="http://www.w3.org/2000/svg">
<path
d="M17.8935 22.5C23.6925 22.5 28.3935 17.799 28.3935 12C28.3935 6.20101 23.6925 1.5 17.8935 1.5C12.0945 1.5 7.39351 6.20101 7.39351 12C7.39351 17.799 12.0945 22.5 17.8935 22.5Z"
fill="#FFB930" />
<path
d="M29.5605 21.3344C29.217 20.9909 28.851 20.6699 28.476 20.3564C27.2159 21.96 25.6078 23.2562 23.7733 24.1472C21.9388 25.0382 19.9259 25.5007 17.8864 25.4996C15.847 25.4986 13.8345 25.0342 12.0009 24.1414C10.1673 23.2486 8.56051 21.9507 7.30199 20.3459C5.447 21.8906 3.95577 23.8256 2.9347 26.013C1.91364 28.2003 1.3879 30.586 1.39499 32.9999C1.39499 33.3978 1.55303 33.7793 1.83433 34.0606C2.11564 34.3419 2.49717 34.4999 2.89499 34.4999H32.895C33.2928 34.4999 33.6743 34.3419 33.9557 34.0606C34.237 33.7793 34.395 33.3978 34.395 32.9999C34.4004 30.8324 33.9759 28.6854 33.146 26.683C32.3162 24.6807 31.0975 22.8627 29.5605 21.3344Z"
fill="#FFB930" />
</svg>
</div>
</div>
</div>
</div>
<div class="col-xl-3 col-xxl-3 col-lg-6 col-sm-6">
<div class="card card-bd">
<div class="bg-primary card-border"></div>
<div class="card-body box-style">
<div class="media align-items-center">
<div class="media-body me-3">
<h2 class="count num-text text-black font-w700">93</h2>
<span class="fs-14">Total Unfinished Task</span>
</div>
<svg class="primary-icon" width="36" height="36" viewBox="0 0 36 36" fill="none"
xmlns="http://www.w3.org/2000/svg">
<path
d="M11.9999 1.5H5.99994C3.51466 1.5 1.49994 3.51472 1.49994 6V29.8125C1.49994 32.2977 3.51466 34.3125 5.99994 34.3125H11.9999C14.4852 34.3125 16.4999 32.2977 16.4999 29.8125V6C16.4999 3.51472 14.4852 1.5 11.9999 1.5Z"
fill="#20F174" />
<path
d="M30 1.5H24C21.5147 1.5 19.5 3.51472 19.5 6V12C19.5 14.4853 21.5147 16.5 24 16.5H30C32.4853 16.5 34.5 14.4853 34.5 12V6C34.5 3.51472 32.4853 1.5 30 1.5Z"
fill="#20F174" />
<path
d="M30 19.5H24C21.5147 19.5 19.5 21.5147 19.5 24V30C19.5 32.4853 21.5147 34.5 24 34.5H30C32.4853 34.5 34.5 32.4853 34.5 30V24C34.5 21.5147 32.4853 19.5 30 19.5Z"
fill="#20F174" />
</svg>
</div>
</div>
</div>
</div>
<div class="col-xl-3 col-xxl-3 col-lg-6 col-sm-6">
<div class="card card-bd">
<div class="bg-info card-border"></div>
<div class="card-body box-style">
<div class="media align-items-center">
<div class="media-body me-3">
<h2 class="count num-text text-black font-w700">12</h2>
<span class="fs-14">Unread Messages</span>
</div>
<svg width="46" height="46" viewBox="0 0 46 46" fill="none"
xmlns="http://www.w3.org/2000/svg">
<path
d="M34.4999 1.91663H11.4999C8.95917 1.91967 6.52338 2.93032 4.72682 4.72688C2.93026 6.52345 1.91961 8.95924 1.91656 11.5V26.8333C1.91935 29.0417 2.6834 31.1816 4.07994 32.8924C5.47648 34.6031 7.42011 35.7801 9.58323 36.225V42.1666C9.58318 42.5136 9.67733 42.8541 9.85564 43.1518C10.0339 43.4495 10.2897 43.6932 10.5957 43.8569C10.9016 44.0206 11.2463 44.0982 11.5929 44.0813C11.9395 44.0645 12.275 43.9539 12.5636 43.7613L23.5749 36.4166H34.4999C37.0406 36.4136 39.4764 35.4029 41.273 33.6064C43.0695 31.8098 44.0802 29.374 44.0832 26.8333V11.5C44.0802 8.95924 43.0695 6.52345 41.273 4.72688C39.4764 2.93032 37.0406 1.91967 34.4999 1.91663ZM30.6666 24.9166H15.3332C14.8249 24.9166 14.3374 24.7147 13.9779 24.3552C13.6185 23.9958 13.4166 23.5083 13.4166 23C13.4166 22.4916 13.6185 22.0041 13.9779 21.6447C14.3374 21.2852 14.8249 21.0833 15.3332 21.0833H30.6666C31.1749 21.0833 31.6624 21.2852 32.0219 21.6447C32.3813 22.0041 32.5832 22.4916 32.5832 23C32.5832 23.5083 32.3813 23.9958 32.0219 24.3552C31.6624 24.7147 31.1749 24.9166 30.6666 24.9166ZM34.4999 17.25H11.4999C10.9916 17.25 10.5041 17.048 10.1446 16.6886C9.78517 16.3291 9.58323 15.8416 9.58323 15.3333C9.58323 14.825 9.78517 14.3374 10.1446 13.978C10.5041 13.6186 10.9916 13.4166 11.4999 13.4166H34.4999C35.0082 13.4166 35.4957 13.6186 35.8552 13.978C36.2146 14.3374 36.4166 14.825 36.4166 15.3333C36.4166 15.8416 36.2146 16.3291 35.8552 16.6886C35.4957 17.048 35.0082 17.25 34.4999 17.25Z"
fill="#3ECDFF" />
</svg>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-xl-6 col-xxl-12">
<div class="card">
<div class="card-header d-block border-0 pb-0">
<div class="d-flex justify-content-between pb-3">
<h4 class="mb-0 text-black fs-20">Project Created</h4>
<div class="dropdown">
<a href="javascript:void(0)" data-bs-toggle="dropdown" aria-expanded="false">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none"
xmlns="http://www.w3.org/2000/svg">
<path
d="M12 13C12.5523 13 13 12.5523 13 12C13 11.4477 12.5523 11 12 11C11.4477 11 11 11.4477 11 12C11 12.5523 11.4477 13 12 13Z"
stroke="#575757" stroke-width="2" stroke-linecap="round"
stroke-linejoin="round"></path>
<path
d="M12 6C12.5523 6 13 5.55228 13 5C13 4.44772 12.5523 4 12 4C11.4477 4 11 4.44772 11 5C11 5.55228 11.4477 6 12 6Z"
stroke="#575757" stroke-width="2" stroke-linecap="round"
stroke-linejoin="round"></path>
<path
d="M12 20C12.5523 20 13 19.5523 13 19C13 18.4477 12.5523 18 12 18C11.4477 18 11 18.4477 11 19C11 19.5523 11.4477 20 12 20Z"
stroke="#575757" stroke-width="2" stroke-linecap="round"
stroke-linejoin="round"></path>
</svg>
</a>
<div class="dropdown-menu dropdown-menu-left">
<a class="dropdown-item" href="javascript:void(0);">Edit</a>
<a class="dropdown-item" href="javascript:void(0);">Delete</a>
</div>
</div>
</div>
<div class="d-flex align-items-center">
<span class="fs-36 text-black font-w600 me-4">25%</span>
<div>
<svg class="me-2" width="27" height="14" viewBox="0 0 27 14" fill="none"
xmlns="http://www.w3.org/2000/svg">
<path d="M0 13.435L13.435 0L26.8701 13.435H0Z" fill="#fc5130"></path>
</svg>
<span>last month $563,443</span>
</div>
</div>
</div>
<div class="card-body pb-0 px-2 pt-2">
<div id="chartTimeline" class="timeline-chart"></div>
</div>
</div>
</div>
<div class="col-xl-3 col-xxl-6 col-sm-6">
<div class="card">
<div class="card-header border-0 pb-0">
<h4 class="fs-20 mb-0 text-black">New Clients</h4>
<div class="dropdown">
<a href="javascript:void(0)" data-bs-toggle="dropdown" aria-expanded="false">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none"
xmlns="http://www.w3.org/2000/svg">
<path
d="M12 13C12.5523 13 13 12.5523 13 12C13 11.4477 12.5523 11 12 11C11.4477 11 11 11.4477 11 12C11 12.5523 11.4477 13 12 13Z"
stroke="#575757" stroke-width="2" stroke-linecap="round"
stroke-linejoin="round"></path>
<path
d="M12 6C12.5523 6 13 5.55228 13 5C13 4.44772 12.5523 4 12 4C11.4477 4 11 4.44772 11 5C11 5.55228 11.4477 6 12 6Z"
stroke="#575757" stroke-width="2" stroke-linecap="round"
stroke-linejoin="round"></path>
<path
d="M12 20C12.5523 20 13 19.5523 13 19C13 18.4477 12.5523 18 12 18C11.4477 18 11 18.4477 11 19C11 19.5523 11.4477 20 12 20Z"
stroke="#575757" stroke-width="2" stroke-linecap="round"
stroke-linejoin="round"></path>
</svg>
</a>
<div class="dropdown-menu dropdown-menu-left">
<a class="dropdown-item" href="javascript:void(0);">Edit</a>
<a class="dropdown-item" href="javascript:void(0);">Delete</a>
</div>
</div>
</div>
<div class="card-body text-center pb-0 px-2 pt-2">
<div id="widgetChart1" class="widgetChart1 dashboard-chart"></div>
</div>
</div>
</div>
<div class="col-xl-3 col-xxl-6 col-sm-6">
<div class="card">
<div class="card-header border-0 pb-0">
<h4 class="fs-20 mb-0 text-black">Monthly Target</h4>
<div class="dropdown">
<a href="javascript:void(0)" data-bs-toggle="dropdown" aria-expanded="false">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none"
xmlns="http://www.w3.org/2000/svg">
<path
d="M12 13C12.5523 13 13 12.5523 13 12C13 11.4477 12.5523 11 12 11C11.4477 11 11 11.4477 11 12C11 12.5523 11.4477 13 12 13Z"
stroke="#575757" stroke-width="2" stroke-linecap="round"
stroke-linejoin="round"></path>
<path
d="M12 6C12.5523 6 13 5.55228 13 5C13 4.44772 12.5523 4 12 4C11.4477 4 11 4.44772 11 5C11 5.55228 11.4477 6 12 6Z"
stroke="#575757" stroke-width="2" stroke-linecap="round"
stroke-linejoin="round"></path>
<path
d="M12 20C12.5523 20 13 19.5523 13 19C13 18.4477 12.5523 18 12 18C11.4477 18 11 18.4477 11 19C11 19.5523 11.4477 20 12 20Z"
stroke="#575757" stroke-width="2" stroke-linecap="round"
stroke-linejoin="round"></path>
</svg>
</a>
<div class="dropdown-menu dropdown-menu-left">
<a class="dropdown-item" href="javascript:void(0);">Edit</a>
<a class="dropdown-item" href="javascript:void(0);">Delete</a>
</div>
</div>
</div>
<div class="card-body text-center pt-0">
<div id="radialChart" class="monthly-project-chart"></div>
<span class="fs-14 text-black d-block op5">100 Projects/ monthy</span>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-xl-6 col-xxl-12">
<div class="row">
<div class="col-sm-6">
<div class="card">
<div class="card-header border-0">
<h4 class="fs-16 text-black font-w500">Project Released</h4>
<div class="d-flex align-items-center">
<svg width="14" height="8" viewBox="0 0 14 8" fill="none"
xmlns="http://www.w3.org/2000/svg">
<path d="M1.90735e-06 0.499999L7 7.5L14 0.5" fill="#FF6746" />
</svg>
<span class="fs-28 font-w600 ms-2 text-black">4%</span>
</div>
</div>
<div class="card-body text-center pb-0 p-0">
<div id="widgetChart2" class="dashboard-chart"></div>
</div>
</div>
</div>
<div class="col-sm-6">
<div class="card">
<div
class="card-body text-center d-flex align-items-center justify-content-between">
<div class="d-inline-block position-relative donut-chart-sale">
<span class="donut1"
data-peity='{ "fill": ["#fc5130", "rgba(241, 241, 241,1)"], "innerRadius": 33, "radius": 10}'>3/8</span>
<small class="text-primary">29%</small>
</div>
<div>
<h2 class="fs-28 font-w600 mb-0 text-end text-black">567</h2>
<p class="mb-0 fs-14 font-w400 text-black">Contacts Added</p>
</div>
</div>
</div>
</div>
<div class="col-xl-12">
<div class="card overflow-hidden">
<div class="card-body">
<div class="text-center">
<div class="profile-photo">
<img src="assets/img/man.png" width="100"
class="img-fluid rounded-circle" alt="">
</div>
<h3 class="mt-4 mb-1">Therichpost</h3>
<p class="text-muted">Youtuber</p>
<a class="btn btn-outline-primary btn-rounded mt-3 px-5"
href="javascript:void(0)">Folllow</a>
</div>
</div>
<div class="card-footer pt-0 pb-0 text-center">
<div class="row">
<div class="col-4 pt-3 pb-3 border-right">
<h3 class="mb-1">150</h3><span>Follower</span>
</div>
<div class="col-4 pt-3 pb-3 border-right">
<h3 class="mb-1">140</h3><span>Place Stay</span>
</div>
<div class="col-4 pt-3 pb-3">
<h3 class="mb-1">45</h3><span>Reviews</span>
</div>
</div>
</div>
</div>
</div>
<div class="col-xl-12">
<div class="card message-bx">
<div class="card-header border-0 d-sm-flex d-block pb-0">
<div>
<h4 class="fs-20 mb-0 text-black mb-sm-0 mb-2">Recent Messages</h4>
</div>
<a href="#" class="btn btn-primary shadow-primary btn-rounded text-white">+ New
Message</a>
</div>
<div class="card-body">
<div class="media mb-3 pb-3 border-bottom">
<div class="image-bx me-sm-4 me-2">
<img src="assets/img/hacker.png" alt="" class="rounded-circle img-1">
<span class="active"></span>
</div>
<div
class="media-body d-sm-flex justify-content-between d-block align-items-center">
<div class="me-sm-3 me-0">
<h6 class="fs-16 font-w600 mb-sm-2 mb-0"><a href="#"
class="text-black">Laura Chyan</a></h6>
<p class="text-black mb-1">Lorem ipsum dolor sit amet, consectetur
adipiscing elit, sed do eiusmod tempor incididunt ut</p>
<span class="fs-14">5m ago</span>
</div>
</div>
</div>
<div class="media mb-3 pb-3 border-bottom">
<div class="image-bx me-sm-4 me-2">
<img src="assets/img/gamer.png" alt="" class="rounded-circle img-1">
</div>
<div
class="media-body d-sm-flex justify-content-between d-block align-items-center">
<div class="me-sm-3 me-0">
<h6 class="fs-16 font-w600 mb-sm-2 mb-0"><a href="#"
class="text-black">Olivia Rellaq</a></h6>
<p class="text-black mb-1">Lorem ipsum dolor sit amet, consectetur
adipiscing elit, sed do eiusmod tempor incididunt ut</p>
<span class="fs-14">41m ago</span>
</div>
</div>
</div>
<div class="media">
<div class="image-bx me-sm-4 me-2">
<img src="assets/img/man (1).png" alt="" class="rounded-circle img-1">
<span class="active"></span>
</div>
<div
class="media-body d-sm-flex justify-content-between d-block align-items-center">
<div class="me-sm-3 me-0">
<h6 class="fs-16 font-w600 mb-sm-2 mb-0"><a href="#"
class="text-black">Keanu Tipes</a></h6>
<p class="text-black mb-1">Nisi ut aliquip ex ea commodo consequat.
Duis aute irure dolor in reprehenderit in voluptate velit esse
cillum...</p>
<span class="fs-14">25m ago</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="col-xl-6 col-xxl-12">
<div class="row">
<div class="col-md-6">
<div class="card">
<div class="card-header border-0 pb-0">
<div class="me-2">
<h4 class="fs-20 mb-0 font-w500 text-black">Upcoming Projects</h4>
</div>
</div>
<div class="card-body">
<div class="border-bottom up-project-bx pb-4 mb-4">
<span class="fs-16 text-primary mb-2 d-block sub-title font-w500">Yoast
Esac</span>
<div class="d-flex">
<p class="font-w500 me-auto mb-2 title fs-20"><a href="#"
class="text-black">Redesign Kripton Mobile App</a></p>
<div class="dropdown mb-3">
<a href="javascript:void(0)" data-bs-toggle="dropdown"
aria-expanded="false">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none"
xmlns="http://www.w3.org/2000/svg">
<path
d="M12 13C12.5523 13 13 12.5523 13 12C13 11.4477 12.5523 11 12 11C11.4477 11 11 11.4477 11 12C11 12.5523 11.4477 13 12 13Z"
stroke="#575757" stroke-width="2" stroke-linecap="round"
stroke-linejoin="round" />
<path
d="M12 6C12.5523 6 13 5.55228 13 5C13 4.44772 12.5523 4 12 4C11.4477 4 11 4.44772 11 5C11 5.55228 11.4477 6 12 6Z"
stroke="#575757" stroke-width="2" stroke-linecap="round"
stroke-linejoin="round" />
<path
d="M12 20C12.5523 20 13 19.5523 13 19C13 18.4477 12.5523 18 12 18C11.4477 18 11 18.4477 11 19C11 19.5523 11.4477 20 12 20Z"
stroke="#575757" stroke-width="2" stroke-linecap="round"
stroke-linejoin="round" />
</svg>
</a>
<div class="dropdown-menu dropdown-menu-left">
<a class="dropdown-item" href="javascript:void(0);">Edit</a>
<a class="dropdown-item" href="javascript:void(0);">Delete</a>
</div>
</div>
</div>
<div class="mb-3"><i class="far fa-calendar me-3"
aria-hidden="true"></i>Created on Sep 8th, 2020</div>
<div class="media align-items-center">
<div class="power-ic me-3">
<i class="fa fa-bolt" aria-hidden="true"></i>
</div>
<div class="media-body">
<p class="mb-1">Deadline</p>
<span class="text-black font-w600">Tuesday, Sep 29th 2020</span>
</div>
</div>
</div>
<div class="border-bottom up-project-bx pb-4 mb-4">
<span class="fs-16 text-primary mb-2 d-block sub-title font-w500">Yoast
Esac</span>
<div class="d-flex">
<p class="font-w500 me-auto title mb-2 fs-20"><a href="#"
class="text-black">Build Branding Persona for Etza.id</a></p>
<div class="dropdown mb-3">
<a href="javascript:void(0)" data-bs-toggle="dropdown"
aria-expanded="false">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none"
xmlns="http://www.w3.org/2000/svg">
<path
d="M12 13C12.5523 13 13 12.5523 13 12C13 11.4477 12.5523 11 12 11C11.4477 11 11 11.4477 11 12C11 12.5523 11.4477 13 12 13Z"
stroke="#575757" stroke-width="2" stroke-linecap="round"
stroke-linejoin="round" />
<path
d="M12 6C12.5523 6 13 5.55228 13 5C13 4.44772 12.5523 4 12 4C11.4477 4 11 4.44772 11 5C11 5.55228 11.4477 6 12 6Z"
stroke="#575757" stroke-width="2" stroke-linecap="round"
stroke-linejoin="round" />
<path
d="M12 20C12.5523 20 13 19.5523 13 19C13 18.4477 12.5523 18 12 18C11.4477 18 11 18.4477 11 19C11 19.5523 11.4477 20 12 20Z"
stroke="#575757" stroke-width="2" stroke-linecap="round"
stroke-linejoin="round" />
</svg>
</a>
<div class="dropdown-menu dropdown-menu-left">
<a class="dropdown-item" href="javascript:void(0);">Edit</a>
<a class="dropdown-item" href="javascript:void(0);">Delete</a>
</div>
</div>
</div>
<div class="mb-3"><i class="far fa-calendar me-3"
aria-hidden="true"></i>Created on Sep 8th, 2020</div>
<div class="media align-items-center">
<div class="power-ic me-3">
<i class="fa fa-bolt" aria-hidden="true"></i>
</div>
<div class="media-body">
<p class="mb-1">Deadline</p>
<span class="text-black font-w600">Tuesday, Sep 29th 2020</span>
</div>
</div>
</div>
<div class="up-project-bx">
<span class="fs-16 text-primary sub-title mb-2 d-block font-w500">Yoast
Esac</span>
<div class="d-flex">
<p class="font-w500 me-auto title mb-2 fs-20"><a href="#"
class="text-black">Manage SEO for Eclan Company Profile</a></p>
<div class="dropdown mb-3">
<a href="javascript:void(0)" data-bs-toggle="dropdown"
aria-expanded="false">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none"
xmlns="http://www.w3.org/2000/svg">
<path
d="M12 13C12.5523 13 13 12.5523 13 12C13 11.4477 12.5523 11 12 11C11.4477 11 11 11.4477 11 12C11 12.5523 11.4477 13 12 13Z"
stroke="#575757" stroke-width="2" stroke-linecap="round"
stroke-linejoin="round" />
<path
d="M12 6C12.5523 6 13 5.55228 13 5C13 4.44772 12.5523 4 12 4C11.4477 4 11 4.44772 11 5C11 5.55228 11.4477 6 12 6Z"
stroke="#575757" stroke-width="2" stroke-linecap="round"
stroke-linejoin="round" />
<path
d="M12 20C12.5523 20 13 19.5523 13 19C13 18.4477 12.5523 18 12 18C11.4477 18 11 18.4477 11 19C11 19.5523 11.4477 20 12 20Z"
stroke="#575757" stroke-width="2" stroke-linecap="round"
stroke-linejoin="round" />
</svg>
</a>
<div class="dropdown-menu dropdown-menu-left">
<a class="dropdown-item" href="javascript:void(0);">Edit</a>
<a class="dropdown-item" href="javascript:void(0);">Delete</a>
</div>
</div>
</div>
<div class="mb-3"><i class="far fa-calendar me-3"
aria-hidden="true"></i>Created on Sep 8th, 2020</div>
<div class="media align-items-center">
<div class="power-ic me-3">
<i class="fa fa-bolt" aria-hidden="true"></i>
</div>
<div class="media-body">
<p class="mb-1">Deadline</p>
<span class="text-black font-w600">Tuesday, Sep 29th 2020</span>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-6">
<div class="card kanbanPreview-bx">
<div class="card-body">
<div class="sub-card bg-secondary d-flex text-white">
<div class="me-auto pe-2">
<h4 class="fs-20 mb-0 font-w600 text-white">Quick To-Do List</h4>
<span class="fs-14 op6 font-w200">Lorem ipsum dolor sit amet</span>
</div>
<a href="#" class="plus-icon"><i class="fa fa-plus"
aria-hidden="true"></i></a>
</div>
<div class="sub-card">
<span class="text-warning sub-title fs-14">Graphic Deisgner</span>
<p class="font-w500"><a href="#" class="text-black">Visual Graphic for
Presentation to Client</a></p>
<div class="row justify-content-between align-items-center">
<div class="col-6">
<span>Aug 4, 2021</span>
</div>
<ul class="users col-6">
<li><img src="assets/img/man (1).png" alt=""></li>
<li><img
src="https://cdn-icons-png.flaticon.com/512/921/921071.png">
</li>
<li><img
src="https://cdn-icons-png.flaticon.com/512/921/921071.png">
</li>
<li><img src="https://cdn-icons-png.flaticon.com/512/1154/1154448.png"
alt=""></li>
</ul>
</div>
</div>
<div class="sub-card">
<span class="text-primary sub-title fs-14">Database Engineer</span>
<p class="font-w500"><a href="#" class="text-black">Build Database Design
for Fasto Admin v2</a></p>
<div class="row justify-content-between align-items-center">
<div class="col-6">
<span>Aug 4, 2021</span>
</div>
<ul class="users col-6">
<li><img src="assets/img/man (1).png" alt=""></li>
<li><img
src="https://cdn-icons-png.flaticon.com/512/921/921071.png">
</li>
<li><img src="assets/img/man (1).png" alt=""></li>
</ul>
</div>
</div>
<div class="sub-card">
<span class="text-secondary sub-title fs-14">Digital Marketing</span>
<p class="font-w500"><a href="#" class="text-black">Make Promotional Ads for
Instagram Fastos</a></p>
<div class="row justify-content-between align-items-center mb-4">
<div class="col-6">
<span>Aug 4, 2021</span>
</div>
<ul class="users col-6">
<li><img src="assets/img/man (1).png" alt=""></li>
<li><img
src="https://cdn-icons-png.flaticon.com/512/921/921071.png">
</li>
<li><img src="assets/img/man (1).png" alt=""></li>
</ul>
</div>
<span><i class="far fa-comment me-2"></i>2 Comment</span>
</div>
</div>
</div>
</div>
<div class="col-md-6">
<div class="card">
<div class="card-header border-0 pb-0">
<h4 class="card-title">Timeline</h4>
</div>
<div class="card-body">
<div id="DZ_W_TimeLine"
class="widget-timeline dz-scroll height370 ps ps--active-y">
<ul class="timeline">
<li>
<div class="timeline-badge primary"></div>
<a class="timeline-panel text-muted" href="javascript:void(0);">
<span>10 minutes ago</span>
<h6 class="mb-0">Youtube, a video-sharing website, goes live
<strong class="text-primary">$500</strong>.</h6>
</a>
</li>
<li>
<div class="timeline-badge info">
</div>
<a class="timeline-panel text-muted" href="javascript:void(0);">
<span>20 minutes ago</span>
<h6 class="mb-0">New order placed <strong
class="text-info">#XF-2356.</strong></h6>
<p class="mb-0">Quisque a consequat ante Sit amet magna at
volutapt...</p>
</a>
</li>
<li>
<div class="timeline-badge danger">
</div>
<a class="timeline-panel text-muted" href="javascript:void(0);">
<span>30 minutes ago</span>
<h6 class="mb-0">john just buy your product <strong
class="text-warning">Sell $250</strong></h6>
</a>
</li>
<li>
<div class="timeline-badge success">
</div>
<a class="timeline-panel text-muted" href="javascript:void(0);">
<span>15 minutes ago</span>
<h6 class="mb-0">StumbleUpon is acquired by eBay. </h6>
</a>
</li>
<li>
<div class="timeline-badge warning">
</div>
<a class="timeline-panel text-muted" href="javascript:void(0);">
<span>20 minutes ago</span>
<h6 class="mb-0">Mashable, a news website and blog, goes live.
</h6>
</a>
</li>
<li>
<div class="timeline-badge dark">
</div>
<a class="timeline-panel text-muted" href="javascript:void(0);">
<span>20 minutes ago</span>
<h6 class="mb-0">Mashable, a news website and blog, goes live.
</h6>
</a>
</li>
<li>
<div class="timeline-badge primary"></div>
<a class="timeline-panel text-muted" href="javascript:void(0);">
<span>10 minutes ago</span>
<h6 class="mb-0">Youtube, a video-sharing website, goes live
<strong class="text-primary">$500</strong>.</h6>
</a>
</li>
<li>
<div class="timeline-badge info">
</div>
<a class="timeline-panel text-muted" href="javascript:void(0);">
<span>20 minutes ago</span>
<h6 class="mb-0">New order placed <strong
class="text-info">#XF-2356.</strong></h6>
<p class="mb-0">Quisque a consequat ante Sit amet magna at
volutapt...</p>
</a>
</li>
<li>
<div class="timeline-badge danger">
</div>
<a class="timeline-panel text-muted" href="javascript:void(0);">
<span>30 minutes ago</span>
<h6 class="mb-0">john just buy your product <strong
class="text-warning">Sell $250</strong></h6>
</a>
</li>
<li>
<div class="timeline-badge success">
</div>
<a class="timeline-panel text-muted" href="javascript:void(0);">
<span>15 minutes ago</span>
<h6 class="mb-0">StumbleUpon is acquired by eBay. </h6>
</a>
</li>
<li>
<div class="timeline-badge warning">
</div>
<a class="timeline-panel text-muted" href="javascript:void(0);">
<span>20 minutes ago</span>
<h6 class="mb-0">Mashable, a news website and blog, goes live.
</h6>
</a>
</li>
<li>
<div class="timeline-badge dark">
</div>
<a class="timeline-panel text-muted" href="javascript:void(0);">
<span>20 minutes ago</span>
<h6 class="mb-0">Mashable, a news website and blog, goes live.
</h6>
</a>
</li>
</ul>
<div class="ps__rail-x" style="left: 0px; bottom: 0px;">
<div class="ps__thumb-x" tabindex="0" style="left: 0px; width: 0px;">
</div>
</div>
<div class="ps__rail-y" style="top: 0px; height: 370px; right: 0px;">
<div class="ps__thumb-y" tabindex="0" style="top: 0px; height: 229px;">
</div>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-6">
<div class="card" style="display: grid; align-content: center;">
<div class="card-body text-center ai-icon text-primary">
<svg id="rocket-icon" class="my-2" viewBox="0 0 24 24" width="80" height="80"
stroke="currentColor" stroke-width="1" fill="none" stroke-linecap="round"
stroke-linejoin="round">
<path d="M6 2L3 6v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V6l-3-4z"></path>
<line x1="3" y1="6" x2="21" y2="6"></line>
<path d="M16 10a4 4 0 0 1-8 0"></path>
</svg>
<h4 class="my-2">You dont have badges yet</h4>
<a href="javascript:void(0);" class="btn my-2 btn-primary btn-lg px-4"><i
class="fa fa-usd"></i> Earn Budges</a>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-xl-3 col-xxl-3 col-sm-6">
<div class="card">
<div class="social-graph-wrapper widget-facebook">
<span class="s-icon"><i class="fab fa-facebook-f"></i></span>
</div>
<div class="row">
<div class="col-6 border-end">
<div class="pt-3 pb-3 pl-0 pr-0 text-center">
<h4 class="m-1"><span class="count counter">89</span> k</h4>
<p class="m-0">Friends</p>
</div>
</div>
<div class="col-6">
<div class="pt-3 pb-3 pl-0 pr-0 text-center">
<h4 class="m-1"><span class="count counter">119</span> k</h4>
<p class="m-0">Followers</p>
</div>
</div>
</div>
</div>
</div>
<div class="col-xl-3 col-xxl-3 col-sm-6">
<div class="card">
<div class="social-graph-wrapper widget-linkedin">
<span class="s-icon"><i class="fab fa-linkedin-in"></i></span>
</div>
<div class="row">
<div class="col-6 border-end">
<div class="pt-3 pb-3 pl-0 pr-0 text-center">
<h4 class="m-1"><span class="count counter">89</span> k</h4>
<p class="m-0">Friends</p>
</div>
</div>
<div class="col-6">
<div class="pt-3 pb-3 pl-0 pr-0 text-center">
<h4 class="m-1"><span class="count counter">119</span> k</h4>
<p class="m-0">Followers</p>
</div>
</div>
</div>
</div>
</div>
<div class="col-xl-3 col-xxl-3 col-sm-6">
<div class="card">
<div class="social-graph-wrapper widget-googleplus">
<span class="s-icon"><i class="fab fa-google-plus-g"></i></span>
</div>
<div class="row">
<div class="col-6 border-end">
<div class="pt-3 pb-3 pl-0 pr-0 text-center">
<h4 class="m-1"><span class="count counter">89</span> k</h4>
<p class="m-0">Friends</p>
</div>
</div>
<div class="col-6">
<div class="pt-3 pb-3 pl-0 pr-0 text-center">
<h4 class="m-1"><span class="count counter">119</span> k</h4>
<p class="m-0">Followers</p>
</div>
</div>
</div>
</div>
</div>
<div class="col-xl-3 col-xxl-3 col-sm-6">
<div class="card">
<div class="social-graph-wrapper widget-twitter">
<span class="s-icon"><i class="fab fa-twitter"></i></span>
</div>
<div class="row">
<div class="col-6 border-end">
<div class="pt-3 pb-3 pl-0 pr-0 text-center">
<h4 class="m-1"><span class="count counter">89</span> k</h4>
<p class="m-0">Friends</p>
</div>
</div>
<div class="col-6">
<div class="pt-3 pb-3 pl-0 pr-0 text-center">
<h4 class="m-1"><span class="count counter">119</span> k</h4>
<p class="m-0">Followers</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{include file="sections/footer.tpl"}

View File

@ -0,0 +1,21 @@
{include file="sections/user-header.tpl"}
<div class="row">
<div class="col-md-4 col-md-offset-2">
<div class="card card-hovered card-primary card-stacked mb30">
<div class="card-header">{$in['invoice']}</div>
<div class="card-body">
<form class="form-horizontal" method="post" action="{$_url}plan/print" target="_blank">
<pre id="content">{$invoice}</pre>
<input type="hidden" name="id" value="{$in['id']}">
<a href="{$_url}voucher/list-activated" class="btn btn-default btn-sm"><i
class="ion-reply-all"></i>{Lang::T('Finish')}</a>
<a href="https://api.whatsapp.com/send/?text={$whatsapp}" target="_blank"
class="btn btn-primary btn-sm">
<i class="glyphicon glyphicon-share"></i> WhatsApp</a>
</form>
</div>
</div>
</div>
</div>
{include file="sections/user-footer.tpl"}

74
ui/ui/invoice-print.tpl Normal file
View File

@ -0,0 +1,74 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="shortcut icon" href="https://laravel.com/img/favicon/favicon-16x16.png" type='image/x-icon'>
<title>{Lang::T('Login')} - {$_c['CompanyName']}</title>
<link rel="stylesheet" href="assets/vendor/chartist/css/chartist.min.css">
<link href="assets/vendor/bootstrap-select/dist/css/bootstrap-select.min.css" rel="stylesheet">
<link href="assets/css/style.css" rel="stylesheet">
<meta http-equiv="refresh" content="{$time}; url={$url}">
<script type="text/javascript">
function printpage() {
window.print();
}
</script>
</head>
<body topmargin="0" leftmargin="0" {if !$nuxprint} onload="printpage()" {/if}>
<div class="row">
<div class="col-md-6">
<table width="200">
<tr>
<td>
{if $content}
<pre style="border-style: none; background-color: white;">{$content}</pre>{else}
<pre style="border-style: none; background-color: white;"><b>{Lang::pad($_c['CompanyName'],' ', 2)}</b>
{Lang::pad($_c['address'],' ', 2)}
{Lang::pad($_c['phone'],' ', 2)}
{Lang::pad("", '=')}
{Lang::pads("Invoice", $in['invoice'], ' ')}
{Lang::pads(Lang::T('Date'), $date, ' ')}
{Lang::pads(Lang::T('Sales'), $_admin['fullname'], ' ')}
{Lang::pad("", '=')}
{Lang::pads(Lang::T('Type'), $in['type'], ' ')}
{Lang::pads(Lang::T('Plan Name'), $in['plan_name'], ' ')}
{Lang::pads(Lang::T('Plan Price'), Lang::moneyFormat($in['price']), ' ')}
{Lang::pad($in['method'], ' ', 2)}
{Lang::pads(Lang::T('Username'), $in['username'], ' ')}
{Lang::pads(Lang::T('Password'), '**********', ' ')}
{if $in['type'] != 'Balance'}
{Lang::pads(Lang::T('Created On'), Lang::dateAndTimeFormat($in['recharged_on'],$in['recharged_time']), ' ')}
{Lang::pads(Lang::T('Expires On'), Lang::dateAndTimeFormat($in['expiration'],$in['time']), ' ')}
{/if}
{Lang::pad("", '=')}
{Lang::pad($_c['note'],' ', 2)}</pre>
{/if}
</td>
</tr>
</table>
{if $nuxprint}
<a href="{$nuxprint}" class="btn btn-success text-black btn-sm" name="nux" value="print">
<i class="fa fa-print"></i>
Print Invoice
<!-- <i class="fa fa-phone"></i> -->
</a>
<br>
<iframe src="{$nuxprint}" style="height: 2px;"><iframe>
{/if}
</div>
</div>
<script src="ui/ui/scripts/jquery.min.js"></script>
<script src="ui/ui/scripts/bootstrap.min.js"></script>
{if isset($xfooter)}
{$xfooter}
{/if}
</body>
</html>

43
ui/ui/invoice.tpl Normal file
View File

@ -0,0 +1,43 @@
{include file="sections/header.tpl"}
<div class="container-fluid">
<div class="row">
<div class="col-md-12 col-sm-12 col-md-offset-3">
<div class="card card-hovered card-primary card-stacked mb30">
<div class="card-header">{$in['invoice']}</div>
<div class="card-body">
<form class="form-horizontal" method="post" action="{$_url}plan/print" target="_blank">
<pre id="content"></pre>
<textarea class="hidden" id="formcontent" name="content">{$invoice}</textarea>
<input type="hidden" name="id" value="{$in['id']}">
<div class="">
<a href="{$_url}plan/list" class="btn btn-success btn-sm"><i
class="fa fa-reply-all"></i>{Lang::T('Finish')}</a>
<a href="https://api.whatsapp.com/send/?text={$whatsapp}" target="_blank"
class="btn btn-primary btn-sm">
<i class="fa fa-share"></i> WhatsApp</a>
<a href="{$_url}plan/view/{$in['id']}/send" class="btn btn-info btn-sm"><i
class="fa fa-envelope"></i> {Lang::T("Resend")}</a>
<button type="submit" class="btn btn-info btn-sm"><i class="fa fa-print"></i>
Print</button>
<!-- <a href="nux://print?text={urlencode($invoice)}"
class="btn btn-success btn-sm hidden-md hidden-lg">
<i class="fa fa-phone"></i>
Print Invoice
</a>
<a href="https://github.com/hotspotbilling/android-printer"
class="btn btn-success btn-sm hidden-xs hidden-sm" target="_blank">
<i class="fa fa-phone"></i>
Print Invoice
</a> -->
</div>
</form>
</div>
</div>
</div>
</div>
<script type="text/javascript">
var s5_taf_parent = window.location;
document.getElementById('content').innerHTML = document.getElementById('formcontent').innerHTML;
</script>
</div>
{include file="sections/footer.tpl"}

31
ui/ui/language-add.tpl Normal file
View File

@ -0,0 +1,31 @@
{include file="sections/header.tpl"}
<div class="container-fluid">
<div class="row">
<div class="col-sm-12 col-md-12">
<div class="card card-primary card-hovered card-stacked mb30">
<div class="card-header">{Lang::T('Translation')}</div>
<div class="card-body">
<form class="form-horizontal" method="post" role="form" action="{$_url}settings/lang-post">
{foreach $langs as $lang}
<div class="form-group">
<div class="col-md-12">
<small>{str_replace('_',' ', $lang@key)}</small>
<input type="text" class="form-control" rows="1" name="{$lang@key}"
placeholder="{$lang@key}" value="{$lang}">
</div>
</div>
{/foreach}
<div class="form-group">
<div class="col-lg-offset-2 col-lg-10">
<button class="btn btn-primary"
type="submit">{Lang::T('Save Changes')}</button>
Or <a href="{$_url}settings/localisation">{Lang::T('Cancel')}</a>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
{include file="sections/footer.tpl"}

71
ui/ui/logs-radius.tpl Normal file
View File

@ -0,0 +1,71 @@
{include file="sections/header.tpl"}
<div class="container-fluid">
<!-- pool -->
<div class="row">
<div class="col-sm-12">
<div class="card card-hovered mb20 card-primary">
<div class="card-header">
Radius
{if in_array($_admin['user_type'],['SuperAdmin','Admin'])}
<div class="btn-group pull-right">
<a class="btn btn-primary btn-xs" title="save" href="{$_url}logs/radius-csv"
onclick="return confirm('This will export to CSV?')"><span class="glyphicon glyphicon-download"
aria-hidden="true"></span> CSV</a>
</div>
{/if}
</div>
<div class="card-body">
<div class="text-center"">
<div class="col-md-12 mb-3">
<form id="site-search" method="post" action="{$_url}logs/radius/">
<div class="input-group">
<div class="input-group-text">
<span class="fa fa-search"></span>
</div>
<input type="text" name="q" class="form-control" value="{$q}"
placeholder="{Lang::T('Search by Name')}...">
<button class="btn btn-success input-group-btn" type="submit">{Lang::T('Search')}</button>
</div>
</form>
</div>
<div class="col-md-12">
<form class="form-inline" method="post" action="{$_url}logs/radius/">
<div class="form-group">
<div class="input-group has-error">
<span class="input-group-text">Keep Logs </span>
<input type="text" name="keep" class="form-control" placeholder="90" value="90">
<span class="input-group-text">Days</span>
<button type="submit" class="btn btn-danger btn-sm input-group-btn"
onclick="return confirm('Clear old logs?')">Clean Logs
</button>
</div>
</div>
</form>
</div>&nbsp;
</div>
<br>
<div class="table-responsive">
<table class="table table-bordered table-striped table-condensed table-hover border-primary">
<tbody>
{foreach $d as $ds}
<tr>
<td width="30px">{$ds['id']}</td>
<td width="200px">{Lang::dateTimeFormat($ds['authdate'])}</td>
<td width="100px">{$ds['username']}</td>
<td width="10px"><input type="password" value="{$ds['pass']}"
style="width:200px;height: 36px; border: 0px; text-align: right;" class="pull-right form-control"
onmouseleave="this.type = 'password'" onmouseenter="this.type = 'text'"
onclick="this.select()"></td>
<td>{$ds['reply']}</td>
</tr>
{/foreach}
</tbody>
</table>
</div>
{include file="pagination.tpl"}
</div>
</div>
</div>
</div>
</div>
{include file="sections/footer.tpl"}

66
ui/ui/logs.tpl Normal file
View File

@ -0,0 +1,66 @@
{include file="sections/header.tpl"}
<!-- pool -->
<div class="row">
<div class="col-sm-12">
<div class="panel panel-hovered mb20 panel-primary">
<div class="panel-heading">
{if in_array($_admin['user_type'],['SuperAdmin','Admin'])}
<div class="btn-group pull-right">
<a class="btn btn-primary btn-xs" title="save" href="{$_url}logs/list-csv"
onclick="return confirm('This will export to CSV?')"><span class="glyphicon glyphicon-download"
aria-hidden="true"></span> CSV</a>
</div>
{/if}
Activity Log
</div>
<div class="panel-body">
<div class="text-center" style="padding: 15px">
<div class="col-md-4">
<form id="site-search" method="post" action="{$_url}logs/list/">
<div class="input-group">
<div class="input-group-addon">
<span class="fa fa-search"></span>
</div>
<input type="text" name="q" class="form-control" value="{$q}"
placeholder="{Lang::T('Search by Name')}...">
<div class="input-group-btn">
<button class="btn btn-success" type="submit">{Lang::T('Search')}</button>
</div>
</div>
</form>
</div>
<div class="col-md-8">
<form class="form-inline" method="post" action="{$_url}logs/list/">
<div class="input-group has-error">
<span class="input-group-addon">Keep Logs </span>
<input type="text" name="keep" class="form-control" placeholder="90" value="90">
<span class="input-group-addon">Days</span>
</div>
<button type="submit" class="btn btn-danger btn-sm"
onclick="return confirm('Clear old logs?')">Clean Logs</button>
</form>
</div>&nbsp;
</div>
<br>
<div class="table-responsive">
<table class="table table-bordered table-striped table-condensed">
<tbody>
{foreach $d as $ds}
<tr>
<td>{$ds['id']}</td>
<td>{Lang::dateTimeFormat($ds['date'])}</td>
<td>{$ds['type']}</td>
<td>{$ds['ip']}</td>
<td style="overflow-x: scroll;">{nl2br($ds['description'])}</td>
</tr>
{/foreach}
</tbody>
</table>
</div>
{include file="pagination.tpl"}
</div>
</div>
</div>
</div>
{include file="sections/footer.tpl"}

209
ui/ui/main.css Normal file
View File

@ -0,0 +1,209 @@
* {
margin: 0;
padding: 0;
-webkit-box-sizing: border-box;
box-sizing: border-box;
}
html {
font-size: 62.5%;
font-family: "Montserrat", sans-serif;
}
body {
min-height: 100vh;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-pack: center;
-ms-flex-pack: center;
justify-content: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
background-color: #ecf0f3;
color: grey;
}
h1 {
color: #000;
font-size: 3rem;
text-transform: capitalize;
}
button {
width: 16rem;
height: 4.5rem;
border-radius: 2.5rem;
font-size: 1.3rem;
font-weight: 700;
letter-spacing: 0.1rem;
text-transform: uppercase;
background-color: #ecf0f3;
color: grey;
border: none;
outline: none;
-webkit-box-shadow: 1rem 1rem 1.6rem #d1d9e6, -0.6rem -0.6rem 1.6rem #fff;
box-shadow: 1rem 1rem 1.6rem #d1d9e6, -0.6rem -0.6rem 1.6rem #fff;
cursor: pointer;
}
button:active {
-webkit-box-shadow: inset 1rem 1rem 1.6rem #d1d9e6, inset -1rem -1rem 1.6rem #fff;
box-shadow: inset 1rem 1rem 1.6rem #d1d9e6, inset -1rem -1rem 1.6rem #fff;
}
.container {
position: relative;
width: 80rem;
height: 60vh;
background-color: #ecf0f3;
-webkit-box-shadow: 1rem 1rem 1rem #d1d9e6, -0.5rem -0.5rem 1rem #fff;
box-shadow: 1rem 1rem 1rem #d1d9e6, -0.5rem -0.5rem 1rem #fff;
border-radius: 1.2rem;
overflow: hidden;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
padding: 12px;
}
.container .circle {
position: absolute;
left: 0;
width: 20rem;
height: 20rem;
border-radius: 50%;
background-color: #ecf0f3;
-webkit-box-shadow: 0.4rem 0.4rem 0.8rem #d1d9e6;
box-shadow: 0.4rem 0.4rem 0.8rem #d1d9e6;
z-index: 10;
}
.container .top-left {
top: 0;
-webkit-transform: translate(-50%, -50%);
transform: translate(-50%, -50%);
}
.container .bottom-left {
bottom: 0;
-webkit-transform: translate(-70%, 70%) rotate(-80deg);
transform: translate(-70%, 70%) rotate(-80deg);
}
.container .side {
position: absolute;
top: 0;
height: 100%;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-pack: center;
-ms-flex-pack: center;
justify-content: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
}
.container .left-side {
width: 40%;
text-align: center;
padding: 12px;
-webkit-box-shadow: 0.4rem 0.4rem 1rem #d1d9e6;
box-shadow: 0.4rem 0.4rem 1rem #d1d9e6;
}
.container .left-side .content p {
margin: 1rem 0 2rem 0;
font-size: 1.4rem;
}
.container .right-side {
width: 60%;
right: 0;
}
.container .right-side .content {
width: 70%;
text-align: center;
}
.container .right-side .content .icon-group {
margin: 1rem 0 3rem 0;
}
.container .right-side .content .icon-group .btn {
margin: 0 1rem;
border: none;
outline: none;
background-color: #ecf0f3;
-webkit-box-shadow: 0.3rem 0.3rem 0.6rem #d1d9e6, -0.3rem -0.3rem 0.6rem #fff;
box-shadow: 0.3rem 0.3rem 0.6rem #d1d9e6, -0.3rem -0.3rem 0.6rem #fff;
border-radius: 50%;
width: 3rem;
height: 3rem;
color: #000;
cursor: pointer;
}
.container .right-side .content .icon-group .btn:hover {
-webkit-box-shadow: inset 0.3rem 0.3rem 0.6rem #d1d9e6, inset -0.3rem -0.3rem 0.6rem #fff;
box-shadow: inset 0.3rem 0.3rem 0.6rem #d1d9e6, inset -0.3rem -0.3rem 0.6rem #fff;
}
.container .right-side .content .line {
width: 100%;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-ms-flex-pack: distribute;
justify-content: space-around;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
margin-bottom: 2rem;
}
.container .right-side .content .line hr {
width: 40%;
border: 1px solid transparent;
border-top: 1px solid #aaa;
}
.container .right-side .content .form form .form-control {
position: relative;
}
.container .right-side .content .form form .form-control i {
position: absolute;
top: 50%;
left: 4%;
-webkit-transform: translate(-50%, -50%);
transform: translate(-50%, -50%);
}
.container .right-side .content .form form .form-control input {
width: 100%;
height: 4rem;
border: none;
outline: none;
background-color: #ecf0f3;
-webkit-box-shadow: inset 0.2rem 0.2rem 0.4rem #d1d9e6, inset -0.4rem -0.4rem 0.4rem #fff;
box-shadow: inset 0.2rem 0.2rem 0.4rem #d1d9e6, inset -0.4rem -0.4rem 0.4rem #fff;
padding-left: 2.5rem;
border-radius: 0.8rem;
color: #000;
margin: 0.5rem 0;
}
.container .right-side .content .form form .form-control input:focus {
-webkit-box-shadow: inset 0.4rem 0.4rem 0.4rem #d1d9e6, inset -0.4rem -0.4rem 0.4rem #fff;
box-shadow: inset 0.4rem 0.4rem 0.4rem #d1d9e6, inset -0.4rem -0.4rem 0.4rem #fff;
}
.container .right-side .content .form form button {
margin-top: 2rem;
}
/*# sourceMappingURL=main.css.map */

106
ui/ui/maintenance-mode.tpl Normal file
View File

@ -0,0 +1,106 @@
{include file="sections/header.tpl"}
<style>
/* Checkbox container */
.switch {
position: relative;
display: inline-block;
width: 50px;
height: 24px;
}
/* Hidden checkbox */
.switch input {
opacity: 0;
width: 0;
height: 0;
}
/* Slider */
.slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #ccc;
-webkit-transition: .4s;
transition: .4s;
border-radius: 24px;
}
.slider:before {
position: absolute;
content: "";
height: 18px;
width: 18px;
left: 3px;
bottom: 3px;
background-color: white;
-webkit-transition: .4s;
transition: .4s;
border-radius: 50%;
}
input:checked+.slider {
background-color: #2196F3;
}
input:focus+.slider {
box-shadow: 0 0 1px #2196F3;
}
input:checked+.slider:before {
-webkit-transform: translateX(26px);
-ms-transform: translateX(26px);
transform: translateX(26px);
}
</style>
<div class="container-fluid">
<form class="form-horizontal" method="post" autocomplete="off" role="form" action="">
<div class="row">
<div class="col-sm-12 col-md-12">
<div class="card card-primary card-hovered card-stacked mb30">
<div class="card-header">{Lang::T('Maintenance Mode')}</div>
<div class="card-body">
<div class="form-group">
<label class="col-md-2 control-label">{Lang::T('Status:')}</label>
<div class="col-md-6">
<label class="switch">
<input type="checkbox" id="maintenance_mode" value="1" name="maintenance_mode" {if
$_c['maintenance_mode']==1}checked{/if}>
<span class="slider"></span>
</label>
</div>
</div>
<div class="form-group">
<label class="col-md-2 control-label">{Lang::T('Force Logout:')}</label>
<div class="col-md-6">
<label class="switch">
<input type="checkbox" id="maintenance_mode_logout" value="1"
name="maintenance_mode_logout" {if $_c['maintenance_mode_logout']==1}checked{/if}>
<span class="slider"></span>
</label>
</div>
</div>
<div class="form-group">
<label class="col-md-2 control-label">{Lang::T('End Date:')}</label>
<div class="col-md-6">
<input class="form-control" value="{$_c['maintenance_date']}" type="date" id="start_date"
name="maintenance_date">
</div>
</div>
<div class="form-group">
<div class="col-lg-offset-2 col-lg-10">
<button class="btn btn-primary waves-effect waves-light" name="save" value="save"
type="submit">{Lang::T('Save')}</button>
</div>
</div>
</div>
</div>
</div>
</div>
</form>
</div>
{include file="sections/footer.tpl"}

249
ui/ui/maintenance.tpl Normal file
View File

@ -0,0 +1,249 @@
<!DOCTYPE html>
<html>
<head>
<title>{Lang::T('Site is down for maintenance')}</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style>
body {
font-family: 'Noto Sans', sans-serif;
color: #616161;
background-color: #eeeeee;
margin: 0;
padding: 0;
display: flex;
align-items: center;
justify-content: center;
height: 100vh;
}
.container {
max-width: 1024px;
width: 100%;
padding: 10px;
box-sizing: border-box;
}
.box {
width: 100%;
background: #fff;
margin: auto;
padding: 20px;
border-radius: 5px;
box-shadow: 6px 18px 18px rgba(0, 0, 0, 0.08), -6px 18px 18px rgba(0, 0, 0, 0.08);
box-sizing: border-box;
text-align: center;
}
.animation {
display: flex;
justify-content: center;
align-items: center;
margin: 20px 0;
}
h1 {
font-size: 32px;
font-weight: 400;
text-transform: uppercase;
margin: 0;
}
p {
font-size: 16px;
font-weight: 700;
margin: 0;
}
a {
color: #f6921e;
font-weight: bold;
text-decoration: none;
margin-left: 5px;
}
.one,
.two,
.three {
display: block;
float: left;
}
.one {
background: url('data:image/svg+xml,%3Csvg%20version%3D%221.1%22%0A%09%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20xmlns%3Aa%3D%22http%3A%2F%2Fns.adobe.com%2FAdobeSVGViewerExtensions%2F3.0%2F%22%0A%09%20x%3D%220px%22%20y%3D%220px%22%20width%3D%2281px%22%20height%3D%2280.5px%22%20viewBox%3D%220%200%2081%2080.5%22%20style%3D%22overflow%3Ascroll%3Benable-background%3Anew%200%200%2081%2080.5%3B%22%0A%09%20xml%3Aspace%3D%22preserve%22%3E%0A%3Cstyle%20type%3D%22text%2Fcss%22%3E%0A%09.st0%7Bfill%3A%23383838%3B%7D%0A%3C%2Fstyle%3E%0A%3Cdefs%3E%0A%3C%2Fdefs%3E%0A%3Cpath%20class%3D%22st0%22%20d%3D%22M30.3%2C68.2c1.2%2C0.2%2C2.3%2C0.9%2C3.8%2C1.2c1.6%2C0.3%2C2.7%2C0.6%2C4%2C0.4l4.9%2C9.6c0.6%2C0.9%2C1.4%2C1.1%2C2.3%2C0.9l15.3-4.9%0A%09c0.5-0.3%2C1-1%2C0.9-2.3l-1.8-10.6c2-1.6%2C3.6-3.7%2C5.3-5.8l10.5%2C0.6c1.1%2C0.6%2C2.1-0.4%2C2.3-1.1L81%2C40.7c0.2-0.8-0.4-2.1-1.1-2.3l-10.2-3.8%0A%09c-0.3-2.5-1.4-4.8-2.5-7.5l5.9-8.5c0.6-1.1%2C0.4-1.9-0.2-2.9l-12-10.7c-0.3-0.5-1.6-0.3-2.5%2C0.3l-8%2C6.9c-1.2-0.2-2.3-0.9-3.8-1.2%0A%09c-1.6-0.3-2.7-0.6-4-0.4L37.7%2C1c-0.6-0.9-1.4-1.1-2.3-0.9L20.1%2C5c-0.5%2C0.3-1%2C1-0.9%2C2.3l1.8%2C10.6c-2%2C1.6-3.6%2C3.7-5.3%2C5.8L5.3%2C23%0A%09c-0.8-0.2-1.7%2C0.4-2%2C1.6L0%2C40.2c-0.2%2C0.8%2C0.4%2C2.1%2C1.1%2C2.3l9.8%2C3.7c0.7%2C2.6%2C1.4%2C5.2%2C2.5%2C7.5l-6%2C8.9c-0.6%2C0.7-0.4%2C2%2C0.3%2C2.5l12%2C10.7%0A%09c0.7%2C0.5%2C1.9%2C0.8%2C2.4%2C0.1L30.3%2C68.2z%20M26.7%2C37.3c1.6-7.4%2C9.1-12.3%2C16.5-10.8S55.6%2C35.7%2C54%2C43.1c-1.6%2C7.4-9.1%2C12.3-16.5%2C10.7%0A%09C30.1%2C52.3%2C25.1%2C44.7%2C26.7%2C37.3L26.7%2C37.3z%22%2F%3E%0A%3C%2Fsvg%3E');
width: 80px;
height: 80px;
background-size: 100% 100%;
background-repeat: no-repeat;
margin-top: -10px;
margin-right: 8px;
}
.two {
background: url('data:image/svg+xml,%3Csvg%20version%3D%221.1%22%0A%09%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20xmlns%3Aa%3D%22http%3A%2F%2Fns.adobe.com%2FAdobeSVGViewerExtensions%2F3.0%2F%22%0A%09%20x%3D%220px%22%20y%3D%220px%22%20width%3D%22103px%22%20height%3D%22103.7px%22%20viewBox%3D%220%200%20103%20103.7%22%0A%09%20style%3D%22overflow%3Ascroll%3Benable-background%3Anew%200%200%20103%20103.7%3B%22%20xml%3Aspace%3D%22preserve%22%3E%0A%3Cstyle%20type%3D%22text%2Fcss%22%3E%0A%09.st0%7Bfill%3A%23F6921E%3B%7D%0A%3C%2Fstyle%3E%0A%3Cdefs%3E%0A%3C%2Fdefs%3E%0A%3Cpath%20class%3D%22st0%22%20d%3D%22M87.3%2C64.8c0.3-1.5%2C1.1-2.9%2C1.6-4.9c0.4-2%2C0.7-3.5%2C0.5-5.1l12.3-6.3c1.2-0.8%2C1.4-1.8%2C1.1-2.9l-6.3-19.6%0A%09c-0.4-0.6-1.3-1.3-2.9-1.1l-13.5%2C2.3c-2.1-2.5-4.7-4.7-7.4-6.8l0.8-13.4C74.3%2C5.8%2C73%2C4.5%2C72%2C4.3L52.1%2C0c-1-0.2-2.7%2C0.5-2.9%2C1.5%0A%09l-4.8%2C13c-3.2%2C0.4-6.1%2C1.8-9.5%2C3.2l-10.9-7.5c-1.4-0.8-2.5-0.5-3.7%2C0.3L6.5%2C25.8c-0.6%2C0.4-0.4%2C2%2C0.4%2C3.2l8.8%2C10.2%0A%09c-0.3%2C1.5-1.1%2C2.9-1.5%2C4.9c-0.4%2C2-0.7%2C3.5-0.6%2C5.1L1.2%2C55.4c-1.2%2C0.8-1.4%2C1.8-1.1%2C2.9l6.3%2C19.6c0.4%2C0.6%2C1.3%2C1.3%2C2.9%2C1.1l13.5-2.3%0A%09c2.1%2C2.5%2C4.7%2C4.7%2C7.4%2C6.8l-0.8%2C13.4c-0.2%2C1%2C0.6%2C2.2%2C2.1%2C2.5l20%2C4.2c1%2C0.2%2C2.7-0.5%2C2.9-1.5l4.7-12.6c3.3-0.9%2C6.6-1.7%2C9.5-3.2L80.1%2C94%0A%09c0.9%2C0.7%2C2.5%2C0.5%2C3.2-0.4L97%2C78.3c0.7-0.9%2C1-2.4%2C0.1-3.1L87.3%2C64.8z%20M47.8%2C69.5C38.3%2C67.5%2C32%2C57.8%2C34%2C48.3%0A%09c2-9.5%2C11.7-15.8%2C21.2-13.8c9.5%2C2%2C15.7%2C11.7%2C13.7%2C21.2C66.9%2C65.2%2C57.3%2C71.5%2C47.8%2C69.5L47.8%2C69.5z%22%2F%3E%0A%3C%2Fsvg%3E');
width: 100px;
height: 100px;
background-size: 100% 100%;
background-repeat: no-repeat;
}
.three {
background: url('data:image/svg+xml,%3Csvg%20version%3D%221.1%22%0A%09%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20xmlns%3Aa%3D%22http%3A%2F%2Fns.adobe.com%2FAdobeSVGViewerExtensions%2F3.0%2F%22%0A%09%20x%3D%220px%22%20y%3D%220px%22%20width%3D%2281px%22%20height%3D%2280.5px%22%20viewBox%3D%220%200%2081%2080.5%22%20style%3D%22overflow%3Ascroll%3Benable-background%3Anew%200%200%2081%2080.5%3B%22%0A%09%20xml%3Aspace%3D%22preserve%22%3E%0A%3Cstyle%20type%3D%22text%2Fcss%22%3E%0A%09.st0%7Bfill%3A%23383838%3B%7D%0A%3C%2Fstyle%3E%0A%3Cdefs%3E%0A%3C%2Fdefs%3E%0A%3Cpath%20class%3D%22st0%22%20d%3D%22M30.3%2C68.2c1.2%2C0.2%2C2.3%2C0.9%2C3.8%2C1.2c1.6%2C0.3%2C2.7%2C0.6%2C4%2C0.4l4.9%2C9.6c0.6%2C0.9%2C1.4%2C1.1%2C2.3%2C0.9l15.3-4.9%0A%09c0.5-0.3%2C1-1%2C0.9-2.3l-1.8-10.6c2-1.6%2C3.6-3.7%2C5.3-5.8l10.5%2C0.6c1.1%2C0.6%2C2.1-0.4%2C2.3-1.1L81%2C40.7c0.2-0.8-0.4-2.1-1.1-2.3l-10.2-3.8%0A%09c-0.3-2.5-1.4-4.8-2.5-7.5l5.9-8.5c0.6-1.1%2C0.4-1.9-0.2-2.9l-12-10.7c-0.3-0.5-1.6-0.3-2.5%2C0.3l-8%2C6.9c-1.2-0.2-2.3-0.9-3.8-1.2%0A%09c-1.6-0.3-2.7-0.6-4-0.4L37.7%2C1c-0.6-0.9-1.4-1.1-2.3-0.9L20.1%2C5c-0.5%2C0.3-1%2C1-0.9%2C2.3l1.8%2C10.6c-2%2C1.6-3.6%2C3.7-5.3%2C5.8L5.3%2C23%0A%09c-0.8-0.2-1.7%2C0.4-2%2C1.6L0%2C40.2c-0.2%2C0.8%2C0.4%2C2.1%2C1.1%2C2.3l9.8%2C3.7c0.7%2C2.6%2C1.4%2C5.2%2C2.5%2C7.5l-6%2C8.9c-0.6%2C0.7-0.4%2C2%2C0.3%2C2.5l12%2C10.7%0A%09c0.7%2C0.5%2C1.9%2C0.8%2C2.4%2C0.1L30.3%2C68.2z%20M26.7%2C37.3c1.6-7.4%2C9.1-12.3%2C16.5-10.8S55.6%2C35.7%2C54%2C43.1c-1.6%2C7.4-9.1%2C12.3-16.5%2C10.7%0A%09C30.1%2C52.3%2C25.1%2C44.7%2C26.7%2C37.3L26.7%2C37.3z%22%2F%3E%0A%3C%2Fsvg%3E');
width: 80px;
height: 80px;
background-size: 100% 100%;
background-repeat: no-repeat;
margin-top: -50px;
margin-left: -10px;
}
@keyframes spin-one {
0% {
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(-359deg);
transform: rotate(-359deg);
}
}
.spin-one {
-webkit-animation: spin-one 1.5s infinite linear;
animation: spin-one 1.5s infinite linear;
}
@keyframes spin-two {
0% {
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(-359deg);
transform: rotate(359deg);
}
}
.spin-two {
-webkit-animation: spin-two 2s infinite linear;
animation: spin-two 2s infinite linear;
}
.day,
.hour,
.minute,
.second {
font-size: 18px;
background: #333;
color: #fff;
padding: 10px;
border-radius: 5px;
margin: 5px;
flex: 1;
text-align: center;
}
.day {
background-color: #1abc9c;
}
.hour {
background-color: #3498db;
}
.minute {
background-color: #f1c40f;
}
.second {
background-color: #e74c3c;
}
.countdown {
display: flex;
justify-content: space-between;
flex-wrap: wrap;
}
@media (max-width: 600px) {
h1 {
font-size: 24px;
}
p {
font-size: 14px;
}
.one,
.two,
.three {
width: 40px;
height: 40px;
}
.day,
.hour,
.minute,
.second {
font-size: 14px;
padding: 8px;
margin: 2px;
}
}
</style>
</head>
<body>
<!-- partial:index.partial.html -->
<link href="https://fonts.googleapis.com/css?family=Noto+Sans:400,700" rel="stylesheet">
<div class="container">
<div class="box">
<div class="animation">
<div class="one spin-one"></div>
<div class="two spin-two"></div>
<div class="three spin-one"></div>
</div>
<h1>{Lang::T('Site is temporarily unavailable.')}</h1>
<p>{Lang::T('Scheduled maintenance is currently in progress. Please check back soon.')}</p>
<p>{Lang::T('We apologize for any inconvenience.')} <br> &mdash; {Lang::T('The ')} {$companyName} {Lang::T('
Team.')}</p>
<br>
{if $date} <div style="display: flex; flex-direction: row; justify-content: space-between;">
<p class="day"></p>
<p class="hour"></p>
<p class="minute"></p>
<p class="second"></p>
</div>
{/if}
</div>
</div>
{if $date}
<script>
const countDown = () => {
const countDay = new Date('{$date}');
const now = new Date();
const counter = countDay - now;
const second = 1000;
const minute = second * 60;
const hour = minute * 60;
const day = hour * 24;
const textDay = Math.floor(counter / day);
const textHour = Math.floor((counter % day) / hour);
const textMinute = Math.floor((counter % hour) / minute);
const textSecond = Math.floor((counter % minute) / second)
document.querySelector(".day").innerText = textDay + ' Days';
document.querySelector(".hour").innerText = textHour + ' Hours';
document.querySelector(".minute").innerText = textMinute + ' Minutes';
document.querySelector(".second").innerText = textSecond + ' Seconds';
}
setInterval(countDown, 1000);
</script>
{/if}
</body>
</html>

144
ui/ui/message-bulk.tpl Normal file
View File

@ -0,0 +1,144 @@
{include file="sections/header.tpl"}
<div class="container-fluid">
<link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/1.11.3/css/jquery.dataTables.min.css">
<div class="row">
<div class="col-sm-12 col-md-12">
<div class="card card-primary card-hovered card-stacked mb30">
<div class="card-header">
<h3 class="card-title">{Lang::T('Send Bulk Message')}</h3>
</div>
<div class="card-body">
<form class="form-horizontal" method="post" role="form" id="bulkMessageForm" action="">
<div class="form-group row">
<label class="col-md-2 control-label">{Lang::T('Group')}</label>
<div class="col-md-6">
<select class="form-select" style="height: 52px; background-color: white;" name="group" id="group">
<option value="all" selected>{Lang::T('All Customers')}</option>
<option value="new">{Lang::T('New Customers')}</option>
<option value="expired">{Lang::T('Expired Customers')}</option>
<option value="active">{Lang::T('Active Customers')}</option>
</select>
</div>
</div>
<div class="form-group row">
<label class="col-md-2 control-label">{Lang::T('Send Via')}</label>
<div class="col-md-6">
<select class="form-select" style="height: 52px; background-color: white;" name="via" id="via">
<option value="sms" selected>{Lang::T('SMS')}</option>
<option value="wa">{Lang::T('WhatsApp')}</option>
<option value="both">{Lang::T('SMS and WhatsApp')}</option>
</select>
</div>
</div>
<div class="form-group row">
<label class="col-md-2 control-label">{Lang::T('Message per time')}</label>
<div class="col-md-6">
<select class="form-select" style="height: 52px; background-color: white;" name="batch" id="batch">
<option value="5">{Lang::T('5 Messages')}</option>
<option value="10" selected>{Lang::T('10 Messages')}</option>
<option value="15">{Lang::T('15 Messages')}</option>
<option value="20">{Lang::T('20 Messages')}</option>
<option value="20">{Lang::T('30 Messages')}</option>
<option value="20">{Lang::T('40 Messages')}</option>
<option value="20">{Lang::T('50 Messages')}</option>
<option value="20">{Lang::T('60 Messages')}</option>
</select>{Lang::T('Use 20 and above if you are sending to all customers to avoid server time out')}
</div>
</div>
<div class="form-group row">
<label class="col-md-2 control-label">{Lang::T('Delay')}</label>
<div class="col-md-6">
<select class="form-select" style="height: 52px; background-color: white;" name="delay" id="delay">
<option value="0" selected>{Lang::T('No Delay')}</option>
<option value="5">{Lang::T('5 Seconds')}</option>
<option value="10">{Lang::T('10 Seconds')}</option>
<option value="15">{Lang::T('15 Seconds')}</option>
<option value="20">{Lang::T('20 Seconds')}</option>
</select>{Lang::T('Use at least 5 secs if you are sending to all customers to avoid being banned by your message provider')}
</div>
</div>
<div class="form-group row">
<label class="col-md-2 control-label">{Lang::T('Message')}</label>
<div class="col-md-6">
<textarea class="form-control" id="message" name="message"
placeholder="{Lang::T('Compose your message...')}" rows="5"></textarea>
<input name="test" type="checkcard"> {Lang::T('Testing [if checked no real message is sent]')}
</div>
<p class="help-block col-md-4">
{Lang::T('Use placeholders:')}
<br>
<b>[[name]]</b> - {Lang::T('Customer Name')}
<br>
<b>[[user_name]]</b> - {Lang::T('Customer Username')}
<br>
<b>[[phone]]</b> - {Lang::T('Customer Phone')}
<br>
<b>[[company_name]]</b> - {Lang::T('Your Company Name')}
</p>
</div>
<center>
<div class="form-group row">
<div class="col-lg-offset-2 col-lg-10">
<button class="btn btn-success mb-3" type="submit" name=send value=now>
{Lang::T('Send Message')}</button>
<a class="btn btn-outline-primary" href="{$_url}dashboard" class="btn btn-default">{Lang::T('Cancel')}</a>
</div>
</div>
</center>
</form>
</div>
</div>
</div>
{if $batchStatus}
<p><span class="label label-success">Total SMS Sent: {$totalSMSSent}</span> <span class="label label-danger">Total SMS
Failed: {$totalSMSFailed}</span> <span class="label label-success">Total WhatsApp Sent:
{$totalWhatsappSent}</span> <span class="label label-danger">Total WhatsApp Failed:
{$totalWhatsappFailed}</span></p>
{/if}
<div class="col-sm-12 col-md-12">
<div class="card card-primary card-hovered card-stacked mb30">
<div class="card-header">
<h3 class="card-title">Message Results</h3>
</div>
<!-- /.card-header -->
<div class="table-responsive">
<table id="messageResultsTable" class="table table-bordered table-striped table-condensed table-hover border-primary">
<thead>
<tr>
<th>Name</th>
<th>Phone</th>
<th>Message</th>
<th>Status</th>
</tr>
</thead>
<tbody>
{foreach $batchStatus as $customer}
<tr>
<td>{$customer.name}</td>
<td>{$customer.phone}</td>
<td>{$customer.message}</td>
<td>{$customer.status}</td>
</tr>
{/foreach}
</tbody>
</table>
</div>
<!-- /.card-body -->
</div>
</div>
<!-- /.card -->
</div>
<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>
var $j = jQuery.noConflict();
$j(document).ready(function () {
$j('#messageResultsTable').DataTable();
});
</script>
</div>
{include file="sections/footer.tpl"}

74
ui/ui/message-list.tpl Normal file
View File

@ -0,0 +1,74 @@
{include file="sections/header.tpl"}
<style>
.dataTables_wrapper .dataTables_paginate .paginate_button {
display: inline-block;
padding: 5px 10px;
margin-right: 5px;
border: 1px solid #ccc;
background-color: #fff;
color: #333;
cursor: pointer;
}
</style>
<div class="container-fluid">
<div class="row">
<div class="col-sm-12">
<div class="card card-hovered mb20 card-primary">
<div class="card-header">
{Lang::T('Manage Messages')}
</div>
<div class="card-body">
<br>&nbsp;
<div class="table-responsive table_mobile">
<table id="messageTable" class="table table-bordered table-striped table-condensed">
<thead>
<tr>
<th>{Lang::T('From')}</th>
<th>{Lang::T('To')}</th>
<th>{Lang::T('Title')}</th>
<th>{Lang::T('Date')}</th>
<th>{Lang::T('Status')}</th>
<th>{Lang::T('Actions')}</th>
</tr>
</thead>
<tbody>
{foreach $messages as $msg}
<tr {if $msg.status == 0}class="info"{/if}>
<td>{$msg.from_user}</td>
<td>{$msg.to_user}</td>
<td onclick="window.location.href = '{$_url}messages/view/{$msg.id}'" style="cursor:pointer;">{$msg.title}</td>
<td>{Lang::dateTimeFormat($msg.date)}</td>
<td>{if $msg.status == 0}{Lang::T('Not Sent')}{else}{Lang::T('Sent')}{/if}</td>
<td align="center">
<a href="{$_url}messages/view/{$msg.id}" class="btn btn-success btn-xs">{Lang::T('View')}</a>
</td>
</tr>
{/foreach}
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<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>
var $j = jQuery.noConflict();
$j(document).ready(function() {
$j('#messageTable').DataTable({
"pagingType": "full_numbers",
"lengthMenu": [
[5, 10, 25, 50, 100, -1],
[5, 10, 25, 50, 100, "All"]
],
"pageLength": 25,
"order": [[3, 'desc']]
});
});
</script>
</div>
{include file="sections/footer.tpl"}

66
ui/ui/message.tpl Normal file
View File

@ -0,0 +1,66 @@
{include file="sections/header.tpl"}
<div class="container-fluid">
<div class="row">
<div class="col-sm-12 col-md-12">
<div class="card card-primary card-hovered card-stacked mb30">
<div class="card-header">
<h3 class="card-title">{Lang::T('Send Personal Message')}</h3>
</div>
<div class="card-body">
<form class="form-horizontal" method="post" role="form" action="{$_url}message/send-post">
<div class="form-group row">
<label class="col-md-2 control-label">{Lang::T('Customer')}</label>
<div class="col-md-6">
<select {if $cust}{else}id="personSelect" {/if} class="form-select" style="height: 52px; background-color: white;"
name="id_customer" style="width: 100%"
data-placeholder="{Lang::T('Select a customer')}...">
{if $cust}
<option value="{$cust['id']}">{$cust['username']} &bull; {$cust['fullname']} &bull;
{$cust['email']}</option>
{/if}
</select>
</div>
</div>
<div class="form-group row">
<label class="col-md-2 control-label">{Lang::T('Send Via')}</label>
<div class="col-md-6">
<select class="form-select" style="height: 52px; background-color: white;" name="via" id="via">
<option value="sms" selected> {Lang::T('SMS')}</option>
<option value="wa"> {Lang::T('WhatsApp')}</option>
<option value="both"> {Lang::T('SMS and WhatsApp')}</option>
</select>
</div>
</div>
<div class="form-group row">
<label class="col-md-2 control-label">{Lang::T('Message')}</label>
<div class="col-md-6">
<textarea class="form-control" id="message" name="message"
placeholder="{Lang::T('Compose your message...')}" rows="5"></textarea>
</div>
<p class="help-block col-md-4">
{Lang::T('Use placeholders:')}
<br>
<b>[[name]]</b> - {Lang::T('Customer Name')}
<br>
<b>[[user_name]]</b> - {Lang::T('Customer Username')}
<br>
<b>[[phone]]</b> - {Lang::T('Customer Phone')}
<br>
<b>[[company_name]]</b> - {Lang::T('Your Company Name')}
</p>
</div>
<center>
<div class="form-group row">
<div class="col-lg-offset-2 col-lg-10">
<button class="btn btn-success mb-3" type="submit">{Lang::T('Send Message')}</button>
<a class="btn btn-outline-primary href="{$_url}dashboard" class="btn btn-default">{Lang::T('Cancel')}</a>
</div>
</div>
</center>
</form>
</div>
</div>
</div>
</div>
</div>
{include file="sections/footer.tpl"}

78
ui/ui/onlinehotspot.tpl Normal file
View File

@ -0,0 +1,78 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Online Hotspot Users</title>
<link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet">
<link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/v/dt/dt-1.11.5/datatables.min.css"/>
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/jquery@3.6.0/dist/jquery.min.js"></script>
<script type="text/javascript" src="https://cdn.datatables.net/v/dt/dt-1.11.5/datatables.min.js"></script>
<style>
table.dataTable thead th,
table.dataTable thead td {
padding: 10px 18px;
border-bottom: 1px solid #e2e8f0;
}
table.dataTable tbody td {
padding: 10px 18px;
border-bottom: 1px solid #e2e8f0;
}
.disconnect-btn {
padding: 5px 10px;
background-color: #d53f8c;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
}
</style>
</head>
<body class="bg-gray-100">
<div class="container mx-auto px-4 py-8">
<h1 class="text-2xl font-bold mb-4">Online Hotspot Users</h1>
<table id="hotspotUsersTable" class="stripe hover" style="width:100%">
<thead>
<tr>
<th>Username</th>
<th>Address</th>
<th>Uptime</th>
<th>Server</th>
<th>MAC</th>
<th>Session Time</th>
<th>Rx Bytes</th>
<th>Tx Bytes</th>
<th>Total Bytes</th>
<th>Action</th> <!-- Added column for action -->
</tr>
</thead>
<tbody>
{foreach $onlineHotspotUsers as $user}
<tr>
<td>{$user.username}</td>
<td>{$user.address}</td>
<td>{$user.uptime}</td>
<td>{$user.server}</td>
<td>{$user.mac}</td>
<td>{$user.session_time}</td>
<td>{$user.rx_bytes}</td>
<td>{$user.tx_bytes}</td>
<td>{$user.total}</td>
<td><button class="disconnect-btn" onclick="disconnectUser('{$user.username}')">Disconnect</button></td> <!-- Disconnect button -->
</tr>
{/foreach}
</tbody>
</table>
</div>
<script>
$(document).ready(function() {
$('#hotspotUsersTable').DataTable();
});
function disconnectUser(username) {
// You can perform disconnect action here, such as making an AJAX call to disconnect the user
alert('Disconnecting user: ' + username);
}
</script>
</body>
</html>

63
ui/ui/page-edit.tpl Normal file
View File

@ -0,0 +1,63 @@
{include file="sections/header.tpl"}
<div class="container-fluid">
<div class="row">
<div class="col-sm-12">
<div class="card mb20 card-primary card-hovered">
<div class="card-header">
<h3 class="card-title">{$pageHeader}</h3>
<div class="btn-group pull-right">
<a class="btn btn-danger btn-xs" title="Reset File" href="{$_url}pages/{$PageFile}-reset" onclick="return confirm('Reset File?')"><span
class="fa fa-refresh" aria-hidden="true"></span></a>
</div>
</div>
<div id="myNiccard" style="width: 100%;"></div>
<div id="card-edit" class="card-body">{$htmls}</div>
{if $writeable}
<div class="card-footer">
<a href="javascript:saveIt()" class="btn btn-primary btn-block">SAVE</a>
<br>
<p class="help-block">{Lang::T("Sometimes you need to refresh 3 times until content change")}</p>
<input type="text" class="form-control" onclick="this.select()" readonly
value="{$app_url}/pages/{$PageFile}.html">
</div>
{else}
<div class="card-footer">
{Lang::T("Failed to save page, make sure i can write to folder pages, <i>chmod 664 pages/*.html<i>")}
</div>
{/if}
{if $PageFile=='Voucher'}
<div class="card-footer">
<p class="help-block">
<b>[[company_name]]</b> Your Company Name at Settings.<br>
<b>[[price]]</b> Plan Price.<br>
<b>[[voucher_code]]</b> Voucher Code.<br>
<b>[[plan]]</b> Voucher Plan.<br>
<b>[[counter]]</b> Counter.<br>
</p>
</div>
{/if}
</div>
</div>
</div>
<form id="formpages" class="hidden" method="post" role="form" action="{$_url}pages/{$PageFile}-post">
<textarea name="html" id="html"></textarea>
</form>
<script src="ui/ui/scripts/nicEdit.js"></script>
{literal}
<script type="text/javascript">
var myNicEditor
bkLib.onDomLoaded(function() {
myNicEditor = new nicEditor({fullcard : true});
myNicEditor.setcard('myNiccard');
myNicEditor.addInstance('card-edit');
});
function saveIt() {
//alert(document.getElementById('card-edit').innerHTML);
document.getElementById('html').value = nicEditors.findEditor('card-edit').getContent()
document.getElementById('formpages').submit();
}
</script>
{/literal}
</div>
{include file="sections/footer.tpl"}

20
ui/ui/pagination.tpl Normal file
View File

@ -0,0 +1,20 @@
{if $paginator}
<nav aria-label="Page navigation example" style="margin-top: 6px;">
<ul class="pagination justify-content-end" style="border-radius: 6px;">
<li style="background: #D7DAE3;" {if empty($paginator['prev'])}class="disabled page-item" {/if}>
<a class="page-link" href="{$paginator['url']}{$paginator['prev']}" aria-label="Previous">
<span aria-hidden="true">{Lang::T('Prev')}</span>
</a>
</li>
{foreach $paginator['pages'] as $page}
<li class="page-item {if $paginator['page'] == $page}active{elseif $page == '...'}disabled{/if}"><a class="page-link"
href="{$paginator['url']}{$page}">{$page}</a></li>
{/foreach}
<li style="background: #D7DAE3;" {if $paginator['page']>=$paginator['count']}class="disabled page-item" {/if}>
<a class="page-link" href="{$paginator['url']}{$paginator['next']}" aria-label="Next">
<span aria-hidden="true">{Lang::T('Next')}</span>
</a>
</li>
</ul>
</nav>
{/if}

35
ui/ui/paymentgateway.tpl Normal file
View File

@ -0,0 +1,35 @@
{include file="sections/header.tpl"}
<div class="container-fluid">
<form method="post">
<div class="row">
<div class="col-md-12 col-md-offset-3">
<div class="card card-info card-hovered">
<div class="card-header">
<h3 class="card-title">{Lang::T('Payment Gateway')}</h3>
</div>
<div class="table-responsive card-body">
<table class="table table-striped table-condensed">
<tbody>
{foreach $pgs as $pg}
<tr>
<td width="12" align="center" valign="center"><input type="checkbox" name="pgs[]"
{if in_array($pg, $actives)}checked{/if} value="{$pg}"></td>
<td><a href="{$_url}paymentgateway/{$pg}"
class="btn btn-block btn-{if in_array($pg, $actives)}info{else}default{/if} text-left">{ucwords($pg)}</a>
</td>
<td width="12"><a href="{$_url}paymentgateway/delete/{$pg}"
onclick="return confirm('{Lang::T('Delete')} {$pg}?')" class="btn btn-danger"><i
class="fa fa-trash"></i></a></td>
</tr>
{/foreach}
</tbody>
</table>
</div>
<div class="card-footer"><button type="submit" class="btn btn-primary btn-block" name="save"
value="actives">{Lang::T('Save Changes')}</button></div>
</div>
</div>
</div>
</form>
</div>
{include file="sections/footer.tpl"}

69
ui/ui/plan-edit.tpl Normal file
View File

@ -0,0 +1,69 @@
{include file="sections/header.tpl"}
<div class="container-fluid">
<div class="row">
<div class="col-sm-12">
<div class="card card-primary">
<div class="card-header">
<h3 class="card-title">Edit Plan</h3>
</div>
<div class="card-body">
<form class="form-horizontal" method="post" role="form" action="{$_url}plan/edit-post">
<input type="hidden" name="id" value="{$d['id']}">
<div class="form-group row">
<label class="col-md-4 control-label">{Lang::T('Select Account')}</label>
<div class="col-md-6">
<input type="text" class="form-control" id="username" name="username"
value="{$d['username']}" readonly>
</div>
</div>
<div class="form-group row">
<label class="col-md-4 control-label">{Lang::T('Service Plan')}</label>
<div class="col-md-6">
<select id="id_plan" name="id_plan" class="form-select" style="height: 52px; background-color: white;">
{foreach $p as $ps}
<option value="{$ps['id']}" {if $d['plan_id'] eq $ps['id']} selected {/if}>
{if $ps['enabled'] neq 1}DISABLED PLAN &bull; {/if}
{if $ps['is_radius']=='1'}Radius{else}{$ps['routers']}{/if} &bull; {$ps['name_plan']}</option>
{/foreach}
</select>
</div>
</div>
<div class="form-group row">
<label class="col-md-4 control-label">{Lang::T('Created On')}</label>
<div class="col-md-4 mb-2">
<input type="date" class="form-control" name="expiration" readonly
value="{$d['recharged_on']}">
</div>
<div class="col-md-2">
<input type="text" class="form-control" placeholder="00:00:00" readonly
value="{$d['recharged_time']}">
</div>
</div>
<div class="form-group row">
<label class="col-md-4 control-label">{Lang::T('Expires On')}</label>
<div class="col-md-4 mb-2">
<input type="date" class="form-control" id="expiration" name="expiration"
value="{$d['expiration']}">
</div>
<div class="col-md-2">
<input type="text" class="form-control" id="time" name="time" placeholder="00:00:00"
value="{$d['time']}">
</div>
</div>
<center>
<div class="form-group row">
<div class="col-lg-offset-2 col-lg-10">
<button class="btn btn-success mb-3"
type="submit">{Lang::T('Edit')}</button>
Or <a href="{$_url}plan/list" class="btn btn-outline-primary">{Lang::T('Cancel')}</a>
</div>
</div>
</center>
</form>
</div>
</div>
</div>
</div>
</div>
{include file="sections/footer.tpl"}

91
ui/ui/plan.tpl Normal file
View File

@ -0,0 +1,91 @@
{include file="sections/header.tpl"}
<div class="container-fluid">
<div class="row">
<div class="col-sm-12">
<div class="card card-hovered mb20 card-primary">
<div class="card-header">
<h3 class="card-title">Plans</h3>
{if in_array($_admin['user_type'],['SuperAdmin','Admin'])}
<div>
<div class="btn-group pull-right">
<a class="btn btn-primary" title="save" href="{$_url}plan/sync"
onclick="return confirm('This will sync/send Caustomer active plan to Mikrotik?')"><span
class="fa fa-refresh" aria-hidden="true"></span> sync</a>
</div>
<div class="btn-group pull-right">
<a class="btn btn-info" title="save" href="{$_url}customers/csv"
onclick="return confirm('This will export to CSV?')"><span class="fa fa-download"
aria-hidden="true"></span> CSV</a>
</div>
</div>
{/if}
&nbsp;
</div>
<div class="card-body">
<div class="md-whiteframe-z1 mb20 text-center row">
<div class="col-md-8 mb-3">
<form id="site-search" method="post" action="{$_url}plan/list/">
<div class="input-group">
<div class="input-group-text">
<span class="fa fa-search"></span>
</div>
<input type="text" name="search" class="form-control"
placeholder="{Lang::T('Search by Username')}..." value="{$search}">
<button class="btn btn-success input-group-btn" type="submit">{Lang::T('Search')}</button>
<!-- <div class="input-group-btn">
</div> -->
</div>
</form>
</div>
<div class="col-md-4">
<a href="{$_url}plan/recharge" class="btn btn-primary btn-block"><i
class="fa fa-add"> </i> {Lang::T('Recharge Account')}</a>
</div>&nbsp;
</div>
<div class="table-responsive">
<table id="datatable" class="table table-bordered table-striped table-condensed table-hover border-primary">
<thead>
<tr>
<th>{Lang::T('Username')}</th>
<th>{Lang::T('Plan Name')}</th>
<th>{Lang::T('Plan Type')}</th>
<th>{Lang::T('Type')}</th>
<th>{Lang::T('Created On')}</th>
<th>{Lang::T('Expires On')}</th>
<th>{Lang::T('Method')}</th>
<th>{Lang::T('Routers')}</th>
<th>{Lang::T('Manage')}</th>
</tr>
</thead>
<tbody>
{foreach $d as $ds}
<tr {if $ds['status']=='off'}class="danger" {/if}>
<td><a href="{$_url}customers/viewu/{$ds['username']}">{$ds['username']}</a></td>
<td>{$ds['namebp']}</td>
<td>{$ds['type']}</td>
<td>{$ds['plan_type']}</td>
<td>{Lang::dateAndTimeFormat($ds['recharged_on'],$ds['recharged_time'])}</td>
<td>{Lang::dateAndTimeFormat($ds['expiration'],$ds['time'])}</td>
<td>{$ds['method']}</td>
<td>{$ds['routers']}</td>
<td>
<a href="{$_url}plan/edit/{$ds['id']}"
class="btn btn-warning btn-xs mb-1">{Lang::T('Edit')}</a>
{if in_array($_admin['user_type'],['SuperAdmin','Admin'])}
<a href="{$_url}plan/delete/{$ds['id']}" id="{$ds['id']}"
onclick="return confirm('{Lang::T('Delete')}?')"
class="btn btn-danger btn-xs"><i class="fa fa-trash"></i></a>
{/if}
</td>
</tr>
{/foreach}
</tbody>
</table>
</div>
{include file="pagination.tpl"}
</div>
</div>
</div>
</div>
</div>
{include file="sections/footer.tpl"}

92
ui/ui/plugin-manager.tpl Normal file
View File

@ -0,0 +1,92 @@
{include file="sections/header.tpl"}
<div class="container-fluid">
<div class="row">
<div class="col-sm-12">
<div class="card card-primary card-hovered">
<div class="card-header">
<h3 class="card-title">{Lang::T('Plugin')}</h3>
</div>
<div class="card-body row">
{foreach $plugins as $plugin}
<div class="col-md-6">
<div class="card kanbanPreview-bx">
<div class="card-body">
<div class="sub-card">
<span class="text-warning sub-title fs-14">{$plugin['name']}</span>
<p class="font-w300">{$plugin['description']}</p>
<div class="row justify-content-between align-items-center">
<div>
<span>
<center>
<small><i>@{$plugin['author']} Last update: {$plugin['last_update']}</i></small>
</center>
</span>
</div>
<div class="btn-group btn-group-justified mb-2" role="group" aria-label="...">
<a href="{$plugin['url']}" target="_blank" class="btn btn-primary"><i
class="fa fa-globe"></i> Web</a>
<a href="{$plugin['github']}" target="_blank" class="btn btn-info"><i
class="fa fa-align-left"></i> Source</a>
</div>
<div class="btn-group btn-group-justified" role="group" aria-label="...">
<a href="{$_url}pluginmanager/delete/plugin/{$plugin['id']}" onclick="return confirm('{Lang::T('Delete')}?')" class="btn btn-danger"><i
class="fa fa-trash"></i> Delete</a>
<a {if $zipExt } href="{$_url}pluginmanager/install/plugin/{$plugin['id']}"
onclick="return confirm('Installing plugin will take some time to complete, do not close the page while it loading to install the plugin')"
{else} href="#" onclick="alert('PHP ZIP extension is not installed')"
{/if}
class="btn btn-success"><i class="fa fa-circle-arrow-down"></i> Install</a>
</div>
</div>
</div>
</div>
</div>
</div>
{/foreach}
</div>
</div>
</div>
<div class="col-sm-12">
<div class="card card-primary card-hovered">
<div class="card-header">
<h3 class="card-title">{Lang::T('Payment Gateway')}</h3>
</div>
<div class="card-body row">
foreach $pgs as $pg}
<div class="col-md-6">
<div class="card kanbanPreview-bx">
<div class="card-body">
<div class="sub-card">
<span class="text-warning sub-title fs-14">{$pg['name']}</span>
<p class="font-w300">{$pg['description']}</p>
<div class="row justify-content-between align-items-center">
<div>
<span>
<center>
<small><i>@{$pg['author']} Last update: {$pg['last_update']}</i></small>
</center>
</span>
</div>
<div class="btn-group btn-group-justified" role="group" aria-label="...">
<a href="{$pg['url']}" target="_blank" class="btn btn-primary"><i
class="fa fa-globe"></i> Web</a>
<a href="{$pg['github']}" target="_blank" class="btn btn-info"><i
class="fa fa-align-left"></i> Source</a>
<a {if $zipExt } href="{$_url}pluginmanager/install/payment/{$pg['id']}"
onclick="return confirm('Installing plugin will take some time to complete, do not close the page while it loading to install the plugin')"
{else} href="#" onclick="alert('PHP ZIP extension is not available')"
{/if}
class="btn btn-success"><i class="fa fa-circle-arrow-down"></i> Install</a>
</div>
</div>
</div>
</div>
</div>
</div>
{/foreach}
</div>
</div>
</div>
</div>
</div>
{include file="sections/footer.tpl"}

Some files were not shown because too many files have changed in this diff Show More