Files
itflow/agent/js/network_form.js
johnnyq 2fc9c7bfe3 Networks and asset interfaces modernization: networks list with type tiles and type filter pills, usage bars, subnet/gateway/DNS/DHCP at a glance, IP-contains search, bulk type/site/star/archive and type quick pick; network page with address plan (documented IPs and device interfaces merged, free runs with Use, DHCP band, gateway, next free IP, conflict/in-pool/MAC-mismatch/held-back/broadcast/outside-subnet flags), overlap notice, devices tab, inline notes, document device addresses, WAN provider details; network quick look; shared network form with icon input groups, type guessed from name, live CIDR info with gateway/DHCP suggestions and overlap check, validated on save (canonical CIDR, gateway and range inside subnet, DNS, VLAN 1-4094, client-owned site, client or own vendor as provider); documented IP types (fixed, DHCP reservation, held back, floating) with MAC, ranges up to 256, bulk type, import/export with type and MAC; asset interfaces moved to their own handler and form with network/IP validation and live check, next free, VLANs, one-to-one connections scoped to the client, disconnect, make primary, switch/firewall/AP port map, run-of-ports modal with preview and padding, ajax import/export with VLANs and connected to, bulk network/VLANs/type/DHCP/disconnect/delete; networks and IP addresses in global search with subnet containment and MAC normalization; location and service pages link networks; demo data with WAN networks tied to ISP vendors, reservations, held-back addresses and switch VLANs; alertMessageHtml no longer double-encodes; db update 2.8.9 adds network_type, network_vendor_id, ip_type, ip_mac, interface_vlans and indexes, backfills types and CIDR prefixes
2026-10-15 20:33:28 -04:00

183 lines
7.3 KiB
JavaScript

// New Network and Edit Network (agent/modals/network/network_form.php):
// the subnet is read as it is typed - its size, mask and first and last
// host, the client's networks it overlaps, and a gateway and DHCP range to
// use when those are empty (ajax.php ?network_subnet_info). A new network's
// type follows its name ("Guest Wireless" is Guest) until one is picked;
// the provider picker shows for an internet connection.
//
// Wrapped in an IIFE because a modal can be opened, closed and opened again
// in one page load, which re-runs this file.
(function () {
document.querySelectorAll('.itflow-network-form:not([data-wired])').forEach(function (form) {
form.setAttribute('data-wired', '1');
const clientId = form.dataset.clientId || '0';
const networkId = form.dataset.networkId || '0';
const nameInput = form.querySelector('[data-network-name]');
const typeSelect = form.querySelector('[data-network-type]');
const provider = form.querySelector('[data-network-provider]');
const cidrInput = form.querySelector('[data-network-cidr]');
const info = form.querySelector('[data-network-cidr-info]');
const gatewayInput = form.querySelector('[data-network-gateway]');
const rangeInput = form.querySelector('[data-network-range]');
const hint = info ? info.innerHTML : '';
function setType(type) {
if (typeSelect.value === type) {
return;
}
if (typeSelect.itflowSelect) {
typeSelect.itflowSelect.setValue(type);
} else {
typeSelect.value = type;
}
typeSelect.dispatchEvent(new Event('change', {bubbles: true}));
}
// The provider is for an internet connection - or anything that has one already
function showProvider() {
if (!provider) {
return;
}
const vendor = provider.querySelector('select');
provider.hidden = typeSelect.value !== 'WAN' && (!vendor || vendor.value === '0');
}
if (typeSelect) {
let picking = false;
const rules = JSON.parse(typeSelect.dataset.typeRules || '{}');
typeSelect.addEventListener('change', function () {
if (!picking) {
typeSelect.dataset.typePicked = '1';
}
showProvider();
});
if (nameInput) {
nameInput.addEventListener('input', function () {
if (typeSelect.dataset.typePicked === '1') {
return;
}
const name = nameInput.value.toLowerCase();
let type = 'LAN';
for (const key in rules) {
if (name && new RegExp(rules[key]).test(name)) {
type = key;
break;
}
}
picking = true;
setType(type);
picking = false;
});
}
}
// A suggestion under a field that is empty - "Use 192.168.1.1"
function suggest(which, input, value) {
const box = form.querySelector('[data-network-suggest="' + which + '"]');
if (!box) {
return;
}
box.innerHTML = '';
box.hidden = !value || input.value.trim() !== '';
if (box.hidden) {
return;
}
const link = document.createElement('a');
link.href = '#';
link.className = 'font-monospace';
link.textContent = value;
link.addEventListener('click', function (e) {
e.preventDefault();
input.value = value;
box.hidden = true;
});
box.append('Use ', link);
}
let timer = null;
let asked = null;
function check() {
const cidr = cidrInput.value.trim();
if (cidr === asked) {
return;
}
asked = cidr;
if (cidr === '') {
info.innerHTML = hint;
cidrInput.classList.remove('is-valid', 'is-invalid');
suggest('gateway', gatewayInput, '');
suggest('range', rangeInput, '');
return;
}
itflowGet('ajax.php', {network_subnet_info: 1, cidr: cidr, client_id: clientId, network_id: networkId}, function (text) {
if (cidrInput.value.trim() !== cidr) {
return;
}
let data;
try {
data = JSON.parse(text);
} catch (e) {
return;
}
info.innerHTML = '';
cidrInput.classList.toggle('is-invalid', !data.ok);
cidrInput.classList.toggle('is-valid', !!data.ok);
if (!data.ok) {
const line = document.createElement('span');
line.className = 'text-danger';
line.textContent = cidr.indexOf('/') === -1 ? 'Add the prefix - /24 for 254 hosts, /16 for 65,534.' : 'Not a subnet yet.';
info.append(line);
suggest('gateway', gatewayInput, '');
suggest('range', rangeInput, '');
return;
}
const line = document.createElement('div');
line.className = 'text-body-secondary';
const icon = document.createElement('i');
icon.className = 'fas fa-fw fa-check text-success me-1';
line.append(icon, data.size + ' · ' + data.first + ' - ' + data.last);
info.append(line);
if (data.cidr !== cidr) {
const saved = document.createElement('div');
saved.className = 'text-body-secondary';
saved.textContent = 'Saved as ' + data.cidr;
info.append(saved);
}
if (data.overlaps && data.overlaps.length) {
const overlap = document.createElement('div');
overlap.className = 'text-warning-emphasis';
const warn = document.createElement('i');
warn.className = 'fas fa-fw fa-clone me-1';
overlap.append(warn, 'Overlaps ' + data.overlaps.join(', '));
info.append(overlap);
}
suggest('gateway', gatewayInput, data.gateway);
suggest('range', rangeInput, data.range);
});
}
if (cidrInput && info) {
cidrInput.addEventListener('input', function () {
clearTimeout(timer);
timer = setTimeout(check, 300);
});
[gatewayInput, rangeInput].forEach(function (input) {
if (input) {
input.addEventListener('input', function () {
asked = null;
clearTimeout(timer);
timer = setTimeout(check, 300);
});
}
});
if (cidrInput.value.trim() !== '') {
check();
}
}
});
})();