431 lines
18 KiB
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 & 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 %} |