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 += '';
+ }
+ if (taken.length) {
+ 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 += '';
+ }
+ if (taken.length) {
+ 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); }
}