Add Roles/MQTT-settings/Backup pages, AdminLTE demo shells, and a separate mobile design system
Backend: real endpoints for role-permission overview, MQTT broker settings (with live reachability check), and SQLite backup download/restore (new system.manage permission, super-admin only; restore validates the SQLite file header and keeps a pre-restore safety copy). Desktop frontend: Roles (read-only permissions view), MQTT Settings, Backup & Restore wired to those endpoints; plus static AdminLTE-matching shells for Profile, Settings (embeds the real 2FA flow in its Security tab), Invoice, Calendar (FullCalendar), Chat, File Manager, and Projects. Mobile frontend: new frontend/src/styles/mobile.css, a from-scratch design system (purple theme, cards, slide-out sidebar, bottom tab bar) scoped entirely under a .mob-app root and mob- prefixed classes so it can never collide with the desktop Bootstrap/AdminLTE styling. Rebuilt TopBar/ BottomNav, added a new SlideOutNav, and reskinned the Dashboard and Users pages to it with real data.
This commit is contained in:
1 parent
84cd474bcb
commit
2a2fac9cdf
31 files changed
+1894
-69
No files matched your search
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Services\Audit\AuditLogger;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
|
||||
class BackupController extends Controller
|
||||
{
|
||||
private const SQLITE_HEADER = "SQLite format 3\000";
|
||||
|
||||
public function download(Request $request, AuditLogger $audit)
|
||||
{
|
||||
abort_unless($request->user()->can('system.manage'), 403);
|
||||
|
||||
$path = config('database.connections.sqlite.database');
|
||||
abort_unless($path && is_file($path), 404, 'No database file to back up.');
|
||||
|
||||
$audit->log($request->user(), 'system.backup_downloaded');
|
||||
|
||||
return response()->download($path, 'backup-'.now()->format('Y-m-d-His').'.sqlite');
|
||||
}
|
||||
|
||||
public function restore(Request $request, AuditLogger $audit)
|
||||
{
|
||||
abort_unless($request->user()->can('system.manage'), 403);
|
||||
|
||||
$request->validate(['backup' => ['required', 'file']]);
|
||||
|
||||
$uploaded = $request->file('backup');
|
||||
$header = file_get_contents($uploaded->getRealPath(), false, null, 0, 16);
|
||||
|
||||
if ($header !== self::SQLITE_HEADER) {
|
||||
throw ValidationException::withMessages([
|
||||
'backup' => 'That file is not a valid SQLite database.',
|
||||
]);
|
||||
}
|
||||
|
||||
$path = config('database.connections.sqlite.database');
|
||||
|
||||
// Release Laravel's open handle on the current file before swapping it.
|
||||
DB::disconnect();
|
||||
|
||||
$safetyCopy = dirname($path).'/pre-restore-'.now()->format('Y-m-d-His').'.sqlite';
|
||||
if (is_file($path)) {
|
||||
copy($path, $safetyCopy);
|
||||
}
|
||||
|
||||
$uploaded->move(dirname($path), basename($path));
|
||||
|
||||
$audit->log($request->user(), 'system.backup_restored');
|
||||
|
||||
return response()->noContent();
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,8 @@ namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\Request;
|
||||
use PhpMqtt\Client\ConnectionSettings;
|
||||
use PhpMqtt\Client\MqttClient;
|
||||
|
||||
class MqttController extends Controller
|
||||
{
|
||||
@@ -25,4 +27,51 @@ class MqttController extends Controller
|
||||
'password' => $credentials['password'],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read-only broker configuration + a live reachability check, for the
|
||||
* MQTT settings page. Never exposes the publisher/role credentials.
|
||||
*/
|
||||
public function settings(Request $request)
|
||||
{
|
||||
abort_unless($request->user()->can('system.manage'), 403);
|
||||
|
||||
$host = config('mqtt-client.connections.default.host');
|
||||
$port = config('mqtt-client.connections.default.port');
|
||||
$tls = (bool) config('mqtt-client.connections.default.connection_settings.tls.enabled');
|
||||
$username = config('mqtt-client.connections.default.connection_settings.auth.username');
|
||||
$password = config('mqtt-client.connections.default.connection_settings.auth.password');
|
||||
|
||||
$connected = $this->checkBrokerReachable($host, $port, $tls, $username, $password);
|
||||
|
||||
return response()->json([
|
||||
'host' => $host,
|
||||
'port' => $port,
|
||||
'ws_url' => config('mqtt.ws_url'),
|
||||
'tls_enabled' => $tls,
|
||||
'connected' => $connected,
|
||||
]);
|
||||
}
|
||||
|
||||
private function checkBrokerReachable(?string $host, ?int $port, bool $tls, ?string $username, ?string $password): bool
|
||||
{
|
||||
if (! $host || ! $port) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
$client = new MqttClient($host, $port, 'health-check-'.uniqid());
|
||||
$settings = (new ConnectionSettings)
|
||||
->setConnectTimeout(2)
|
||||
->setUseTls($tls)
|
||||
->setUsername($username)
|
||||
->setPassword($password);
|
||||
$client->connect($settings, true);
|
||||
$client->disconnect();
|
||||
|
||||
return true;
|
||||
} catch (\Throwable) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,4 +17,18 @@ class RoleController extends Controller
|
||||
// seeding/tinker, never assignable through the Users module.
|
||||
return RoleResource::collection(Role::where('name', '!=', 'super-admin')->orderBy('name')->get());
|
||||
}
|
||||
|
||||
/**
|
||||
* Read-only view of every role (including Super Admin) and its
|
||||
* permission set — for the Roles page, distinct from index() which
|
||||
* feeds the assignable-role dropdown in the Users module.
|
||||
*/
|
||||
public function overview(Request $request)
|
||||
{
|
||||
abort_unless($request->user()->can('roles.view'), 403);
|
||||
|
||||
$roles = Role::with('permissions')->orderBy('name')->get();
|
||||
|
||||
return RoleResource::collection($roles);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user