Files
AthonetTools/templates/pages/gaf_desk.html

431 lines
18 KiB
HTML

{% extends "layout.html" %}
{% set active_page = 'gaf_desk' %}
{% block title %}GAF Desk - {{ super() }}{% endblock %}
{% block content %}
<div class="d-flex justify-content-between align-items-center mb-4">
<h2 id="page-title" class="mb-0">GAF Desk: Configuration Generator</h2>
</div>
<hr>
<div class="row">
<div class="col-md-4">
<label for="blueprint-select" class="form-label"><h5>1. Select a Blueprint</h5></label>
<select class="form-select" id="blueprint-select">
<option selected value="">Choose a blueprint...</option>
<option value="00_simple_5G_only">00_simple_5G_only</option>
<option value="01_single_site">01_single_site</option>
<option value="02_high_availability">02_high_availability</option>
<option value="03_distributed">03_distributed</option>
<option value="04_high_availability_ipv6">04_high_availability_ipv6</option>
<option value="05_high_availability_ospf">05_high_availability_ospf</option>
<option value="06_upf_active_standby">06_upf_active_standby</option>
<option value="07_high_availability_proxmox">07_high_availability_proxmox</option>
<option value="08_all_in_one">08_all_in_one</option>
<option value="10_4G_MVNO">10_4G_MVNO</option>
</select>
</div>
</div>
<div class="card my-4">
<div class="card-header">
<h5>2. Global Configuration</h5>
</div>
<div class="card-body">
<div class="row g-3">
<div class="col-md-3">
<label for="network-name-input" class="form-label">Network Name</label>
<input type="text" class="form-control" id="network-name-input" value="JohnWayne" readonly
data-bs-toggle="tooltip" title="This is the hardcoded network name.">
</div>
<div class="col-md-3">
<label for="plmn-input" class="form-label">PLMN</label>
<input type="text" class="form-control" id="plmn-input" value="315-010" readonly
data-bs-toggle="tooltip" title="The hardcoded PLMN ID for this configuration.">
</div>
<div class="col-md-3">
<label for="dns-input" class="form-label">DNS</label>
<input type="text" class="form-control" id="dns-input" value="8.8.8.8" readonly
data-bs-toggle="tooltip" title="Primary DNS server. This value is fixed.">
</div>
<div class="col-md-3">
<label for="ntp-input" class="form-label">NTP</label>
<input type="text" class="form-control" id="ntp-input" value="0.pool.ntp.org, 1.pool.ntp.org" readonly
data-bs-toggle="tooltip" title="NTP servers for time synchronization.">
</div>
<div class="row g-3 mt-2">
<!-- RAN -->
<div class="col-md-4">
<div class="mb-1"><span class="form-label d-block fw-semibold">RAN Network</span></div>
<input type="text" class="form-control" id="ip-core-ran"
placeholder="Core IP addr in format 172.28.20.25/24"
data-bs-toggle="tooltip"
title="Enter the IP range for the connection between the Cell Site and the Core.">
<input type="text" class="form-control mt-2" id="ip-core-ran-gw"
placeholder="RAN Gateway IP e.g. 172.28.20.1">
</div>
<div class="col-md-4">
<div class="mb-1"><span class="form-label d-block fw-semibold">Management Network</span></div>
<input type="text" class="form-control" id="ip-core-mgmt"
value="via DHCP" readonly
placeholder="Will be set in Step 1"
data-bs-toggle="tooltip"
title="This IP will be set automatically based on the current DHCP address during prechecks.">
<input type="text" class="form-control mt-2" id="ip-core-mgmt-gw"
value="via DHCP" readonly
placeholder="Gateway will be set in Step 1"
data-bs-toggle="tooltip"
title="This gateway will be set automatically based on the current DHCP address during prechecks.">
</div>
<div class="col-md-4">
<div class="mb-1"><span class="form-label d-block fw-semibold">Data Network</span></div>
<input type="text" class="form-control" id="ip-core-dn"
placeholder="Core DN/APN IP addr in format 172.28.10.25/24"
data-bs-toggle="tooltip"
title="Enter the IP range for the connection between the Core and the Data Network.">
<input type="text" class="form-control mt-2" id="ip-core-dn-gw"
placeholder="DN/APN Gateway IP e.g. 172.28.20.1">
<label for="vlan-core-dn" class="visually-hidden">Data Network VLAN</label>
<input type="number" class="form-control mt-2" id="ip-core-dn-vlan"
placeholder="VLAN (e.g. 200)" min="1" max="4094">
<label for="ue-ip-pool" class="visually-hidden">UE IP Pool (CIDR)</label>
<input type="text" class="form-control mt-2" id="ip-core-dn-uepool"
placeholder="UE IP Pool (e.g. 10.20.0.0/16)">
</div>
</div>
</div>
</div>
<h5>3. Blueprint Details</h5>
<div id="blueprint-diagram-area" class="mt-3">
<div id="cy-5g" class="d-none" style="height:520px; border:1px solid #e5e7eb; border-radius:8px;"></div>
<div id="diagram-empty" class="text-muted text-center py-4" aria-live="polite">
Please select a blueprint to see its diagram.
</div>
</div>
<hr>
<div id="deployment-wizard">
<h5 class="mb-3">4. Deployment Wizard</h5>
<div class="progress mb-4" style="height: 25px;">
<div class="progress-bar" role="progressbar" style="width: 0%;" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100">0%</div>
</div>
<div class="accordion" id="deploymentAccordion">
<div class="accordion-item">
<h2 class="accordion-header" id="headingOne">
<button class="accordion-button" type="button" data-bs-toggle="collapse" data-bs-target="#collapseOne">
Stage 1: Prechecks
</button>
</h2>
<div id="collapseOne" class="accordion-collapse collapse show" data-bs-parent="#deploymentAccordion">
<div class="accordion-body">
<ul class="list-group">
<li class="list-group-item" id="target-host-row">
<div class="d-flex flex-wrap align-items-center gap-2">
<strong class="me-2">Target host (VPN IP):</strong>
<div class="input-group" style="max-width: 260px;">
<span class="input-group-text">IPv4</span>
<input id="target-ip-input" type="text" class="form-control" placeholder="10.x.x.x" inputmode="numeric" autocomplete="off">
</div>
<button id="btn-enable-access" class="btn btn-sm btn-primary">Enable Access</button>
<span id="access-badge" class="badge bg-secondary ms-2">Pending</span>
</div>
<div class="d-flex flex-wrap align-items-center gap-2 mt-2">
<strong class="me-2">Retrieve eth0 network info:</strong>
<button class="btn btn-sm btn-primary" id="btn-capture-oam">Run</button>
<span class="badge bg-secondary ms-2" id="oam-badge">Pending</span>
</div>
<div class="small text-muted mt-2">
Enables SSH &amp; Webconsole (enable → enable-autostart → start).<br>
Retrieves eth0 network info and configures static address.
</div>
</li>
</ul>
</div>
</div>
</div>
<div class="accordion-item">
<h2 class="accordion-header" id="headingTwo">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseTwo">
Stage 2: Preparation
</button>
</h2>
<div id="collapseTwo" class="accordion-collapse collapse" data-bs-parent="#deploymentAccordion">
<div class="accordion-body">
<ul class="list-group">
<li class="list-group-item" id="create-yaml-row">
<div class="d-flex flex-wrap align-items-center gap-2">
<strong class="me-2">Create YAML files:</strong>
<button class="btn btn-sm btn-primary" id="btn-create-yaml">Create</button>
<span class="badge bg-secondary ms-2" id="yaml-badge">Pending</span>
</div>
<div class="small text-muted mt-2">
Generates YAML files for deployment based on the configuration above.
</div>
</li>
</ul>
</div>
</div>
</div>
<div class="accordion-item">
<h2 class="accordion-header" id="headingThree">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseThree">
Stage 3: Execution
</button>
</h2>
<div id="collapseThree" class="accordion-collapse collapse" data-bs-parent="#deploymentAccordion">
<div class="accordion-body">
<ul class="list-group">
<li class="list-group-item" id="run-gaf-row">
<div class="d-flex flex-wrap align-items-center gap-2">
<strong class="me-2">Run GAF:</strong>
<button class="btn btn-sm btn-primary" id="btn-run-gaf">Run</button>
</div>
<div class="small text-muted mt-2">
Executes the GAF deployment process.
</div>
<div class="mt-3">
<pre id="gaf-output" style="background:#f8f9fa; border-radius:6px; padding:12px; max-height:300px; overflow:auto;"></pre>
</div>
</li>
</ul>
</div>
</div>
</div>
<div class="accordion-item">
<h2 class="accordion-header" id="headingFour">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseFour">
Stage 4: Postchecks
</button>
</h2>
<div id="collapseFour" class="accordion-collapse collapse" data-bs-parent="#deploymentAccordion">
<div class="accordion-body">
<ul class="list-group">
<li class="list-group-item" id="post-backup-row">
<div class="d-flex flex-wrap align-items-center gap-2">
<strong class="me-2">Post-backup:</strong>
<button class="btn btn-sm btn-primary" id="btn-post-backup" disabled>Run</button>
</div>
<div class="small text-muted mt-2">
Performs post-deployment backup operations.
</div>
</li>
</ul>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block extra_scripts %}
<script src="https://unpkg.com/cytoscape@3.28.0/dist/cytoscape.min.js"></script>
<script src="https://unpkg.com/dagre@0.8.5/dist/dagre.min.js"></script>
<script src="https://unpkg.com/cytoscape-dagre@2.5.0/cytoscape-dagre.js"></script>
<script>
document.addEventListener('DOMContentLoaded', () => {
const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]');
[...tooltipTriggerList].map(tooltipTriggerEl => new bootstrap.Tooltip(tooltipTriggerEl));
});
</script>
<script type="module">
import { mountCy } from '/static/js/graph/initGraph.js';
import { mountWizard } from '/static/js/wizard/steps.js';
import { mountStep3Deploy } from '/static/js/wizard/step3_deploy.js';
mountWizard();
mountStep3Deploy();
// === Recapture OAM (eth0) on demand ===
// Stage 3 deployment logic is now handled by static/js/wizard/step3_deploy.js
async function recaptureOAM(host) {
const oamBadge = document.getElementById('oam-badge');
const mgmtInput = document.getElementById('ip-core-mgmt'); // the read-only field in "Management Network"
if (!host) {
alert('Enter the Target host first (Stage 1).');
return null;
}
try {
oamBadge.textContent = 'Running…';
oamBadge.className = 'badge bg-warning';
const res = await fetch('/api/local/eth0/capture', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ host })
});
const j = await res.json();
if (!res.ok || !j.ok) throw new Error(j.error || `HTTP ${res.status}`);
// Update UI with fresh values
oamBadge.textContent = 'Captured';
oamBadge.className = 'badge bg-success';
if (mgmtInput) mgmtInput.value = j.cidr; // e.g. 192.168.86.55/24
const mgmtGwInput = document.getElementById('ip-core-mgmt-gw');
if (mgmtGwInput) mgmtGwInput.value = j.gw || '';
return j; // { cidr, gw, ok:true }
} catch (e) {
console.error('OAM capture failed:', e);
oamBadge.textContent = 'Failed';
oamBadge.className = 'badge bg-danger';
alert(`OAM capture failed: ${e.message}`);
return null;
}
}
// Wire the Stage 1 "Run" button to always re-capture
document.getElementById('btn-capture-oam')?.addEventListener('click', async () => {
const targetIp = document.querySelector('#target-host-row input')?.value?.trim();
await recaptureOAM(targetIp);
});
const blueprintSelect = document.getElementById('blueprint-select');
const cyContainer = document.getElementById('cy-5g');
const emptyMsg = document.getElementById('diagram-empty');
let cy = null;
const cache = new Map();
function showEmpty() {
if (cy) { cy.destroy(); cy = null; }
cyContainer.classList.add('d-none');
emptyMsg.textContent = 'Please select a blueprint to see its diagram.'; // reset
emptyMsg.classList.remove('d-none');
}
let dagreRegistered = false;
async function loadBlueprint(name) {
if (!name) return showEmpty();
const url = `/static/blueprints/${name}.json`;
try {
const data = cache.has(url) ? cache.get(url) : await (await fetch(url, { cache: 'no-cache' })).json();
cache.set(url, data);
emptyMsg.classList.add('d-none');
cyContainer.classList.remove('d-none');
if (cy) { cy.destroy(); cy = null; }
if (!dagreRegistered && window.cytoscapeDagre) {
cytoscape.use(cytoscapeDagre);
dagreRegistered = true;
}
cy = mountCy(cyContainer, data);
} catch (err) {
console.error('Failed to load blueprint:', err);
showEmpty();
emptyMsg.textContent = 'Failed to load the selected blueprint.';
}
}
blueprintSelect.addEventListener('change', () => {
const selectedValue = blueprintSelect.value;
loadBlueprint(selectedValue);
});
// Handle Create YAML button click (calls /api/ansible/render)
document.getElementById('btn-create-yaml').addEventListener('click', async () => {
const val = id => (document.getElementById(id)?.value || '').trim();
// Arrays
const dns = (val('dns-input') || '8.8.8.8')
.split(',')
.map(s => s.trim())
.filter(Boolean);
const ntp = (val('ntp-input') || '0.pool.ntp.org, 1.pool.ntp.org')
.split(',')
.map(s => s.trim())
.filter(Boolean);
// DN VLAN as number (or undefined)
const vlanStr = val('ip-core-dn-vlan');
const vlanNum = vlanStr ? Number(vlanStr) : undefined;
// Target IP from Stage 1
const targetIp = document.querySelector('#target-host-row input')?.value?.trim() || '';
// Always recapture just before render so we never use stale cache
const cap = await recaptureOAM(targetIp); // <- requires the helper added earlier
if (!cap) return; // capture failed; stop here
// Update the Management field from fresh capture (nice UX)
const mgmtField = document.getElementById('ip-core-mgmt');
if (mgmtField && cap.cidr) mgmtField.value = cap.cidr;
// Build payload; mgmt is determined server-side from the fresh capture
const payload = {
hostname: val('network-name-input') || 'AIO-1',
network_name: val('network-name-input') || 'Network',
plmn: val('plmn-input') || '315-010',
dns,
ntp,
ran: { cidr: val('ip-core-ran'), gw: val('ip-core-ran-gw') },
dn: {
cidr: val('ip-core-dn'),
gw: val('ip-core-dn-gw'),
vlan: vlanNum,
ue_pool: val('ip-core-dn-uepool'),
dnn: 'internet'
},
inventory_host: 'GBP08-AIO-1',
esxi_host: 'ESXI-1',
version: '25.1',
ova_file: '/home/mjensen/OVA/HPE_ANW_P5G_Core-1.25.1.1-qemux86-64.ova',
// Make sure backend knows which host to use; it will read the fresh snapshot
ansible_host_ip: targetIp,
force_oam_refresh: true // harmless hint; backend may ignore if not implemented
};
try {
const res = await fetch('/api/ansible/render', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
});
const j = await res.json();
if (!res.ok || !j.ok) throw new Error(j.error || `HTTP ${res.status}`);
document.getElementById('yaml-badge').textContent = 'Created';
document.getElementById('yaml-badge').className = 'badge bg-success';
alert(`YAML files created in: ${j.staging}`);
} catch (err) {
console.error('Error creating YAML:', err);
document.getElementById('yaml-badge').textContent = 'Failed';
document.getElementById('yaml-badge').className = 'badge bg-danger';
}
});
showEmpty();
</script>
{% endblock %}