From a84acfc38f54d17659ce8a18de1b930f03ace9cd Mon Sep 17 00:00:00 2001 From: Voxan Date: Wed, 21 Oct 2026 22:00:44 +0200 Subject: [PATCH] Import --- admin/manual_add.php | 249 +++++++++++++++++++++ admin/map_layers.php | 197 +++++++++++++++++ admin/operator_sites.php | 185 ++++++++++++++++ admin/review.php | 383 +++++++++++++++++++++++++++++++++ admin/search_site.php | 454 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 1468 insertions(+) create mode 100644 admin/manual_add.php create mode 100644 admin/map_layers.php create mode 100644 admin/operator_sites.php create mode 100644 admin/review.php create mode 100644 admin/search_site.php diff --git a/admin/manual_add.php b/admin/manual_add.php new file mode 100644 index 0000000..d221251 --- /dev/null +++ b/admin/manual_add.php @@ -0,0 +1,249 @@ +query('SELECT id, country_id, mcc, mnc, brand FROM operators ORDER BY brand')->fetchAll(PDO::FETCH_ASSOC); +$successMessage = null; +$errorMessage = null; + +function composeLocationDesc(string $tech, ?string $enb, ?string $band, ?string $frequency, $azimuth, ?string $bandwidth, string $address): string +{ + $techLabel = ['2G' => 'GSM', '3G' => 'UMTS', '4G' => 'LTE', '5G' => 'NR', 'CDMA' => 'CDMA'][$tech] ?? $tech; + $segments = []; + + if ($enb !== null && $enb !== '') { + $segments[] = "eNB ID $enb"; + } + + $radioLine = trim($techLabel . ' ' . ($band ?? '') . ' ' . ($frequency ?? '')); + if ($radioLine !== $techLabel) { + $segments[] = $radioLine; + } + + if ($azimuth !== null && $azimuth !== '') { + $segments[] = $azimuth . '°'; + } + + if ($bandwidth !== null && $bandwidth !== '') { + $segments[] = $bandwidth; + } + + if ($address !== '') { + $segments[] = $address; + } + + return implode(' - ', $segments); +} + +if ($_SERVER['REQUEST_METHOD'] === 'POST') { + require_csrf_token(); + + $operatorId = (int)($_POST['operator_id'] ?? 0); + $lat = (float)($_POST['lat'] ?? 0); + $lon = (float)($_POST['lon'] ?? 0); + $addressDesc = trim($_POST['address_desc'] ?? ''); + $batchLabel = trim($_POST['batch_label'] ?? ('Saisie manuelle - ' . $addressDesc)); + + $techs = $_POST['cell_tech'] ?? []; + $cellIds = $_POST['cell_id'] ?? []; + $areaCodes = $_POST['cell_area_code'] ?? []; + $subIds = $_POST['cell_sub_id'] ?? []; + $subIds2 = $_POST['cell_sub_id2'] ?? []; + $arfcns = $_POST['cell_arfcn'] ?? []; + $bands = $_POST['cell_band'] ?? []; + $frequencies = $_POST['cell_frequency'] ?? []; + $azimuths = $_POST['cell_azimuth'] ?? []; + $bandwidths = $_POST['cell_bandwidth'] ?? []; + + if (!$operatorId || !$lat || !$lon || empty($techs)) { + $errorMessage = "Operateur, coordonnees et au moins une cellule sont obligatoires."; + } else { + $opStmt = $db->prepare('SELECT mcc, mnc FROM operators WHERE id = ?'); + $opStmt->execute([$operatorId]); + $op = $opStmt->fetch(PDO::FETCH_ASSOC); + + if (!$op) { + $errorMessage = "Operateur invalide."; + } else { + $db->beginTransaction(); + try { + $batchStmt = $db->prepare('INSERT INTO staging_batches (label, created_by) VALUES (?, ?)'); + $batchStmt->execute([$batchLabel, $user]); + $batchId = (int)$db->lastInsertId(); + + $insertStmt = $db->prepare(' + INSERT INTO staging_cells + (batch_id, tech, mcc, mnc, operator_guess, cell_id, area_code, sub_id, sub_id2, + band, frequency, azimuth, bandwidth, address_desc, + lat, lon, location_desc, arfcn, source, status) + VALUES + (:batch_id, :tech, :mcc, :mnc, :operator_guess, :cell_id, :area_code, :sub_id, :sub_id2, + :band, :frequency, :azimuth, :bandwidth, :address_desc, + :lat, :lon, :location_desc, :arfcn, "manual_form", "pending") + '); + + $count = 0; + foreach ($techs as $i => $tech) { + if ($tech === '' || $cellIds[$i] === '' || $areaCodes[$i] === '') continue; + + $enb = $subIds2[$i] !== '' ? $subIds2[$i] : null; + $band = $bands[$i] !== '' ? $bands[$i] : null; + $frequency = $frequencies[$i] !== '' ? $frequencies[$i] : null; + $azimuth = $azimuths[$i] !== '' ? (int)$azimuths[$i] : null; + $bandwidth = $bandwidths[$i] !== '' ? $bandwidths[$i] : null; + + $composedLocation = composeLocationDesc($tech, $enb, $band, $frequency, $azimuth, $bandwidth, $addressDesc); + + $insertStmt->execute([ + ':batch_id' => $batchId, ':tech' => $tech, ':mcc' => $op['mcc'], ':mnc' => $op['mnc'], ':operator_guess' => $operatorId, + ':cell_id' => $cellIds[$i], ':area_code' => $areaCodes[$i], + ':sub_id' => $subIds[$i] !== '' ? $subIds[$i] : null, ':sub_id2' => $enb, + ':band' => $band, ':frequency' => $frequency, ':azimuth' => $azimuth, ':bandwidth' => $bandwidth, + ':address_desc' => $addressDesc, + ':lat' => $lat, ':lon' => $lon, ':location_desc' => $composedLocation, + ':arfcn' => $arfcns[$i] !== '' ? $arfcns[$i] : null, + ]); + $count++; + } + $db->commit(); + $successMessage = "$count cellule(s) envoyee(s) en validation (lot #$batchId)."; + } catch (Throwable $e) { + $db->rollBack(); + $errorMessage = "Erreur : " . $e->getMessage(); + } + } + } +} +?> + + + +NetMonster Admin - Ajout manuel + + + + + + +
+

Ajout manuel d'un site

+

Un site peut porter plusieurs cellules/secteurs, chacune avec sa propre bande, frequence, azimut et largeur de bande.

+ +

+

+ +
+ +
+ Localisation du site +
+
+ + +
+
+
+
+
+
+
+
+
+ +
+ Cellules / secteurs de ce site + + + + + + + + + + +
TechnoCell IDArea codeSub ID (BSIC/PSC/PCI)eNB/RNCBandeFrequenceAzimut (°)Largeur bandeARFCN
+ +

Apercu du champ Location genere pour la premiere ligne :

+
—
+
+ + +
+
+ + + + \ No newline at end of file diff --git a/admin/map_layers.php b/admin/map_layers.php new file mode 100644 index 0000000..cf66cdc --- /dev/null +++ b/admin/map_layers.php @@ -0,0 +1,197 @@ +prepare( + 'INSERT INTO map_layers (name, layer_key, color, is_visible, sort_order, description) + VALUES (?, ?, ?, 1, (SELECT COALESCE(MAX(sort_order), 0) + 1 FROM map_layers AS m2), ?)' + ); + $stmt->execute([$name, $layerKey, $color, $description]); + $feedback = 'Couche créée.'; + } catch (Throwable $e) { + $feedback = 'Erreur lors de la création (clé·· déjà utilisée ?) : ' . $e->getMessage(); + $feedbackType = 'error'; + } + } + } elseif ($action === 'toggle_visibility') { + $layerId = filter_input(INPUT_POST, 'layer_id', FILTER_VALIDATE_INT); + if ($layerId) { + $stmt = $db->prepare('UPDATE map_layers SET is_visible = 1 - is_visible WHERE id = ?'); + $stmt->execute([$layerId]); + $feedback = 'Visibilité·· mise à jour.'; + } + } elseif ($action === 'update_color') { + $layerId = filter_input(INPUT_POST, 'layer_id', FILTER_VALIDATE_INT); + $color = sanitize_hex_color((string)($_POST['color'] ?? '')); + if ($layerId && $color !== null) { + $stmt = $db->prepare('UPDATE map_layers SET color = ? WHERE id = ?'); + $stmt->execute([$color, $layerId]); + $feedback = 'Couleur mise à jour.'; + } else { + $feedback = 'Couleur invalide.'; + $feedbackType = 'error'; + } + } elseif ($action === 'delete_layer') { + $layerId = filter_input(INPUT_POST, 'layer_id', FILTER_VALIDATE_INT); + $confirmed = isset($_POST['confirm']) && $_POST['confirm'] === '1'; + if (!$confirmed || !$layerId) { + $feedback = 'Suppression refusé··e : confirmation manquante.'; + $feedbackType = 'error'; + } else { + $stmt = $db->prepare('DELETE FROM map_layers WHERE id = ? LIMIT 1'); + $stmt->execute([$layerId]); + $feedback = $stmt->rowCount() > 0 ? 'Couche supprimé··e.' : 'Couche introuvable.'; + } + } +} + +$layers = $db->query('SELECT id, name, layer_key, color, is_visible, sort_order, description FROM map_layers ORDER BY sort_order ASC')->fetchAll(PDO::FETCH_ASSOC); +$user = current_admin_username(); +?> + + + + + Couches cartographiques — NetMonster Admin + + + + + +
+

Couches cartographiques

+

Gé··rez les couches affich ées sur la carte (visibilité··, couleur, ordre). Toute suppression demande une confirmation explicite.

+ + +
+ +
+ + +
+

Nouvelle couche

+
+ + + + + + + +
+
+ +
+

Couches existantes ()

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
OrdreNomClé··CouleurVisibleDescriptionActions
+
+ + + + +
+
+
+ + + + +
+
+
+ + + + + +
+
Aucune couche configuré··e.
+
+
+ + \ No newline at end of file diff --git a/admin/operator_sites.php b/admin/operator_sites.php new file mode 100644 index 0000000..e671a63 --- /dev/null +++ b/admin/operator_sites.php @@ -0,0 +1,185 @@ +query('SELECT id, name FROM countries ORDER BY name')->fetchAll(PDO::FETCH_ASSOC); + +$countryId = filter_input(INPUT_GET, 'country_id', FILTER_VALIDATE_INT) ?: null; + +$operatorsSql = 'SELECT id, name, mcc, mnc, country_id FROM operators'; +$operatorsParams = []; +if ($countryId) { + $operatorsSql .= ' WHERE country_id = ?'; + $operatorsParams[] = $countryId; +} +$operatorsSql .= ' ORDER BY name'; +$stmt = $db->prepare($operatorsSql); +$stmt->execute($operatorsParams); +$operators = $stmt->fetchAll(PDO::FETCH_ASSOC); + +$operatorId = filter_input(INPUT_GET, 'operator_id', FILTER_VALIDATE_INT) ?: null; + +$page = max(1, filter_input(INPUT_GET, 'page', FILTER_VALIDATE_INT) ?: 1); +$perPage = 50; +$offset = ($page - 1) * $perPage; + +$sites = []; +$totalSites = 0; +$stats = null; + +if ($operatorId) { + $countStmt = $db->prepare('SELECT COUNT(*) FROM sites WHERE operator_id = ?'); + $countStmt->execute([$operatorId]); + $totalSites = (int)$countStmt->fetchColumn(); + + $stmt = $db->prepare( + 'SELECT s.id, s.name, s.lat, s.lon, + (SELECT COUNT(*) FROM cells c WHERE c.site_id = s.id) AS cell_count + FROM sites s + WHERE s.operator_id = ? + ORDER BY s.name + LIMIT :limit OFFSET :offset' + ); + $stmt->bindValue(1, $operatorId, PDO::PARAM_INT); + $stmt->bindValue(':limit', $perPage, PDO::PARAM_INT); + $stmt->bindValue(':offset', $offset, PDO::PARAM_INT); + $stmt->execute(); + $sites = $stmt->fetchAll(PDO::FETCH_ASSOC); + + $statStmt = $db->prepare( + "SELECT c.technology, COUNT(*) AS total + FROM cells c + INNER JOIN sites s ON s.id = c.site_id + WHERE s.operator_id = ? + GROUP BY c.technology + ORDER BY total DESC" + ); + $statStmt->execute([$operatorId]); + $stats = $statStmt->fetchAll(PDO::FETCH_ASSOC); +} + +$totalPages = max(1, (int)ceil($totalSites / $perPage)); +$user = current_admin_username(); +?> + + + + + Antennes par opé··rateur — NetMonster Admin + + + + + +
+

Antennes par opé··rateur

+

Vue de consultation, sans action d'ecriture. Filtrez par pays puis par operateur pour lister les sites et leur nombre de cellules.

+ +
+
+ + +
+
+ + +
+

Ré··partition par technologie

+ +

Aucune cellule pour cet opé··rateur.

+ + + + + + + + + + + +
TechnologieNombre de cellules
+ +
+ +
+

Sites ()

+ + + + + + + + + + + + + + + + + + + + + + + + +
NomLatitudeLongitudeCellulesDé··tail
Ouvrir
Aucun site pour cet opé··rateur.
+ + +
+ +
+

Sé··lectionnez un opé··rateur pour afficher ses sites.

+
+ +
+ + \ No newline at end of file diff --git a/admin/review.php b/admin/review.php new file mode 100644 index 0000000..aca305e --- /dev/null +++ b/admin/review.php @@ -0,0 +1,383 @@ +prepare('SELECT country_id FROM operators WHERE id = ?'); + $opCheck->execute([$operatorId]); + $countryId = $opCheck->fetchColumn(); + + if ($stagingIds && $operatorId && $countryId !== false) { + $db->beginTransaction(); + try { + $near = $db->prepare('SELECT id FROM sites WHERE operator_id = ? AND ABS(lat - ?) < 0.0003 AND ABS(lon - ?) < 0.0003 LIMIT 1'); + $near->execute([$operatorId, $lat, $lon]); + $siteId = $near->fetchColumn(); + + if (!$siteId) { + $addrStmt = $db->prepare('SELECT address_desc FROM staging_cells WHERE id = ? LIMIT 1'); + $addrStmt->execute([$stagingIds[0]]); + $addressDesc = $addrStmt->fetchColumn() ?: null; + + $insertSite = $db->prepare('INSERT INTO sites (country_id, operator_id, lat, lon, location_desc, address_desc, created_by) VALUES (?, ?, ?, ?, ?, ?, ?)'); + $insertSite->execute([$countryId, $operatorId, $lat, $lon, $locationDesc, $addressDesc, $user]); + $siteId = (int)$db->lastInsertId(); + } + + $fetchStaging = $db->prepare('SELECT * FROM staging_cells WHERE id = ? AND status = "pending"'); + $insertCell = $db->prepare(' + INSERT INTO cells (site_id, tech, mcc, mnc, cell_id, area_code, sub_id, sub_id2, band, frequency, azimuth, bandwidth, arfcn, validated_by) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + ON DUPLICATE KEY UPDATE + site_id = VALUES(site_id), band = VALUES(band), frequency = VALUES(frequency), + azimuth = VALUES(azimuth), bandwidth = VALUES(bandwidth), + validated_by = VALUES(validated_by), validated_at = NOW() + '); + $markValidated = $db->prepare('UPDATE staging_cells SET status = "validated", reviewed_at = NOW(), reviewed_by = ? WHERE id = ?'); + + foreach ($stagingIds as $sid) { + $fetchStaging->execute([$sid]); + $cell = $fetchStaging->fetch(PDO::FETCH_ASSOC); + if (!$cell) continue; + $insertCell->execute([ + $siteId, $cell['tech'], $cell['mcc'], $cell['mnc'], $cell['cell_id'], $cell['area_code'], + $cell['sub_id'], $cell['sub_id2'], $cell['band'], $cell['frequency'], $cell['azimuth'], $cell['bandwidth'], + $cell['arfcn'], $user, + ]); + $markValidated->execute([$user, $sid]); + } + $db->commit(); + } catch (Throwable $e) { + $db->rollBack(); + // Corrige : l'echec n'est plus silencieux. On loggue le + // detail technique cote serveur et on informe + // l'administrateur qu'aucune validation n'a ete + // appliquee, plutot que de rediriger comme si de rien + // n'etait alors que la transaction a echoue. + error_log('[review] validate_group: ' . $e->getMessage()); + $flashError = "La validation du site a echoue et a ete annulee (aucune donnee ecrite). Consultez les journaux du serveur si le probleme persiste."; + } + } else { + $flashError = "Validation refusee : operateur invalide ou selection de cellules vide."; + } + } + + if ($action === 'reject') { + $stagingId = (int)($_POST['staging_id'] ?? 0); + try { + $db->prepare('UPDATE staging_cells SET status = "rejected", reviewed_at = NOW(), reviewed_by = ?, reject_reason = ? WHERE id = ? AND status = "pending"') + ->execute([$user, $_POST['reject_reason'] ?? null, $stagingId]); + } catch (Throwable $e) { + error_log('[review] reject: ' . $e->getMessage()); + $flashError = 'Le rejet de cette cellule a echoue.'; + } + } + + if ($action === 'add_missing_cell') { + // Verifie que l'operateur/pays declares existent reellement (coherence + // MCC/MNC) avant d'inserer une nouvelle ligne staging manuelle. + $operatorId = (int)($_POST['operator_id'] ?? 0); + $opCheck = $db->prepare('SELECT mcc, mnc FROM operators WHERE id = ?'); + $opCheck->execute([$operatorId]); + $op = $opCheck->fetch(PDO::FETCH_ASSOC); + + if ($op && in_array($_POST['tech'] ?? '', ['2G', '3G', '4G', '5G', 'CDMA'], true)) { + try { + $db->prepare(' + INSERT INTO staging_cells (tech, mcc, mnc, operator_guess, cell_id, area_code, sub_id, sub_id2, band, frequency, azimuth, bandwidth, lat, lon, location_desc, arfcn, source, status) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, "manual_form", "pending") + ')->execute([ + $_POST['tech'], $op['mcc'], $op['mnc'], $operatorId, + $_POST['cell_id'], $_POST['area_code'], + $_POST['sub_id'] !== '' ? $_POST['sub_id'] : null, $_POST['sub_id2'] !== '' ? $_POST['sub_id2'] : null, + $_POST['band'] !== '' ? $_POST['band'] : null, $_POST['frequency'] !== '' ? $_POST['frequency'] : null, + $_POST['azimuth'] !== '' ? (int)$_POST['azimuth'] : null, $_POST['bandwidth'] !== '' ? $_POST['bandwidth'] : null, + $_POST['lat'], $_POST['lon'], $_POST['location_desc'], $_POST['arfcn'] !== '' ? $_POST['arfcn'] : null, + ]); + } catch (Throwable $e) { + error_log('[review] add_missing_cell: ' . $e->getMessage()); + $flashError = "L'ajout de la cellule manquante a echoue."; + } + } else { + $flashError = "Ajout refuse : operateur ou technologie invalide."; + } + } + + if ($flashError !== null) { + $_SESSION['review_flash_error'] = $flashError; + } + + header('Location: review.php' . (isset($_GET['country']) ? '?country=' . urlencode($_GET['country']) : '')); + exit; +} + +$flashError = $_SESSION['review_flash_error'] ?? null; +unset($_SESSION['review_flash_error']); + +$countryFilter = $_GET['country'] ?? ''; $operatorFilter = $_GET['operator'] ?? ''; + +$sql = ' + SELECT sc.*, o.brand AS operator_brand, c.name AS country_name + FROM staging_cells sc + LEFT JOIN operators o ON o.id = sc.operator_guess + LEFT JOIN countries c ON c.mcc = sc.mcc + WHERE sc.status = "pending" +'; +$params = []; +if ($countryFilter !== '') { $sql .= ' AND c.iso2 = ?'; $params[] = $countryFilter; } +if ($operatorFilter !== '') { $sql .= ' AND sc.operator_guess = ?'; $params[] = $operatorFilter; } +$sql .= ' ORDER BY sc.submitted_at DESC LIMIT 500'; + +$stmt = $db->prepare($sql); $stmt->execute($params); +$allPending = $stmt->fetchAll(PDO::FETCH_ASSOC); + +$groups = []; +foreach ($allPending as $row) { + $key = deriveSiteKey($row); + $groups[$key]['cells'][] = $row; + $groups[$key]['operator_brand'] = $row['operator_brand']; + $groups[$key]['operator_id'] = $row['operator_guess']; + $groups[$key]['mcc'] = $row['mcc']; $groups[$key]['mnc'] = $row['mnc']; + $groups[$key]['lat'] = $groups[$key]['lat'] ?? $row['lat']; + $groups[$key]['lon'] = $groups[$key]['lon'] ?? $row['lon']; + $groups[$key]['location_desc'] = $groups[$key]['location_desc'] ?? $row['location_desc']; + $groups[$key]['country_name'] = $row['country_name']; + $groups[$key]['has_dup'] = ($groups[$key]['has_dup'] ?? false) || (bool)$row['possible_dup_of']; +} + +$operators = $db->query('SELECT id, mcc, mnc, brand FROM operators ORDER BY brand')->fetchAll(PDO::FETCH_ASSOC); +$countries = $db->query('SELECT iso2, name, mcc, default_lat, default_lon, default_zoom, cadastre_wms_url, cadastre_wms_layer, cadastre_attribution_html, cadastre_srs FROM countries ORDER BY name')->fetchAll(PDO::FETCH_ASSOC); + +$activeCountry = null; +if ($countryFilter !== '') { + foreach ($countries as $c) { if ($c['iso2'] === $countryFilter) { $activeCountry = $c; break; } } +} elseif (!empty($groups)) { + $firstMcc = reset($groups)['mcc']; + foreach ($countries as $c) { if ($c['mcc'] === $firstMcc) { $activeCountry = $c; break; } } +} +if (!$activeCountry) { + $activeCountry = ['default_lat' => 20, 'default_lon' => 0, 'default_zoom' => 2, 'cadastre_wms_url' => null, 'cadastre_wms_layer' => null, 'cadastre_attribution_html' => null, 'cadastre_srs' => 'EPSG:3857']; +} + +$mapPoints = []; +foreach ($groups as $key => $g) { + if ($g['lat'] && $g['lon']) { + $mapPoints[] = ['key' => $key, 'lat' => (float)$g['lat'], 'lon' => (float)$g['lon'], 'operator' => $g['operator_brand'], 'count' => count($g['cells']), 'has_dup' => $g['has_dup']]; + } +} +$hasCadastre = !empty($activeCountry['cadastre_wms_url']) && !empty($activeCountry['cadastre_wms_layer']); +$totalPending = count($allPending); +$totalDup = count(array_filter($allPending, fn($r) => (bool)$r['possible_dup_of'])); +?> + + + +NetMonster Admin - Validation + + + + + + + +
+

Validation groupee par site

+

Verifiez chaque site avant qu'il ne rejoigne la base finale.

+ +

+ +
+
site(s) en attente
+
ligne(s) au total
+
doublon(s) potentiel(s)
+
+ +
+ +
+
+ +
+ + +
+ + $group): ?> +
+
+

· · ,

+
+ cellule 1 ? 's' : '' ?> + Doublons potentiels +
+
+ + + + + + + + + + + + + + + + + + +
TechnoCell IDArea codeSub IDeNB/RNCBandeFrequenceAzimutLargeurARFCNDup.
+
+ + + + +
+
+
+
+ + + + + + + + +
+ +
+
+
+ + + + + + + + +
+ + + + + + + + + + +
+ +
+
+
+ +
+ + + + \ No newline at end of file diff --git a/admin/search_site.php b/admin/search_site.php new file mode 100644 index 0000000..dbda92b --- /dev/null +++ b/admin/search_site.php @@ -0,0 +1,454 @@ +query('SELECT id, iso2, name FROM countries ORDER BY name')->fetchAll(PDO::FETCH_ASSOC); +$operators = $db->query('SELECT id, mcc, mnc, brand FROM operators ORDER BY brand')->fetchAll(PDO::FETCH_ASSOC); + +$sites = []; +$selectedSite = null; +$selectedCells = []; + +if ($query !== '' || $countryFilter !== '' || $operatorFilter !== '' || $techFilter !== '' || $selectedSiteId > 0) { + $sql = ' + SELECT + s.id AS site_id, + s.lat, + s.lon, + s.location_desc, + s.address_desc, + s.country_id, + s.operator_id, + o.brand AS operator_brand, + o.mcc AS operator_mcc, + o.mnc AS operator_mnc, + c.name AS country_name, + cl.id AS cell_pk, + cl.mcc, + cl.mnc, + cl.tech, + cl.cell_id, + cl.area_code, + cl.sub_id, + cl.sub_id2, + cl.band, + cl.frequency, + cl.azimuth, + cl.bandwidth, + cl.arfcn + FROM sites s + JOIN operators o ON o.id = s.operator_id + JOIN countries c ON c.id = s.country_id + JOIN cells cl ON cl.site_id = s.id + WHERE 1=1 + '; + $params = []; + + if ($query !== '') { + $sql .= ' + AND ( + cl.sub_id2 LIKE ? + OR cl.cell_id LIKE ? + OR cl.sub_id LIKE ? + OR cl.band LIKE ? + OR cl.frequency LIKE ? + OR s.location_desc LIKE ? + OR s.address_desc LIKE ? + ) + '; + $like = '%' . $query . '%'; + array_push($params, $like, $like, $like, $like, $like, $like, $like); + } + + if ($countryFilter !== '') { + $sql .= ' AND c.iso2 = ?'; + $params[] = $countryFilter; + } + + if ($operatorFilter !== '') { + $sql .= ' AND o.id = ?'; + $params[] = (int)$operatorFilter; + } + + if ($techFilter !== '') { + $sql .= ' AND cl.tech = ?'; + $params[] = $techFilter; + } + + if ($selectedSiteId > 0) { + $sql .= ' AND s.id = ?'; + $params[] = $selectedSiteId; + } + + $sql .= ' ORDER BY s.id, cl.sub_id2, cl.tech, cl.band, cl.frequency, cl.id LIMIT 1000'; + + $stmt = $db->prepare($sql); + $stmt->execute($params); + $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); + + foreach ($rows as $row) { + $siteId = (int)$row['site_id']; + + if (!isset($sites[$siteId])) { + $sites[$siteId] = [ + 'site_id' => $siteId, + 'lat' => (float)$row['lat'], + 'lon' => (float)$row['lon'], + 'location_desc' => (string)($row['location_desc'] ?? ''), + 'address_desc' => (string)($row['address_desc'] ?? ''), + 'operator_brand' => (string)$row['operator_brand'], + 'country_name' => (string)$row['country_name'], + 'cell_count' => 0, + 'cells' => [], + ]; + } + + $cell = [ + 'cell_pk' => (int)$row['cell_pk'], + 'mcc' => (string)($row['mcc'] ?? ''), + 'mnc' => (string)($row['mnc'] ?? ''), + 'tech' => (string)($row['tech'] ?? ''), + 'cell_id' => (string)($row['cell_id'] ?? ''), + 'area_code' => (string)($row['area_code'] ?? ''), + 'sub_id' => (string)($row['sub_id'] ?? ''), + 'sub_id2' => (string)($row['sub_id2'] ?? ''), + 'band' => (string)($row['band'] ?? ''), + 'frequency' => (string)($row['frequency'] ?? ''), + 'azimuth' => $row['azimuth'] !== null && $row['azimuth'] !== '' ? (float)$row['azimuth'] : null, + 'bandwidth' => (string)($row['bandwidth'] ?? ''), + 'arfcn' => (string)($row['arfcn'] ?? ''), + ]; + + $sites[$siteId]['cells'][] = $cell; + $sites[$siteId]['cell_count']++; + } + + $sites = array_values($sites); + + if ($selectedSiteId > 0 && isset($sites[0]) && (int)$sites[0]['site_id'] === $selectedSiteId) { + $selectedSite = $sites[0]; + $selectedCells = $selectedSite['cells']; + } elseif (count($sites) === 1) { + $selectedSite = $sites[0]; + $selectedCells = $selectedSite['cells']; + } +} + +function h(mixed $value): string +{ + return htmlspecialchars((string)$value, ENT_QUOTES, 'UTF-8'); +} + +function techColor(string $tech): string +{ + return match (strtoupper($tech)) { + '2G' => '#eab308', + '3G' => '#2563eb', + '4G' => '#16a34a', + '5G' => '#9333ea', + 'CDMA' => '#ea580c', + default => '#64748b', + }; +} + +$mapSites = $selectedSite ? [$selectedSite] : $sites; +?> + + + + + +NetMonster Admin - Recherche antenne + + + + + + + + +
+

Recherche antenne / eNB

+

Recherchez un eNB, une cellule, une bande, une frequence ou une adresse. Les secteurs avec azimut sont dessines sur la carte.

+ +
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ +
+
+ +
+
+
+

Antennes trouvees ()

+ +
Saisissez un eNB, un Cell ID, une bande ou une adresse.
+ +
Aucune antenne trouvee.
+ + + +
+ + +
+

Cellules de l'antenne

+

—

+ + + + + + + + + + + + + + + +
TechnoCell IDeNB/RNCBandeFrequenceLargeurAzimutARFCN
+
+ +
+ +
+

Carte des azimuts

+
+
+ +
+
+
+
+ + + + \ No newline at end of file