From e4a6a40d161a5c4a6ff0fd886b26b48d982cc982 Mon Sep 17 00:00:00 2001 From: Kwesi Banson Jnr Date: Mon, 17 Aug 2026 19:37:22 +0000 Subject: [PATCH] added vpn feature to mno --- .../NetworkOperatorsController.php | 60 ++++- app/Models/NetworkOperator.php | 5 + app/Models/NetworkOperatorConnection.php | 16 ++ .../views/network_operators/index.blade.php | 6 +- .../partials/mno_modals.blade.php | 4 +- .../views/network_operators/show.blade.php | 244 +++++++++++++++++- routes/web.php | 2 + 7 files changed, 329 insertions(+), 8 deletions(-) create mode 100644 app/Models/NetworkOperatorConnection.php diff --git a/app/Http/Controllers/NetworkOperatorsController.php b/app/Http/Controllers/NetworkOperatorsController.php index 7d6ee85..1207044 100644 --- a/app/Http/Controllers/NetworkOperatorsController.php +++ b/app/Http/Controllers/NetworkOperatorsController.php @@ -4,6 +4,7 @@ namespace App\Http\Controllers; use Illuminate\Http\Request; use App\Models\NetworkOperator; +use App\Models\NetworkOperatorConnection; class NetworkOperatorsController extends Controller { @@ -34,9 +35,10 @@ class NetworkOperatorsController extends Controller $totalActive = NetworkOperator::where('connection_status', 'Active')->count(); $totalSipLinks = NetworkOperator::where('connection_type', 'like', '%VPN%')->count(); + $uniqueCountries = $uniqueCountriesCount = NetworkOperator::distinct('country')->count('country'); $totalOperators = NetworkOperator::count(); - return view('network_operators.index', compact('operators', 'totalActive', 'totalSipLinks', 'totalOperators')); + return view('network_operators.index', compact('operators', 'totalActive', 'uniqueCountries', 'totalOperators')); } public function store(Request $request) @@ -111,5 +113,61 @@ class NetworkOperatorsController extends Controller return view('network_operators.show', compact('operator')); } + public function storeConnection(Request $request, $operatorId) + { + $request->validate([ + 'connection_mode' => 'required|string', + 'identifier' => [ + 'required', + 'string', + function ($attribute, $value, $fail) { + // 1. Check if valid IP Address (IPv4 or IPv6) + $isIp = filter_var($value, FILTER_VALIDATE_IP); + + // 2. Check if valid Domain Name (e.g., smpp.operator.com) + $isDomain = preg_match('/^([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,}$/i', $value); + + // 3. Check if valid CIDR Subnet (e.g., 196.200.15.0/24) + $isSubnet = preg_match('/^([0-9]{1,3}\.){3}[0-9]{1,3}\/([0-9]|[1-2][0-9]|3[0-2])$/', $value); + + if (!$isIp && !$isDomain && !$isSubnet) { + $fail('The identifier must be a valid IP address, CIDR subnet, or Domain Name.'); + } + }, + ], + 'port' => 'nullable|integer|min:1|max:65535', + 'vpn_file' => 'nullable|file|mimes:pdf,doc,docx|max:5120', + ]); + + $filePath = null; + if ($request->hasFile('vpn_file')) { + $filePath = $request->file('vpn_file')->store('vpn_forms', 'public'); + } + + $connection = NetworkOperatorConnection::create([ + 'network_operator_id' => $operatorId, + 'connection_mode' => $request->connection_mode, + 'identifier' => $request->identifier, + 'port' => $request->port, + 'status' => $request->status ?? 'Active', + 'notes' => $request->notes, + 'vpn_file_path' => $filePath, + ]); + + return response()->json([ + 'success' => true, + 'message' => 'Connection detail added successfully!', + 'connection' => $connection + ]); + } + + public function destroyConnection($id) + { + $connection = NetworkOperatorConnection::findOrFail($id); + $connection->delete(); + + return redirect()->back()->with('success', 'Connection detail removed.'); + } + } \ No newline at end of file diff --git a/app/Models/NetworkOperator.php b/app/Models/NetworkOperator.php index 71901e8..937f9df 100755 --- a/app/Models/NetworkOperator.php +++ b/app/Models/NetworkOperator.php @@ -19,6 +19,10 @@ class NetworkOperator extends Model public function account_manager_info(){ return $this->hasOne('App\Models\StaffMember', 'id', 'account_manager_id'); } + public function connectionDetails() + { + return $this->hasMany(NetworkOperatorConnection::class, 'network_operator_id'); + } /* public function setBuyingRateAttribute($value) { @@ -32,4 +36,5 @@ class NetworkOperator extends Model set: fn (string $value) => filter_var($value, FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION) ); } + } diff --git a/app/Models/NetworkOperatorConnection.php b/app/Models/NetworkOperatorConnection.php new file mode 100644 index 0000000..d0b38a0 --- /dev/null +++ b/app/Models/NetworkOperatorConnection.php @@ -0,0 +1,16 @@ +belongsTo(NetworkOperator::class, 'network_operator_id'); + } +} \ No newline at end of file diff --git a/resources/views/network_operators/index.blade.php b/resources/views/network_operators/index.blade.php index a2d892a..a82be2e 100644 --- a/resources/views/network_operators/index.blade.php +++ b/resources/views/network_operators/index.blade.php @@ -34,13 +34,13 @@
ACTIVE CONNECTIONS
-

{{ $totalActive }} Online

+

{{ $totalActive }} Active

-
VPN LINKS
-

{{ $totalSipLinks }} Links

+
Countries
+

{{ $uniqueCountries }}

diff --git a/resources/views/network_operators/partials/mno_modals.blade.php b/resources/views/network_operators/partials/mno_modals.blade.php index 63f7800..4cad304 100644 --- a/resources/views/network_operators/partials/mno_modals.blade.php +++ b/resources/views/network_operators/partials/mno_modals.blade.php @@ -108,8 +108,8 @@
- - + +
diff --git a/resources/views/network_operators/show.blade.php b/resources/views/network_operators/show.blade.php index 3772099..14d91c0 100644 --- a/resources/views/network_operators/show.blade.php +++ b/resources/views/network_operators/show.blade.php @@ -155,7 +155,247 @@ @endif
- + + + + +
+
+
+ Connection Endpoints (VPN / Whitelisted IPs & Domains) +
+ +
+
+ +
+ + + + + + + + + + + + + @forelse($operator->connectionDetails as $conn) + + + + + + + + + @empty + + + + @endforelse + +
ModeIdentifier (IP / Domain / VPN)PortStatusNotesActions
+ {{ $conn->connection_mode }} + @if($conn->vpn_file_path) + + View Form + + @endif + {{ $conn->identifier }}{{ $conn->port ?? 'N/A' }} + + {{ $conn->status }} + + {{ $conn->notes ?? '-' }} +
+ @csrf + @method('DELETE') + +
+
No specific VPN or IP whitelisting rules added yet.
-@endsection \ No newline at end of file + + + + +@endsection +@push('modals') + + + + +@endpush +@push('scripts') + +@endpush \ No newline at end of file diff --git a/routes/web.php b/routes/web.php index 4199c77..8756b88 100644 --- a/routes/web.php +++ b/routes/web.php @@ -27,6 +27,8 @@ Route::middleware(['auth'])->group(function () { Route::resource('clients', App\Http\Controllers\ClientsController::class); #MNOs mnos.store + Route::post('/mnos/{id}/connections', [App\Http\Controllers\NetworkOperatorsController::class, 'storeConnection'])->name('mnos.connections.store'); + Route::delete('/mnos/connections/{id}', [App\Http\Controllers\NetworkOperatorsController::class, 'destroyConnection'])->name('mnos.connections.destroy'); Route::get('/mnos/{id}', [App\Http\Controllers\NetworkOperatorsController::class, 'show'])->name('mnos.show'); Route::get('/mnos', [App\Http\Controllers\NetworkOperatorsController::class, 'index'])->name('mnos.index'); Route::post('/mnos', [App\Http\Controllers\NetworkOperatorsController::class, 'store'])->name('mnos.store');