From 9140c912cdf29c3df1d22d5937d57de116f23074 Mon Sep 17 00:00:00 2001 From: Jukka Lampikoski Date: Tue, 10 Mar 2026 20:18:56 +0200 Subject: [PATCH] feat: Tekniikka-moduuli sub-tabeilla (Laitteet + Sijainnit + IPAM) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Laitteet-tabi → Tekniikka (sub-tabit: Laitteet, Sijainnit, IPAM) - Sijainnit siirretty omaksi taulukkonäkymäksi (+ "Lisää sijainti" laitteiden yhteydessä) - Uusi IPAM-näkymä: IP-osoitteet, subnetit ja VLANit hallintaan - IPAM: tyyppi (subnet/vlan/ip), verkko, VLAN-nro, sijainti, tila, asiakas - Sub-tab-tyylit ja logiikka - Yhteensopivuus: vanha 'devices' moduuli → 'tekniikka' Co-Authored-By: Claude Opus 4.6 --- api.php | 47 ++++++++++ db.php | 64 +++++++++++++ index.html | 192 +++++++++++++++++++++++++++++++------ script.js | 271 +++++++++++++++++++++++++++++++++++++++++++++++------ style.css | 40 ++++++++ 5 files changed, 558 insertions(+), 56 deletions(-) diff --git a/api.php b/api.php index e2c04e3..3d21331 100644 --- a/api.php +++ b/api.php @@ -1408,6 +1408,53 @@ switch ($action) { echo json_encode(['success' => true]); break; + // ---------- IPAM ---------- + case 'ipam': + requireAuth(); + $companyId = requireCompany(); + echo json_encode(dbLoadIpam($companyId)); + break; + + case 'ipam_save': + requireAuth(); + $companyId = requireCompany(); + if ($method !== 'POST') break; + $input = json_decode(file_get_contents('php://input'), true); + $entry = [ + 'id' => $input['id'] ?? generateId(), + 'tyyppi' => $input['tyyppi'] ?? 'ip', + 'nimi' => trim($input['nimi'] ?? ''), + 'verkko' => trim($input['verkko'] ?? ''), + 'vlan_id' => $input['vlan_id'] ?? null, + 'site_id' => $input['site_id'] ?? null, + 'tila' => $input['tila'] ?? 'vapaa', + 'asiakas' => trim($input['asiakas'] ?? ''), + 'lisatiedot' => trim($input['lisatiedot'] ?? ''), + 'luotu' => $input['luotu'] ?? date('Y-m-d H:i:s'), + 'muokattu' => date('Y-m-d H:i:s'), + 'muokkaaja' => currentUser(), + ]; + dbSaveIpam($companyId, $entry); + $action_label = isset($input['id']) && !empty($input['id']) ? 'ipam_update' : 'ipam_create'; + $desc = ($entry['tyyppi'] === 'vlan' ? 'VLAN ' . ($entry['vlan_id'] ?? '') : $entry['verkko']) . ' ' . $entry['nimi']; + dbAddLog($companyId, currentUser(), $action_label, $entry['id'], $desc, $action_label === 'ipam_create' ? 'Lisäsi IPAM-merkinnän' : 'Muokkasi IPAM-merkintää'); + echo json_encode($entry); + break; + + case 'ipam_delete': + requireAuth(); + $companyId = requireCompany(); + if ($method !== 'POST') break; + $input = json_decode(file_get_contents('php://input'), true); + $id = $input['id'] ?? ''; + $all = dbLoadIpam($companyId); + $entryName = ''; + foreach ($all as $e) { if ($e['id'] === $id) { $entryName = ($e['tyyppi'] === 'vlan' ? 'VLAN ' . $e['vlan_id'] : $e['verkko']) . ' ' . $e['nimi']; break; } } + dbDeleteIpam($id); + dbAddLog($companyId, currentUser(), 'ipam_delete', $id, $entryName, 'Poisti IPAM-merkinnän'); + echo json_encode(['success' => true]); + break; + // ---------- ARCHIVE ---------- case 'archived_customers': requireAuth(); diff --git a/db.php b/db.php index acb01d5..b7e6c97 100644 --- a/db.php +++ b/db.php @@ -381,6 +381,24 @@ function initDatabase(): void { INDEX idx_company (company_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", + "CREATE TABLE IF NOT EXISTS ipam ( + id VARCHAR(20) PRIMARY KEY, + company_id VARCHAR(50) NOT NULL, + tyyppi VARCHAR(20) NOT NULL DEFAULT 'ip', + nimi VARCHAR(255) DEFAULT '', + verkko VARCHAR(50) DEFAULT '', + vlan_id INT DEFAULT NULL, + site_id VARCHAR(20) NULL, + tila VARCHAR(20) DEFAULT 'vapaa', + asiakas VARCHAR(255) DEFAULT '', + lisatiedot TEXT, + luotu DATETIME, + muokattu DATETIME NULL, + muokkaaja VARCHAR(100) DEFAULT '', + FOREIGN KEY (company_id) REFERENCES companies(id) ON DELETE CASCADE, + INDEX idx_company (company_id) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", + "CREATE TABLE IF NOT EXISTS files ( id INT AUTO_INCREMENT PRIMARY KEY, company_id VARCHAR(50) NOT NULL, @@ -868,6 +886,52 @@ function dbDeleteDevice(string $deviceId): void { _dbExecute("DELETE FROM devices WHERE id = ?", [$deviceId]); } +// ==================== IPAM ==================== + +function dbLoadIpam(string $companyId): array { + $rows = _dbFetchAll(" + SELECT i.*, s.nimi AS site_name + FROM ipam i + LEFT JOIN sites s ON i.site_id = s.id + WHERE i.company_id = ? + ORDER BY i.tyyppi, i.vlan_id, i.verkko + ", [$companyId]); + foreach ($rows as &$r) { + unset($r['company_id']); + } + return $rows; +} + +function dbSaveIpam(string $companyId, array $entry): void { + _dbExecute(" + INSERT INTO ipam (id, company_id, tyyppi, nimi, verkko, vlan_id, site_id, tila, asiakas, lisatiedot, luotu, muokattu, muokkaaja) + VALUES (:id, :company_id, :tyyppi, :nimi, :verkko, :vlan_id, :site_id, :tila, :asiakas, :lisatiedot, :luotu, :muokattu, :muokkaaja) + ON DUPLICATE KEY UPDATE + tyyppi = VALUES(tyyppi), nimi = VALUES(nimi), verkko = VALUES(verkko), + vlan_id = VALUES(vlan_id), site_id = VALUES(site_id), tila = VALUES(tila), + asiakas = VALUES(asiakas), lisatiedot = VALUES(lisatiedot), + muokattu = VALUES(muokattu), muokkaaja = VALUES(muokkaaja) + ", [ + 'id' => $entry['id'], + 'company_id' => $companyId, + 'tyyppi' => $entry['tyyppi'] ?? 'ip', + 'nimi' => $entry['nimi'] ?? '', + 'verkko' => $entry['verkko'] ?? '', + 'vlan_id' => !empty($entry['vlan_id']) ? (int)$entry['vlan_id'] : null, + 'site_id' => !empty($entry['site_id']) ? $entry['site_id'] : null, + 'tila' => $entry['tila'] ?? 'vapaa', + 'asiakas' => $entry['asiakas'] ?? '', + 'lisatiedot' => $entry['lisatiedot'] ?? '', + 'luotu' => $entry['luotu'] ?? date('Y-m-d H:i:s'), + 'muokattu' => $entry['muokattu'] ?? null, + 'muokkaaja' => $entry['muokkaaja'] ?? '', + ]); +} + +function dbDeleteIpam(string $id): void { + _dbExecute("DELETE FROM ipam WHERE id = ?", [$id]); +} + // ==================== LIIDIT ==================== function dbLoadLeads(string $companyId): array { diff --git a/index.html b/index.html index 1a36055..0cb17f7 100644 --- a/index.html +++ b/index.html @@ -79,7 +79,7 @@ - + @@ -194,35 +194,108 @@ -
-
- -
- - - - - - - - - - - - - - - -
Nimi ↕HallintaosoiteSerialSijaintiFunktioTyyppiMalliPingToiminnot
- + + +