From cf8c53640abf7dcd0fe9273696c4e62ead8f7195 Mon Sep 17 00:00:00 2001 From: Flatlogic Bot Date: Wed, 8 Oct 2025 06:10:35 +0000 Subject: [PATCH] Auto commit: 2025-10-08T06:10:35.241Z --- accounts.php | 9 +- billing.php | 8 +- db/config.php | 43 ++++- db/migrate.php | 47 ++++++ includes/routeros_api.class.php | 214 ++++++++++++++++++++++++ monitoring.php | 8 +- packages.php | 163 +++++++++++++++++- routers.php | 287 +++++++++++++++++++++++++++++++- settings.php | 8 +- users.php | 9 +- vouchers.php | 9 +- 11 files changed, 785 insertions(+), 20 deletions(-) create mode 100644 db/migrate.php create mode 100644 includes/routeros_api.class.php diff --git a/accounts.php b/accounts.php index 14285cc..96582dc 100644 --- a/accounts.php +++ b/accounts.php @@ -8,8 +8,13 @@ require_once 'partials/header.php';
-

- +

+
+ + Kembali + + +

PPPoE/Hotspot account management page content goes here.

diff --git a/billing.php b/billing.php index 9782106..53722a6 100644 --- a/billing.php +++ b/billing.php @@ -7,7 +7,13 @@ require_once 'partials/header.php'; ?>
-

+ +

Billing page content goes here.

diff --git a/db/config.php b/db/config.php index bb98f7d..b1723dd 100644 --- a/db/config.php +++ b/db/config.php @@ -5,6 +5,47 @@ define('DB_NAME', 'app_30953'); define('DB_USER', 'app_30953'); define('DB_PASS', 'e45f2778-db1f-450c-99c6-29efb4601472'); +// --- Encryption Settings --- +// WARNING: Changing this key will make all existing encrypted data unreadable. +// For production, use a key from a secure source like an environment variable. +define('ENCRYPTION_KEY', 'def0000068fcf8f7483bde1c8a45b53289f734814842116f7238e4375290654f27a845b20d3435324d83a335e86c45000a7649364e4358612743677d6a336e3c'); +define('ENCRYPTION_CIPHER', 'aes-256-cbc'); + +/** + * Encrypts a string. + * + * @param string $plaintext The string to encrypt. + * @return string The encrypted string (base64 encoded). + */ +function encrypt($plaintext) { + $ivlen = openssl_cipher_iv_length(ENCRYPTION_CIPHER); + $iv = openssl_random_pseudo_bytes($ivlen); + $ciphertext_raw = openssl_encrypt($plaintext, ENCRYPTION_CIPHER, ENCRYPTION_KEY, OPENSSL_RAW_DATA, $iv); + $hmac = hash_hmac('sha256', $ciphertext_raw, ENCRYPTION_KEY, true); + return base64_encode($iv . $hmac . $ciphertext_raw); +} + +/** + * Decrypts a string. + * + * @param string $ciphertext_base64 The base64 encoded ciphertext. + * @return string|false The decrypted string, or false on failure. + */ +function decrypt($ciphertext_base64) { + $c = base64_decode($ciphertext_base64); + $ivlen = openssl_cipher_iv_length(ENCRYPTION_CIPHER); + $iv = substr($c, 0, $ivlen); + $hmac = substr($c, $ivlen, 32); + $ciphertext_raw = substr($c, $ivlen + 32); + $original_plaintext = openssl_decrypt($ciphertext_raw, ENCRYPTION_CIPHER, ENCRYPTION_KEY, OPENSSL_RAW_DATA, $iv); + $calcmac = hash_hmac('sha256', $ciphertext_raw, ENCRYPTION_KEY, true); + if (hash_equals($hmac, $calcmac)) { + return $original_plaintext; + } + return false; +} + + function db() { static $pdo; if (!$pdo) { @@ -14,4 +55,4 @@ function db() { ]); } return $pdo; -} +} \ No newline at end of file diff --git a/db/migrate.php b/db/migrate.php new file mode 100644 index 0000000..dd2734a --- /dev/null +++ b/db/migrate.php @@ -0,0 +1,47 @@ + PDO::ERRMODE_EXCEPTION, + ]); + + // Create the database if it doesn't exist + $pdo->exec("CREATE DATABASE IF NOT EXISTS " . DB_NAME . ";"); + $pdo->exec("USE " . DB_NAME . ";"); + + echo "Database '" . DB_NAME . "' created or already exists.\n"; + + // Packages table + $pdo->exec("CREATE TABLE IF NOT EXISTS packages ( + id INT AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(255) NOT NULL, + price INT NOT NULL, + duration_days INT NOT NULL, + description TEXT, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP + ) ENGINE=INNODB;"); + + echo "Migration successful: 'packages' table created or already exists.\n"; + + // Routers table + $pdo->exec("CREATE TABLE IF NOT EXISTS routers ( + id INT AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(255) NOT NULL, + ip_address VARCHAR(45) NOT NULL, + username VARCHAR(255) NOT NULL, + password TEXT NOT NULL, -- Encrypted + description TEXT, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + UNIQUE KEY ip_address (ip_address) + ) ENGINE=INNODB;"); + + echo "Migration successful: 'routers' table created or already exists.\n"; + + +} catch (PDOException $e) { + die("Migration failed: " . $e->getMessage()); +} diff --git a/includes/routeros_api.class.php b/includes/routeros_api.class.php new file mode 100644 index 0000000..ed58c20 --- /dev/null +++ b/includes/routeros_api.class.php @@ -0,0 +1,214 @@ +attempts; $ATTEMPT++) { + $this->connected = false; + $this->debug('Connection attempt #' . $ATTEMPT . ' to ' . $ip . ':' . $this->port . '...'); + $this->socket = @fsockopen($ip, $this->port, $this->error_no, $this->error_str, $this->timeout); + if ($this->socket) { + socket_set_timeout($this->socket, $this->timeout); + $this->write('/login'); + $RESPONSE = $this->read(false); + if (isset($RESPONSE[0]) && $RESPONSE[0] == '!done') { + if (preg_match_all('/[^=]+/i', $RESPONSE[1], $MATCHES)) { + if ($MATCHES[0][0] == 'ret' && strlen($MATCHES[0][1]) == 32) { + $this->write('/login', false); + $this->write('=name=' . $login, false); + $this->write('=response=00' . md5(chr(0) . $password . pack('H*', $MATCHES[0][1]))); + $RESPONSE = $this->read(false); + if (isset($RESPONSE[0]) && $RESPONSE[0] == '!done') { + $this->connected = true; + break; + } + } + } + } + fclose($this->socket); + } + sleep($this->delay); + } + + if ($this->connected) { + $this->debug('Connected successfully to ' . $ip . ':' . $this->port); + } else { + $this->debug('Error connecting to ' . $ip . ':' . $this->port); + } + return $this->connected; + } + + /** + * Disconnect from RouterOS + */ + public function disconnect() + { + fclose($this->socket); + $this->connected = false; + $this->debug('Disconnected'); + } + + /** + * Parse response from RouterOS + * + * @param array $response Response data + * + * @return array Parsed data + */ + public function parseResponse($response) + { + if (is_array($response)) { + $PARSED = array(); + $CURRENT = null; + $singlevalue = null; + foreach ($response as $x) { + if (in_array($x, array('!fatal', '!re', '!trap'))) { + if ($x == '!re') { + $CURRENT =& $PARSED[]; + } else { + $CURRENT =& $PARSED[$x][]; + } + } elseif ($x != '!done') { + if (preg_match_all('/[^=]+/i', $x, $MATCHES)) { + if ($MATCHES[0][0] == 'ret') { + $singlevalue = $MATCHES[0][1]; + } + $CURRENT[$MATCHES[0][0]] = (isset($MATCHES[0][1]) ? $MATCHES[0][1] : ''); + } + } + } + + if (empty($PARSED) && !is_null($singlevalue)) { + $PARSED = $singlevalue; + } + + return $PARSED; + } else { + return array(); + } + } + + /** + * Read data from RouterOS + * + * @param boolean $parse Parse the data? + * + * @return array Data array + */ + public function read($parse = true) + { + $RESPONSE = array(); + $line = ''; + while (true) { + $BYTE = fread($this->socket, 1); + $line .= $BYTE; + if ($BYTE == "\0") { + $RESPONSE[] = $line; + if (substr($line, -5) == "!done\0") { + break; + } + $line = ''; + } + } + + if ($parse) { + return $this->parseResponse($RESPONSE); + } else { + return $RESPONSE; + } + } + + /** + * Write (send) data to RouterOS + * + * @param string $command A string with the command to send + * @param boolean $param2 If we are sending a command, or a parameter + * + * @return void + */ + public function write($command, $param2 = true) + { + if ($command) { + $data = explode("\n", $command); + foreach ($data as $com) { + $com = trim($com); + fwrite($this->socket, $this->encodeLength(strlen($com)) . $com); + $this->debug('<<< ' . $com); + } + + if (gettype($param2) == 'integer') { + fwrite($this->socket, $this->encodeLength(strlen('.tag=' . $param2)) . '.tag=' . $param2 . "\0"); + $this->debug('<<< .tag=' . $param2); + } elseif (gettype($param2) == 'boolean') { + fwrite($this->socket, ($param2 ? '' : "\0")); + } + } + } + + /** + * Encode length of the string + * + * @param integer $length Length of the string + * + * @return string Encoded length + */ + private function encodeLength($length) + { + if ($length < 0x80) { + return chr($length); + } + if ($length < 0x4000) { + return chr(($length >> 8) | 0x80) . chr($length & 0xFF); + } + if ($length < 0x200000) { + return chr(($length >> 16) | 0xC0) . chr(($length >> 8) & 0xFF) . chr($length & 0xFF); + } + if ($length < 0x10000000) { + return chr(($length >> 24) | 0xE0) . chr(($length >> 16) & 0xFF) . chr(($length >> 8) & 0xFF) . chr($length & 0xFF); + } + return chr(0xF0) . chr(($length >> 24) & 0xFF) . chr(($length >> 16) & 0xFF) . chr(($length >> 8) & 0xFF) . chr($length & 0xFF); + } + + /** + * Print debug information + * + * @param string $text Debug text + * + * @return void + */ + private function debug($text) + { + if ($this->debug) { + echo $text . "\n"; + } + } +} diff --git a/monitoring.php b/monitoring.php index 41c9d4d..830f3f6 100644 --- a/monitoring.php +++ b/monitoring.php @@ -7,7 +7,13 @@ require_once 'partials/header.php'; ?>
-

+ +

Monitoring page content goes here.

diff --git a/packages.php b/packages.php index 87bef37..0da3d78 100644 --- a/packages.php +++ b/packages.php @@ -1,5 +1,6 @@ prepare("SELECT * FROM packages WHERE id = ?"); + $stmt->execute([$_GET['edit_id']]); + $edit_package = $stmt->fetch(); +} + +// Handle Delete Request +if (isset($_POST['delete_id'])) { + try { + $stmt = $pdo->prepare("DELETE FROM packages WHERE id = ?"); + $stmt->execute([$_POST['delete_id']]); + $feedback = ['type' => 'success', 'message' => 'Paket berhasil dihapus.']; + } catch (PDOException $e) { + $feedback = ['type' => 'danger', 'message' => 'Gagal menghapus paket: ' . $e->getMessage()]; + } +} + +// Handle Add/Update Request +if (isset($_POST['save_package'])) { + $name = $_POST['name']; + $price = $_POST['price']; + $duration_days = $_POST['duration_days']; + $description = $_POST['description']; + $id = $_POST['id']; + + // Basic validation + if (empty($name) || !is_numeric($price) || !is_numeric($duration_days)) { + $feedback = ['type' => 'danger', 'message' => 'Nama, Harga, dan Durasi harus diisi dengan benar.']; + } else { + try { + if (empty($id)) { // Add new + $stmt = $pdo->prepare("INSERT INTO packages (name, price, duration_days, description) VALUES (?, ?, ?, ?)"); + $stmt->execute([$name, $price, $duration_days, $description]); + $feedback = ['type' => 'success', 'message' => 'Paket baru berhasil ditambahkan.']; + } else { // Update existing + $stmt = $pdo->prepare("UPDATE packages SET name = ?, price = ?, duration_days = ?, description = ? WHERE id = ?"); + $stmt->execute([$name, $price, $duration_days, $description, $id]); + $feedback = ['type' => 'success', 'message' => 'Paket berhasil diperbarui.']; + // Redirect to clear edit state + header("Location: packages.php"); + exit; + } + } catch (PDOException $e) { + $feedback = ['type' => 'danger', 'message' => 'Operasi gagal: ' . $e->getMessage()]; + } + } +} + + +// Fetch all packages for display +$packages = $pdo->query("SELECT * FROM packages ORDER BY name ASC")->fetchAll(); + +$page_title = 'Paket Layanan'; require_once 'partials/header.php'; ?>
-
-

- + + + + +
+ +
+ + + +
+
+
+
+
+
+ +
+
+ + +
+
+ + +
+
+ + +
+
+
+ + +
+ + + Batal + +
+
+
+ + +
+
+
Daftar Paket
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
NamaHargaDurasiDeskripsiAksi
Belum ada paket yang ditambahkan.
Rp hari + + + +
+ + +
+
+
+
-
-

Service package management page content goes here.

- + \ No newline at end of file diff --git a/routers.php b/routers.php index add0f36..27d0c63 100644 --- a/routers.php +++ b/routers.php @@ -1,5 +1,7 @@ debug = false; + +// Handle Test Connection (AJAX) +if (isset($_GET['action']) && $_GET['action'] == 'test_connection') { + header('Content-Type: application/json'); + $id = $_GET['id'] ?? 0; + $stmt = db()->prepare("SELECT * FROM routers WHERE id = ?"); + $stmt->execute([$id]); + $router = $stmt->fetch(); + + if ($router) { + $password = decrypt($router['password']); + if ($API->connect($router['ip_address'], $router['username'], $password)) { + $API->write('/system/resource/print'); + $resource = $API->read(); + $API->disconnect(); + echo json_encode(['success' => true, 'message' => 'Connection successful!', 'data' => $resource[0]]); + } else { + echo json_encode(['success' => false, 'message' => 'Connection failed. Check IP, username, and password.']); + } + } else { + echo json_encode(['success' => false, 'message' => 'Router not found.']); + } + exit; +} + + +// Handle form submissions (Add/Edit) +if ($_SERVER['REQUEST_METHOD'] === 'POST') { + $id = $_POST['id'] ?? null; + $name = trim($_POST['name']); + $ip_address = trim($_POST['ip_address']); + $username = trim($_POST['username']); + $password = $_POST['password']; + $description = trim($_POST['description']); + + if (empty($name)) $errors[] = 'Router name is required.'; + if (empty($ip_address) || !filter_var($ip_address, FILTER_VALIDATE_IP)) $errors[] = 'A valid IP address is required.'; + if (empty($username)) $errors[] = 'Username is required.'; + if (empty($id) && empty($password)) $errors[] = 'Password is required for a new router.'; + + if (empty($errors)) { + if ($id) { // Update + if (!empty($password)) { + $encrypted_password = encrypt($password); + $stmt = db()->prepare("UPDATE routers SET name = ?, ip_address = ?, username = ?, password = ?, description = ? WHERE id = ?"); + $stmt->execute([$name, $ip_address, $username, $encrypted_password, $description, $id]); + } else { + $stmt = db()->prepare("UPDATE routers SET name = ?, ip_address = ?, username = ?, description = ? WHERE id = ?"); + $stmt->execute([$name, $ip_address, $username, $description, $id]); + } + $success = "Router updated successfully!"; + } else { // Insert + $encrypted_password = encrypt($password); + $stmt = db()->prepare("INSERT INTO routers (name, ip_address, username, password, description) VALUES (?, ?, ?, ?, ?)"); + try { + $stmt->execute([$name, $ip_address, $username, $encrypted_password, $description]); + $success = "Router added successfully!"; + } catch (PDOException $e) { + if ($e->errorInfo[1] == 1062) { // Duplicate entry + $errors[] = "A router with this IP address already exists."; + } else { + $errors[] = "Database error: " . $e->getMessage(); + } + } + } + } +} + +// Handle Delete +if (isset($_GET['action']) && $_GET['action'] == 'delete') { + $id = $_GET['id'] ?? 0; + $stmt = db()->prepare("DELETE FROM routers WHERE id = ?"); + $stmt->execute([$id]); + header('Location: routers.php?deleted=true'); + exit; +} + +if(isset($_GET['deleted'])) { + $success = "Router deleted successfully!"; +} + +// Fetch all routers +$routers = db()->query("SELECT * FROM routers ORDER BY name ASC")->fetchAll(); + +// Fetch router for editing +$edit_router = null; +if (isset($_GET['action']) && $_GET['action'] == 'edit') { + $id = $_GET['id'] ?? 0; + $stmt = db()->prepare("SELECT * FROM routers WHERE id = ?"); + $stmt->execute([$id]); + $edit_router = $stmt->fetch(); +} require_once 'partials/header.php'; ?>
-

- +

+ Kembali

-

Router management page content goes here.

+

+ +
+ +
+
+
+
+
+
+
+
+ +
+ + +
+
+ + +
+
+ + +
+
+ + > + Leave blank to keep the current password. +
+
+ + +
+ + Cancel Edit +
+
+
+
+
+
+
+
Router List
+
+
+
+ + + + + + + + + + + + + + + + + + +
NameIP AddressUsernameActions
+ + + + + + + +
No routers found. Add one to get started.
+
+
+
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/settings.php b/settings.php index a14ecfa..eb418ed 100644 --- a/settings.php +++ b/settings.php @@ -13,7 +13,13 @@ require_once 'partials/header.php'; ?>
-

+ +

Settings page content goes here.

diff --git a/users.php b/users.php index fe9c3ec..bcddd6f 100644 --- a/users.php +++ b/users.php @@ -14,8 +14,13 @@ require_once 'partials/header.php';
-

- +

+
+ + Kembali + + +

Operator management page content goes here.

diff --git a/vouchers.php b/vouchers.php index 29652e3..cab7f47 100644 --- a/vouchers.php +++ b/vouchers.php @@ -8,8 +8,13 @@ require_once 'partials/header.php';
-

- +

+
+ + Kembali + + +

Voucher management page content goes here.