Chore: Bump version to v1.1.0 and implement automated release system

This commit is contained in:
dyzulk
2026-01-17 13:01:05 +07:00
parent 64609a5821
commit 5b0b6de2dc
69 changed files with 3157 additions and 2375 deletions

View File

@@ -32,7 +32,8 @@ class ApiController extends Controller {
$configModel = new Config();
$session = $configModel->getSessionById($id);
if ($session && !empty($session['password'])) {
$pass = EncryptionHelper::decrypt($session['password']);
// Config::getSessionById already decrypts the password
$pass = $session['password'];
}
}

View File

@@ -10,7 +10,7 @@ use App\Core\Middleware;
class DashboardController extends Controller {
public function __construct() {
Middleware::auth();
// Auth handled by Router Middleware
}
public function index($session) {
@@ -101,6 +101,7 @@ class DashboardController extends Controller {
'hotspot_users' => 'Hotspot Users',
'hotspot_users' => 'Hotspot Users',
],
'reload_interval' => $creds['reload'] ?? 5, // Default 5s if not set
'interface' => $creds['interface'] ?? 'ether1'
];
// Pass Users Link (Optional: could be part of layout or card link)
@@ -108,7 +109,9 @@ class DashboardController extends Controller {
return $this->view('dashboard', $data);
} else {
echo "Connection Failed to " . $creds['ip'];
\App\Helpers\FlashHelper::set('error', 'Connection Failed', 'Could not connect to router at ' . $creds['ip']);
header('Location: ' . ($_SERVER['HTTP_REFERER'] ?? '/'));
exit;
}
}
}

View File

@@ -26,7 +26,10 @@ class DhcpController extends Controller
if ($API->connect($config['ip_address'], $config['username'], $config['password'])) {
// Fetch DHCP Leases
$leases = $API->comm("/ip/dhcp-server/lease/print");
$API->disconnect();
} else {
\App\Helpers\FlashHelper::set('error', 'Connection Failed', 'Could not connect to router at ' . $config['ip_address']);
header('Location: ' . ($_SERVER['HTTP_REFERER'] ?? '/' . $session . '/dashboard'));
exit;
}
// Add index for viewing

View File

@@ -34,8 +34,9 @@ class GeneratorController extends Controller {
$this->view('hotspot/generate', $data);
} else {
// Handle connection error (flash message ideally, but for now redirect or show error)
echo "Connection failed to " . $creds['ip'];
\App\Helpers\FlashHelper::set('error', 'Connection Failed', 'Could not connect to router at ' . $creds['ip']);
header('Location: ' . ($_SERVER['HTTP_REFERER'] ?? '/' . $session . '/dashboard'));
exit;
}
}

View File

@@ -25,6 +25,7 @@ class HotspotController extends Controller {
$userId = $session; // For view context
$users = [];
$servers = [];
$error = null;
$API = new RouterOSAPI();
@@ -40,17 +41,20 @@ class HotspotController extends Controller {
// Get all hotspot users
$users = $API->comm("/ip/hotspot/user/print");
// Get active users to mark status (optional, can be done later for optimization)
// $active = $API->comm("/ip/hotspot/active/print");
// Get servers for dropdown
$servers = $API->comm("/ip/hotspot/server/print");
$API->disconnect();
} else {
$error = "Connection Failed to " . $creds['ip'];
\App\Helpers\FlashHelper::set('error', 'Connection Failed', 'Could not connect to router at ' . $creds['ip']);
header('Location: ' . ($_SERVER['HTTP_REFERER'] ?? '/' . $session . '/dashboard'));
exit;
}
$data = [
'session' => $session,
'users' => $users,
'servers' => $servers,
'error' => $error
];
@@ -389,7 +393,9 @@ class HotspotController extends Controller {
$items = $API->comm("/ip/hotspot/active/print");
$API->disconnect();
} else {
$error = "Connection Failed to " . $creds['ip'];
\App\Helpers\FlashHelper::set('error', 'Connection Failed', 'Could not connect to router at ' . $creds['ip']);
header('Location: ' . ($_SERVER['HTTP_REFERER'] ?? '/' . $session . '/dashboard'));
exit;
}
$data = [
@@ -451,7 +457,9 @@ class HotspotController extends Controller {
$items = $API->comm("/ip/hotspot/host/print");
$API->disconnect();
} else {
$error = "Connection Failed to " . $creds['ip'];
\App\Helpers\FlashHelper::set('error', 'Connection Failed', 'Could not connect to router at ' . $creds['ip']);
header('Location: ' . ($_SERVER['HTTP_REFERER'] ?? '/' . $session . '/dashboard'));
exit;
}
$data = [
@@ -484,7 +492,9 @@ class HotspotController extends Controller {
$items = $API->comm("/ip/hotspot/ip-binding/print");
$API->disconnect();
} else {
$error = "Connection Failed to " . $creds['ip'];
\App\Helpers\FlashHelper::set('error', 'Connection Failed', 'Could not connect to router at ' . $creds['ip']);
header('Location: ' . ($_SERVER['HTTP_REFERER'] ?? '/' . $session . '/dashboard'));
exit;
}
$data = [
@@ -606,7 +616,9 @@ class HotspotController extends Controller {
$items = $API->comm("/ip/hotspot/walled-garden/ip/print");
$API->disconnect();
} else {
$error = "Connection Failed to " . $creds['ip'];
\App\Helpers\FlashHelper::set('error', 'Connection Failed', 'Could not connect to router at ' . $creds['ip']);
header('Location: ' . ($_SERVER['HTTP_REFERER'] ?? '/' . $session . '/dashboard'));
exit;
}
$data = [
@@ -837,8 +849,9 @@ class HotspotController extends Controller {
$templateContent = $tpl['content'];
$viewName = 'print/custom';
} else {
// Fallback if ID invalid
$currentTemplate = 'default';
\App\Helpers\FlashHelper::set('error', 'Template Not Found', 'The selected print template could not be found.');
header('Location: ' . ($_SERVER['HTTP_REFERER'] ?? '/' . $session . '/hotspot/users'));
exit;
}
}

View File

@@ -44,7 +44,10 @@ class LogController extends Controller
$logs = array_reverse($logs);
}
$API->disconnect();
} else {
\App\Helpers\FlashHelper::set('error', 'Connection Failed', 'Could not connect to router at ' . $config['ip_address']);
header('Location: ' . ($_SERVER['HTTP_REFERER'] ?? '/' . $session . '/dashboard'));
exit;
}
return $this->view('reports/user_log', [

View File

@@ -21,6 +21,21 @@ class ProfileController extends Controller
// Use default port 8728 if not specified
if ($API->connect($creds['ip'], $creds['user'], $creds['password'])) {
$profiles = $API->comm('/ip/hotspot/user/profile/print');
// Fetch Pools & Queues for the Modal Form
$pools = $API->comm('/ip/pool/print');
$simple = $API->comm('/queue/simple/print');
$tree = $API->comm('/queue/tree/print');
$queues = [];
foreach ($simple as $q) {
if(isset($q['name'])) $queues[] = $q['name'];
}
foreach ($tree as $q) {
if(isset($q['name'])) $queues[] = $q['name'];
}
sort($queues);
$API->disconnect();
// Process profiles to add metadata from on-login script
@@ -33,15 +48,14 @@ class ProfileController extends Controller
$this->view('hotspot/profiles/index', [
'session' => $session,
'profiles' => $profiles,
'pools' => $pools,
'queues' => $queues,
'title' => 'User Profiles'
]);
} else {
$this->view('hotspot/profiles/index', [
'session' => $session,
'profiles' => [],
'error' => 'Connection Failed to ' . $creds['ip'],
'title' => 'User Profiles'
]);
\App\Helpers\FlashHelper::set('error', 'Connection Failed', 'Could not connect to router at ' . $creds['ip']);
header('Location: ' . ($_SERVER['HTTP_REFERER'] ?? '/' . $session . '/dashboard'));
exit;
}
}

View File

@@ -14,14 +14,9 @@ class PublicStatusController extends Controller {
// View: Show Search Page
public function index($session) {
// Just verify session existence to display Hotspot Name
// Session verified by RouterCheckMiddleware
$configModel = new Config();
$creds = $configModel->getSession($session);
if (!$creds) {
// If session invalid, maybe show 404 or generic error
echo "Session not found.";
return;
}
$data = [
'session' => $session,
@@ -92,9 +87,6 @@ class PublicStatusController extends Controller {
if (!empty($user)) {
$u = $user[0];
// DEBUG: Log the user data to see raw values
error_log("Status Debug: " . json_encode($u));
// --- SECURITY CHECK: Hide Unused Vouchers ---
$uptimeRaw = $u['uptime'] ?? '0s';
$bytesIn = intval($u['bytes-in'] ?? 0);

View File

@@ -19,7 +19,14 @@ class QuickPrintController extends Controller {
// Dashboard: List Cards
public function index($session) {
$qpModel = new QuickPrintModel();
$packages = $qpModel->getAllBySession($session);
$configModel = new Config();
$creds = $configModel->getSession($session);
$routerId = $creds['id'] ?? null;
// If no ID (Legacy), fallback to empty list or handle gracefully.
// For now, we assume ID exists as per migration plan.
$packages = $routerId ? $qpModel->getAllByRouterId($routerId) : [];
$data = [
'session' => $session,
@@ -32,11 +39,12 @@ class QuickPrintController extends Controller {
// List/Manage Packages (CRUD)
public function manage($session) {
$qpModel = new QuickPrintModel();
$packages = $qpModel->getAllBySession($session);
// Need profiles for the Add/Edit Modal
$configModel = new Config();
$creds = $configModel->getSession($session);
$routerId = $creds['id'] ?? null;
$packages = $routerId ? $qpModel->getAllByRouterId($routerId) : [];
$profiles = [];
if ($creds) {
$API = new RouterOSAPI();
@@ -63,7 +71,13 @@ class QuickPrintController extends Controller {
if ($_SERVER['REQUEST_METHOD'] !== 'POST') return;
$session = $_POST['session'] ?? '';
$configModel = new Config();
$creds = $configModel->getSession($session);
$routerId = $creds['id'] ?? 0;
$data = [
'router_id' => $routerId,
'session_name' => $session,
'name' => $_POST['name'] ?? 'Package',
'server' => $_POST['server'] ?? 'all',
@@ -71,6 +85,7 @@ class QuickPrintController extends Controller {
'prefix' => $_POST['prefix'] ?? '',
'char_length' => $_POST['char_length'] ?? 4,
'price' => $_POST['price'] ?? 0,
'selling_price' => $_POST['selling_price'] ?? ($_POST['price'] ?? 0),
'time_limit' => $_POST['time_limit'] ?? '',
'data_limit' => $_POST['data_limit'] ?? '',
'comment' => $_POST['comment'] ?? '',
@@ -85,6 +100,40 @@ class QuickPrintController extends Controller {
exit;
}
// CRUD: Update
public function update() {
if ($_SERVER['REQUEST_METHOD'] !== 'POST') return;
$session = $_POST['session'] ?? '';
$id = $_POST['id'] ?? '';
if (empty($id)) {
\App\Helpers\FlashHelper::set('error', 'common.error', 'toasts.error_missing_id', [], true);
header("Location: /" . $session . "/quick-print/manage");
exit;
}
$data = [
'name' => $_POST['name'] ?? 'Package',
'profile' => $_POST['profile'] ?? 'default',
'prefix' => $_POST['prefix'] ?? '',
'char_length' => $_POST['char_length'] ?? 4,
'price' => $_POST['price'] ?? 0,
'selling_price' => $_POST['selling_price'] ?? ($_POST['price'] ?? 0),
'time_limit' => $_POST['time_limit'] ?? '',
'data_limit' => $_POST['data_limit'] ?? '',
'comment' => $_POST['comment'] ?? '',
'color' => $_POST['color'] ?? 'bg-blue-500'
];
$qpModel = new QuickPrintModel();
$qpModel->update($id, $data); // Assuming update method exists in simple JSON model
\App\Helpers\FlashHelper::set('success', 'toasts.package_updated', 'toasts.package_updated_desc', [], true);
header("Location: /" . $session . "/quick-print/manage");
exit;
}
// CRUD: Delete
public function delete() {
if ($_SERVER['REQUEST_METHOD'] !== 'POST') return;
@@ -158,7 +207,9 @@ class QuickPrintController extends Controller {
$API->comm("/ip/hotspot/user/add", $userData);
$API->disconnect();
} else {
die("Connection failed");
\App\Helpers\FlashHelper::set('error', 'Connection Failed', 'Could not connect to router at ' . $creds['ip']);
header('Location: ' . ($_SERVER['HTTP_REFERER'] ?? '/' . $session . '/quick-print/manage'));
exit;
}

View File

@@ -10,7 +10,7 @@ use App\Helpers\FormatHelper;
class SettingsController extends Controller {
public function __construct() {
Middleware::auth();
// Auth handled by Router Middleware
}
public function system() {
@@ -33,10 +33,6 @@ class SettingsController extends Controller {
return $this->view('settings/index', ['routers' => $routers]);
}
public function add() {
return $this->view('settings/form');
}
// ... (Existing Store methods) ...
public function store() {
// Sanitize Session Name (Duplicate Frontend Logic)
@@ -102,33 +98,7 @@ class SettingsController extends Controller {
}
public function edit() {
// ID passed via query param or route param?
// Our router supports {id} but let's check how we handle it.
// Router: /settings/edit/{id}
// In Router.php, params are passed to method.
// So method signature should be edit($id)
// Wait, Router.php passes matches as params array to invokeCallback.
// So we need to capture arguments here.
$args = func_get_args();
$id = $args[0] ?? null;
if (!$id) {
header('Location: /settings/routers');
exit;
}
$configModel = new Config();
$session = $configModel->getSessionById($id);
if (!$session) {
header('Location: /settings/routers');
exit;
}
return $this->view('settings/form', ['router' => $session]);
}
public function update() {
$id = $_POST['id'];
@@ -316,7 +286,7 @@ class SettingsController extends Controller {
// Restore Logos
if (isset($json['logos'])) {
$logoModel = new \App\Models\Logo();
$uploadDir = ROOT . '/public/assets/img/logos/';
$uploadDir = ROOT . '/public/uploads/logos/';
if (!file_exists($uploadDir)) {
mkdir($uploadDir, 0777, true);
}
@@ -341,7 +311,7 @@ class SettingsController extends Controller {
ON CONFLICT(id) DO UPDATE SET name=excluded.name, path=excluded.path, type=excluded.type, size=excluded.size", [
'id' => $logo['id'],
'name' => $logo['name'],
'path' => '/assets/img/logos/' . $filename,
'path' => '/uploads/logos/' . $filename,
'type' => $extension,
'size' => $logo['size']
]);
@@ -371,22 +341,24 @@ class SettingsController extends Controller {
}
public function uploadLogo() {
if (!isset($_FILES['logo_file'])) {
if (!isset($_FILES['logo_file']) || $_FILES['logo_file']['error'] !== UPLOAD_ERR_OK) {
\App\Helpers\FlashHelper::set('error', 'toasts.upload_failed', 'toasts.no_file_selected', [], true);
header('Location: /settings/logos');
exit;
}
$logoModel = new \App\Models\Logo();
try {
$logoModel->add($_FILES['logo_file']);
$result = $logoModel->add($_FILES['logo_file']);
if ($result) {
\App\Helpers\FlashHelper::set('success', 'toasts.logo_uploaded', 'toasts.logo_uploaded_desc', [], true);
} else {
\App\Helpers\FlashHelper::set('error', 'toasts.upload_failed', 'Generic upload error', [], true);
}
} catch (\Exception $e) {
// Ideally flash error message to session
// For now, redirect (logging error via debug or ignoring as per simple req)
// session_start() is implicit in Middleware usually or index
// $_SESSION['error'] = $e->getMessage();
\App\Helpers\FlashHelper::set('error', 'toasts.upload_failed', $e->getMessage(), [], true);
}
\App\Helpers\FlashHelper::set('success', 'toasts.logo_uploaded', 'toasts.logo_uploaded_desc', [], true);
header('Location: /settings/logos');
}

View File

@@ -6,7 +6,7 @@ use App\Core\Controller;
use App\Models\VoucherTemplateModel;
use App\Core\Middleware;
class TemplateController extends Controller {
class VoucherTemplateController extends Controller {
public function __construct() {
Middleware::auth();
@@ -19,7 +19,7 @@ class TemplateController extends Controller {
$data = [
'templates' => $templates
];
return $this->view('settings/templates/index', $data);
return $this->view('settings/voucher_templates/index', $data);
}
public function preview($id) {
@@ -48,7 +48,7 @@ class TemplateController extends Controller {
$data = [
'logoMap' => $logoMap
];
return $this->view('settings/templates/add', $data); // Note: add.php likely includes edit.php or is alias. View above says 'Template Editor (Shared)'
return $this->view('settings/voucher_templates/add', $data);
}
public function store() {
@@ -62,6 +62,7 @@ class TemplateController extends Controller {
// I will use 'global' for templates created in Settings.
$data = [
'router_id' => 0, // Global templates
'session_name' => 'global',
'name' => $name,
'content' => $content
@@ -71,7 +72,7 @@ class TemplateController extends Controller {
$templateModel->add($data);
\App\Helpers\FlashHelper::set('success', 'toasts.template_created', 'toasts.template_created_desc', ['name' => $name], true);
header("Location: /settings/templates");
header("Location: /settings/voucher-templates");
exit;
}
@@ -80,7 +81,7 @@ class TemplateController extends Controller {
$template = $templateModel->getById($id);
if (!$template) {
header("Location: /settings/templates");
header("Location: /settings/voucher-templates");
exit;
}
@@ -95,7 +96,7 @@ class TemplateController extends Controller {
'template' => $template,
'logoMap' => $logoMap
];
return $this->view('settings/templates/edit', $data);
return $this->view('settings/voucher_templates/edit', $data);
}
public function update() {
@@ -114,7 +115,7 @@ class TemplateController extends Controller {
$templateModel->update($id, $data);
\App\Helpers\FlashHelper::set('success', 'toasts.template_updated', 'toasts.template_updated_desc', ['name' => $name], true);
header("Location: /settings/templates");
header("Location: /settings/voucher-templates");
exit;
}
@@ -126,7 +127,7 @@ class TemplateController extends Controller {
$templateModel->delete($id);
\App\Helpers\FlashHelper::set('success', 'toasts.template_deleted', 'toasts.template_deleted_desc', [], true);
header("Location: /settings/templates");
header("Location: /settings/voucher-templates");
exit;
}
}