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';
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';
?>
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ | Nama |
+ Harga |
+ Durasi |
+ Deskripsi |
+ Aksi |
+
+
+
+
+
+ | 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.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ | Name |
+ IP Address |
+ Username |
+ Actions |
+
+
+
+
+ |
+ |
+ |
+
+
+
+
+
+
+
+
+ |
+
+ No routers found. Add one to get started. |
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Are you sure you want to delete this router?
+
+
+
+
+
+
+
+
+
+
\ 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';
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';
Voucher management page content goes here.