added vpn feature to mno

This commit is contained in:
Kwesi Banson Jnr
2026-08-17 19:37:22 +00:00
parent a797d9587c
commit e4a6a40d16
7 changed files with 329 additions and 8 deletions

View File

@@ -34,13 +34,13 @@
<div class="col-xl-4 col-md-6">
<div class="content-card p-3 h-100 border-start border-4 border-0" style="border-left-color: #5c4df0 !important;">
<div class="text-secondary fw-bold mb-1" style="font-size: 0.75rem; letter-spacing: 0.5px;">ACTIVE CONNECTIONS</div>
<h3 class="fw-bold mb-0">{{ $totalActive }} <span class="fs-6 fw-normal text-success"><i class="bi bi-arrow-up"></i> Online</span></h3>
<h3 class="fw-bold mb-0">{{ $totalActive }} <span class="fs-6 fw-normal text-success"><i class="bi bi-arrow-up"></i> Active</span></h3>
</div>
</div>
<div class="col-xl-4 col-md-6">
<div class="content-card p-3 h-100 border-start border-4 border-0" style="border-left-color: #10b981 !important;">
<div class="text-secondary fw-bold mb-1" style="font-size: 0.75rem; letter-spacing: 0.5px;">VPN LINKS </div>
<h3 class="fw-bold mb-0">{{ $totalSipLinks }} Links</h3>
<div class="text-secondary fw-bold mb-1" style="font-size: 0.75rem; letter-spacing: 0.5px;">Countries </div>
<h3 class="fw-bold mb-0">{{ $uniqueCountries }} </h3>
</div>
</div>
<div class="col-xl-4 col-md-6">

View File

@@ -108,8 +108,8 @@
<input type="text" class="form-control" id="mnoTechSupport" name="technical_support_person" placeholder="Tech contact">
</div>
<div class="col-md-4">
<label class="form-label text-secondary fw-semibold" style="font-size: 0.85rem;">Support Skype</label>
<input type="text" class="form-control" id="mnoSupportSkype" name="support_skype" placeholder="Skype ID">
<label class="form-label text-secondary fw-semibold" style="font-size: 0.85rem;">Support MS Teams</label>
<input type="text" class="form-control" id="mnoSupportSkype" name="support_skype" placeholder="MS Teams ID">
</div>
<div class="col-md-4">
<label class="form-label text-secondary fw-semibold" style="font-size: 0.85rem;">Account Manager</label>

View File

@@ -155,7 +155,247 @@
@endif
</div>
</div>
</div>
</div> <!-- Close Support Contacts content-card -->
</div> <!-- Close Right Column (col-lg-4) -->
</div> <!-- Close the main row (row g-4) -->
<!-- Connections & Interconnect Details Section -->
<div class="content-card p-4 mb-4">
<div class="d-flex justify-content-between align-items-center mb-3">
<h5 class="fw-bold mb-0 text-secondary" style="font-size: 0.9rem; text-transform: uppercase; letter-spacing: 0.5px;">
<i class="bi bi-shield-lock me-2 text-primary"></i> Connection Endpoints (VPN / Whitelisted IPs & Domains)
</h5>
<button type="button" class="btn btn-sm text-white fw-bold" style="background-color: #5c4df0;" data-bs-toggle="modal" data-bs-target="#addConnectionModal">
<i class="bi bi-plus-lg me-1"></i> Add Connection Detail
</button>
</div>
<hr class="text-muted opacity-25">
<div class="table-responsive">
<table class="table table-custom align-middle mb-0">
<thead>
<tr>
<th>Mode</th>
<th>Identifier (IP / Domain / VPN)</th>
<th>Port</th>
<th>Status</th>
<th>Notes</th>
<th class="text-end">Actions</th>
</tr>
</thead>
<tbody>
@forelse($operator->connectionDetails as $conn)
<tr>
<td>
<span class="badge bg-secondary bg-opacity-10 text-dark border">{{ $conn->connection_mode }}</span>
@if($conn->vpn_file_path)
<a href="{{ asset('storage/' . $conn->vpn_file_path) }}" target="_blank" class="d-block mt-1" style="font-size: 0.75rem;">
<i class="bi bi-paperclip"></i> View Form
</a>
@endif
</td>
<td><code>{{ $conn->identifier }}</code></td>
<td>{{ $conn->port ?? 'N/A' }}</td>
<td>
<span class="badge {{ $conn->status == 'Active' ? 'bg-success' : 'bg-warning' }} bg-opacity-10 text-success px-2 py-1">
{{ $conn->status }}
</span>
</td>
<td>{{ $conn->notes ?? '-' }}</td>
<td class="text-end">
<form action="{{ route('mnos.connections.destroy', $conn->id) }}" method="POST" class="d-inline">
@csrf
@method('DELETE')
<button type="submit" class="btn btn-sm btn-light text-danger" onclick="return confirm('Delete this connection detail?')">
<i class="bi bi-trash"></i>
</button>
</form>
</td>
</tr>
@empty
<tr>
<td colspan="6" class="text-center text-secondary py-4">No specific VPN or IP whitelisting rules added yet.</td>
</tr>
@endforelse
</tbody>
</table>
</div>
</div>
@endsection
</div>
</div>
</div>
@endsection
@push('modals')
<!-- Add Connection Modal -->
<!-- Add Connection Modal -->
<div class="modal fade" id="addConnectionModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header bg-light">
<h5 class="modal-title fw-bold">Add Interconnect Detail</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<!-- Added ID to the form -->
<form id="addConnectionForm" action="{{ route('mnos.connections.store', $operator->id) }}" method="POST" enctype="multipart/form-data">
@csrf
<div class="modal-body p-4">
<!-- Alert container for AJAX errors -->
<div id="connectionModalAlert"></div>
<div class="mb-3">
<label class="form-label text-secondary fw-semibold small">Connection Mode *</label>
<select name="connection_mode" id="connectionModeSelect" class="form-select" required>
<option value="Whitelisted IP">Whitelisted IP Address</option>
<option value="Domain">Domain / Hostname</option>
<option value="VPN Tunnel">VPN Tunnel Endpoint</option>
</select>
</div>
<div class="mb-3" id="vpnFormContainer" style="display: none;">
<label class="form-label text-secondary fw-semibold small">Upload VPN Form (PDF, DOCX)</label>
<input type="file" name="vpn_file" id="vpnFileInput" class="form-control" accept=".pdf,.doc,.docx,.xlsx,.jpg,.png">
</div>
<div class="mb-3">
<label class="form-label text-secondary fw-semibold small">Identifier (IP / Domain / VPN Peer) *</label>
<input type="text" name="identifier" class="form-control" required placeholder="e.g. 196.200.15.22 or smpp.operator.com">
</div>
<div class="row g-3 mb-3">
<div class="col-md-6">
<label class="form-label text-secondary fw-semibold small">Port</label>
<!-- Added type="number", min, and max -->
<input type="number" name="port" class="form-control" placeholder="e.g. 2775" min="1" max="65535">
</div>
<div class="col-md-6">
<label class="form-label text-secondary fw-semibold small">Status</label>
<select name="status" class="form-select">
<option value="Active">Active</option>
<option value="Testing">Testing</option>
<option value="Disabled">Disabled</option>
</select>
</div>
</div>
<div class="mb-3">
<label class="form-label text-secondary fw-semibold small">Notes / Description</label>
<textarea name="notes" class="form-control" rows="2" placeholder="Optional context or routing details..."></textarea>
</div>
</div>
<div class="modal-footer bg-light">
<button type="button" class="btn btn-outline-secondary btn-sm" data-bs-dismiss="modal">Cancel</button>
<button type="submit" id="btnSubmitConnection" class="btn text-white btn-sm fw-bold px-4" style="background-color: #5c4df0;">Save Endpoint</button>
</div>
</form>
</div>
</div>
</div>
@endpush
@push('scripts')
<script>
$(document).ready(function() {
console.log('foo bar')
$('#connectionModeSelect').on('change', function() {
if ($(this).val() === 'VPN Tunnel') {
$('#vpnFormContainer').slideDown();
} else {
$('#vpnFormContainer').slideUp();
$('#vpnFileInput').val(''); // Clear the file input if they switch away
}
});
});
$(document).ready(function() {
// Toggle VPN File input visibility
$('#connectionModeSelect').on('change', function() {
if ($(this).val() === 'VPN Tunnel') {
$('#vpnFormContainer').slideDown();
} else {
$('#vpnFormContainer').slideUp();
$('#vpnFileInput').val('');
}
});
// Handle AJAX Form Submission
$('#addConnectionForm').on('submit', function(e) {
e.preventDefault();
let $form = $(this);
let $submitBtn = $('#btnSubmitConnection');
let originalText = $submitBtn.html();
let $alertBox = $('#connectionModalAlert');
// Use FormData to handle the file upload payload
let formData = new FormData(this);
$submitBtn.html('<span class="spinner-border spinner-border-sm me-2"></span>Saving...').prop('disabled', true);
$alertBox.html('');
$form.find('.is-invalid').removeClass('is-invalid');
$form.find('.invalid-feedback').remove();
$.ajax({
url: $form.attr('action'),
type: 'POST',
data: formData,
processData: false, // Prevent jQuery from converting the data into a query string
contentType: false, // Prevent jQuery from overriding the multipart header
success: function(response) {
if (response.success) {
$('#addConnectionModal').modal('hide');
Swal.fire({
icon: 'success',
title: 'Success!',
text: response.message,
confirmButtonColor: '#5c4df0'
}).then(() => {
// Reload the page to reflect the new connection in the table
location.reload();
});
}
},
error: function(xhr) {
if (xhr.status === 422) {
let errors = xhr.responseJSON.errors;
$alertBox.html(`
<div class="alert alert-danger d-flex align-items-center py-2 px-3 small" role="alert">
<i class="bi bi-exclamation-triangle-fill me-2"></i>
<div>Please check the highlighted fields below.</div>
</div>
`);
// Highlight specific fields
$.each(errors, function(key, value) {
let $input = $form.find('[name="' + key + '"]');
if ($input.length) {
$input.addClass('is-invalid');
$input.parent().append('<div class="invalid-feedback d-block fw-medium small">' + value[0] + '</div>');
}
});
} else {
$alertBox.html(`
<div class="alert alert-danger d-flex align-items-center py-2 px-3 small" role="alert">
<i class="bi bi-x-circle-fill me-2"></i>
<div>An error occurred. Please try again.</div>
</div>
`);
}
},
complete: function() {
$submitBtn.html(originalText).prop('disabled', false);
}
});
});
// Reset the form when the modal is closed
$('#addConnectionModal').on('hidden.bs.modal', function () {
$('#addConnectionForm')[0].reset();
$('#vpnFormContainer').hide();
$('#connectionModalAlert').html('');
$('#addConnectionForm').find('.is-invalid').removeClass('is-invalid');
$('#addConnectionForm').find('.invalid-feedback').remove();
});
});
</script>
@endpush