You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

2161 lines
106 KiB

{% extends "base.html" %}
{% block title %}
ONT Details
{% endblock %}
{% block styles %}
<style>
/* Simplified modal fixes that work with base.html */
.modal {
z-index: 1055 !important;
}
/* QR Code modals need higher z-index */
#wifiQrModal, #appQrModal, #nokiaCredentialsModal {
z-index: 1070 !important;
}
#wifiQrModal .modal-dialog,
#appQrModal .modal-dialog,
#nokiaCredentialsModal .modal-dialog {
z-index: 1071 !important;
}
#wifiQrModal .modal-content,
#appQrModal .modal-content,
#nokiaCredentialsModal .modal-content {
z-index: 1072 !important;
}
/* Ensure QR modal backdrops are layered correctly */
.modal-backdrop.show {
z-index: 1050 !important;
}
/* When QR modal is open, its backdrop should be higher */
#wifiQrModal ~ .modal-backdrop,
#appQrModal ~ .modal-backdrop,
#nokiaCredentialsModal ~ .modal-backdrop {
z-index: 1065 !important;
}
/* Fix for modal interaction */
.modal-dialog {
position: relative;
z-index: inherit;
}
.modal-content {
position: relative;
z-index: inherit;
}
/* Ensure modals can be interacted with */
.modal.show {
display: block !important;
}
.modal.show .modal-dialog {
pointer-events: all !important;
}
.modal.show .modal-content {
pointer-events: all !important;
}
/* Fix QR code centering in the credentials modal */
.qr-code-container {
display: flex;
align-items: center;
justify-content: center;
min-height: 120px;
padding: 10px;
}
.qr-code-container img {
max-width: 100px;
max-height: 100px;
width: auto;
height: auto;
}
/* Ensure the QR code section has proper text alignment */
#wifiQrContainer,
#appQrContainer {
text-align: center;
}
/* Center the "no QR code available" message */
.qr-code-container p {
margin: 0;
display: flex;
align-items: center;
justify-content: center;
min-height: 100px;
}
</style>
<script>
function form_submit() {
document.getElementById("ontswapform").submit();
}
function form_tempservice_submit() {
document.getElementById("tempserviceform").submit();
}
function form_addBeacon_submit() {
document.getElementById("addBeaconform").submit();
}
// Copy to clipboard function for credentials
// Simplified modal management that works with Bootstrap 5.3.2
document.addEventListener('DOMContentLoaded', function() {
console.log('Modal: DOM loaded, initializing modal functionality');
// Copy to clipboard function for credentials - WORKS IN NON-SECURE CONTEXTS
window.copyToClipboard = function(elementId, buttonElement) {
console.log('Copy function called for element:', elementId);
console.log('Secure context:', window.isSecureContext);
console.log('Clipboard API available:', !!navigator.clipboard);
const element = document.getElementById(elementId);
if (!element) {
console.error('Copy function: Element not found -', elementId);
return;
}
const text = element.textContent.trim();
console.log('Copying text:', text);
// Get the button that was clicked
const button = buttonElement || event.target.closest('button');
if (!button) {
console.error('Copy function: Button not found');
return;
}
// Check if we're in a secure context AND clipboard API is available
if (navigator.clipboard && window.isSecureContext) {
console.log('Using Clipboard API (secure context)');
navigator.clipboard.writeText(text)
.then(() => {
console.log('Copy successful via Clipboard API');
showCopySuccess(button);
})
.catch(err => {
console.error('Clipboard API failed:', err);
fallbackCopy(text, button);
});
} else {
console.log('Using fallback copy method (non-secure context or no Clipboard API)');
fallbackCopy(text, button);
}
};
// Fallback copy method
function fallbackCopy(text, button) {
const textarea = document.createElement('textarea');
textarea.value = text;
textarea.style.position = 'fixed';
textarea.style.left = '-999999px';
textarea.style.top = '-999999px';
document.body.appendChild(textarea);
try {
textarea.focus();
textarea.select();
textarea.setSelectionRange(0, 99999); // For mobile devices
const success = document.execCommand('copy');
console.log('Fallback copy result:', success);
if (success) {
showCopySuccess(button);
} else {
showCopyError(button);
}
} catch (err) {
console.error('Fallback copy failed:', err);
showCopyError(button);
} finally {
document.body.removeChild(textarea);
}
}
// Show success feedback
function showCopySuccess(button) {
const originalContent = button.innerHTML;
const originalClasses = button.className;
button.innerHTML = '<i class="bi bi-check"></i>';
button.classList.remove('btn-outline-secondary');
button.classList.add('btn-success');
setTimeout(() => {
button.innerHTML = originalContent;
button.className = originalClasses;
}, 1500);
}
// Show error feedback
function showCopyError(button) {
const originalContent = button.innerHTML;
const originalClasses = button.className;
button.innerHTML = '<i class="bi bi-x"></i>';
button.classList.remove('btn-outline-secondary');
button.classList.add('btn-danger');
setTimeout(() => {
button.innerHTML = originalContent;
button.className = originalClasses;
}, 1500);
}
// Handle QR Modal interactions
// Handle Nokia Credentials Modal
const nokiaCredentialsModal = document.getElementById('nokiaCredentialsModal');
if (nokiaCredentialsModal) {
console.log('Modal: Nokia Credentials Modal found, setting up event listeners');
nokiaCredentialsModal.addEventListener('show.bs.modal', function (event) {
console.log('Modal: Nokia Credentials Modal show event triggered');
const button = event.relatedTarget;
if (!button) {
console.error('Modal: No button found in event.relatedTarget');
return;
}
// Extract data from button attributes
const username = button.getAttribute('data-username');
const password = button.getAttribute('data-password');
const superusername = button.getAttribute('data-superusername');
const superpassword = button.getAttribute('data-superpassword');
const ssid = button.getAttribute('data-ssid');
const wifikey = button.getAttribute('data-wifikey');
const wifiQrData = button.getAttribute('data-wifiQR');
const appQrData = button.getAttribute('data-appQR');
console.log('Modal: Credential data loaded:', { username, ssid });
console.log('Modal: WiFi QR Data:', wifiQrData);
console.log('Modal: App QR Data:', appQrData);
// Update modal content
if (document.getElementById('adminUsername')) {
document.getElementById('adminUsername').textContent = username || 'admin';
}
if (document.getElementById('adminPassword')) {
document.getElementById('adminPassword').textContent = password || 'password';
}
if (document.getElementById('superUsername')) {
document.getElementById('superUsername').textContent = superusername || 'superadmin';
}
if (document.getElementById('superPassword')) {
document.getElementById('superPassword').textContent = superpassword || 'password';
}
if (document.getElementById('wifiSSID')) {
document.getElementById('wifiSSID').textContent = ssid || 'ssid';
}
if (document.getElementById('wifiPassword')) {
document.getElementById('wifiPassword').textContent = wifikey || 'password';
}
// Handle QR codes
const wifiQrPreview = document.getElementById('wifiQrPreview');
const wifiQrFull = document.getElementById('wifiQrFull');
const appQrPreview = document.getElementById('appQrPreview');
const appQrFull = document.getElementById('appQrFull');
const wifiQrContainer = document.getElementById('wifiQrContainer');
const appQrContainer = document.getElementById('appQrContainer');
// Handle WiFi QR Code
if (wifiQrData && wifiQrData !== 'None' && wifiQrData !== '' && wifiQrData !== 'null') {
const wifiSrc = 'data:image/png;base64,' + wifiQrData;
if (wifiQrPreview) {
wifiQrPreview.src = wifiSrc;
wifiQrPreview.style.display = 'block';
}
if (wifiQrFull) {
wifiQrFull.src = wifiSrc;
}
if (wifiQrContainer) {
wifiQrContainer.style.pointerEvents = 'auto';
wifiQrContainer.style.cursor = 'pointer';
}
console.log('Modal: WiFi QR code loaded successfully');
} else {
if (wifiQrPreview) {
wifiQrPreview.style.display = 'none';
}
if (wifiQrContainer) {
wifiQrContainer.innerHTML = '<p class="text-muted small mb-0">No WiFi QR code available</p>';
wifiQrContainer.style.pointerEvents = 'none';
wifiQrContainer.style.cursor = 'default';
}
console.log('Modal: No WiFi QR code data available');
}
// Handle App QR Code
if (appQrData && appQrData !== 'None' && appQrData !== '' && appQrData !== 'null') {
const appSrc = 'data:image/png;base64,' + appQrData;
if (appQrPreview) {
appQrPreview.src = appSrc;
appQrPreview.style.display = 'block';
}
if (appQrFull) {
appQrFull.src = appSrc;
}
if (appQrContainer) {
appQrContainer.style.pointerEvents = 'auto';
appQrContainer.style.cursor = 'pointer';
}
console.log('Modal: App QR code loaded successfully');
} else {
if (appQrPreview) {
appQrPreview.style.display = 'none';
}
if (appQrContainer) {
appQrContainer.innerHTML = '<p class="text-muted small mb-0">No App QR code available</p>';
appQrContainer.style.pointerEvents = 'none';
appQrContainer.style.cursor = 'default';
}
console.log('Modal: No App QR code data available');
}
});
nokiaCredentialsModal.addEventListener('shown.bs.modal', function() {
console.log('Modal: Nokia Credentials Modal is now visible and interactive');
});
nokiaCredentialsModal.addEventListener('hidden.bs.modal', function() {
console.log('Modal: Nokia Credentials Modal is now hidden');
});
} else {
console.error('Modal: Nokia Credentials Modal element not found!');
}
// Handle QR Modal interactions
const qrModals = ['wifiQrModal', 'appQrModal'];
});
</script>
{% endblock %}
{% block content %}
{% set rebooting = false %}
<div class="container py-4">
<div class="row">
<div class="col-12">
<!-- Page Header -->
<div class="card mb-4 shadow-sm">
<div class="card-header bg-secondary-subtle border-secondary-subtle text-dark">
<div class="row align-items-center">
<div class="col">
<h3 class="mb-0">{{ locID.LocationIdentifier }}</h3>
<strong class="text-end">Unit:</strong>
<span class="badge bg-light text-dark border fs-6">{{ locID.UnitIdentifier }}</span>
</div>
<div class="col-auto text-end">
<a href="{{ url_for('location.location_parent_detail', parentID=parent_loc.id) }}"
class="text-decoration-none text-dark">
<h5 class="mb-0 opacity-75">{{ parent_loc.Alias }}</h5>
<small class="text-end d-block">
{{ locID.StreetAddress }}<br>
{{ locID.Suburb }} {{ locID.State }} {{ locID.Postcode }}
</small>
</a>
</div>
</div>
</div>
</div>
<!-- Flash Messages -->
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
{% for category, message in messages %}
{% if category == "ontReboot" %}
{% set rebooting = true %}
<div class="alert alert-success alert-dismissible fade show" role="alert">
{{ message }}
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
{% elif category == "ontSwap" %}
<div class="alert alert-success alert-dismissible fade show" role="alert">
{{ message }}
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
{% elif category == "success" %}
<div class="alert alert-success alert-dismissible fade show" role="alert">
{{ message }}
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
{% elif category == "BeaconAdd" %}
<div class="alert alert-success alert-dismissible fade show" role="alert">
{{ message }}
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
{% else %}
<div class="alert alert-danger alert-dismissible fade show" role="alert">
{{ message }}
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
{% endif %}
{% endfor %}
{% endif %}
{% endwith %}
<div class="row">
<!-- Left Column - ONT Details -->
<div class="col-lg-8">
<div class="card mb-4 shadow-sm">
<div class="card-header bg-light">
<h5 class="mb-0">
<i class="bi bi-router me-2"></i>ONT Information
</h5>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-6 mb-2">
<strong>ONT Name:</strong><br>
{% if adtran_details['Service']['downlink-endpoint'] is defined %}
<span class="text-muted">{{ adtran_details['Service']['downlink-endpoint']['interface-endpoint']['device-name'] }}</span>
{% else %}
<span class="text-muted">none</span>
{% endif %}
</div>
<div class="col-md-6 mb-2">
<strong>ONT Model:</strong><br>
<span class="text-muted">{{ adtran_details['ONTModel'] }}</span>
</div>
<div class="col-6 mb-2">
<strong>Serial Number:</strong><br>
{% if adtran_details['SerialNumber'] == adtran_details['SerialNumber_db'] %}
<span class="text-muted font-monospace">{{ adtran_details['SerialNumber'] }}</span>
{% else %}
<div class="alert alert-danger py-2 mb-0">
<strong>Mismatch Detected!</strong><br>
OLT: <span class="font-monospace">{{ adtran_details['SerialNumber'] }}</span><br>
Hades: <span class="font-monospace">{{ adtran_details['SerialNumber_db'] }}</span>
</div>
{% endif %}
</div>
<div class="col-md-6 mb-2">
<strong>MAC Address:</strong><br>
<span class="text-muted font-monospace">{{ adtran_details['MACAddress_db'] }}</span>
</div>
<div class="col-md-6 mb-2">
<strong>OLT:</strong><br>
<span class="text-muted font-monospace">{{ adtran_details['OLT'] }}</span>
</div>
<div class="col-md-6 mb-2">
<strong>OLT PON Interface:</strong><br>
<span class="text-muted font-monospace">{{ adtran_details['OLT_PON_Interface'] }}</span>
</div>
</div>
</div>
</div>
<!-- Ethernet Port Status -->
<div class="card mb-4 shadow-sm">
<div class="card-header bg-light">
<h5 class="mb-0">
<i class="bi bi-ethernet me-2"></i>Ethernet Port Status
</h5>
</div>
<div class="card-body p-0">
{% if adtran_details['intfStatus'] and adtran_details['intfStatus']|length > 0 %}
<div class="table-responsive">
<table class="table table-hover mb-0">
<thead class="table-light">
<tr>
<th scope="col">Port</th>
<th scope="col">Status</th>
<th scope="col">Speed</th>
<th scope="col">Duplex</th>
<th scope="col">Auto-Negotiated</th>
<th scope="col">Admin Status</th>
</tr>
</thead>
<tbody>
{% for port_key, port_data in adtran_details['intfStatus']|dictsort %}
{% if port_data['port-number'] != "0" %}
<tr class="align-middle">
<td>
<span class="badge bg-secondary px-3 py-2">Port {{ port_data['port-number'] }}</span>
</td>
<td>
{% if port_data['status'] == "up" %}
<span class="badge bg-success px-3 py-2">UP</span>
{% else %}
<span class="badge bg-danger px-3 py-2">DOWN</span>
{% endif %}
</td>
<td>
{% if port_data['actual-speed'] %}
{% set speed_mbps = (port_data['actual-speed'] * 1000)|int %}
{% if speed_mbps >= 1000 %}
<span class="text-dark">{{ (speed_mbps / 1000)|int }}Gbps</span>
{% else %}
<span class="text-dark">{{ speed_mbps }}Mbps</span>
{% endif %}
{% else %}
<span class="text-muted">-</span>
{% endif %}
</td>
<td>
{% if port_data['actual-duplex'] == "full" %}
<span class="badge bg-success px-2 py-1">Full</span>
{% elif port_data['actual-duplex'] == "half" %}
<span class="badge bg-warning text-dark px-2 py-1">Half</span>
{% else %}
<span class="text-muted">-</span>
{% endif %}
</td>
<td>
{% if port_data['auto-negotiated'] %}
<span class="badge bg-info px-2 py-1">Yes</span>
{% else %}
<span class="badge bg-secondary px-2 py-1">No</span>
{% endif %}
</td>
<td>
{% if port_data['admin-status'] == "up" %}
<span class="badge bg-success px-2 py-1">Enabled</span>
{% else %}
<span class="badge bg-danger px-2 py-1">Disabled</span>
{% endif %}
</td>
</tr>
{% endif %}
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<div class="text-center py-5">
<i class="bi bi-ethernet text-muted" style="font-size: 3rem;"></i>
<h5 class="text-muted mt-3">No Port Information</h5>
<p class="text-muted">No ethernet port data available for this ONT.</p>
</div>
{% endif %}
</div>
</div>
<!-- Services Section -->
<div class="card mb-4 shadow-sm">
<div class="card-header bg-light d-flex justify-content-between align-items-center">
<h5 class="mb-0">
<i class="bi bi-globe2 me-2"></i>Services
</h5>
<a href="#newTempServiceModal" class="btn btn-sm btn-primary" data-bs-toggle="modal">
New Temp Service
</a>
</div>
<div class="card-body p-0">
{% if adtran_details['srvList']|length > 0 %}
<div class="table-responsive">
<table class="table table-hover mb-0">
<thead class="table-light">
<tr>
<th>Service ID</th>
<th>Profile</th>
<th>VLANs</th>
<th>Customer</th>
<th>Status</th>
</tr>
</thead>
<tbody>
{% for data in adtran_details['srvList'] %}
<tr class="align-middle">
{% if data.hades_service != "none" %}
<td>
{% if data['customer_id'] == "none" %}
<a href="{{ url_for('services.service_id', service_ID=data.hades_service.id) }}"
class="text-decoration-none">
{{ data['service-id'] }}
</a>
{% else %}
<a href="{{ url_for('services.service_id_splynx', service_ID=data.hades_service.id) }}"
class="text-decoration-none">
{{ data['service-id'] }}
</a>
{% endif %}
</td>
<td>{{ data['profile'] }}</td>
<td><span class="badge bg-secondary">{{ data['uplink-vlans'] }}</span></td>
{% if data['customer_id'] == "none" %}
<td><div class="text-secondary text-opacity-50">No Splynx Service</div></td>
{% else %}
<td>
<a href="https://billing.interphone.com.au/admin/customers/view?id={{ data['customer_id'] }}"
target="_blank" rel="noopener noreferrer" class="text-decoration-none">
{{ data['customer_name'] }}
</a>
</td>
{% endif %}
<td>
{% if data['status'] == "Online" %}
<span class="badge bg-success">Online</span>
{% elif data['status'] == "stopped" %}
<span class="badge bg-warning text-dark">Paused</span>
{% elif data['status'] == "active" %}
<span class="badge bg-danger">Offline</span>
{% elif data['status'] == "no service found" %}
<span class=""></span>
{% else %}
<span class="badge bg-secondary">{{ data['status'] }}</span>
{% endif %}
</td>
{% else %}
<td colspan="5"><em class="text-muted">No services configured</em></td>
{% endif %}
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<div class="text-center py-4">
<p class="text-muted mb-0">No services configured</p>
</div>
{% endif %}
</div>
</div>
<!-- Nokia Beacon -->
<div class="card mb-4 shadow-sm">
<div class="card-header bg-light d-flex justify-content-between align-items-center">
<h5 class="mb-0">
<i class="bi bi-wifi me-2"></i>Nokia Beacon
</h5>
<a href="#addBeaconModal" class="btn btn-sm btn-primary" data-bs-toggle="modal">
Add Beacon
</a>
</div>
<div class="card-body p-0">
{% if nokias|length > 0 %}
<div class="table-responsive">
<table class="table table-hover mb-0">
<thead class="table-light">
<tr>
<th>Model</th>
<th>Serial</th>
<th>MAC</th>
<th>Corteca</th>
<th>Status</th>
<th>Admin</th>
</tr>
</thead>
<tbody>
{% for data in nokias %}
<tr class="align-middle">
{% if data %}
<td>
{{ data['Model'] }}
</td>
<td>{{ data['Serial'] }}</td>
{% set nokia_mac = data['MAC'][:2] ~ '-' ~ data['MAC'][2:4] ~ '-' ~ data['MAC'][4:6] ~ '-' ~ data['MAC'][6:8] ~ '-' ~ data['MAC'][8:10] ~ '-' ~ data['MAC'][10:12] %}
<td>{{ nokia_mac }}</td>
<td>
<a href="https://homecontroller.apeu.wifi.nokia.com/interphone/home-troubleshooting/dashboard?mac={{ nokia_mac }}"
target="_blank" rel="noopener noreferrer" class="text-decoration-none">
View
</a>
</td>
<td>
<!-- <span class="badge bg-secondary">Not Yet Implemented</span> -->
{% if nokia_status[data['Serial']] %}
<span class="badge bg-success">Online</span>
{% else %}
<span class="badge bg-secondary">Offline</span>
{% endif %}
</td>
<td>
<button type="button" class="btn btn-sm btn-outline-primary" style="--bs-btn-padding-y: .25rem; --bs-btn-padding-x: .5rem; --bs-btn-font-size: .75rem;"
data-bs-toggle="modal"
data-bs-target="#nokiaCredentialsModal"
data-username="{{ data['Username'] }}"
data-password="{{ data['Password'] }}"
data-superusername="{{ data['SuperUsername'] }}"
data-superpassword="{{ data['SuperPassword'] }}"
data-ssid="{{ data['SSID'] }}"
data-wifikey="{{ data['WiFiKey'] }}"
data-wifiQR="{{ beacon_qr[data['id']]['wifiQR'] }}"
data-appQR="{{ beacon_qr[data['id']]['nokiaQR'] }}">
<i class="bi bi-key"></i> Credentials
</button>
</td>
{% else %}
<td colspan="6"><em class="text-muted">No Beacons assigned</em></td>
{% endif %}
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<div class="text-center py-4">
<p class="text-muted mb-0">No Beacons assigned</p>
</div>
{% endif %}
</div>
</div>
<!-- Events Section -->
<div class="card shadow-sm">
<div class="card-header bg-light">
<h5 class="mb-0">
<i class="bi bi-clock-history me-2"></i>Recent Events
</h5>
</div>
<div class="card-body p-0">
{% if adtran_details['eventList']|length > 0 %}
<div class="table-responsive">
<table class="table table-hover mb-0">
<thead class="table-light">
<tr>
<th>Timestamp</th>
<th>Event</th>
</tr>
</thead>
<tbody>
{% for data in adtran_details['eventList'] %}
<tr class="align-middle">
<td class="text-nowrap">{{ data['timestamp'] }}</td>
<td>{{ data['event'] }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<div class="text-center py-4">
<p class="text-muted mb-0">No recent events</p>
</div>
{% endif %}
</div>
</div>
</div>
<!-- Right Column - Status & Actions -->
<div class="col-lg-4">
<!-- Pending Job Alert -->
{% if pending_swap_job %}
<div class="card mb-4 shadow-sm border-warning">
<div class="card-header bg-warning text-dark">
<h5 class="mb-0">
<i class="bi bi-exclamation-triangle me-2"></i>Pending ONT Swap Job
</h5>
</div>
<div class="card-body">
<p class="mb-2">An ONT swap job is currently in progress for this location.</p>
<div class="d-flex justify-content-between align-items-center">
<small class="text-muted">
Started: {{ pending_swap_job.Created.strftime('%Y-%m-%d %H:%M:%S') }}
</small>
<a href="{{ url_for('location.ont_swap_progress', locid=locID['id'], job_id=pending_swap_job.id) }}"
class="btn btn-sm btn-warning">
<i class="bi bi-eye me-2"></i>View Progress
</a>
</div>
</div>
</div>
{% endif %}
<!-- Status Overview -->
<div class="card mb-4 shadow-sm">
<div class="card-header bg-light">
<h5 class="mb-0">
<i class="bi bi-activity me-2"></i>Status Overview
</h5>
</div>
<div class="card-body">
<!-- Service Class -->
<div class="d-flex align-items-center mb-2">
<strong class="flex-shrink-0" style="width: 140px;">Service Class:</strong>
<div>
{% if adtran_details['ServiceClass'][0] %}
<span class="badge bg-success px-3 py-2 me-1">{{ locID.ServiceClass }}</span>
<span class="badge bg-success px-3 py-2">{{ adtran_details['ServiceClass'][1] }}</span>
{% else %}
<span class="badge bg-danger px-3 py-2 me-1">{{ locID.ServiceClass }}</span>
<span class="badge bg-danger px-3 py-2">{{ adtran_details['ServiceClass'][1] }}</span>
{% endif %}
</div>
</div>
<!-- Connection Status -->
<div class="d-flex align-items-center mb-2">
<strong class="flex-shrink-0" style="width: 140px;">Connected:</strong>
{% if adtran_details['Connected'] and rebooting == false %}
<span class="badge bg-success px-3 py-2">{{ adtran_details['Connected'] }}</span>
{% else %}
<span class="badge bg-danger px-3 py-2">{{ adtran_details['Connected'] }}</span>
{% endif %}
</div>
<!-- State -->
<div class="d-flex align-items-center mb-2">
<strong class="flex-shrink-0" style="width: 140px;">State:</strong>
{% if adtran_details['State'] == "Deployed" %}
<span class="badge bg-success px-3 py-2">{{ adtran_details['State'] }}</span>
{% else %}
<span class="badge bg-danger px-3 py-2">{{ adtran_details['State'] }}</span>
{% endif %}
</div>
<!-- Uptime -->
<div class="d-flex align-items-center mb-2">
{% if adtran_details['Details'] is defined and rebooting == false %}
{% set onu_uptime = adtran_details['Details']['single-slot-device']['onu']['uptime'] %}
{% else %}
{% set onu_uptime = 0 %}
{% endif %}
<strong class="flex-shrink-0" style="width: 140px;">ONU Uptime:</strong>
{% if onu_uptime == 0 %}
<span class="badge bg-danger px-3 py-2">Not Up</span>
{% elif onu_uptime > 259200 %}
<span class="badge bg-success px-3 py-2">{{ adtran_details['ONU Uptime'] }}</span>
{% elif onu_uptime > 86400 and onu_uptime < 259200 %}
<span class="badge bg-warning text-dark px-3 py-2">{{ adtran_details['ONU Uptime'] }}</span>
{% else %}
<span class="badge bg-danger px-3 py-2">{{ adtran_details['ONU Uptime'] }}</span>
{% endif %}
</div>
<!-- Receive Light -->
<div class="d-flex align-items-center mb-2">
<strong class="flex-shrink-0" style="width: 140px;">Receive Light:</strong>
{% if adtran_details['Cached_RX'] is defined and rebooting == false %}
{% set rx_power = adtran_details['Cached_RX'] %}
{% if rx_power == 0 %}
<span class="badge bg-danger px-3 py-2">0 dBm</span>
{% elif rx_power > parent_loc.MaxONT_dB %}
<span class="badge bg-success px-3 py-2">{{ adtran_details['Received Light'] }}</span>
{% elif rx_power < parent_loc.MaxONT_dB and rx_power > parent_loc.MaxONT_dB-20 %}
<span class="badge bg-warning text-dark px-3 py-2">{{ adtran_details['Received Light'] }}</span>
{% else %}
<span class="badge bg-danger px-3 py-2">{{ adtran_details['Received Light'] }}</span>
{% endif %}
{% else %}
<span class="badge bg-secondary px-3 py-2">No Data</span>
{% endif %}
</div>
<!-- Commission Light -->
<div class="d-flex align-items-center mb-2">
<strong class="flex-shrink-0" style="width: 140px;">Commissioned:</strong>
{% if ont.Commissioned_RX != None %}
<span class="badge bg-secondary px-3 py-2 me-2">{{ ont.Commissioned_RX/10 }} dBm</span>
<span class="badge bg-secondary px-3 py-2">{{ ont.Commissioned_Date.strftime('%d-%m-%Y') }}</span>
{% else %}
<span class="badge bg-secondary px-3 py-2">Not Commissioned</span>
{% endif %}
</div>
<!-- Fibre Distance -->
<div class="d-flex align-items-center mb-2">
<strong class="flex-shrink-0" style="width: 140px;">Fibre Distance:</strong>
{% if adtran_details['Distance'] is defined and adtran_details['Distance'] != None %}
<span class="badge text-bg-secondary px-3 py-2">{{ adtran_details['Distance'] }}m</span>
{% else %}
<span class="badge text-bg-secondary px-3 py-2">{{ adtran_details['Distance'] }}</span>
{% endif %}
</div>
<!-- Port Status -->
{% for key, value in adtran_details['intfStatus']|dictsort %}
{% if value['port-number'] != "0" %}
<div class="d-flex align-items-center mb-2">
<strong class="flex-shrink-0" style="width: 140px;">Port {{ value['port-number'] }}:</strong>
{% if value['status'] == "up" %}
<span class="badge bg-success px-3 py-2">{{ value['status'].upper() }}</span>
{% else %}
<span class="badge bg-danger text-dark px-3 py-2">{{ value['status'].upper() }}</span>
{% endif %}
</div>
{% endif %}
{% endfor %}
</div>
</div>
<!-- Actions -->
<div class="card shadow-sm">
<div class="card-header bg-light">
<h5 class="mb-0">
<i class="bi bi-tools me-2"></i>Actions
</h5>
</div>
<div class="card-body">
<div class="d-grid gap-2">
{% if current_user.Permissions in ["Admin", "Project Delivery", "Helpdesk L1", "Helpdesk L2"] %}
<!-- Permission-specific actions can be added here -->
<a href="#swapModal" class="btn btn-outline-primary" data-bs-toggle="modal">
<i class="bi bi-arrow-repeat"></i> Swap ONT
</a>
<a href="#complexSwapModal" class="btn btn-outline-info" data-bs-toggle="modal">
<i class="bi bi-gear"></i> Complex Swap
</a>
{% endif %}
<a href="#rebootModal" class="btn btn-outline-warning" data-bs-toggle="modal" style="color: #b45309; border-color: #b45309;">
<i class="bi bi-power"></i> Reboot ONT
</a>
{% if current_user.Permissions in ["Admin", "Finance", "Project Delivery"] %}
<hr>
{% if locID.ServiceClass in [12,13] and current_user.Permissions in ["Admin", "Finance", "Helpdesk L2"] %}
<a href="#provisionServiceModal" class="btn btn-outline-success" data-bs-toggle="modal">
<i class="bi bi-plus-circle"></i> Provision Service
</a>
{% elif not adtran_details['Connected'] and adtran_details['State'] == "none" and current_user.Permissions in ["Admin", "Project Delivery", "Helpdesk L2"] %}
<a href="{{ url_for('onts.ont_provision', locid=locID['id']) }}" class="btn btn-outline-success">
<i class="bi bi-router"></i> Provision ONT
</a>
{% endif %}
{% endif %}
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Reboot ONT Modal -->
<div class="modal fade" id="rebootModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header text-dark">
<h5 class="modal-title">Reboot ONT Confirmation</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<p class="text-dark">Are you sure you want to reboot <strong>{{ ont['ONTIdentifier'] }}</strong>?</p>
<div class="alert alert-warning">
<small><strong>Warning:</strong> This action will affect customer service.</small>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<a href="{{ url_for('location.location_id_reboot', locid=locID['id']) }}" class="btn btn-warning">
Confirm Reboot
</a>
</div>
</div>
</div>
</div>
<!-- Swap ONT Modal -->
<div class="modal fade" id="swapModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header text-dark">
<h5 class="modal-title">Swap ONT</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<form method="POST" action="/location/ID/{{ ont['Location_ID'] }}/swap" id="ontswapform">
<input type="hidden" name="ont_id" value="{{ ont['Location_ID'] }}">
<div class="mb-2">
<label for="new_serial" class="form-label text-dark">New Serial Number</label>
<input type="text" class="form-control font-monospace" id="new_serial" name="new_serial"
placeholder="ADTN11112222" required>
</div>
<div class="alert alert-info">
<small><strong>Note:</strong> Swap can only work with the same ONT model.</small>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-primary" onclick="form_submit()">Swap ONT</button>
</div>
</div>
</div>
</div>
<!-- Provision Temp Internet Service Modal -->
<div class="modal fade" id="newTempServiceModal" tabindex="-1">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title text-dark">
<i class="bi bi-plus-circle me-2"></i>New Temp Internet Service
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<!-- Step 1: Enter Splynx ID -->
<div id="tempStep1" class="temp-provision-step">
<div class="mb-3">
<h6 class="text-dark mb-3">
<i class="bi bi-search me-2"></i>Customer Lookup
</h6>
<label for="temp_lookup_splynx_id" class="form-label text-dark">Splynx Customer ID</label>
<input type="number" class="form-control" id="temp_lookup_splynx_id" placeholder="Enter customer ID" required>
<div class="form-text text-muted">Enter the Splynx customer ID to fetch customer details</div>
</div>
<!-- Loading State -->
<div id="tempLoading" class="text-center py-4 d-none">
<div class="spinner-border text-primary" role="status">
<span class="visually-hidden">Loading...</span>
</div>
<p class="mt-3 text-muted mb-0">Fetching customer details...</p>
</div>
<!-- Error State -->
<div id="tempCustomerError" class="alert alert-danger d-none">
<i class="bi bi-exclamation-triangle me-2"></i>
<span id="tempErrorMessage">Customer not found or error occurred</span>
</div>
</div>
<!-- Step 2: Confirm Customer & Configure Service -->
<div id="tempStep2" class="temp-provision-step d-none">
<div class="mb-4">
<h6 class="text-dark mb-3">
<i class="bi bi-person-check me-2"></i>Confirm Customer Details
</h6>
<div class="card bg-light">
<div class="card-body">
<div id="tempCustomerDetails">
<!-- Customer details will be populated here -->
</div>
</div>
</div>
</div>
<form id="tempServiceForm">
<input type="hidden" id="temp_confirmed_splynx_id" name="splynx_id">
<input type="hidden" name="ont_id" value="{{ ont['Location_ID'] }}">
<div class="mb-3">
<label for="temp_uni_interface" class="form-label text-dark">UNI Interface</label>
<select class="form-select" id="temp_uni_interface" name="uni_interface" required>
<option value="">Select UNI interface...</option>
{% for uni in ont_uni %}
<option value="{{ uni.id }}">{{ uni.UNI_Name }}</option>
{% endfor %}
</select>
</div>
<div class="alert alert-info">
<small>
<i class="bi bi-info-circle me-1"></i>
<strong>Note:</strong> This will create a temporary internet service for the customer.
</small>
</div>
</form>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<!-- Step 1 buttons -->
<div id="tempStep1Buttons">
<button type="button" class="btn btn-primary px-4" id="tempNextBtn" onclick="fetchTempCustomerDetails()">
<i class="bi bi-arrow-right me-2"></i>Next
</button>
</div>
<!-- Step 2 buttons -->
<div id="tempStep2Buttons" class="d-none">
<button type="button" class="btn btn-outline-secondary px-4" id="tempBackBtn" onclick="goBackToTempStep1()">
<i class="bi bi-arrow-left me-2"></i>Back
</button>
<button type="button" class="btn btn-primary px-4" id="tempProvisionBtn" onclick="provisionTempService()">
<i class="bi bi-plus-circle me-2"></i>Create Temp Service
</button>
</div>
</div>
</div>
</div>
</div>
<!-- Temp Service Success Modal -->
<div class="modal fade" id="tempServiceSuccessModal" tabindex="-1">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header bg-success text-white">
<h5 class="modal-title">
<i class="bi bi-check-circle me-2"></i>Temp Service Created Successfully
</h5>
</div>
<div class="modal-body">
<div class="text-center py-3">
<i class="bi bi-check-circle-fill text-success" style="font-size: 3rem;"></i>
<h6 class="mt-3 mb-3 text-dark">Success!</h6>
<div id="tempSuccessMessage" class="text-dark">
Temporary internet service has been created successfully.
</div>
</div>
</div>
<div class="modal-footer justify-content-center">
<button type="button" class="btn btn-primary px-4" onclick="closeTempSuccessModal()">
<i class="bi bi-x-lg me-2"></i>Close
</button>
</div>
</div>
</div>
</div>
<!-- Add Beacon Modal -->
<div class="modal fade" id="addBeaconModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header text-dark">
<h5 class="modal-title">Add Beacon</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<form method="POST" action="/location/ID/{{ ont['Location_ID'] }}/addBeacon" id="addBeaconform">
<input type="hidden" name="ont_id" value="{{ ont['Location_ID'] }}">
<div class="mb-2">
<label for="beacon_serial" class="form-label text-dark">Beacon Serial Number</label>
<input type="text" class="form-control" id="beacon_serial" name="beacon_serial" placeholder="ALCLB459301F" required>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-primary" onclick="form_addBeacon_submit()">Add</button>
</div>
</div>
</div>
</div>
<!-- Nokia Credentials Modal -->
<div class="modal fade" id="nokiaCredentialsModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title text-dark">
<i class="bi bi-router me-2"></i>Nokia Beacon Credentials
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div class="row g-3">
<!-- Admin Credentials -->
<div class="col-12">
<div class="card bg-light">
<div class="card-header bg-primary text-white">
<h6 class="mb-0">
<i class="bi bi-person-gear me-2"></i>Administrator Access
</h6>
</div>
<div class="card-body">
<div class="row">
<div class="col-4">
<strong>Username:</strong>
</div>
<div class="col-8">
<span class="font-monospace text-dark" id="adminUsername">admin</span>
<button class="btn btn-sm btn-outline-secondary ms-2" onclick="copyToClipboard('adminUsername', this)" title="Copy username">
<i class="bi bi-clipboard"></i>
</button>
</div>
</div>
<div class="row mt-2">
<div class="col-4">
<strong>Password:</strong>
</div>
<div class="col-8">
<span class="font-monospace text-dark" id="adminPassword">password</span>
<button class="btn btn-sm btn-outline-secondary ms-2" onclick="copyToClipboard('adminPassword', this)" title="Copy password">
<i class="bi bi-clipboard"></i>
</button>
</div>
</div>
</div>
</div>
</div>
<!-- Super Admin Credentials -->
<div class="col-12">
<div class="card bg-light">
<div class="card-header bg-warning text-dark">
<h6 class="mb-0">
<i class="bi bi-shield-exclamation me-2"></i>Super Administrator Access
</h6>
</div>
<div class="card-body">
<div class="row">
<div class="col-4">
<strong>Username:</strong>
</div>
<div class="col-8">
<span class="font-monospace text-dark" id="superUsername">superadmin</span>
<button class="btn btn-sm btn-outline-secondary ms-2" onclick="copyToClipboard('superUsername', this)" title="Copy username">
<i class="bi bi-clipboard"></i>
</button>
</div>
</div>
<div class="row mt-2">
<div class="col-4">
<strong>Password:</strong>
</div>
<div class="col-8">
<span class="font-monospace text-dark" id="superPassword">password</span>
<button class="btn btn-sm btn-outline-secondary ms-2" onclick="copyToClipboard('superPassword', this)" title="Copy password">
<i class="bi bi-clipboard"></i>
</button>
</div>
</div>
</div>
</div>
</div>
<!-- SSID Credentials -->
<div class="col-12">
<div class="card bg-light">
<div class="card-header bg-info text-white">
<h6 class="mb-0">
<i class="bi bi-wifi me-2"></i>WiFi Network Access
</h6>
</div>
<div class="card-body">
<div class="row">
<div class="col-4">
<strong>SSID:</strong>
</div>
<div class="col-8">
<span class="font-monospace text-dark" id="wifiSSID">ssid</span>
<button class="btn btn-sm btn-outline-secondary ms-2" onclick="copyToClipboard('wifiSSID', this)" title="Copy SSID">
<i class="bi bi-clipboard"></i>
</button>
</div>
</div>
<div class="row mt-2">
<div class="col-4">
<strong>Password:</strong>
</div>
<div class="col-8">
<span class="font-monospace text-dark" id="wifiPassword">password</span>
<button class="btn btn-sm btn-outline-secondary ms-2" onclick="copyToClipboard('wifiPassword', this)" title="Copy password">
<i class="bi bi-clipboard"></i>
</button>
</div>
</div>
</div>
</div>
</div>
<!-- QR Codes Section -->
<div class="col-12 mt-3">
<div class="card bg-light">
<div class="card-header bg-success text-white">
<h6 class="mb-0">
<i class="bi bi-qr-code me-2"></i>QR Codes
</h6>
</div>
<div class="card-body">
<div class="row text-center">
<div class="col-6">
<h6 class="text-dark">WiFi Setup</h6>
<div class="qr-code-container mb-2" style="cursor: pointer;"
data-bs-toggle="modal" data-bs-target="#wifiQrModal" id="wifiQrContainer">
<img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"
alt="WiFi QR Code" class="img-fluid border rounded"
style="max-width: 100px; max-height: 100px; display: none;" id="wifiQrPreview">
</div>
<small class="text-muted">Click to view full size</small>
</div>
<div class="col-6">
<h6 class="text-dark">Nokia App</h6>
<div class="qr-code-container mb-2" style="cursor: pointer;"
data-bs-toggle="modal" data-bs-target="#appQrModal" id="appQrContainer">
<img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"
alt="App QR Code" class="img-fluid border rounded"
style="max-width: 100px; max-height: 100px; display: none;" id="appQrPreview">
</div>
<small class="text-muted">Click to view full size</small>
</div>
</div>
</div>
</div>
</div>
<div class="alert alert-info mt-3">
<small>
<i class="bi bi-info-circle me-1"></i>
<strong>Note:</strong> These are the default credentials for Nokia Beacon devices.
</small>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
<!-- WiFi QR Modal -->
<div class="modal fade" id="wifiQrModal" tabindex="-1">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header text-dark">
<h5 class="modal-title">Nokia WiFi QR Code</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body text-center">
<img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"
alt="WiFi QR Code" class="img-fluid" id="wifiQrFull">
<p class="mt-3 text-muted small">Scan with device to connect to WiFi network</p>
</div>
</div>
</div>
</div>
<!-- App QR Modal -->
<div class="modal fade" id="appQrModal" tabindex="-1">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header text-dark">
<h5 class="modal-title">Nokia App QR Code</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body text-center">
<img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"
alt="App QR Code" class="img-fluid" id="appQrFull">
<p class="mt-3 text-muted small">Scan with device to access Nokia app</p>
</div>
</div>
</div>
</div>
<!-- Provision Service Modal -->
<div class="modal fade" id="provisionServiceModal" tabindex="-1">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title text-dark">
<i class="bi bi-plus-circle me-2"></i>Provision Service
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<!-- Step 1: Enter Splynx ID -->
<div id="step1" class="provision-step">
<div class="mb-3">
<h6 class="text-dark mb-3">
<i class="bi bi-search me-2"></i>Customer Lookup
</h6>
<label for="lookup_splynx_id" class="form-label text-dark">Splynx Customer ID</label>
<input type="number" class="form-control" id="lookup_splynx_id" placeholder="Enter customer ID" required>
<div class="form-text text-muted">Enter the Splynx customer ID to fetch customer details</div>
</div>
<!-- Loading State -->
<div id="loading" class="text-center py-4 d-none">
<div class="spinner-border text-primary" role="status">
<span class="visually-hidden">Loading...</span>
</div>
<p class="mt-3 text-muted mb-0">Fetching customer details...</p>
</div>
<!-- Error State -->
<div id="customerError" class="alert alert-danger d-none">
<i class="bi bi-exclamation-triangle me-2"></i>
<span id="errorMessage">Customer not found or error occurred</span>
</div>
</div>
<!-- Step 2: Confirm Customer & Configure Service -->
<div id="step2" class="provision-step d-none">
<div class="mb-4">
<h6 class="text-dark mb-3">
<i class="bi bi-person-check me-2"></i>Confirm Customer Details
</h6>
<div class="card bg-light">
<div class="card-body">
<div id="customerDetails">
<!-- Customer details will be populated here -->
</div>
</div>
</div>
</div>
<form id="provisionForm">
<input type="hidden" id="confirmed_splynx_id" name="splynx_id">
<input type="hidden" name="ont_id" value="{{ ont['Location_ID'] }}">
<div class="mb-3">
<label for="plan_id" class="form-label text-dark">Service Plan</label>
<select class="form-select" id="plan_id" name="plan_id" required>
<option value="">Select a service plan...</option>
{% for plan in plans %}
<option value="{{ plan.id }}">{{ plan.DisplayName }}</option>
{% endfor %}
</select>
</div>
<div class="mb-3">
<label for="uni_interface" class="form-label text-dark">UNI Interface</label>
<select class="form-select" id="uni_interface" name="uni_interface" required>
<option value="">Select UNI interface...</option>
{% for uni in ont_uni %}
<option value="{{ uni.id }}">{{ uni.UNI_Name }}</option>
{% endfor %}
</select>
</div>
<div class="mb-3">
<label for="vlan_option" class="form-label text-dark">VLAN Configuration</label>
<div class="mb-2">
<div class="form-check">
<input class="form-check-input" type="radio" name="vlan_option" id="vlan_untagged" value="untagged" checked>
<label class="form-check-label text-dark" for="vlan_untagged">
Untagged
</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="vlan_option" id="vlan_tagged" value="tagged">
<label class="form-check-label text-dark" for="vlan_tagged">
Tagged VLAN
</label>
</div>
</div>
<div id="vlan_id_container" style="display: none;">
<label for="vlan_id" class="form-label text-dark">VLAN ID</label>
<input type="number" class="form-control" id="vlan_id" name="vlan_id" min="2" max="4096" placeholder="Enter VLAN ID (2-4096)">
<div class="form-text text-muted">Valid range: 2-4096</div>
</div>
</div>
<!-- Additional Options -->
<div class="mb-3">
<label class="form-label text-dark">Additional Options</label>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="free_trial" name="free_trial">
<label class="form-check-label text-dark" for="free_trial">
<strong>Fibre 1000 Free Trial first</strong>
<div class="form-text text-muted">Enable free trial period before regular service</div>
</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="add_development_fee" name="add_development_fee">
<label class="form-check-label text-dark" for="add_development_fee">
<strong>Add New Development Fee</strong>
<div class="form-text text-muted">Add a new development fee to the customer account</div>
</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="billing_next_month" name="billing_next_month">
<label class="form-check-label text-dark" for="billing_next_month">
<strong>Billing Next Month</strong>
<div class="form-text text-muted">Set billing to begin from next month</div>
</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="add_month_credit" name="add_month_credit" disabled>
<label class="form-check-label text-dark" for="add_month_credit">
<strong>Add One Month Credit</strong>
<div class="form-text text-muted">Add a one month service credit to the customer account</div>
</label>
</div>
</div>
<div class="alert alert-info">
<small>
<i class="bi bi-info-circle me-1"></i>
<strong>Note:</strong> This will provision the service on the ONT and link it to the Splynx customer.
</small>
</div>
</form>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<!-- Step 1 buttons -->
<div id="step1Buttons">
<button type="button" class="btn btn-primary px-4" id="nextBtn" onclick="fetchCustomerDetails()">
<i class="bi bi-arrow-right me-2"></i>Next
</button>
</div>
<!-- Step 2 buttons -->
<div id="step2Buttons" class="d-none">
<button type="button" class="btn btn-outline-secondary px-4" id="backBtn" onclick="goBackToStep1()">
<i class="bi bi-arrow-left me-2"></i>Back
</button>
<button type="button" class="btn btn-primary px-4" id="provisionBtn" onclick="provisionService()">
<i class="bi bi-plus-circle me-2"></i>Provision Service
</button>
</div>
</div>
</div>
</div>
</div>
<!-- Success Modal -->
<div class="modal fade" id="provisionSuccessModal" tabindex="-1">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header bg-success text-white">
<h5 class="modal-title">
<i class="bi bi-check-circle me-2"></i>Service Provisioned Successfully
</h5>
</div>
<div class="modal-body">
<div class="text-center py-3">
<i class="bi bi-check-circle-fill text-success" style="font-size: 3rem;"></i>
<h6 class="mt-3 mb-3 text-dark">Success!</h6>
<div id="successMessage" class="text-dark">
Service has been provisioned successfully.
</div>
</div>
</div>
<div class="modal-footer justify-content-center">
<button type="button" class="btn btn-primary px-4" onclick="closeSuccessModal()">
<i class="bi bi-x-lg me-2"></i>Close
</button>
</div>
</div>
</div>
</div>
<!-- Complex Swap ONT Modal -->
<div class="modal fade" id="complexSwapModal" tabindex="-1">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header text-dark">
<h5 class="modal-title">Complex ONT Swap</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<form method="POST" action="/location/ID/{{ ont['Location_ID'] }}/complex_swap" id="complexSwapForm">
<input type="hidden" name="ont_id" value="{{ ont['Location_ID'] }}">
<div class="mb-3">
<label for="ont_model" class="form-label text-dark">ONT Model</label>
<select class="form-select" id="ont_model" name="ont_model" required onchange="checkInterfaceCompatibility()">
<option value="">Select ONT Model...</option>
{% for model in ont_models %}
<option value="{{ model.id }}" data-interface-count="{{ model.InterfaceCount }}">{{ model.Model }}</option>
{% endfor %}
</select>
</div>
<div class="mb-3">
<label for="serial_number" class="form-label text-dark">ONT Serial Number</label>
<input type="text" class="form-control font-monospace" id="serial_number" name="serial_number"
placeholder="ADTN24025698" required>
</div>
<div class="mb-3">
<label for="mac_address" class="form-label text-dark">ONT MAC Address</label>
<input type="text" class="form-control font-monospace" id="mac_address" name="mac_address"
placeholder="AA:BB:CC:DD:EE:FF" required>
</div>
<!-- Interface Check Results -->
<div id="interfaceCheckResult" class="mb-3 d-none">
<!-- Results will be populated by JavaScript -->
</div>
<div class="alert alert-info">
<small><strong>Note:</strong> This will create a job that runs asynchronously. You will be redirected to monitor the progress.</small>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-primary" id="submitComplexSwap" onclick="submitComplexSwap()" disabled>
Create Swap Job
</button>
</div>
</div>
</div>
</div>
<script>
function resetTempServiceModal() {
// Reset to step 1
document.getElementById('tempStep1').classList.remove('d-none');
document.getElementById('tempStep2').classList.add('d-none');
document.getElementById('tempStep1Buttons').classList.remove('d-none');
document.getElementById('tempStep2Buttons').classList.add('d-none');
// Clear form data
const tempLookupInput = document.getElementById('temp_lookup_splynx_id');
if (tempLookupInput) {
tempLookupInput.value = '';
}
document.getElementById('tempCustomerDetails').innerHTML = '';
// Reset the form
const tempServiceForm = document.getElementById('tempServiceForm');
if (tempServiceForm) {
tempServiceForm.reset();
}
// Hide loading and error states
document.getElementById('tempLoading').classList.add('d-none');
document.getElementById('tempCustomerError').classList.add('d-none');
// Re-enable next button
document.getElementById('tempNextBtn').disabled = false;
}
function fetchTempCustomerDetails() {
const splynxIdElement = document.getElementById('temp_lookup_splynx_id');
const splynxId = splynxIdElement ? splynxIdElement.value : '';
// Clear previous errors
document.getElementById('tempCustomerError').classList.add('d-none');
if (!splynxId || splynxId.trim() === '' || splynxId.trim() === '0') {
showTempError('Please enter a valid Splynx Customer ID');
return;
}
// Show loading state
document.getElementById('tempLoading').classList.remove('d-none');
document.getElementById('tempNextBtn').disabled = true;
const apiUrl = `/api/splynx/customer/${splynxId.trim()}/`;
// Make API call
fetch(apiUrl)
.then(response => {
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return response.json();
})
.then(data => {
// Hide loading
document.getElementById('tempLoading').classList.add('d-none');
document.getElementById('tempNextBtn').disabled = false;
if (data && data.id) {
displayTempCustomerDetails(data);
goToTempStep2();
} else {
showTempError('Customer not found or invalid data received');
}
})
.catch(error => {
console.error('Error fetching customer:', error);
document.getElementById('tempLoading').classList.add('d-none');
document.getElementById('tempNextBtn').disabled = false;
showTempError(`Failed to fetch customer details: ${error.message}`);
});
}
function displayTempCustomerDetails(customer) {
const detailsHtml = `
<div class="row g-2">
<div class="col-6">
<strong>Name:</strong><br>
<span class="text-dark">${customer.name || 'N/A'}</span>
</div>
<div class="col-6">
<strong>Customer ID:</strong><br>
<span class="badge bg-secondary px-2 py-1">${customer.id}</span>
</div>
<div class="col-6">
<strong>Status:</strong><br>
${customer.status === 'active'
? '<span class="badge bg-success px-2 py-1">Active</span>'
: `<span class="badge bg-warning text-dark px-2 py-1">${customer.status || 'Unknown'}</span>`
}
</div>
<div class="col-6">
<strong>Email:</strong><br>
<span class="text-dark">${customer.email || 'N/A'}</span>
</div>
<div class="col-12">
<strong>Address:</strong><br>
<span class="text-dark">${customer.street_1 || ''} ${customer.street_2 || ''}<br>
${customer.city || ''} ${customer.zip_code || ''}</span>
</div>
<div class="col-6">
<strong>Phone:</strong><br>
<span class="text-dark">${customer.phone || 'N/A'}</span>
</div>
</div>
`;
document.getElementById('tempCustomerDetails').innerHTML = detailsHtml;
document.getElementById('temp_confirmed_splynx_id').value = customer.id;
}
function showTempError(message) {
document.getElementById('tempErrorMessage').textContent = message;
document.getElementById('tempCustomerError').classList.remove('d-none');
}
function goToTempStep2() {
// Hide step 1, show step 2
document.getElementById('tempStep1').classList.add('d-none');
document.getElementById('tempStep2').classList.remove('d-none');
// Switch buttons
document.getElementById('tempStep1Buttons').classList.add('d-none');
document.getElementById('tempStep2Buttons').classList.remove('d-none');
}
function goBackToTempStep1() {
// Show step 1, hide step 2
document.getElementById('tempStep1').classList.remove('d-none');
document.getElementById('tempStep2').classList.add('d-none');
// Switch buttons
document.getElementById('tempStep1Buttons').classList.remove('d-none');
document.getElementById('tempStep2Buttons').classList.add('d-none');
// Clear any errors
document.getElementById('tempCustomerError').classList.add('d-none');
}
function provisionTempService() {
const form = document.getElementById('tempServiceForm');
const formData = new FormData(form);
// Validate required fields
const uniInterface = document.getElementById('temp_uni_interface').value;
if (!uniInterface) {
alert('Please select a UNI interface');
return;
}
// Disable provision button to prevent double-clicks
const tempProvisionBtn = document.getElementById('tempProvisionBtn');
const originalText = tempProvisionBtn.innerHTML;
tempProvisionBtn.disabled = true;
tempProvisionBtn.innerHTML = '<span class="spinner-border spinner-border-sm me-2"></span>Creating...';
// Submit the form to the original endpoint
fetch(`/location/ID/{{ ont['Location_ID'] }}/tempService`, {
method: 'POST',
body: formData
})
.then(response => {
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return response.json();
})
.then(data => {
console.log('temp service provisioning data:', data);
if (data.success) {
// Show success modal instead of alert
showTempSuccessModal(data.message || 'Temporary internet service has been created successfully.');
} else {
throw new Error(data.message || 'Failed to create temporary service');
}
})
.catch(error => {
console.error('Error creating temp service:', error);
alert(`Failed to create temporary service: ${error.message}`);
// Re-enable button on error
tempProvisionBtn.disabled = false;
tempProvisionBtn.innerHTML = originalText;
});
// Note: Button re-enabling on success is handled by page refresh
}
function closeTempSuccessModal() {
// Close the success modal and refresh the page
const modal = bootstrap.Modal.getInstance(document.getElementById('tempServiceSuccessModal'));
if (modal) {
modal.hide();
}
// Add a small delay to ensure modal closes gracefully
setTimeout(() => {
window.location.reload();
}, 300);
}
function showTempSuccessModal(message) {
// Set the success message
document.getElementById('tempSuccessMessage').innerHTML = message;
// Hide the temp service modal first
const tempServiceModal = bootstrap.Modal.getInstance(document.getElementById('newTempServiceModal'));
if (tempServiceModal) {
tempServiceModal.hide();
}
// Show success modal after a brief delay to ensure temp service modal is hidden
setTimeout(() => {
const successModal = new bootstrap.Modal(document.getElementById('tempServiceSuccessModal'), {
backdrop: 'static',
keyboard: false
});
successModal.show();
}, 300);
}
function resetProvisionModal() {
// Reset to step 1
document.getElementById('step1').classList.remove('d-none');
document.getElementById('step2').classList.add('d-none');
document.getElementById('step1Buttons').classList.remove('d-none');
document.getElementById('step2Buttons').classList.add('d-none');
// Clear form data - be specific about which elements to clear
const lookupInput = document.getElementById('lookup_splynx_id');
if (lookupInput) {
lookupInput.value = '';
}
document.getElementById('customerDetails').innerHTML = '';
// Only reset the actual form, not the input outside of it
const provisionForm = document.getElementById('provisionForm');
if (provisionForm) {
provisionForm.reset();
}
// Reset VLAN option to untagged and hide VLAN ID field
const untaggedRadio = document.getElementById('vlan_untagged');
if (untaggedRadio) {
untaggedRadio.checked = true;
handleVlanOptionChange();
}
// Hide loading and error states
document.getElementById('loading').classList.add('d-none');
document.getElementById('customerError').classList.add('d-none');
// Re-enable next button
document.getElementById('nextBtn').disabled = false;
}
function handleVlanOptionChange() {
const vlanContainer = document.getElementById('vlan_id_container');
const vlanIdInput = document.getElementById('vlan_id');
const untaggedSelected = document.getElementById('vlan_untagged').checked;
if (untaggedSelected) {
vlanContainer.style.display = 'none';
vlanIdInput.required = false;
vlanIdInput.value = '';
} else {
vlanContainer.style.display = 'block';
vlanIdInput.required = true;
}
}
function fetchCustomerDetails() {
const splynxIdElement = document.getElementById('lookup_splynx_id');
const splynxId = splynxIdElement ? splynxIdElement.value : '';
// Clear previous errors
document.getElementById('customerError').classList.add('d-none');
if (!splynxId || splynxId.trim() === '' || splynxId.trim() === '0') {
showError('Please enter a valid Splynx Customer ID');
return;
}
// Show loading state
document.getElementById('loading').classList.remove('d-none');
document.getElementById('nextBtn').disabled = true;
const apiUrl = `/api/splynx/customer/${splynxId.trim()}/`;
// Make API call
fetch(apiUrl)
.then(response => {
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return response.json();
})
.then(data => {
// Hide loading
document.getElementById('loading').classList.add('d-none');
document.getElementById('nextBtn').disabled = false;
if (data && data.id) {
displayCustomerDetails(data);
goToStep2();
} else {
showError('Customer not found or invalid data received');
}
})
.catch(error => {
console.error('Error fetching customer:', error);
document.getElementById('loading').classList.add('d-none');
document.getElementById('nextBtn').disabled = false;
showError(`Failed to fetch customer details: ${error.message}`);
});
}
function displayCustomerDetails(customer) {
const detailsHtml = `
<div class="row g-2">
<div class="col-6">
<strong>Name:</strong><br>
<span class="text-dark">${customer.name || 'N/A'}</span>
</div>
<div class="col-6">
<strong>Customer ID:</strong><br>
<span class="badge bg-secondary px-2 py-1">${customer.id}</span>
</div>
<div class="col-6">
<strong>Status:</strong><br>
${customer.status === 'active'
? '<span class="badge bg-success px-2 py-1">Active</span>'
: `<span class="badge bg-warning text-dark px-2 py-1">${customer.status || 'Unknown'}</span>`
}
</div>
<div class="col-6">
<strong>Email:</strong><br>
<span class="text-dark">${customer.email || 'N/A'}</span>
</div>
<div class="col-12">
<strong>Address:</strong><br>
<span class="text-dark">${customer.street_1 || ''} ${customer.street_2 || ''}<br>
${customer.city || ''} ${customer.zip_code || ''}</span>
</div>
<div class="col-6">
<strong>Phone:</strong><br>
<span class="text-dark">${customer.phone || 'N/A'}</span>
</div>
</div>
`;
document.getElementById('customerDetails').innerHTML = detailsHtml;
document.getElementById('confirmed_splynx_id').value = customer.id;
}
function showError(message) {
document.getElementById('errorMessage').textContent = message;
document.getElementById('customerError').classList.remove('d-none');
}
function goToStep2() {
// Hide step 1, show step 2
document.getElementById('step1').classList.add('d-none');
document.getElementById('step2').classList.remove('d-none');
// Switch buttons
document.getElementById('step1Buttons').classList.add('d-none');
document.getElementById('step2Buttons').classList.remove('d-none');
}
function goBackToStep1() {
// Show step 1, hide step 2
document.getElementById('step1').classList.remove('d-none');
document.getElementById('step2').classList.add('d-none');
// Switch buttons
document.getElementById('step1Buttons').classList.remove('d-none');
document.getElementById('step2Buttons').classList.add('d-none');
// Clear any errors
document.getElementById('customerError').classList.add('d-none');
}
function provisionService() {
const form = document.getElementById('provisionForm');
const formData = new FormData(form);
// Validate required fields
const planId = document.getElementById('plan_id').value;
const uniInterface = document.getElementById('uni_interface').value;
const vlanOption = document.querySelector('input[name="vlan_option"]:checked').value;
const vlanId = document.getElementById('vlan_id').value;
if (!planId) {
alert('Please select a service plan');
return;
}
if (!uniInterface) {
alert('Please select a UNI interface');
return;
}
if (vlanOption === 'tagged' && (!vlanId || vlanId < 2 || vlanId > 4096)) {
alert('Please enter a valid VLAN ID (2-4096) for tagged VLAN');
return;
}
// Add VLAN info to form data
if (vlanOption === 'untagged') {
formData.append('vlan_type', 'untagged');
} else {
formData.append('vlan_type', 'tagged');
formData.append('vlan_id', vlanId);
}
// Disable provision button to prevent double-clicks
const provisionBtn = document.getElementById('provisionBtn');
const originalText = provisionBtn.innerHTML;
provisionBtn.disabled = true;
provisionBtn.innerHTML = '<span class="spinner-border spinner-border-sm me-2"></span>Provisioning...';
// Submit the form
fetch('/service/provision', {
method: 'POST',
body: formData
})
.then(response => {
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return response.json();
})
.then(data => {
console.log('provisioning data:', data);
if (data.success) {
// Show success modal instead of alert
showSuccessModal(data.message || 'Service has been provisioned successfully.');
} else {
throw new Error(data.message || 'Failed to provision service');
}
})
.catch(error => {
console.error('Error provisioning service:', error);
alert(`Failed to provision service: ${error.message}`);
// Re-enable button on error
provisionBtn.disabled = false;
provisionBtn.innerHTML = originalText;
});
// Note: Button re-enabling on success is handled by page refresh
}
function closeSuccessModal() {
// Close the success modal and refresh the page
const modal = bootstrap.Modal.getInstance(document.getElementById('provisionSuccessModal'));
if (modal) {
modal.hide();
}
// Add a small delay to ensure modal closes gracefully
setTimeout(() => {
window.location.reload();
}, 300);
}
function showSuccessModal(message) {
// Set the success message
document.getElementById('successMessage').innerHTML = message;
// Hide the provision modal first
const provisionModal = bootstrap.Modal.getInstance(document.getElementById('provisionServiceModal'));
if (provisionModal) {
provisionModal.hide();
}
// Show success modal after a brief delay to ensure provision modal is hidden
setTimeout(() => {
const successModal = new bootstrap.Modal(document.getElementById('provisionSuccessModal'), {
backdrop: 'static',
keyboard: false
});
successModal.show();
}, 300);
}
// Complex Swap Functions
function checkInterfaceCompatibility() {
const ontModelSelect = document.getElementById('ont_model');
const resultDiv = document.getElementById('interfaceCheckResult');
const submitBtn = document.getElementById('submitComplexSwap');
if (!ontModelSelect.value) {
resultDiv.classList.add('d-none');
submitBtn.disabled = true;
return;
}
const ontId = {{ ont['Location_ID'] }};
// Show loading state
resultDiv.innerHTML = `
<div class="alert alert-info">
<div class="spinner-border spinner-border-sm me-2"></div>
Checking interface compatibility...
</div>
`;
resultDiv.classList.remove('d-none');
submitBtn.disabled = true;
// Make API call
fetch(`/api/ont/interface_check/${ontId}/`)
.then(response => response.json())
.then(data => {
if (data.passed) {
resultDiv.innerHTML = `
<div class="alert alert-success">
<i class="bi bi-check-circle me-2"></i>
Interface compatibility check passed. Ready to proceed.
</div>
`;
submitBtn.disabled = false;
} else {
resultDiv.innerHTML = `
<div class="alert alert-danger">
<i class="bi bi-exclamation-triangle me-2"></i>
<strong>Compatibility Issue:</strong> ${data.reason || 'Interface check failed'}
</div>
`;
submitBtn.disabled = true;
}
})
.catch(error => {
console.error('Interface check error:', error);
resultDiv.innerHTML = `
<div class="alert alert-warning">
<i class="bi bi-exclamation-triangle me-2"></i>
Unable to verify interface compatibility. Please check manually.
</div>
`;
submitBtn.disabled = false; // Allow user to proceed at their own risk
});
}
function submitComplexSwap() {
const form = document.getElementById('complexSwapForm');
const submitBtn = document.getElementById('submitComplexSwap');
// Validate form
if (!form.checkValidity()) {
form.reportValidity();
return;
}
// Disable button and show loading
const originalText = submitBtn.innerHTML;
submitBtn.disabled = true;
submitBtn.innerHTML = '<span class="spinner-border spinner-border-sm me-2"></span>Creating Job...';
// Submit form
form.submit();
}
</script>
{% endblock %}