diff --git a/api.php b/api.php index c9ba867..dc48f61 100644 --- a/api.php +++ b/api.php @@ -1882,47 +1882,12 @@ switch ($action) { echo json_encode(['success' => true]); break; - // ---------- SIJAINNIT (SITES) ---------- + // ---------- SIJAINNIT (SITES) — YHDISTETTY LAITETILOIHIN ---------- + // sites-endpoint palauttaa nyt laitetilat (taaksepäin yhteensopivuus) case 'sites': requireAuth(); $companyId = requireCompany(); - echo json_encode(dbLoadSites($companyId)); - break; - - case 'site_save': - requireAdmin(); - $companyId = requireCompany(); - if ($method !== 'POST') break; - $input = json_decode(file_get_contents('php://input'), true); - $site = [ - 'id' => $input['id'] ?? generateId(), - 'nimi' => trim($input['nimi'] ?? ''), - 'osoite' => trim($input['osoite'] ?? ''), - 'kaupunki' => trim($input['kaupunki'] ?? ''), - ]; - if (empty($site['nimi'])) { - http_response_code(400); - echo json_encode(['error' => 'Sijainnin nimi vaaditaan']); - break; - } - dbSaveSite($companyId, $site); - dbAddLog($companyId, currentUser(), 'site_save', $site['id'], $site['nimi'], (isset($input['id']) ? 'Muokkasi' : 'Lisäsi') . ' sijainnin'); - echo json_encode($site); - break; - - case 'site_delete': - requireAdmin(); - $companyId = requireCompany(); - if ($method !== 'POST') break; - $input = json_decode(file_get_contents('php://input'), true); - $id = $input['id'] ?? ''; - // Hae nimi logitusta varten - $sites = dbLoadSites($companyId); - $siteName = ''; - foreach ($sites as $s) { if ($s['id'] === $id) { $siteName = $s['nimi']; break; } } - dbDeleteSite($id); - dbAddLog($companyId, currentUser(), 'site_delete', $id, $siteName, 'Poisti sijainnin'); - echo json_encode(['success' => true]); + echo json_encode(dbLoadLaitetilat($companyId)); break; // ---------- LAITTEET (DEVICES) ---------- @@ -1943,7 +1908,7 @@ switch ($action) { 'nimi' => trim($input['nimi'] ?? ''), 'hallintaosoite' => trim($input['hallintaosoite'] ?? ''), 'serial' => trim($input['serial'] ?? ''), - 'site_id' => $input['site_id'] ?? null, + 'site_id' => null, 'laitetila_id' => $input['laitetila_id'] ?? null, 'funktio' => trim($input['funktio'] ?? ''), 'tyyppi' => trim($input['tyyppi'] ?? ''), @@ -1979,7 +1944,7 @@ switch ($action) { // Päivitä olemassa oleva: merkitse varatuksi laitteelle $existing['tila'] = 'varattu'; $existing['nimi'] = $device['nimi']; - $existing['site_id'] = $device['site_id']; + $existing['site_id'] = $device['laitetila_id']; $existing['muokattu'] = date('Y-m-d H:i:s'); $existing['muokkaaja'] = currentUser(); dbSaveIpam($companyId, $existing); @@ -1991,7 +1956,7 @@ switch ($action) { 'nimi' => $device['nimi'], 'verkko' => $cleanIp, 'vlan_id' => null, - 'site_id' => $device['site_id'], + 'site_id' => $device['laitetila_id'], 'tila' => 'varattu', 'asiakas' => '', 'lisatiedot' => 'Automaattinen varaus laitteelta: ' . $device['nimi'], diff --git a/db.php b/db.php index fe558f0..c5217a5 100644 --- a/db.php +++ b/db.php @@ -639,6 +639,20 @@ function initDatabase(): void { try { $db->query("UPDATE users SET role = 'user' WHERE role = 'admin'"); } catch (\Throwable $e) { /* ohitetaan */ } + + // Migraatio: yhdistä sites → laitetilat (kopioi vanhat sijainnit laitetiloiksi) + try { + // Kopioi sites-taulun rivit laitetilat-tauluun (ohita duplikaatit) + $db->query(" + INSERT IGNORE INTO laitetilat (id, company_id, nimi, kuvaus, osoite, luotu, muokattu, muokkaaja) + SELECT id, company_id, nimi, '', CONCAT(IFNULL(osoite,''), IF(kaupunki != '', CONCAT(', ', kaupunki), '')), NOW(), NOW(), '' + FROM sites + "); + // Päivitä laitteiden laitetila_id vanhoista site_id-viittauksista + $db->query("UPDATE devices SET laitetila_id = site_id WHERE laitetila_id IS NULL AND site_id IS NOT NULL"); + // Päivitä IPAM-merkintöjen site_id viittaamaan laitetiloihin (jo sama ID) + // (ei tarvitse muuttaa koska ID:t ovat samat) + } catch (\Throwable $e) { /* ohitetaan */ } } // ==================== YRITYKSET ==================== @@ -1040,40 +1054,16 @@ function dbDeleteCustomer(string $customerId): void { _dbExecute("DELETE FROM customers WHERE id = ?", [$customerId]); } -// ==================== SIJAINNIT (SITES) ==================== - -function dbLoadSites(string $companyId): array { - return _dbFetchAll("SELECT * FROM sites WHERE company_id = ? ORDER BY nimi", [$companyId]); -} - -function dbSaveSite(string $companyId, array $site): void { - _dbExecute(" - INSERT INTO sites (id, company_id, nimi, osoite, kaupunki) - VALUES (:id, :company_id, :nimi, :osoite, :kaupunki) - ON DUPLICATE KEY UPDATE - nimi = VALUES(nimi), osoite = VALUES(osoite), kaupunki = VALUES(kaupunki) - ", [ - 'id' => $site['id'], - 'company_id' => $companyId, - 'nimi' => $site['nimi'] ?? '', - 'osoite' => $site['osoite'] ?? '', - 'kaupunki' => $site['kaupunki'] ?? '', - ]); -} - -function dbDeleteSite(string $siteId): void { - // Nollaa viittaavien laitteiden site_id - _dbExecute("UPDATE devices SET site_id = NULL WHERE site_id = ?", [$siteId]); - _dbExecute("DELETE FROM sites WHERE id = ?", [$siteId]); -} +// ==================== SIJAINNIT (SITES) — POISTETTU, KÄYTETÄÄN LAITETILOJA ==================== +// Sites on yhdistetty laitetiloihin. Migraatio kopioi vanhat sites → laitetilat. +// dbLoadSites, dbSaveSite, dbDeleteSite poistettu. // ==================== LAITTEET (DEVICES) ==================== function dbLoadDevices(string $companyId): array { $devices = _dbFetchAll(" - SELECT d.*, s.nimi AS site_name, lt.nimi AS laitetila_name + SELECT d.*, lt.nimi AS laitetila_name FROM devices d - LEFT JOIN sites s ON d.site_id = s.id LEFT JOIN laitetilat lt ON d.laitetila_id = lt.id WHERE d.company_id = ? ORDER BY d.nimi @@ -1121,9 +1111,9 @@ function dbDeleteDevice(string $deviceId): void { function dbLoadIpam(string $companyId): array { $rows = _dbFetchAll(" - SELECT i.*, s.nimi AS site_name + SELECT i.*, lt.nimi AS site_name FROM ipam i - LEFT JOIN sites s ON i.site_id = s.id + LEFT JOIN laitetilat lt ON i.site_id = lt.id WHERE i.company_id = ? ORDER BY i.tyyppi, i.vlan_id, i.verkko ", [$companyId]); diff --git a/index.html b/index.html index 6473bff..a3879f0 100644 --- a/index.html +++ b/index.html @@ -203,7 +203,6 @@
-
@@ -241,35 +240,6 @@
- -
-
- -
- - - - - - - - - - - -
NimiOsoiteKaupunkiLaitteitaToiminnot
- -
-
- 0 sijaintia -
-
-
-
@@ -1763,15 +1733,9 @@
- - -
-
- +
@@ -1860,7 +1824,7 @@
- + diff --git a/script.js b/script.js index 6d0ee60..7b574b3 100644 --- a/script.js +++ b/script.js @@ -274,9 +274,9 @@ function switchToTab(target, subTab) { if (target === 'customers') loadCustomers(); if (target === 'leads') loadLeads(); if (target === 'tekniikka') { - loadDevices(); loadSitesTab(); loadIpam(); + loadDevices(); loadIpam(); // Palauta sub-tab - const validSubTabs = ['devices', 'sites', 'ipam']; + const validSubTabs = ['devices', 'ipam']; if (subTab && validSubTabs.includes(subTab)) switchSubTab(subTab); } if (target === 'archive') loadArchive(); @@ -2493,8 +2493,6 @@ async function showCompanyDetail(id) { // Lataa postilaatikot loadMailboxes(); - // Lataa sijainnit - loadSites(); // Lataa käyttäjäoikeudet loadCompanyUsers(id); } @@ -2848,7 +2846,6 @@ async function toggleCompanyUser(userId, companyId, add) { // ==================== LAITTEET (DEVICES) ==================== let devicesData = []; -let sitesData = []; async function loadDevices() { try { @@ -2865,7 +2862,7 @@ function renderDevices() { (d.nimi || '').toLowerCase().includes(query) || (d.hallintaosoite || '').toLowerCase().includes(query) || (d.serial || '').toLowerCase().includes(query) || - (d.site_name || '').toLowerCase().includes(query) || + (d.laitetila_name || '').toLowerCase().includes(query) || (d.funktio || '').toLowerCase().includes(query) || (d.tyyppi || '').toLowerCase().includes(query) || (d.malli || '').toLowerCase().includes(query) @@ -2884,7 +2881,7 @@ function renderDevices() { ${esc(d.nimi)} ${esc(d.hallintaosoite || '-')} ${esc(d.serial || '-')} - ${d.site_name ? esc(d.site_name) : '-'} + ${d.laitetila_name ? esc(d.laitetila_name) : '-'} ${esc(d.tyyppi || '-')} ${esc(d.funktio || '-')} ${esc(d.malli || '-')} @@ -2924,8 +2921,7 @@ async function editDevice(id) { document.getElementById('device-form-malli').value = d.malli || ''; document.getElementById('device-form-ping-check').checked = d.ping_check || false; document.getElementById('device-form-lisatiedot').value = d.lisatiedot || ''; - await loadSitesAndLaitetilatForDropdown(); - document.getElementById('device-form-site').value = d.site_id || ''; + await loadLaitetilatForDropdown(); document.getElementById('device-form-laitetila').value = d.laitetila_id || ''; document.getElementById('device-modal-title').textContent = 'Muokkaa laitetta'; document.getElementById('device-modal').style.display = 'flex'; @@ -2939,17 +2935,14 @@ async function deleteDevice(id, name) { } catch (e) { alert(e.message); } } -async function loadSitesForDropdown() { await loadSitesAndLaitetilatForDropdown(); } +async function loadSitesForDropdown() { await loadLaitetilatForDropdown(); } +async function loadSitesAndLaitetilatForDropdown() { await loadLaitetilatForDropdown(); } -async function loadSitesAndLaitetilatForDropdown() { +async function loadLaitetilatForDropdown() { try { - const [sites, tilat] = await Promise.all([apiCall('sites'), apiCall('laitetilat')]); - sitesData = sites; - const siteSel = document.getElementById('device-form-site'); - siteSel.innerHTML = '' + - sitesData.map(s => ``).join(''); + const tilat = await apiCall('laitetilat'); const tilaSel = document.getElementById('device-form-laitetila'); - tilaSel.innerHTML = '' + + tilaSel.innerHTML = '' + tilat.map(t => ``).join(''); } catch (e) { console.error(e); } } @@ -2957,7 +2950,7 @@ async function loadSitesAndLaitetilatForDropdown() { document.getElementById('btn-add-device')?.addEventListener('click', async () => { document.getElementById('device-form-id').value = ''; document.getElementById('device-form').reset(); - await loadSitesAndLaitetilatForDropdown(); + await loadLaitetilatForDropdown(); document.getElementById('device-modal-title').textContent = 'Lisää laite'; document.getElementById('device-modal').style.display = 'flex'; }); @@ -2976,7 +2969,6 @@ document.getElementById('device-form')?.addEventListener('submit', async (e) => nimi: document.getElementById('device-form-nimi').value.trim(), hallintaosoite: document.getElementById('device-form-hallintaosoite').value.trim(), serial: document.getElementById('device-form-serial').value.trim(), - site_id: document.getElementById('device-form-site').value || null, laitetila_id: document.getElementById('device-form-laitetila').value || null, funktio: document.getElementById('device-form-funktio').value.trim(), tyyppi: document.getElementById('device-form-tyyppi').value.trim(), @@ -3011,86 +3003,8 @@ document.querySelectorAll('#tab-content-tekniikka .sub-tab').forEach(btn => { btn.addEventListener('click', () => switchSubTab(btn.dataset.subtab)); }); -// ==================== SIJAINNIT (SITES) — TEKNIIKKA TAB ==================== - -let sitesTabData = []; - -async function loadSitesTab() { - try { - sitesData = await apiCall('sites'); - sitesTabData = sitesData; - renderSitesTab(); - } catch (e) { console.error(e); } -} - -function renderSitesTab() { - const query = (document.getElementById('site-search-input')?.value || '').toLowerCase().trim(); - let filtered = sitesTabData; - if (query) { - filtered = sitesTabData.filter(s => - (s.nimi || '').toLowerCase().includes(query) || - (s.osoite || '').toLowerCase().includes(query) || - (s.kaupunki || '').toLowerCase().includes(query) - ); - } - const tbody = document.getElementById('site-tbody'); - const noSites = document.getElementById('no-sites-tab'); - if (filtered.length === 0) { - tbody.innerHTML = ''; - if (noSites) noSites.style.display = 'block'; - } else { - if (noSites) noSites.style.display = 'none'; - tbody.innerHTML = filtered.map(s => { - const deviceCount = devicesData.filter(d => d.site_id === s.id).length; - return ` - ${esc(s.nimi)} - ${esc(s.osoite || '-')} - ${esc(s.kaupunki || '-')} - ${deviceCount} - - - - - `; - }).join(''); - } - document.getElementById('site-count').textContent = filtered.length + ' sijaintia'; -} - -function editSiteTab(id) { - const s = sitesData.find(x => x.id === id); - if (!s) return; - document.getElementById('site-form-id').value = s.id; - document.getElementById('site-form-nimi').value = s.nimi || ''; - document.getElementById('site-form-osoite').value = s.osoite || ''; - document.getElementById('site-form-kaupunki').value = s.kaupunki || ''; - document.getElementById('site-form-title').textContent = 'Muokkaa sijaintia'; - document.getElementById('site-form-container').style.display = ''; - // Varmista että asetukset-tab ja yrityksen tiedot näkyvissä, scrollaa lomakkeeseen - switchToTab('settings'); - document.getElementById('company-detail-view').style.display = ''; - document.getElementById('companies-list-view').style.display = 'none'; - setTimeout(() => document.getElementById('site-form-container')?.scrollIntoView({ behavior: 'smooth', block: 'center' }), 100); -} - -// Alias vanhalle editSite-funktiolle -function editSite(id) { editSiteTab(id); } - -async function deleteSite(id, name) { - if (!confirm(`Poistetaanko sijainti "${name}"? Laitteet joissa tämä sijainti on menettävät sijainti-viittauksen.`)) return; - try { - await apiCall('site_delete', 'POST', { id }); - loadSitesTab(); - loadDevices(); - } catch (e) { alert(e.message); } -} - -// Alias loadSites (Tekniikka sub-tab kutsuun) -async function loadSites() { await loadSitesTab(); } - -function renderSites() {} - -document.getElementById('site-search-input')?.addEventListener('input', () => renderSitesTab()); +// ==================== SIJAINNIT — YHDISTETTY LAITETILOIHIN ==================== +// Sites-koodi poistettu: sijainnit hallitaan nyt Laitetilat-välilehdellä. // ==================== IPAM ==================== @@ -3521,12 +3435,13 @@ async function ipamAddFromFree(verkko) { document.getElementById('ipam-form-nimi')?.focus(); } +let _ipamLaitetilatCache = null; async function loadIpamSitesDropdown() { try { - if (!sitesData || sitesData.length === 0) sitesData = await apiCall('sites'); + if (!_ipamLaitetilatCache || _ipamLaitetilatCache.length === 0) _ipamLaitetilatCache = await apiCall('laitetilat'); const sel = document.getElementById('ipam-form-site'); sel.innerHTML = '' + - sitesData.map(s => ``).join(''); + _ipamLaitetilatCache.map(t => ``).join(''); } catch (e) { console.error(e); } }