From 7ed17c163f83c3fdb407a2b5a50b6f4b23adf609 Mon Sep 17 00:00:00 2001 From: Jukka Lampikoski Date: Thu, 12 Mar 2026 00:20:42 +0200 Subject: [PATCH] =?UTF-8?q?Liittymien=20VLAN/Laite/IP-kent=C3=A4t=20hakeva?= =?UTF-8?q?t=20nyt=20tiedot=20IPAM:sta=20ja=20laiterekisterist=C3=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - NetAdmin liittymälomakkeen VLAN, Laite ja IP muutettu tekstikentistä dropdown-valikoiksi - Asiakasformin liittymäkentät samoin muutettu dropdown-valikoiksi - Dropdownit populoidaan IPAM:n VLANeista, IP-osoitteista ja Tekniikan laiterekisteristä - IP-dropdown ryhmittelee vapaat ja varatut IP:t optgroupeilla - Laite-dropdown näyttää ping-statuksen, hallintaosoitteen ja mallin - VLAN-dropdown näyttää VLAN ID:n, nimen ja sijainnin - Jos nykyinen arvo ei ole IPAM/laiterekisterissä, näytetään se (manuaalinen)-lisätekstillä - IPAM-tilan automaattipäivitys: kun liittymälle asetetaan IP, IPAM merkitsee sen varatuksi - Kun IP poistetaan tai vaihdetaan, vanha IP vapautetaan IPAM:ssa automaattisesti - API palauttaa nyt vlans ja ips -listat netadmin_connections-endpointissa Co-Authored-By: Claude Opus 4.6 --- api.php | 43 +++++++++++++- index.html | 12 +++- script.js | 164 ++++++++++++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 206 insertions(+), 13 deletions(-) diff --git a/api.php b/api.php index 6ca3e26..b7139e8 100644 --- a/api.php +++ b/api.php @@ -4412,6 +4412,11 @@ switch ($action) { $deviceMap = []; foreach ($devices as $d) { $deviceMap[$d['nimi']] = $d; } + // Hae IPAM VLANit ja IP:t dropdown-valikkoja varten + $ipamAll = dbLoadIpam($companyId); + $vlans = array_values(array_filter($ipamAll, fn($e) => $e['tyyppi'] === 'vlan')); + $ips = array_values(array_filter($ipamAll, fn($e) => $e['tyyppi'] === 'ip')); + // Rikasta liittymädata laitetiedoilla foreach ($connections as &$conn) { $deviceName = $conn['laite'] ?? ''; @@ -4423,7 +4428,9 @@ switch ($action) { echo json_encode([ 'connections' => $connections, 'total' => count($connections), - 'devices' => $devices + 'devices' => $devices, + 'vlans' => $vlans, + 'ips' => $ips ]); } catch (Exception $e) { http_response_code(500); @@ -4472,6 +4479,40 @@ switch ($action) { echo json_encode(['error' => 'Liittymää ei löytynyt']); break; } + // Automaattinen IPAM-tilan päivitys kun IP muuttuu + $oldIp = trim($conn['ip'] ?? ''); + $newIp = trim($input['ip'] ?? ''); + if ($oldIp !== $newIp) { + $ipamAll = dbLoadIpam($companyId); + // Vapauta vanha IP + if ($oldIp) { + foreach ($ipamAll as $entry) { + if ($entry['tyyppi'] === 'ip' && $entry['verkko'] === $oldIp && $entry['tila'] === 'varattu') { + $entry['tila'] = 'vapaa'; + $entry['asiakas'] = ''; + $entry['muokattu'] = date('Y-m-d H:i:s'); + $entry['muokkaaja'] = currentUser(); + dbSaveIpam($companyId, $entry); + break; + } + } + } + // Varaa uusi IP + if ($newIp) { + $customerName = $conn['customer_name'] ?? ''; + foreach ($ipamAll as $entry) { + if ($entry['tyyppi'] === 'ip' && $entry['verkko'] === $newIp) { + $entry['tila'] = 'varattu'; + $entry['asiakas'] = $customerName; + $entry['muokattu'] = date('Y-m-d H:i:s'); + $entry['muokkaaja'] = currentUser(); + dbSaveIpam($companyId, $entry); + break; + } + } + } + } + dbUpdateConnection($connId, $input); $updated = dbLoadConnection($connId); echo json_encode($updated); diff --git a/index.html b/index.html index 1cd669e..ac28ccb 100644 --- a/index.html +++ b/index.html @@ -1105,11 +1105,15 @@
- +
- +
@@ -1117,7 +1121,9 @@
- +
diff --git a/script.js b/script.js index 04c9c0b..5e137ae 100644 --- a/script.js +++ b/script.js @@ -684,15 +684,70 @@ function createLiittymaRow(data = {}, index = 0) {
-
-
+
+
-
+
`; div.querySelector('.btn-remove-row').addEventListener('click', () => { div.remove(); renumberLiittymaRows(); }); + // Populoi dropdownit IPAM/laite-datasta + populateLiittymaRowDropdowns(div, data); return div; } +// Populoi yksittäisen liittymärivin VLAN/Laite/IP dropdownit +function populateLiittymaRowDropdowns(row, data = {}) { + const vlanSel = row.querySelector('.l-vlan'); + const laiteSel = row.querySelector('.l-laite'); + const ipSel = row.querySelector('.l-ip'); + // Käytä netadminData:a tai fallback ipamData/devicesData:an + const vlans = (netadminData.vlans && netadminData.vlans.length) ? netadminData.vlans : (ipamData || []).filter(e => e.tyyppi === 'vlan'); + const ips = (netadminData.ips && netadminData.ips.length) ? netadminData.ips : (ipamData || []).filter(e => e.tyyppi === 'ip'); + const devices = (netadminData.devices && netadminData.devices.length) ? netadminData.devices : (devicesData || []); + // VLAN + let vlanHtml = ''; + [...vlans].sort((a, b) => (a.vlan_id || 0) - (b.vlan_id || 0)).forEach(v => { + const val = String(v.vlan_id || ''); + const label = val + (v.nimi ? ` — ${v.nimi}` : ''); + vlanHtml += ``; + }); + if (data.vlan && !vlans.some(v => String(v.vlan_id) === String(data.vlan))) { + vlanHtml += ``; + } + vlanSel.innerHTML = vlanHtml; + vlanSel.value = data.vlan || ''; + // Laite + let laiteHtml = ''; + [...devices].sort((a, b) => (a.nimi || '').localeCompare(b.nimi || '')).forEach(d => { + const label = d.nimi + (d.hallintaosoite ? ` (${d.hallintaosoite})` : ''); + laiteHtml += ``; + }); + if (data.laite && !devices.some(d => d.nimi === data.laite)) { + laiteHtml += ``; + } + laiteSel.innerHTML = laiteHtml; + laiteSel.value = data.laite || ''; + // IP + let ipHtml = ''; + const free = ips.filter(i => i.tila === 'vapaa').sort((a, b) => (a.verkko || '').localeCompare(b.verkko || '')); + const taken = ips.filter(i => i.tila === 'varattu').sort((a, b) => (a.verkko || '').localeCompare(b.verkko || '')); + if (free.length) { + ipHtml += ''; + free.forEach(i => { ipHtml += ``; }); + ipHtml += ''; + } + if (taken.length) { + ipHtml += ''; + taken.forEach(i => { ipHtml += ``; }); + ipHtml += ''; + } + if (data.ip && !ips.some(i => i.verkko === data.ip)) { + ipHtml += ``; + } + ipSel.innerHTML = ipHtml; + ipSel.value = data.ip || ''; +} + function renumberLiittymaRows() { document.getElementById('liittymat-container').querySelectorAll('.liittyma-row').forEach((row, i) => { row.dataset.index = i; @@ -739,7 +794,7 @@ document.getElementById('btn-add').addEventListener('click', () => openCustomerF document.getElementById('modal-close').addEventListener('click', () => customerModal.style.display = 'none'); document.getElementById('form-cancel').addEventListener('click', () => customerModal.style.display = 'none'); -function openCustomerForm(customer = null) { +async function openCustomerForm(customer = null) { const c = customer; document.getElementById('modal-title').textContent = c ? 'Muokkaa asiakasta' : 'Lisää asiakas'; document.getElementById('form-submit').textContent = c ? 'Päivitä' : 'Tallenna'; @@ -759,6 +814,8 @@ function openCustomerForm(customer = null) { document.getElementById('form-priority-emails').value = c ? (c.priority_emails || '') : ''; document.getElementById('form-billing-same').checked = false; document.getElementById('billing-fields').style.display = 'block'; + // Lataa IPAM/laite-data dropdowendeja varten (jos ei vielä ladattu) + await ensureIpamDevicesLoaded(); const container = document.getElementById('liittymat-container'); container.innerHTML = ''; (c ? (c.liittymat || []) : [{}]).forEach((l, i) => container.appendChild(createLiittymaRow(l, i))); @@ -766,7 +823,27 @@ function openCustomerForm(customer = null) { document.getElementById('form-yritys').focus(); } -function editCustomer(id) { const c = customers.find(x => x.id === id); if (c) openCustomerForm(c); } +// Varmista IPAM/laite-data on ladattu dropdowneja varten +async function ensureIpamDevicesLoaded() { + try { + // Jos netadminData:ssa ei ole IPAM-dataa, lataa suoraan + if (!netadminData.vlans || !netadminData.vlans.length || !netadminData.devices || !netadminData.devices.length) { + const [ipam, devices] = await Promise.all([ + apiCall('ipam'), + apiCall('devices') + ]); + if (!netadminData.vlans || !netadminData.vlans.length) { + netadminData.vlans = ipam.filter(e => e.tyyppi === 'vlan'); + netadminData.ips = ipam.filter(e => e.tyyppi === 'ip'); + } + if (!netadminData.devices || !netadminData.devices.length) { + netadminData.devices = devices; + } + } + } catch (e) { console.error('IPAM/laite-datan lataus epäonnistui:', e); } +} + +async function editCustomer(id) { const c = customers.find(x => x.id === id); if (c) await openCustomerForm(c); } async function deleteCustomer(id, name) { if (!confirm(`Arkistoidaanko asiakas "${name}"?\n\nAsiakas siirretään arkistoon, josta sen voi palauttaa.`)) return; @@ -4518,7 +4595,7 @@ document.getElementById('btn-time-save')?.addEventListener('click', () => addTim // ==================== NETADMIN ==================== -let netadminData = { connections: [], devices: [] }; +let netadminData = { connections: [], devices: [], vlans: [], ips: [] }; async function loadNetadmin() { try { @@ -4615,6 +4692,74 @@ document.getElementById('netadmin-filter-city')?.addEventListener('change', rend document.getElementById('netadmin-filter-speed')?.addEventListener('change', renderNetadminTable); document.getElementById('netadmin-filter-device')?.addEventListener('change', renderNetadminTable); +// Populoi VLAN-dropdown IPAM VLANeista +function populateVlanDropdown(selectEl, currentValue) { + const vlans = netadminData.vlans || []; + let html = ''; + vlans.sort((a, b) => (a.vlan_id || 0) - (b.vlan_id || 0)); + vlans.forEach(v => { + const val = String(v.vlan_id || ''); + const label = val + (v.nimi ? ` — ${v.nimi}` : '') + (v.site_name ? ` (${v.site_name})` : ''); + html += ``; + }); + // Jos nykyinen arvo ei ole listalla, lisää se custom-optiona + if (currentValue && !vlans.some(v => String(v.vlan_id) === String(currentValue))) { + html += ``; + } + selectEl.innerHTML = html; + selectEl.value = currentValue || ''; +} + +// Populoi laite-dropdown +function populateDeviceDropdown(selectEl, currentValue) { + const devices = netadminData.devices || []; + let html = ''; + devices.sort((a, b) => (a.nimi || '').localeCompare(b.nimi || '')); + devices.forEach(d => { + const label = d.nimi + (d.hallintaosoite ? ` (${d.hallintaosoite})` : '') + (d.malli ? ` — ${d.malli}` : ''); + const pingDot = d.ping_status === 'up' ? '🟢 ' : d.ping_status === 'down' ? '🔴 ' : ''; + html += ``; + }); + // Jos nykyinen arvo ei ole listalla, lisää se custom-optiona + if (currentValue && !devices.some(d => d.nimi === currentValue)) { + html += ``; + } + selectEl.innerHTML = html; + selectEl.value = currentValue || ''; +} + +// Populoi IP-dropdown IPAM IP-osoitteista +function populateIpDropdown(selectEl, currentValue) { + const ips = netadminData.ips || []; + let html = ''; + // Ryhmittele: vapaat ensin, sitten varatut + const free = ips.filter(i => i.tila === 'vapaa').sort((a, b) => (a.verkko || '').localeCompare(b.verkko || '')); + const taken = ips.filter(i => i.tila === 'varattu').sort((a, b) => (a.verkko || '').localeCompare(b.verkko || '')); + + if (free.length) { + html += ''; + free.forEach(i => { + const label = i.verkko + (i.nimi ? ` — ${i.nimi}` : '') + (i.site_name ? ` (${i.site_name})` : ''); + html += ``; + }); + html += ''; + } + if (taken.length) { + html += ''; + taken.forEach(i => { + const label = i.verkko + (i.asiakas ? ` ← ${i.asiakas}` : '') + (i.nimi ? ` — ${i.nimi}` : ''); + html += ``; + }); + html += ''; + } + // Jos nykyinen arvo ei ole listalla, lisää se custom-optiona + if (currentValue && !ips.some(i => i.verkko === currentValue)) { + html += ``; + } + selectEl.innerHTML = html; + selectEl.value = currentValue || ''; +} + async function openNetadminDetail(connId) { try { const conn = await apiCall(`netadmin_connection&id=${connId}`); @@ -4634,10 +4779,11 @@ async function openNetadminDetail(connId) { speedSel.insertBefore(opt, speedSel.lastElementChild); } speedSel.value = speed; - document.getElementById('na-edit-vlan').value = conn.vlan || ''; - document.getElementById('na-edit-laite').value = conn.laite || ''; + // Populoi VLAN, Laite ja IP dropdownit IPAM/Tekniikka-datasta + populateVlanDropdown(document.getElementById('na-edit-vlan'), conn.vlan || ''); + populateDeviceDropdown(document.getElementById('na-edit-laite'), conn.laite || ''); + populateIpDropdown(document.getElementById('na-edit-ip'), conn.ip || ''); document.getElementById('na-edit-portti').value = conn.portti || ''; - document.getElementById('na-edit-ip').value = conn.ip || ''; document.getElementById('netadmin-detail-modal').style.display = ''; } catch (e) { alert('Liittymän avaus epäonnistui: ' + e.message); } }