285 lines
12 KiB
HTML
285 lines
12 KiB
HTML
<!doctype html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<title>Core Network Tool</title>
|
|
<link rel="icon" href="/static/images/favicon.ico">
|
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
|
|
<style>
|
|
body { min-height: 100vh; }
|
|
main { display: flex; flex-wrap: nowrap; height: 100vh; max-height: 100vh; overflow-x: auto; overflow-y: hidden; }
|
|
.b-example-vr { flex-shrink: 0; width: 1.5rem; height: 100vh; background-color: rgba(0, 0, 0, .1); border: solid rgba(0, 0, 0, .15); border-width: 1px 0; }
|
|
#results-output { background-color: #212529; color: #f8f9fa; border-radius: .25rem; min-height: 300px; font-family: monospace; }
|
|
.table-hover tbody tr:hover { cursor: pointer; }
|
|
.table-compact td,
|
|
.table-compact th {
|
|
padding-top: 0.2rem;
|
|
padding-bottom: 0.2rem;
|
|
}
|
|
.accordion-button::after {
|
|
filter: invert(1) grayscale(100%);
|
|
}
|
|
.network-card { cursor: pointer; }
|
|
.network-card:hover { border-color: #0d6efd !important; }
|
|
|
|
.nav-pills .nav-link:not(.active) {
|
|
color: white;
|
|
}
|
|
|
|
.sidebar-logo {
|
|
height: 40px;
|
|
width: auto;
|
|
transition: transform 0.2s ease-in-out;
|
|
}
|
|
.sidebar-logo:hover {
|
|
transform: scale(1.05);
|
|
}
|
|
.menu-header {
|
|
padding: .5rem 1rem .25rem;
|
|
font-size: .875rem;
|
|
color: #6c757d;
|
|
font-weight: 500;
|
|
}
|
|
input#host::placeholder {
|
|
color: #6c757d;
|
|
opacity: 1;
|
|
}
|
|
.tree-item { cursor: pointer; }
|
|
.tree-item:hover { background-color: #495057; }
|
|
.tree-item .bi { transition: transform 0.2s; }
|
|
.tree-item.open > .bi-chevron-right { transform: rotate(90deg); }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
|
|
<main>
|
|
<div class="d-flex flex-column flex-shrink-0 p-3 text-bg-dark" style="width: 280px;">
|
|
<a href="/" class="d-flex align-items-center mb-3 mb-md-0 me-md-auto text-white text-decoration-none">
|
|
<img src="/static/images/hpe_logo_animated.gif" alt="HPE Logo" class="sidebar-logo">
|
|
</a>
|
|
<hr>
|
|
<ul class="nav nav-pills flex-column mb-auto" id="menu">
|
|
<li class="menu-header">Dashboard Functions</li>
|
|
<li class="nav-item">
|
|
<a href="{{ url_for('system_browser_page') }}" class="nav-link {% if active_page == 'system_browser' %}active{% endif %}" data-page-title="System Browser">
|
|
<i class="bi bi-diagram-3-fill me-2"></i> System Browser
|
|
</a>
|
|
</li>
|
|
<li class="nav-item">
|
|
<a href="{{ url_for('vpn_status_page') }}" class="nav-link {% if active_page == 'vpn_status' %}active{% endif %}" data-page-title="m2000 Status">
|
|
<i class="bi bi-shield-lock-fill me-2"></i> m2000 Status
|
|
</a>
|
|
</li>
|
|
<li>
|
|
<a href="{{ url_for('network_config_page') }}" class="nav-link {% if active_page == 'network_config' %}active{% endif %}" data-page-title="Network Configuration">
|
|
<i class="bi bi-hdd-network-fill me-2"></i> Network Config
|
|
</a>
|
|
</li>
|
|
<li>
|
|
<a href="{{ url_for('tenants_page') }}" class="nav-link {% if active_page == 'tenants' %}active{% endif %}" data-page-title="Dashboard Tenant Management">
|
|
<i class="bi bi-building-fill me-2"></i> Dashboard Tenant
|
|
</a>
|
|
</li>
|
|
|
|
<li><hr class="my-2"></li>
|
|
|
|
<li class="menu-header">ComcoCore Functions</li>
|
|
<li>
|
|
<a href="{{ url_for('hnk_page') }}" class="nav-link {% if active_page == 'hnk' %}active{% endif %}" data-page-title="Home Network Key (HNK) Management">
|
|
<i class="bi bi-key-fill me-2"></i> Home Network Keys
|
|
</a>
|
|
</li>
|
|
<li>
|
|
<a href="{{ url_for('network_clients_page') }}" class="nav-link {% if active_page == 'network_clients' %}active{% endif %}" data-page-title="Network Clients (SUPI)">
|
|
<i class="bi bi-person-fill-gear me-2"></i> Network Clients
|
|
</a>
|
|
</li>
|
|
</ul>
|
|
<hr>
|
|
<div id="vpn-controls">
|
|
<h6 class="text-white">Dashboard VPNs</h6>
|
|
<ul class="list-unstyled">
|
|
<li>
|
|
<div class="form-check form-switch text-white">
|
|
<input class="form-check-input vpn-toggle" type="checkbox" role="switch" id="vpn-triton" data-vpn-name="Triton">
|
|
<label class="form-check-label" for="vpn-triton">Triton</label>
|
|
</div>
|
|
</li>
|
|
<li>
|
|
<div class="form-check form-switch text-white">
|
|
<input class="form-check-input vpn-toggle" type="checkbox" role="switch" id="vpn-star" data-vpn-name="Star">
|
|
<label class="form-check-label" for="vpn-star">Star</label>
|
|
</div>
|
|
</li>
|
|
<li>
|
|
<div class="form-check form-switch text-white">
|
|
<input class="form-check-input vpn-toggle" type="checkbox" role="switch" id="vpn-bluebonnet" data-vpn-name="Bluebonnet">
|
|
<label class="form-check-label" for="vpn-bluebonnet">Bluebonnet</label>
|
|
</div>
|
|
</li>
|
|
<li>
|
|
<div class="form-check form-switch text-white">
|
|
<input class="form-check-input vpn-toggle" type="checkbox" role="switch" id="vpn-lonestar" data-vpn-name="Lonestar">
|
|
<label class="form-check-label" for="vpn-lonestar">Lonestar</label>
|
|
</div>
|
|
</li>
|
|
<li>
|
|
<div class="form-check form-switch text-white">
|
|
<input class="form-check-input vpn-toggle" type="checkbox" role="switch" id="vpn-production" data-vpn-name="Production">
|
|
<label class="form-check-label" for="vpn-production">Production</label>
|
|
</div>
|
|
</li>
|
|
</ul>
|
|
<hr class="my-2">
|
|
<h6 class="text-white">HPE P5G Support VPNs</h6>
|
|
<ul class="list-unstyled">
|
|
<li>
|
|
<div class="form-check form-switch text-white">
|
|
<input class="form-check-input vpn-toggle" type="checkbox" role="switch" id="vpn-us-support" data-vpn-name="US-Support">
|
|
<label class="form-check-label" for="vpn-us-support">US Support</label>
|
|
</div>
|
|
</li>
|
|
<li>
|
|
<div class="form-check form-switch text-white">
|
|
<input class="form-check-input vpn-toggle" type="checkbox" role="switch" id="vpn-eu-support" data-vpn-name="EU-Support">
|
|
<label class="form-check-label" for="vpn-eu-support">EU Support</label>
|
|
</div>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
<div class="b-example-vr"></div>
|
|
<div class="flex-grow-1 p-4 overflow-y-auto">
|
|
{% block content %}{% endblock %}
|
|
</div>
|
|
</main>
|
|
|
|
<div class="modal fade" id="networkDetailModal" tabindex="-1">
|
|
<div class="modal-dialog modal-lg modal-dialog-scrollable">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title" id="networkDetailModalLabel">Network Details</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<pre id="modalJsonOutput"></pre>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" defer></script>
|
|
<script defer>
|
|
const resultsOutput = document.getElementById('results-output');
|
|
const spinner = document.getElementById('spinner');
|
|
const networkDetailModal = document.getElementById('networkDetailModal');
|
|
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]');
|
|
[...tooltipTriggerList].map(tooltipTriggerEl => new bootstrap.Tooltip(tooltipTriggerEl));
|
|
checkVpnStatus();
|
|
});
|
|
|
|
function formatHostIp(ipString) {
|
|
if (!ipString) return '';
|
|
if (ipString.includes(':') && !ipString.startsWith('[') && !ipString.endsWith(']')) {
|
|
return `[${ipString}]`;
|
|
}
|
|
return ipString;
|
|
}
|
|
|
|
async function apiCall(endpoint, body, clearResults = true) {
|
|
spinner.classList.remove('d-none');
|
|
if (clearResults) {
|
|
resultsOutput.innerHTML = '';
|
|
}
|
|
try {
|
|
const response = await fetch(endpoint, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(body),
|
|
});
|
|
const result = await response.json();
|
|
if (!response.ok) {
|
|
if (result.error === 'Network Unreachable') throw new Error(result.message);
|
|
throw new Error(result.error || 'Unknown server error');
|
|
}
|
|
return result;
|
|
} catch (error) {
|
|
alert(`Error: ${error.message}`);
|
|
return null;
|
|
} finally {
|
|
spinner.classList.add('d-none');
|
|
}
|
|
}
|
|
|
|
function getStatusClass(status) {
|
|
switch (status) {
|
|
case 'DEPLOYED': case 'up': return 'bg-success';
|
|
case 'ALL_HW_NOT_ONLINE': return 'bg-danger';
|
|
case 'TO_DEPLOY': return 'bg-warning text-dark';
|
|
default: return 'bg-secondary';
|
|
}
|
|
}
|
|
|
|
function initializeTooltips() {
|
|
const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]');
|
|
[...tooltipTriggerList].map(tooltipTriggerEl => new bootstrap.Tooltip(tooltipTriggerEl));
|
|
}
|
|
|
|
networkDetailModal.addEventListener('show.bs.modal', event => {
|
|
const card = event.relatedTarget;
|
|
const nodeName = card.getAttribute('data-node-name');
|
|
const nodeDetails = JSON.parse(card.getAttribute('data-node-details'));
|
|
const modalTitle = networkDetailModal.querySelector('.modal-title');
|
|
const modalBody = networkDetailModal.querySelector('#modalJsonOutput');
|
|
modalTitle.textContent = `Details for: ${nodeName}`;
|
|
modalBody.textContent = JSON.stringify(nodeDetails, null, 2);
|
|
});
|
|
|
|
const vpnToggles = document.querySelectorAll('.vpn-toggle');
|
|
function updateVpnTogglesUI(activeVpn) {
|
|
vpnToggles.forEach(toggle => {
|
|
toggle.checked = (toggle.dataset.vpnName === activeVpn);
|
|
toggle.disabled = false;
|
|
});
|
|
}
|
|
|
|
async function checkVpnStatus() {
|
|
try {
|
|
const response = await fetch('/api/vpn/status');
|
|
const data = await response.json();
|
|
if (response.ok) updateVpnTogglesUI(data.active_vpn);
|
|
} catch (error) {
|
|
console.error("Failed to fetch VPN status:", error);
|
|
}
|
|
}
|
|
|
|
vpnToggles.forEach(toggle => {
|
|
toggle.addEventListener('change', async (event) => {
|
|
const vpnName = event.target.dataset.vpnName;
|
|
const turnOn = event.target.checked;
|
|
vpnToggles.forEach(t => t.disabled = true);
|
|
try {
|
|
const response = await fetch('/api/vpn/toggle', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ vpn_name: vpnName, state: turnOn })
|
|
});
|
|
const data = await response.json();
|
|
if (!response.ok) {
|
|
alert(`Error: ${data.error}`);
|
|
}
|
|
} catch (error) {
|
|
alert(`Error: ${error.message}`);
|
|
} finally {
|
|
checkVpnStatus();
|
|
}
|
|
});
|
|
});
|
|
</script>
|
|
{% block extra_scripts %}{% endblock %}
|
|
</body>
|
|
</html> |