Files
AthonetTools/templates/index.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>