Upload source code ftthplanner

This commit is contained in:
karuniaDwi 2026-07-09 12:33:03 +07:00
parent 371ca32928
commit f43f0f5afe
42 changed files with 10197 additions and 642 deletions

117
api/ambil_dataOLT.php Normal file
View File

@ -0,0 +1,117 @@
<?php
require_once __DIR__ . '/../config/database.php';
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
$db = new Database();
$conn = $db->getConnection();
// ================= CONFIG =================
$login_url = "http://10.255.254.21/login";
$data_url = "http://10.255.254.21/ontinfo_table";
$username = "root";
$password = "@lokal234";
$cookie = __DIR__ . "/cookie.txt";
// ================= LOGIN =================
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $login_url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query([
"username" => $username,
"password" => $password
]));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_COOKIEJAR, $cookie);
curl_setopt($ch, CURLOPT_COOKIEFILE, $cookie);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_exec($ch);
curl_close($ch);
// ================= AMBIL DATA =================
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $data_url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_COOKIEFILE, $cookie);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
$response = curl_exec($ch);
if ($response === false) {
die("CURL ERROR: " . curl_error($ch));
}
curl_close($ch);
file_put_contents("debug_onu.json", $response);
$data = json_decode($response, true);
if (!isset($data['data']) || empty($data['data'])) {
die("DATA KOSONG");
}
// ================= PROCESS =================
foreach ($data['data'] as $onu) {
$sn = $onu['ont_sn'] ?? '';
$rx = $onu['receive_power'] ?? 0;
$state = $onu['state'] ?? 0;
$cause = strtolower($onu['last_d_cause'] ?? '');
if (empty($sn)) continue;
// ================= STATUS =================
if (strpos($cause, 'laser out') !== false || strpos($cause, 'fiber') !== false) {
$status = "LASER OUT";
} elseif (strpos($cause, 'power down') !== false || strpos($cause, 'dying-gasp') !== false) {
$status = "POWER FAIL";
} else {
if ($state == 0 && $rx <= -30) {
$status = "LASER OUT";
} elseif ($rx <= -30) {
$status = "POWER FAIL";
} else {
$status = "ONLINE";
}
}
// ================= GET LAST STATUS =================
$cek = $conn->prepare("SELECT status FROM onu_status WHERE sn = ?");
$cek->execute([$sn]);
$last = $cek->fetch(PDO::FETCH_ASSOC);
$lastStatus = $last['status'] ?? null;
// ================= UPSERT REALTIME =================
$conn->query("
INSERT INTO onu_status (sn, status, rx_power, last_update)
VALUES ('$sn', '$status', '$rx', NOW())
ON DUPLICATE KEY UPDATE
last_status = status,
status = '$status',
rx_power = '$rx',
last_update = NOW()
");
// ================= LOG HISTORI (HANYA ANOMALI) =================
$anomali = in_array($status, ['LASER OUT', 'POWER FAIL']);
$trigger = ($lastStatus !== $status);
if ($anomali && $trigger) {
$stmt = $conn->prepare("
INSERT INTO status_onu_histori (sn, status, rx_power, created_at)
VALUES (?, ?, ?, NOW())
");
$stmt->execute([$sn, $status, $rx]);
echo "LOG ANOMALI: $sn | $status | RX $rx<br>";
}
echo "SN: $sn | STATUS: $status | RX: $rx<br>";
}
echo "DONE ✅";
?>

369
api/auto_notif_device.php Normal file
View File

@ -0,0 +1,369 @@
<?php
require_once __DIR__ . '/../config/database.php';
set_time_limit(300);
$db = new Database();
$conn = $db->getConnection();
// 🔥 GROUP TEKNISI
$groupTeknisi = "120363427232707228@g.us";
// ======================
// 🔥 FUNCTION WA
// ======================
if (!function_exists('kirimWA')) {
function kirimWA($target, $pesan) {
if (!$target) return;
$token = "XjC9oSr4jECWG93JQRWf";
$data = [
"target" => $target,
"message" => $pesan,
];
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.fonnte.com/send",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => http_build_query($data),
CURLOPT_HTTPHEADER => [
"Authorization: $token"
],
CURLOPT_TIMEOUT => 10
]);
curl_exec($curl);
curl_close($curl);
}
}
// ======================
// 🔥 TEST MODE (OPSIONAL)
// ======================
$TEST_MODE = false;
// ======================
// 🔥 AMBIL DATA DARI DATABASE (JOIN HISTORY)
// ======================
$stmt = $conn->query("
SELECT
f.id,
f.name,
f.sn_onu,
f.latitude,
f.longitude,
f.customer_phone,
o.rx_power,
o.voltage,
o.updated_at
FROM ftth_items f
LEFT JOIN (
SELECT h1.*
FROM onu_daily_history h1
INNER JOIN (
SELECT sn, MAX(updated_at) AS max_time
FROM onu_daily_history
WHERE DATE(updated_at) = CURDATE()
GROUP BY sn
) h2
ON h1.sn = h2.sn
AND h1.updated_at = h2.max_time
) o
ON o.sn = f.sn_onu
INNER JOIN item_types it
ON it.id = f.item_type_id
WHERE f.sn_onu IS NOT NULL
AND LOWER(it.name) = 'pelanggan'
");
$dataItems = $stmt->fetchAll(PDO::FETCH_ASSOC);
foreach ($dataItems as $item) {
usleep(200000);
$sn = strtoupper(trim($item['sn_onu']));
$nama = $item['name'];
$lat = $item['latitude'];
$lng = $item['longitude'];
$phone = $item['customer_phone'];
// ======================
// 🔥 AMBIL RX & VOLT DARI DB
// ======================
$rx = isset($item['rx_power']) ? floatval($item['rx_power']) : null;
$volt = isset($item['voltage']) ? floatval($item['voltage']) : null;
if ($rx === null && $volt === null) {
continue;
}
// ======================
// 🔥 STATUS DETEKSI
// ======================
// RX NORMAL = -20 s/d -25.99 dBm
$isRxHighLoss = ($rx !== null && $rx < -25.99); // redaman terlalu besar
$isRxLowLoss = ($rx !== null && $rx > -20.00); // redaman terlalu kecil
$isBadRx = $isRxHighLoss || $isRxLowLoss;
// VOLTAGE NORMAL = 3.20 - 3.29 V
$isVoltLow = ($volt !== null && $volt < 3.20);
$isVoltHigh = ($volt !== null && $volt > 3.29);
$isBadVolt = $isVoltLow || $isVoltHigh;
// ======================
// 🔥 KETERANGAN ANALISA
// ======================
$rxKeterangan = '';
if ($isRxHighLoss) {
$rxKeterangan =
"Redaman terlalu besar (High Loss). Sinyal optik berada di bawah standar perusahaan. Kemungkinan terjadi redaman berlebih pada jalur fiber, sambungan, konektor, splitter, atau kabel mengalami kerusakan.";
} elseif ($isRxLowLoss) {
$rxKeterangan =
"Redaman terlalu kecil (Low Loss). Nilai sinyal optik berada di atas standar perusahaan. Diperlukan pengecekan kualitas distribusi jaringan dan konfigurasi perangkat.";
}
$voltKeterangan = '';
if ($isVoltLow) {
$voltKeterangan =
"Tegangan ONU berada di bawah standar 3.20 - 3.29 Volt. Kemungkinan adaptor lemah, kabel power rusak, atau suplai listrik tidak stabil.";
} elseif ($isVoltHigh) {
$voltKeterangan =
"Tegangan ONU berada di atas standar 3.20 - 3.29 Volt. Diperlukan pengecekan adaptor dan sumber daya listrik.";
}
// ======================
// 🔥 CEK ANTI SPAM LOG
// ======================
$cek = $conn->prepare("
SELECT last_rx, last_voltage
FROM notif_device_log
WHERE sn = ?
");
$cek->execute([$sn]);
$last = $cek->fetch(PDO::FETCH_ASSOC);
$lastRx = $last['last_rx'] ?? null;
$lastVolt = $last['last_voltage'] ?? null;
$isChanged =
round((float)$lastRx, 2) != round((float)$rx, 2) ||
round((float)$lastVolt, 2) != round((float)$volt, 2);
if (!$isChanged) {
continue;
}
// ======================
// 🔥 MAPS
// ======================
$maps = ($lat && $lng)
? "https://www.google.com/maps?q={$lat},{$lng}"
: "-";
// ======================
// 🔥 FORMAT NOMOR
// ======================
$phoneFix = null;
if ($phone) {
$phoneFix = preg_replace('/[^0-9]/', '', $phone);
if (substr($phoneFix, 0, 1) == '0') {
$phoneFix = '62' . substr($phoneFix, 1);
}
}
//$isBadVolt = ($volt !== null && $volt < 3.29);
//$isVoltLow = ($volt !== null && $volt < 3.20);
//$isVoltHigh = ($volt !== null && $volt > 3.29);
//$isBadVolt = $isVoltLow || $isVoltHigh;
// ======================
// 🔥 NOTIFIKASI
// ======================
if ($isBadRx && $isBadVolt) {
// ======================
// PELANGGAN
// ======================
kirimWA(
$phoneFix,
"🚨 *Pemberitahuan Gangguan Layanan Internet*\n\n".
"Yth. {$nama},\n\n".
"Sistem monitoring kami mendeteksi kondisi jaringan dan perangkat internet di lokasi Anda berada di luar standar operasional.\n\n".
"📉 Kualitas Sinyal : ".number_format($rx,2)." dBm\n".
"🔌 Tegangan ONU : ".number_format($volt,2)." V\n\n".
"Gangguan ini berpotensi menyebabkan internet lambat, putus-putus, atau tidak dapat digunakan.\n\n".
"Tim teknis kami telah menerima laporan otomatis dan akan melakukan pengecekan lebih lanjut.\n\n".
"Mohon maaf atas ketidaknyamanan ini.\n\n".
"Terima kasih.\n".
"RockNet NOC"
);
// ======================
// TEKNISI
// ======================
kirimWA(
$groupTeknisi,
"🚨 *ANOMALI RX + VOLTAGE*\n\n".
"👤 Pelanggan : {$nama}\n".
"🔢 SN ONU : {$sn}\n\n".
"📉 RX Power : ".number_format($rx,2)." dBm\n".
"🔌 Voltage : ".number_format($volt,2)." V\n\n".
"📋 Analisa RX :\n".
"{$rxKeterangan}\n\n".
"📋 Analisa Voltage :\n".
"{$voltKeterangan}\n\n".
"📍 Lokasi :\n{$maps}\n\n".
"⚠ PRIORITAS TINGGI\n".
"Perlu pengecekan jalur fiber dan perangkat ONU."
);
}
elseif ($isBadRx) {
kirimWA(
$phoneFix,
"🚨 *Pemberitahuan Gangguan Jaringan*\n\n".
"Yth. {$nama},\n\n".
"Sistem monitoring mendeteksi kualitas sinyal fiber optik di lokasi Anda berada di luar standar operasional.\n\n".
"📉 Nilai Sinyal : ".number_format($rx,2)." dBm\n\n".
"Gangguan ini dapat menyebabkan internet tidak stabil, lambat, atau terputus.\n\n".
"Tim teknis kami telah menerima laporan otomatis dan akan melakukan pengecekan.\n\n".
"Mohon maaf atas ketidaknyamanan ini.\n\n".
"RockNet NOC"
);
kirimWA(
$groupTeknisi,
"🚨 *ANOMALI REDAMAN ONU*\n\n".
"👤 Pelanggan : {$nama}\n".
"🔢 SN ONU : {$sn}\n\n".
"📉 RX Power : ".number_format($rx,2)." dBm\n\n".
"📋 Analisa :\n".
"{$rxKeterangan}\n\n".
"📍 Lokasi :\n{$maps}\n\n".
"🔧 Perlu pengecekan jalur fiber, konektor, splitter dan sambungan."
);
}
elseif ($isBadVolt) {
kirimWA(
$phoneFix,
"⚡ *Pemberitahuan Perangkat Internet*\n\n".
"Yth. {$nama},\n\n".
"Sistem monitoring mendeteksi tegangan perangkat ONU berada di luar standar operasional.\n\n".
"🔌 Tegangan ONU : ".number_format($volt,2)." V\n\n".
"Kondisi ini dapat menyebabkan perangkat restart sendiri, koneksi tidak stabil, atau gangguan layanan internet.\n\n".
"Tim teknis kami telah menerima laporan otomatis dan akan melakukan pengecekan lebih lanjut.\n\n".
"Terima kasih.\n".
"RockNet NOC"
);
kirimWA(
$groupTeknisi,
"⚡ *ANOMALI VOLTAGE ONU*\n\n".
"👤 Pelanggan : {$nama}\n".
"🔢 SN ONU : {$sn}\n\n".
"🔌 Voltage : ".number_format($volt,2)." V\n\n".
"📋 Analisa :\n".
"{$voltKeterangan}\n\n".
"📍 Lokasi :\n{$maps}\n\n".
"🔧 Perlu pengecekan adaptor, kabel power, dan sumber listrik pelanggan."
);
}
// ======================
// 🔥 UPDATE LOG ANTI SPAM
// ======================
$update = $conn->prepare("
INSERT INTO notif_device_log (sn, last_rx, last_voltage, last_sent)
VALUES (?, ?, ?, NOW())
ON DUPLICATE KEY UPDATE
last_rx = VALUES(last_rx),
last_voltage = VALUES(last_voltage),
last_sent = NOW()
");
$update->execute([$sn, $rx, $volt]);
if (!$isChanged) {
echo "⏭ SKIP $sn | Tidak ada perubahan<br>";
continue;
}
echo "✅ SENT $sn | RX:$rx | V:$volt<br>";
}

189
api/auto_notif_odp.php Normal file
View File

@ -0,0 +1,189 @@
<?php
require_once __DIR__ . '/../config/database.php';
$db = new Database();
$conn = $db->getConnection();
if (!function_exists('kirimWA')) {
function kirimWA($pesan) {
$token = "XjC9oSr4jECWG93JQRWf";
$data = [
"target" => "120363407747513164@g.us",
"message" => $pesan,
];
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.fonnte.com/send",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => http_build_query($data),
CURLOPT_HTTPHEADER => [
"Authorization: $token"
],
]);
curl_exec($curl);
curl_close($curl);
}
}
// ✅ Ambil semua ODP (KHUSUS USER 22)
$stmt = $conn->query("
SELECT i.id, i.name, i.latitude, i.longitude
FROM ftth_items i
LEFT JOIN item_types it ON i.item_type_id = it.id
WHERE LOWER(it.name) = 'odp'
AND i.user_id = 22
");
$odps = $stmt->fetchAll(PDO::FETCH_ASSOC);
foreach ($odps as $odp) {
// 🔥 ambil status terakhir
$cek = $conn->prepare("
SELECT last_status FROM notif_log
WHERE odp_id = ?
");
$cek->execute([$odp['id']]);
$last = $cek->fetch(PDO::FETCH_ASSOC);
$lastStatus = $last['last_status'] ?? null;
// 🔥 ambil client
$stmtClient = $conn->prepare("
SELECT fi.id, fi.name, fi.serial_number
FROM cable_routes cr
JOIN ftth_items fi
ON (cr.to_item_id = fi.id OR cr.from_item_id = fi.id)
WHERE (cr.from_item_id = :id OR cr.to_item_id = :id)
AND fi.id != :id
");
$stmtClient->bindParam(':id', $odp['id']);
$stmtClient->execute();
$clients = $stmtClient->fetchAll(PDO::FETCH_ASSOC);
$rxValues = [];
$clientBad = [];
foreach ($clients as $c) {
if (empty($c['serial_number'])) continue;
$query = json_encode(["_id" => $c['serial_number']]);
$url = "http://rmtstb.megadataisp.net:7557/devices/?query=" . urlencode($query);
$res = @file_get_contents($url);
if (!$res) continue;
$json = json_decode($res, true);
if (
isset($json[0]['VirtualParameters']['RXPower']['_value']) &&
is_numeric($json[0]['VirtualParameters']['RXPower']['_value'])
) {
$rx = floatval($json[0]['VirtualParameters']['RXPower']['_value']);
$rxValues[] = $rx;
if ($rx < -23) {
$clientBad[] = $c['name'] . " (" . $rx . " dBm)";
}
}
}
// 🔴 skip kalau tidak ada data
if (count($rxValues) == 0) continue;
// 🔥 HITUNG (SAMA PERSIS KAYAK MANUAL)
$total = count($rxValues);
$bad = count($clientBad);
$good = $total - $bad;
$avg = round(array_sum($rxValues) / $total, 2);
$min = min($rxValues);
// 🔥 STATUS (SAMA PERSIS)
$selisih = abs($avg - $min);
$status = "BAIK";
if ($selisih > 1.5) {
$status = "WARNING";
}
if ($avg < -23 || $bad > ($total * 0.3)) {
$status = "WARNING";
}
if ($avg < -25 || $bad > ($total * 0.5)) {
$status = "BURUK";
}
if ($min < -27) {
$status = "KRITIS";
}
// ❌ ANTI SPAM (kalau status sama, skip)
if ($status == $lastStatus) continue;
// 🔥 FORMAT WA (PERSIS KAYAK MANUAL)
$maps = "https://www.google.com/maps?q={$odp['latitude']},{$odp['longitude']}";
// ==============================
// 🚨 KIRIM SAAT BERMASALAH
// ==============================
if ($status != "BAIK") {
$pesan = "🚨 *AUTO LAPORAN ODP*\n\n";
$pesan .= "📍 *ODP:* {$odp['name']}\n";
$pesan .= "📊 *Status:* {$status}\n\n";
$pesan .= "👥 *Total Client:* {$total}\n";
$pesan .= "🟢 Client Baik: {$good}\n";
$pesan .= "🔴 Client Buruk: {$bad}\n\n";
$pesan .= "📶 *AVG RX:* {$avg} dBm\n";
$pesan .= "📉 *MIN RX:* {$min} dBm\n\n";
if (!empty($clientBad)) {
$pesan .= "⚠ *Client Bermasalah:*\n";
$pesan .= implode("\n", array_slice($clientBad, 0, 10));
$pesan .= "\n\n";
}
$pesan .= "📌 Lokasi:\n$maps\n\n";
$pesan .= "🔎 Silakan cek detail di dashboard.";
kirimWA($pesan);
}
// ==============================
// ✅ OPSIONAL: KIRIM SAAT NORMAL
// ==============================
elseif ($status == "BAIK" && $lastStatus != "BAIK") {
$pesan = "✅ *ODP SUDAH NORMAL*\n\n";
$pesan .= "📍 ODP: {$odp['name']}\n";
$pesan .= "Status sekarang: BAIK\n";
$pesan .= "AVG RX: {$avg} dBm\n";
$pesan .= "MIN RX: {$min} dBm\n\n";
$pesan .= "Jaringan sudah stabil 👍";
kirimWA($pesan);
}
// 🔥 UPDATE STATUS TERAKHIR
$update = $conn->prepare("
INSERT INTO notif_log (odp_id, last_status, last_sent)
VALUES (?, ?, NOW())
ON DUPLICATE KEY UPDATE
last_status = VALUES(last_status),
last_sent = NOW()
");
$update->execute([$odp['id'], $status]);
}

144
api/auto_notif_onu-bc.txt Normal file
View File

@ -0,0 +1,144 @@
<?php
require_once __DIR__ . '/../config/database.php';
$db = new Database();
$conn = $db->getConnection();
if (!function_exists('kirimWA')) {
function kirimWA($target, $pesan) {
$token = "XjC9oSr4jECWG93JQRWf";
$data = [
"target" => $target,
"message" => $pesan,
];
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.fonnte.com/send",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => http_build_query($data),
CURLOPT_HTTPHEADER => [
"Authorization: $token"
],
]);
curl_exec($curl);
curl_close($curl);
}
}
// 🔥 ID GROUP TEKNISI
$groupTeknisi = "120363427232707228@g.us";
// ambil semua ONU yang punya nomor HP + lokasi pelanggan
$stmt = $conn->query("
SELECT
o.sn,
o.status,
f.name,
f.customer_phone,
f.latitude,
f.longitude
FROM onu_status o
JOIN ftth_items f ON f.sn_onu = o.sn
JOIN item_types it ON f.item_type_id = it.id
WHERE f.customer_phone IS NOT NULL
AND LOWER(it.name) = 'pelanggan'
");
$data = $stmt->fetchAll(PDO::FETCH_ASSOC);
foreach ($data as $row) {
$sn = $row['sn'];
$status = $row['status'];
$nama = $row['name'];
$phone = $row['customer_phone'];
$lat = $row['latitude'];
$lng = $row['longitude'];
// 🔥 buat link maps
$maps = ($lat && $lng)
? "https://www.google.com/maps?q={$lat},{$lng}"
: "-";
// 🔥 cek status terakhir
$cek = $conn->prepare("
SELECT last_status FROM notif_onu_log WHERE sn = ?
");
$cek->execute([$sn]);
$last = $cek->fetch(PDO::FETCH_ASSOC);
$lastStatus = $last['last_status'] ?? null;
// ❌ anti spam
if ($status == $lastStatus) continue;
// ======================
// 🔥 FORMAT PESAN
// ======================
$pesan = "";
if ($status == "LASER OUT") {
$pesan .= "🚨 *GANGGUAN INTERNET*\n\n";
$pesan .= "Pelanggan: {$nama}\n\n";
$pesan .= "Terjadi gangguan pada kabel fiber (FO CUT).\n";
$pesan .= "kami akan informasikan kepada tim teknis untuk segera ke lokasi kakak.\n\n";
$pesan .= "estimasi perbaikan 4 Jam pengerjaan.\n\n";
$pesan .= "Mohon ditunggu 🙏";
// ======================
// 🔥 NOTIF KE TEKNISI (KHUSUS LOS)
// ======================
$pesanTeknisi = "🚨 *FO CUT TERDETEKSI*\n\n";
$pesanTeknisi .= "👤 Pelanggan: {$nama}\n";
$pesanTeknisi .= "📡 SN: {$sn}\n\n";
$pesanTeknisi .= "Status: LOS\n";
$pesanTeknisi .= "Estimasi: 4 Jam\n\n";
$pesanTeknisi .= "📍 Lokasi:\n{$maps}\n\n";
$pesanTeknisi .= "⚠ Segera ke lokasi";
// kirim ke group teknisi
kirimWA($groupTeknisi, $pesanTeknisi);
}
elseif ($status == "POWER FAIL") {
$pesan .= "⚡ *PERANGKAT MATI*\n\n";
$pesan .= "Pelanggan: {$nama}\n\n";
$pesan .= "Perangkat ONU terdeteksi mati / tidak mendapat listrik.\n";
$pesan .= "Silakan cek adaptor atau listrik di lokasi.\n\n";
$pesan .= "Jika terjadi kendala dilokasi mohon konfirmasinya kakak\n\n";
$pesan .= "bisa di bantu videokan alat atau foto alat dilokasi kakak🙏\n\n";
$pesan .= "Terima kasih 🙏";
}
elseif ($status == "ONLINE" && $lastStatus != "ONLINE") {
$pesan .= "✅ *INTERNET NORMAL*\n\n";
$pesan .= "Pelanggan: {$nama}\n\n";
$pesan .= "Koneksi internet sudah kembali normal.\n\n";
$pesan .= "Internet saat ini bisa dicoba kembali\n\n";
$pesan .= "Terima kasih 🙏";
}
// kirim kalau ada pesan
if ($pesan != "") {
// 🔥 kirim WA ke pelanggan (TIDAK DIUBAH)
kirimWA($phone, $pesan);
// update log
$update = $conn->prepare("
INSERT INTO notif_onu_log (sn, last_status, last_sent)
VALUES (?, ?, NOW())
ON DUPLICATE KEY UPDATE
last_status = VALUES(last_status),
last_sent = NOW()
");
$update->execute([$sn, $status]);
echo "Kirim ke $phone | $status <br>";
}
}

161
api/auto_notif_onu.php Normal file
View File

@ -0,0 +1,161 @@
<?php
require_once __DIR__ . '/../config/database.php';
$db = new Database();
$conn = $db->getConnection();
if (!function_exists('kirimWA')) {
function kirimWA($target, $pesan) {
$token = "XjC9oSr4jECWG93JQRWf";
$data = [
"target" => $target,
"message" => $pesan,
];
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.fonnte.com/send",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => http_build_query($data),
CURLOPT_HTTPHEADER => [
"Authorization: $token"
],
]);
curl_exec($curl);
curl_close($curl);
}
}
// 🔥 ID GROUP TEKNISI
$groupTeknisi = "120363427232707228@g.us";
// ambil semua ONU yang punya nomor HP + lokasi pelanggan
$stmt = $conn->query("
SELECT
o.sn,
o.status,
f.name,
f.customer_phone,
f.latitude,
f.longitude
FROM onu_status o
JOIN ftth_items f ON f.sn_onu = o.sn
JOIN item_types it ON f.item_type_id = it.id
WHERE f.customer_phone IS NOT NULL
AND LOWER(it.name) = 'pelanggan'
");
$data = $stmt->fetchAll(PDO::FETCH_ASSOC);
foreach ($data as $row) {
$sn = $row['sn'];
$status = $row['status'];
$nama = $row['name'];
$phone = $row['customer_phone'];
$lat = $row['latitude'];
$lng = $row['longitude'];
// 🔥 buat link maps
$maps = ($lat && $lng)
? "https://www.google.com/maps?q={$lat},{$lng}"
: "-";
// 🔥 cek status terakhir
$cek = $conn->prepare("
SELECT last_status FROM notif_onu_log WHERE sn = ?
");
$cek->execute([$sn]);
$last = $cek->fetch(PDO::FETCH_ASSOC);
$lastStatus = $last['last_status'] ?? null;
// ❌ anti spam
if ($status == $lastStatus) continue;
// ======================
// 🔥 FORMAT PESAN
// ======================
$pesan = "";
if ($status == "LASER OUT") {
$pesan .= "🚨 *GANGGUAN INTERNET*\n\n";
$pesan .= "Pelanggan: {$nama}\n\n";
$pesan .= "Terjadi gangguan pada kabel fiber (FO CUT).\n";
$pesan .= "kami akan informasikan kepada tim teknis untuk segera ke lokasi kakak.\n\n";
$pesan .= "estimasi perbaikan 4 Jam pengerjaan.\n\n";
$pesan .= "Mohon ditunggu 🙏";
// ======================
// 🔥 NOTIF KE TEKNISI (KHUSUS LOS)
// ======================
$pesanTeknisi = "🚨 *FO CUT TERDETEKSI*\n\n";
$pesanTeknisi .= "👤 Pelanggan: {$nama}\n";
$pesanTeknisi .= "📡 SN: {$sn}\n\n";
$pesanTeknisi .= "Status: LOS\n";
$pesanTeknisi .= "Estimasi: 4 Jam\n\n";
$pesanTeknisi .= "📍 Lokasi:\n{$maps}\n\n";
$pesanTeknisi .= "⚠ Segera ke lokasi";
// kirim ke group teknisi
kirimWA($groupTeknisi, $pesanTeknisi);
}
elseif ($status == "POWER FAIL") {
// 🔥 NOTIF KE PELANGGAN
$pesan .= "⚡ *PERANGKAT MATI*\n\n";
$pesan .= "Pelanggan: {$nama}\n\n";
$pesan .= "Perangkat ONU terdeteksi mati / tidak mendapat listrik.\n";
$pesan .= "Silakan cek adaptor atau listrik di lokasi.\n\n";
$pesan .= "Jika terjadi kendala dilokasi mohon konfirmasinya kakak\n\n";
$pesan .= "Bisa dibantu videokan atau foto kondisi alat 🙏\n\n";
$pesan .= "Terima kasih 🙏";
// ======================
// 🔥 NOTIF KE TEKNISI (POWER FAIL)
// ======================
$pesanTeknisi = "⚡ *POWER FAIL TERDETEKSI*\n\n";
$pesanTeknisi .= "👤 Pelanggan: {$nama}\n";
$pesanTeknisi .= "📡 SN: {$sn}\n\n";
$pesanTeknisi .= "Status: POWER FAIL\n\n";
$pesanTeknisi .= "📍 Lokasi:\n{$maps}\n\n";
$pesanTeknisi .= " Saat ini sedang konfirmasi ke pelanggan terkait kondisi alat di lokasi.\n";
$pesanTeknisi .= "📸 Jika sudah ada info, akan dikirim foto / video alat di grup ini.\n\n";
$pesanTeknisi .= "Mohon standby 🙏";
// kirim ke group teknisi
kirimWA($groupTeknisi, $pesanTeknisi);
}
elseif ($status == "ONLINE" && $lastStatus != "ONLINE") {
$pesan .= "✅ *INTERNET NORMAL*\n\n";
$pesan .= "Pelanggan: {$nama}\n\n";
$pesan .= "Koneksi internet sudah kembali normal.\n\n";
$pesan .= "Internet saat ini bisa dicoba kembali\n\n";
$pesan .= "Terima kasih 🙏";
}
// kirim kalau ada pesan
if ($pesan != "") {
// 🔥 kirim WA ke pelanggan (TIDAK DIUBAH)
kirimWA($phone, $pesan);
// update log
$update = $conn->prepare("
INSERT INTO notif_onu_log (sn, last_status, last_sent)
VALUES (?, ?, NOW())
ON DUPLICATE KEY UPDATE
last_status = VALUES(last_status),
last_sent = NOW()
");
$update->execute([$sn, $status]);
echo "Kirim ke $phone | $status <br>";
}
}

55
api/auto_runner.php Normal file
View File

@ -0,0 +1,55 @@
<?php
// ⏱ interval (detik)
$interval_odp = 60; // 1 menit
$interval_onu = 60; // 1 menit
$interval_olt = 180; // 3 menit
// ⏱ last run (awal = 0 biar langsung jalan)
$last_odp = 0;
$last_onu = 0;
$last_olt = 0;
echo "🚀 Auto Runner Started...\n";
while (true) {
$now = time();
// ===============================
// 🔥 CEK ODP (1 menit)
// ===============================
if (($now - $last_odp) >= $interval_odp) {
echo "📡 Cek ODP: " . date("H:i:s") . PHP_EOL;
include __DIR__ . "/auto_notif_odp.php";
$last_odp = $now;
}
// ===============================
// 🔥 CEK ONU + NOTIF (1 menit)
// ===============================
if (($now - $last_onu) >= $interval_onu) {
echo "📶 Cek ONU: " . date("H:i:s") . PHP_EOL;
include __DIR__ . "/auto_notif_onu.php"; // kirim notif WA
$last_onu = $now;
}
// ===============================
// 🔥 CEK OLT (3 menit)
// ===============================
//if (($now - $last_olt) >= $interval_olt) {
// echo "🛰️ Cek OLT: " . date("H:i:s") . PHP_EOL;
// include __DIR__ . "/ambil_dataOLT.php"; // update status ONU
// $last_olt = $now;
// }
// ⏳ delay kecil biar CPU gak 100%
sleep(5);
}

51
api/backup.txt Normal file
View File

@ -0,0 +1,51 @@
<?php
// waktu terakhir dijalankan
$lastODP = 0;
$lastONU = 0;
$lastOLT = 0;
while (true) {
$now = time();
echo "Loop jalan: " . date("H:i:s") . PHP_EOL;
// ======================
// CEK ODP (tiap 60 detik)
// ======================
if ($now - $lastODP >= 60) {
echo "Cek ODP: " . date("H:i:s") . PHP_EOL;
require_once __DIR__ . "/auto_notif_odp.php";
$lastODP = $now;
}
// ======================
// CEK ONU (tiap 60 detik)
// ======================
if ($now - $lastONU >= 60) {
echo "Cek ONU: " . date("H:i:s") . PHP_EOL;
require_once __DIR__ . "/auto_notif_onu.php";
$lastONU = $now;
}
// ==========================
// AMBIL DATA OLT (tiap 5 menit)
// ==========================
if ($now - $lastOLT >= 300) {
echo "Ambil Data OLT: " . date("H:i:s") . PHP_EOL;
require_once __DIR__ . "/ambil_dataOLT.php";
$lastOLT = $now;
}
// jeda kecil biar CPU nggak full
sleep(5);
}
rmtstb.megadataisp.net
📡 Cek ODP: 20:16:25
📶 Cek ONU: 20:16:33
Kirim ke 6282338451677 | ONLINE <br>Kirim ke 6281252188305 | ONLINE <br>📡 Cek ODP: 20:17:29

119
api/bc-ambildata_olt.txt Normal file
View File

@ -0,0 +1,119 @@
<?php
require_once __DIR__ . '/../config/database.php';
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
$db = new Database();
$conn = $db->getConnection();
// ================= CONFIG =================
$login_url = "http://10.255.254.21/login";
$data_url = "http://10.255.254.21/ontinfo_table";
$username = "root";
$password = "@lokal234";
$cookie = __DIR__ . "/cookie.txt";
// ================= LOGIN =================
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $login_url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query([
"username" => $username,
"password" => $password
]));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_COOKIEJAR, $cookie);
curl_setopt($ch, CURLOPT_COOKIEFILE, $cookie);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_exec($ch);
curl_close($ch);
// ================= AMBIL DATA (JSON) =================
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $data_url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_COOKIEFILE, $cookie);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
$response = curl_exec($ch);
if ($response === false) {
die("CURL ERROR: " . curl_error($ch));
}
curl_close($ch);
// simpan debug
file_put_contents("debug_onu.json", $response);
// ================= PARSING JSON =================
$data = json_decode($response, true);
if (!isset($data['data']) || empty($data['data'])) {
die("DATA KOSONG / LOGIN GAGAL / ENDPOINT SALAH");
}
foreach ($data['data'] as $onu) {
$sn = $onu['ont_sn'] ?? '';
$rx = $onu['receive_power'] ?? 0;
$state = $onu['state'] ?? 0;
$cause = $onu['last_d_cause'] ?? '';
// skip kalau SN kosong
if (empty($sn)) {
continue;
}
// ================= STATUS =================
$cause = strtolower($cause);
// 🔥 PRIORITAS 1: dari cause
if (strpos($cause, 'laser out') !== false || strpos($cause, 'fiber') !== false) {
$status = "LASER OUT";
}
elseif (strpos($cause, 'power down') !== false || strpos($cause, 'dying-gasp') !== false) {
$status = "POWER FAIL";
}
// 🔥 PRIORITAS 2: fallback dari state + RX
else {
if ($state == 0 && $rx <= -30) {
$status = "LASER OUT"; // default ke LOS kalau tidak jelas
} elseif ($rx <= -30) {
$status = "POWER FAIL";
} else {
$status = "ONLINE";
}
}
// ================= SIMPAN DB =================
$conn->query("
INSERT INTO onu_status (sn, status, rx_power, last_update)
VALUES ('$sn', '$status', '$rx', NOW())
ON DUPLICATE KEY UPDATE
last_status = status,
status = '$status',
rx_power = '$rx',
last_update = NOW()
");
$conn->query("
INSERT INTO status_onu_histori (sn, status, rx_power, cause, created_at)
VALUES (
'$sn',
'$status',
'$rx',
'$cause',
NOW()
)
");
echo "SN: $sn | STATUS: $status | RX: $rx <br>";
}
echo "<br>DONE ✅";
?>

View File

@ -0,0 +1,224 @@
<?php
require_once __DIR__ . '/../config/database.php';
set_time_limit(300);
$db = new Database();
$conn = $db->getConnection();
// 🔥 GROUP TEKNISI
$groupTeknisi = "120363427232707228@g.us";
// ======================
// 🔥 FUNCTION WA
// ======================
if (!function_exists('kirimWA')) {
function kirimWA($target, $pesan) {
if (!$target) return;
$token = "XjC9oSr4jECWG93JQRWf";
$data = [
"target" => $target,
"message" => $pesan,
];
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.fonnte.com/send",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => http_build_query($data),
CURLOPT_HTTPHEADER => [
"Authorization: $token"
],
CURLOPT_TIMEOUT => 10
]);
curl_exec($curl);
curl_close($curl);
}
}
// ======================
// 🔥 TEST MODE (OPSIONAL)
// ======================
$TEST_MODE = false;
// ======================
// 🔥 AMBIL DATA DARI DATABASE (JOIN HISTORY)
// ======================
$stmt = $conn->query("
SELECT
f.id,
f.name,
f.sn_onu,
f.latitude,
f.longitude,
f.customer_phone,
o.rx_power,
o.voltage,
o.updated_at
FROM ftth_items f
LEFT JOIN onu_daily_history o
ON o.sn = f.sn_onu
INNER JOIN item_types it
ON it.id = f.item_type_id
WHERE f.sn_onu IS NOT NULL
AND LOWER(it.name) = 'pelanggan'
");
$dataItems = $stmt->fetchAll(PDO::FETCH_ASSOC);
foreach ($dataItems as $item) {
usleep(200000);
$sn = strtoupper(trim($item['sn_onu']));
$nama = $item['name'];
$lat = $item['latitude'];
$lng = $item['longitude'];
$phone = $item['customer_phone'];
// ======================
// 🔥 AMBIL RX & VOLT DARI DB
// ======================
$rx = isset($item['rx_power']) ? floatval($item['rx_power']) : null;
$volt = isset($item['voltage']) ? floatval($item['voltage']) : null;
if ($rx === null && $volt === null) {
continue;
}
// ======================
// 🔥 STATUS DETEKSI
// ======================
$isBadRx = ($rx !== null && $rx <= -25.99);
$isBadVolt = ($volt !== null && $volt < 3.29);
echo "DEBUG $sn | RX:$rx | Volt:$volt<br>";
if (!$isBadRx && !$isBadVolt) {
continue;
}
// ======================
// 🔥 CEK ANTI SPAM LOG
// ======================
$cek = $conn->prepare("
SELECT last_rx, last_voltage
FROM notif_device_log
WHERE sn = ?
");
$cek->execute([$sn]);
$last = $cek->fetch(PDO::FETCH_ASSOC);
$lastRx = $last['last_rx'] ?? null;
$lastVolt = $last['last_voltage'] ?? null;
$isChanged = ($lastRx != $rx || $lastVolt != $volt);
if (!$isChanged) {
continue;
}
// ======================
// 🔥 MAPS
// ======================
$maps = ($lat && $lng)
? "https://www.google.com/maps?q={$lat},{$lng}"
: "-";
// ======================
// 🔥 FORMAT NOMOR
// ======================
$phoneFix = null;
if ($phone) {
$phoneFix = preg_replace('/[^0-9]/', '', $phone);
if (substr($phoneFix, 0, 1) == '0') {
$phoneFix = '62' . substr($phoneFix, 1);
}
}
// ======================
// 🔥 NOTIFIKASI
// ======================
if ($isBadRx && $isBadVolt) {
kirimWA($phoneFix,
"🚨 *Gangguan Jaringan & Perangkat*\n\n" .
"Yth. {$nama},\n" .
"📉 RX: " . number_format($rx,2) . " dBm\n" .
"🔌 Volt: " . number_format($volt,2) . " V\n\n" .
"Gangguan pada jaringan & perangkat terdeteksi.\n" .
"Estimasi perbaikan ±2 jam.\n\n" .
"Mohon maaf 🙏"
);
kirimWA($groupTeknisi,
"🚨 *DOUBLE ISSUE*\n\n" .
"👤 {$nama}\n" .
"📉 RX: {$rx} dBm\n" .
"🔌 Volt: " . number_format($volt,2) . " V\n\n" .
"📡 Gangguan IKR + ONU\n" .
"📍 {$maps}\n\n" .
"⚠ PRIORITAS TINGGI"
);
} elseif ($isBadRx) {
kirimWA($phoneFix,
"🚨 *Gangguan Jaringan*\n\n" .
"Yth. {$nama},\n" .
"📉 RX: {$rx} dBm\n\n" .
"Terjadi gangguan pada jalur fiber.\n" .
"Tim akan segera ke lokasi.\n"
);
kirimWA($groupTeknisi,
"🚨 *REDAMAN TIDAK NORMAL*\n\n" .
"👤 {$nama}\n" .
"📉 RX: {$rx} dBm\n" .
"📍 {$maps}"
);
} elseif ($isBadVolt) {
kirimWA($phoneFix,
"⚡ *Perangkat Bermasalah*\n\n" .
"Yth. {$nama},\n" .
"🔌 Volt: " . number_format($volt,2) . " V\n\n" .
"Indikasi power tidak stabil.\n"
);
kirimWA($groupTeknisi,
"⚡ *VOLTAGE TIDAK NORMAL*\n\n" .
"👤 {$nama}\n" .
"🔌 Volt: " . number_format($volt,2) . " V\n" .
"📍 {$maps}"
);
}
// ======================
// 🔥 UPDATE LOG ANTI SPAM
// ======================
$update = $conn->prepare("
INSERT INTO notif_device_log (sn, last_rx, last_voltage, last_sent)
VALUES (?, ?, ?, NOW())
ON DUPLICATE KEY UPDATE
last_rx = VALUES(last_rx),
last_voltage = VALUES(last_voltage),
last_sent = NOW()
");
$update->execute([$sn, $rx, $volt]);
echo "✅ SENT $sn | RX:$rx | V:$volt<br>";
}

4
api/cookie.txt Normal file
View File

@ -0,0 +1,4 @@
# Netscape HTTP Cookie File
# https://curl.se/docs/http-cookies.html
# This file was generated by libcurl! Edit at your own risk.

87
api/data_log_hsgq.php Normal file
View File

@ -0,0 +1,87 @@
<?php
// ================= CONFIG =================
$login_url = "http://10.255.254.21/login";
$data_url = "http://10.255.254.21/ontinfo_table";
$username = "root";
$password = "@lokal234";
$cookie = __DIR__ . "/cookie.txt";
// ================= LOGIN =================
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $login_url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query([
"username" => $username,
"password" => $password
]));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_COOKIEJAR, $cookie);
curl_setopt($ch, CURLOPT_COOKIEFILE, $cookie);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
$result = curl_exec($ch);
if (curl_errno($ch)) {
die("LOGIN ERROR : " . curl_error($ch));
}
curl_close($ch);
// ================= AMBIL DATA =================
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $data_url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_COOKIEFILE, $cookie);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
$response = curl_exec($ch);
if (curl_errno($ch)) {
die("DATA ERROR : " . curl_error($ch));
}
curl_close($ch);
// simpan raw response
file_put_contents("debug_hsgq.json", $response);
// ================= TAMPILKAN =================
$data = json_decode($response, true);
echo "<h3>RAW RESPONSE</h3>";
if ($data === null) {
echo "<pre>";
echo htmlspecialchars(substr($response, 0, 5000));
echo "</pre>";
exit;
}
echo "<h3>FIELD YANG TERSEDIA</h3>";
echo "<pre>";
if (isset($data['data'][0])) {
echo "=== DATA ONU PERTAMA ===\n\n";
print_r($data['data'][0]);
echo "\n\n=== NAMA FIELD ===\n\n";
foreach ($data['data'][0] as $key => $value) {
echo $key . "\n";
}
} else {
print_r($data);
}
echo "</pre>";

1
api/debug_hsgq.json Normal file
View File

@ -0,0 +1 @@
Not found

1176
api/debug_onu.html Normal file

File diff suppressed because it is too large Load Diff

1426
api/debug_onu.json Normal file

File diff suppressed because it is too large Load Diff

95
api/detail_redaman-bc.txt Normal file
View File

@ -0,0 +1,95 @@
<?php
require_once __DIR__ . '/../config/database.php';
header('Content-Type: application/json');
header('Access-Control-Allow-Origin: *');
$database = new Database();
$conn = $database->getConnection();
$id = intval($_GET['id'] ?? 0);
if ($id <= 0) {
echo json_encode(['success'=>false,'message'=>'ID tidak valid']);
exit;
}
$stmt = $conn->prepare("SELECT serial_number FROM ftth_items WHERE id=:id");
$stmt->bindParam(':id', $id, PDO::PARAM_INT);
$stmt->execute();
$row = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$row) {
echo json_encode(['success'=>false,'message'=>'Data tidak ditemukan']);
exit;
}
$serialNumber = $row['serial_number'];
// Default nilai
$rxPower = null;
$pppoeIP = null;
$voltage = null;
$uptime = null;
$inform = null;
if ($serialNumber) {
$query = json_encode(["_id" => $serialNumber]);
$url = "http://rmtstb.megadataisp.net:7557/devices/?query=" . urlencode($query);
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch);
if ($response) {
$data = json_decode($response, true);
if (isset($data[0])) {
// RX Power
if (isset($data[0]['VirtualParameters']['RXPower']['_value'])) {
$rxPower = $data[0]['VirtualParameters']['RXPower']['_value'] . " dBm";
}
// PPPoE IP
if (isset($data[0]['VirtualParameters']['pppoeIP']['_value'])) {
$pppoeIP = $data[0]['VirtualParameters']['pppoeIP']['_value'];
}
// Voltage (VP)
if (isset($data[0]['VirtualParameters']['Voltage']['_value'])) {
$rawVoltage = $data[0]['VirtualParameters']['Voltage']['_value'];
// konversi ke Volt (dibagi 10000)
$voltageConvert = $rawVoltage / 10000;
// ambil 2 angka belakang
$voltage = number_format($voltageConvert, 2) . " V";
}
// Uptime (detik → HH:MM:SS)
if (isset($data[0]['InternetGatewayDevice']['DeviceInfo']['UpTime']['_value'])) {
$seconds = (int)$data[0]['InternetGatewayDevice']['DeviceInfo']['UpTime']['_value'];
$uptime = gmdate("H:i:s", $seconds);
}
// Inform terakhir
if (isset($data[0]['Events']['Inform'])) {
$inform = $data[0]['Events']['Inform'];
}
}
}
}
// Output JSON
echo json_encode([
'success' => true,
'data' => [
'serial_number' => $serialNumber,
'rx_power' => $rxPower ?? 'Tidak tersedia',
'pppoe_ip' => $pppoeIP ?? 'Tidak tersedia',
'voltage' => $voltage ?? 'Tidak tersedia',
'uptime' => $uptime ?? 'Tidak tersedia',
'inform' => $inform ?? 'Tidak tersedia'
]
]);

View File

@ -1,19 +1,47 @@
<?php
require_once __DIR__ . '/../config/database.php';
header('Content-Type: application/json');
header('Access-Control-Allow-Origin: *');
//header('Content-Type: application/json');
$database = new Database();
$conn = $database->getConnection();
// ======================
// 🔥 TEST MODE
// ======================
// url test : http://localhost/ftthplanner/api/detail_redaman.php?id=859
$TEST_MODE = true;
$TEST_DATA = [
"08A5AE-F609-ZXIC6A7451C337D" => [
"rx" => -23,
"volt" => 2.9
],
//id 836 Client putri 081252188305 sn : GGCL298413e0
"08A5AE-F609-ZXIC03C33C4B433" => [
"rx" => -32,
"volt" => 3.2
],
//id : 859 difta dwi RT via kantor sn : CDTCaf8ee4ed
"08A5AE-F609-ZXIC1D6EF05BDD7" => [
"rx" => -28,
"volt" => 3.2
],
];
// ======================
// 🔥 VALIDASI ID
// ======================
$id = intval($_GET['id'] ?? 0);
if ($id <= 0) {
echo json_encode(['success'=>false,'message'=>'ID tidak valid']);
exit;
}
// ======================
// 🔥 AMBIL SN
// ======================
$stmt = $conn->prepare("SELECT serial_number FROM ftth_items WHERE id=:id");
$stmt->bindParam(':id', $id, PDO::PARAM_INT);
$stmt->execute();
@ -24,43 +52,100 @@ if (!$row) {
exit;
}
$serialNumber = $row['serial_number'];
// 🔥 NORMALISASI SN
$serialNumber = strtoupper(trim($row['serial_number']));
// Ambil RX Power dan PPPoE IP dari API
// ======================
// 🔥 DEFAULT
// ======================
$rxPower = null;
$pppoeIP = null;
$voltage = null;
$uptime = null;
$inform = null;
if ($serialNumber) {
$query = json_encode(["_id" => $serialNumber]);
$url = "http://localhost:7557/devices/?query=" . urlencode($query);
// ======================
// 🔥 CEK: MANUAL ATAU AUTO
// ======================
$useManual = ($TEST_MODE && isset($TEST_DATA[$serialNumber]));
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch);
if ($useManual) {
if ($response) {
$data = json_decode($response, true);
// ======================
// 🔥 MODE MANUAL (HANYA SN YANG ADA DI TEST_DATA)
// ======================
$rxPower = $TEST_DATA[$serialNumber]['rx'] . " dBm";
$voltage = number_format($TEST_DATA[$serialNumber]['volt'], 2) . " V";
// Ambil RX Power
if (isset($data[0]['VirtualParameters']['RXPower']['_value'])) {
$rxPower = $data[0]['VirtualParameters']['RXPower']['_value'] . " dBm";
}
$pppoeIP = "TEST MODE";
$uptime = "-";
$inform = "MANUAL";
// Ambil PPPoE IP
if (isset($data[0]['VirtualParameters']['pppoeIP']['_value'])) {
$pppoeIP = $data[0]['VirtualParameters']['pppoeIP']['_value'];
} else {
// ======================
// 🔥 MODE AUTO (GENIEACS)
// ======================
if ($serialNumber) {
$query = json_encode(["_id" => $serialNumber]);
$url = "http://rmtstb.megadataisp.net:7557/devices/?query=" . urlencode($query);
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_TIMEOUT, 5);
$response = curl_exec($ch);
curl_close($ch);
if ($response) {
$data = json_decode($response, true);
if (isset($data[0])) {
// RX
if (isset($data[0]['VirtualParameters']['RXPower']['_value'])) {
$rxPower = $data[0]['VirtualParameters']['RXPower']['_value'] . " dBm";
}
// PPPoE
if (isset($data[0]['VirtualParameters']['pppoeIP']['_value'])) {
$pppoeIP = $data[0]['VirtualParameters']['pppoeIP']['_value'];
}
// Voltage
if (isset($data[0]['VirtualParameters']['Voltage']['_value'])) {
$rawVoltage = $data[0]['VirtualParameters']['Voltage']['_value'];
$voltageConvert = $rawVoltage / 10000;
$voltage = number_format($voltageConvert, 2) . " V";
}
// Uptime
if (isset($data[0]['InternetGatewayDevice']['DeviceInfo']['UpTime']['_value'])) {
$seconds = (int)$data[0]['InternetGatewayDevice']['DeviceInfo']['UpTime']['_value'];
$uptime = gmdate("H:i:s", $seconds);
}
// Inform
if (isset($data[0]['Events']['Inform'])) {
$inform = $data[0]['Events']['Inform'];
}
}
}
}
}
// Output JSON untuk AJAX
// ======================
// 🔥 OUTPUT
// ======================
echo json_encode([
'success' => true,
'data' => [
'serial_number' => $serialNumber,
'rx_power' => $rxPower ?? 'Tidak tersedia',
'pppoe_ip' => $pppoeIP ?? 'Tidak tersedia'
'pppoe_ip' => $pppoeIP ?? 'Tidak tersedia',
'voltage' => $voltage ?? 'Tidak tersedia',
'uptime' => $uptime ?? 'Tidak tersedia',
'inform' => $inform ?? 'Tidak tersedia'
]
]);
]);

0
api/deteksi loss Normal file
View File

62
api/generate.php Normal file
View File

@ -0,0 +1,62 @@
<?php
$baseData = [
['sn' => 'VSOL00addd35', 'name' => 'ARTINI', 'rx' => -25.5300, 'volt' => 3.24],
['sn' => 'VSOL00ad1105', 'name' => 'SATRIA PERUM PANGGUNG', 'rx' => -24.3180, 'volt' => 3.26],
['sn' => 'HWTCa90adda0', 'name' => 'RIDA AGUS', 'rx' => -21.2500, 'volt' => 3.24],
['sn' => 'HWTCa90add88', 'name' => 'SMP 6 SITUBONDO', 'rx' => -21.4280, 'volt' => 3.26],
['sn' => 'HWTCa90aaf00', 'name' => 'BASRIYADI', 'rx' => -21.1360, 'volt' => 3.26],
['sn' => 'HWTCd1b3aa28', 'name' => 'IRWAN', 'rx' => -22.0780, 'volt' => 3.28],
['sn' => 'HWTCa90a4760', 'name' => 'TEGUH ADI', 'rx' => -21.0240, 'volt' => 3.24],
['sn' => 'CDTCaf8e7bf1', 'name' => 'MALD/H1/1/3/13', 'rx' => -22.4420, 'volt' => 3.30],
['sn' => 'HWTCa90aaed8', 'name' => 'FENTI', 'rx' => -21.4280, 'volt' => 3.28],
['sn' => 'HWTCa90aabf8', 'name' => 'DESY/SUPARJO', 'rx' => -22.2920, 'volt' => 3.28],
['sn' => 'HWTCa90addb0', 'name' => 'AYANG', 'rx' => -20.6060, 'volt' => 3.22],
];
$dates = [];
for ($d = 12; $d <= 19; $d++) {
$dates[] = "2026-06-" . str_pad($d, 2, "0", STR_PAD_LEFT);
}
$sql = "";
foreach ($dates as $date) {
foreach ($baseData as $row) {
// 🔥 variasi biar realistis (fluktuasi kecil)
$rx = $row['rx'] + rand(-80, 80) / 1000; // ±0.08 dBm
$volt = $row['volt'] + rand(-5, 5) / 1000; // ±0.005 V
$sample = rand(6, 12);
$min_rx = $rx - rand(5, 20) / 1000;
$max_rx = $rx + rand(5, 20) / 1000;
$min_v = $volt - rand(1, 5) / 1000;
$max_v = $volt + rand(1, 5) / 1000;
$sql .= "INSERT INTO onu_daily_history
(sn, ont_name, rx_power, voltage, min_rx, max_rx, min_voltage, max_voltage, last_cause, sample_count, log_date, created_at, updated_at)
VALUES
(
'{$row['sn']}',
'{$row['name']}',
" . round($rx, 4) . ",
" . round($volt, 3) . ",
" . round($min_rx, 4) . ",
" . round($max_rx, 4) . ",
" . round($min_v, 3) . ",
" . round($max_v, 3) . ",
NULL,
{$sample},
'{$date}',
'{$date} 08:00:00',
'{$date} 17:00:00'
);\n\n";
}
}
file_put_contents("onu_history_12_19_june.sql", $sql);
echo "DONE: file SQL berhasil dibuat";

50
api/get_onu_by_item.php Normal file
View File

@ -0,0 +1,50 @@
<?php
header('Content-Type: application/json');
require_once __DIR__ . '/../config/database.php';
$db = (new Database())->getConnection();
$id = $_GET['id'] ?? null;
if (!$id) {
echo json_encode(['success' => false, 'message' => 'ID kosong']);
exit;
}
// 1. ambil SN dari ftth_items
$query = "SELECT sn_onu FROM ftth_items WHERE id = :id";
$stmt = $db->prepare($query);
$stmt->bindParam(':id', $id);
$stmt->execute();
$item = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$item || empty($item['sn_onu'])) {
echo json_encode(['success' => false, 'message' => 'SN tidak ada']);
exit;
}
// 2. ambil dari onu_status
$query = "SELECT status, rx_power, last_update
FROM onu_status
WHERE sn = :sn
LIMIT 1";
$stmt = $db->prepare($query);
$stmt->bindParam(':sn', $item['sn_onu']);
$stmt->execute();
$data = $stmt->fetch(PDO::FETCH_ASSOC);
if ($data) {
echo json_encode([
'success' => true,
'data' => $data
]);
} else {
echo json_encode([
'success' => false,
'message' => 'Data ONU tidak ditemukan'
]);
}

105
api/get_onu_volt.php Normal file
View File

@ -0,0 +1,105 @@
<?php
header('Content-Type: application/json');
require_once __DIR__ . '/../config/database.php';
$db = new Database();
$conn = $db->getConnection();
$item_id = intval($_GET['id'] ?? 0);
if (!$item_id) {
echo json_encode([
'success' => false,
'message' => 'ID tidak valid'
]);
exit;
}
/*
|--------------------------------------------------------------------------
| Ambil SN dari ftth_items
|--------------------------------------------------------------------------
*/
$stmt = $conn->prepare("
SELECT sn_onu
FROM ftth_items
WHERE id = ?
");
$stmt->execute([$item_id]);
$item = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$item || empty($item['sn_onu'])) {
echo json_encode([
'success' => false,
'message' => 'SN tidak ada'
]);
exit;
}
$sn = trim($item['sn_onu']);
/*
|--------------------------------------------------------------------------
| Ambil optical terakhir
|--------------------------------------------------------------------------
*/
$stmt = $conn->prepare("
SELECT
sn,
ont_name,
rx_power,
voltage,
min_rx,
max_rx,
min_voltage,
max_voltage,
updated_at
FROM onu_daily_history
WHERE sn = ?
ORDER BY updated_at DESC
LIMIT 1
");
$stmt->execute([$sn]);
$data = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$data) {
echo json_encode([
'success' => false,
'message' => 'Data optical tidak ditemukan'
]);
exit;
}
/*
|--------------------------------------------------------------------------
| Format Data
|--------------------------------------------------------------------------
*/
$data['rx_power'] = number_format((float)$data['rx_power'], 2) . ' dBm';
$data['voltage'] = number_format((float)$data['voltage'], 2) . ' V';
$data['min_rx'] = number_format((float)$data['min_rx'], 2) . ' dBm';
$data['max_rx'] = number_format((float)$data['max_rx'], 2) . ' dBm';
$data['min_voltage'] = number_format((float)$data['min_voltage'], 2) . ' V';
$data['max_voltage'] = number_format((float)$data['max_voltage'], 2) . ' V';
echo json_encode([
'success' => true,
'data' => $data
]);

42
api/histori_onu.php Normal file
View File

@ -0,0 +1,42 @@
<?php
require_once __DIR__ . '/../config/database.php';
$db = new Database();
$conn = $db->getConnection();
$id = $_GET['id'] ?? null;
if (!$id) {
echo json_encode(['success' => false]);
exit;
}
// ambil SN dari item
$stmt = $conn->prepare("SELECT sn_onu FROM ftth_items WHERE id = ?");
$stmt->execute([$id]);
$item = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$item) {
echo json_encode(['success' => false]);
exit;
}
$sn = $item['sn_onu'];
// ambil histori anomali saja
$stmt = $conn->prepare("
SELECT *
FROM status_onu_histori
WHERE sn = ?
ORDER BY created_at DESC
LIMIT 100
");
$stmt->execute([$sn]);
$data = $stmt->fetchAll(PDO::FETCH_ASSOC);
echo json_encode([
'success' => true,
'sn' => $sn,
'data' => $data
]);

94
api/histori_redaman.php Normal file
View File

@ -0,0 +1,94 @@
<?php
header('Content-Type: application/json');
require_once __DIR__ . '/../config/database.php';
$db = new Database();
$conn = $db->getConnection();
$item_id = intval($_GET['id'] ?? 0);
if (!$item_id) {
echo json_encode([
'success' => false,
'message' => 'ID tidak valid'
]);
exit;
}
/*
|--------------------------------------------------------------------------
| Ambil SN dari ftth_items
|--------------------------------------------------------------------------
*/
$stmt = $conn->prepare("
SELECT sn_onu, name
FROM ftth_items
WHERE id = ?
");
$stmt->execute([$item_id]);
$item = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$item || empty($item['sn_onu'])) {
echo json_encode([
'success' => false,
'message' => 'SN tidak ditemukan'
]);
exit;
}
$sn = trim($item['sn_onu']);
/*
|--------------------------------------------------------------------------
| Ambil histori redaman & voltage
|--------------------------------------------------------------------------
*/
$stmt = $conn->prepare("
SELECT
rx_power,
voltage,
updated_at
FROM onu_daily_history
WHERE sn = ?
ORDER BY updated_at DESC
LIMIT 30
");
$stmt->execute([$sn]);
$data = $stmt->fetchAll(PDO::FETCH_ASSOC);
if (!$data) {
echo json_encode([
'success' => false,
'message' => 'Histori kosong'
]);
exit;
}
/*
|--------------------------------------------------------------------------
| Format biar rapi (opsional)
|--------------------------------------------------------------------------
*/
foreach ($data as &$row) {
// format RX
if ($row['rx_power'] !== null) {
$row['rx_power'] = number_format((float)$row['rx_power'], 2) . ' dBm';
}
// format voltage
if ($row['voltage'] !== null) {
$row['voltage'] = number_format((float)$row['voltage'], 2) . ' V';
}
// tanggal lebih enak dibaca
$row['updated_at'] = date('d-m-Y H:i:s', strtotime($row['updated_at']));
}
echo json_encode([
'success' => true,
'sn' => $sn,
'data' => $data
]);

View File

@ -11,81 +11,65 @@ if ($_SERVER['REQUEST_METHOD'] == 'OPTIONS') {
exit();
}
session_start(); // <-- Tambahan penting
require_once __DIR__ . '/../config/database.php';
// Ambil user info dari session
$user_id = $_SESSION['user_id'] ?? null;
$role = $_SESSION['role'] ?? 'teknisi1';
// Function to manually parse multipart form data
function parseMultipartFormData($input, $contentType) {
$data = array();
// Extract boundary from content type
if (preg_match('/boundary=(.+)$/', $contentType, $matches)) {
$boundary = $matches[1];
// Split the input by boundary
$parts = array_slice(explode('--' . $boundary, $input), 1);
foreach ($parts as $part) {
// Skip empty parts and closing boundary
if (trim($part) == '--' || empty(trim($part))) continue;
// Split headers and body
$sections = explode("\r\n\r\n", $part, 2);
if (count($sections) != 2) continue;
$headers = $sections[0];
$body = rtrim($sections[1], "\r\n");
// Extract field name from Content-Disposition header
if (preg_match('/name="([^"]*)"/', $headers, $matches)) {
$fieldName = $matches[1];
$data[$fieldName] = $body;
}
}
}
return $data;
}
$database = new Database();
$db = $database->getConnection();
// Handle method override and multipart data parsing
$method = $_SERVER['REQUEST_METHOD'];
$parsed_data = array();
// Parse multipart form data manually if needed
if (($method === 'PUT' || $method === 'PATCH') && empty($_POST) &&
isset($_SERVER['CONTENT_TYPE']) && strpos($_SERVER['CONTENT_TYPE'], 'multipart/form-data') !== false) {
// Force treat as POST to get parsed form data
$raw_input = file_get_contents('php://input');
$parsed_data = parseMultipartFormData($raw_input, $_SERVER['CONTENT_TYPE']);
// If we found form data, treat this as a method override
if (!empty($parsed_data)) {
if (isset($parsed_data['_method'])) {
$method = strtoupper($parsed_data['_method']);
unset($parsed_data['_method']);
}
// Populate $_POST with parsed data for compatibility
$_POST = $parsed_data;
}
}
// Check for X-HTTP-Method-Override header
if (isset($_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'])) {
$method = $_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'];
}
// Check for _method parameter (Laravel style) - now works with manually parsed data too
if (isset($_POST['_method'])) {
$method = strtoupper($_POST['_method']);
unset($_POST['_method']); // Clean it up
unset($_POST['_method']);
}
$response = array('success' => false, 'message' => '', 'data' => null);
// Log only important requests for production
if ($method === 'PUT' || $method === 'DELETE') {
error_log("FTTH API - " . $method . " request, ID: " . (isset($_POST['id']) ? $_POST['id'] : 'N/A'));
}
@ -94,24 +78,30 @@ try {
switch($method) {
case 'GET':
if (isset($_GET['id'])) {
// Get single item
$query = "SELECT i.*, i.serial_number,
it.name as item_type_name, it.icon, it.color,
tc.color_name as tube_color_name, tc.hex_code,
cc.color_name as core_color_name, cc.hex_code as core_hex_code,
sm.ratio as splitter_main_ratio,
so.ratio as splitter_odp_ratio
FROM ftth_items i
LEFT JOIN item_types it ON i.item_type_id = it.id
LEFT JOIN tube_colors tc ON i.tube_color_id = tc.id
LEFT JOIN tube_colors cc ON i.core_color_id = cc.id
LEFT JOIN splitter_types sm ON i.splitter_main_id = sm.id
LEFT JOIN splitter_types so ON i.splitter_odp_id = so.id
WHERE i.id = :id";
it.name as item_type_name, it.icon, it.color,
tc.color_name as tube_color_name, tc.hex_code,
cc.color_name as core_color_name, cc.hex_code as core_hex_code,
sm.ratio as splitter_main_ratio,
so.ratio as splitter_odp_ratio
FROM ftth_items i
LEFT JOIN item_types it ON i.item_type_id = it.id
LEFT JOIN tube_colors tc ON i.tube_color_id = tc.id
LEFT JOIN tube_colors cc ON i.core_color_id = cc.id
LEFT JOIN splitter_types sm ON i.splitter_main_id = sm.id
LEFT JOIN splitter_types so ON i.splitter_odp_id = so.id
WHERE i.id = :id";
// kalau teknisi, batasi ke user_id
if ($role !== 'engineer') {
$query .= " AND i.user_id = :user_id";
}
$stmt = $db->prepare($query);
$stmt->bindParam(':id', $_GET['id']);
if ($role !== 'engineer') {
$stmt->bindParam(':user_id', $user_id);
}
$stmt->execute();
$item = $stmt->fetch(PDO::FETCH_ASSOC);
@ -122,23 +112,28 @@ try {
$response['message'] = 'Item not found';
}
} else {
// Get all items
$query = "SELECT i.*, i.serial_number,
it.name as item_type_name, it.icon, it.color,
tc.color_name as tube_color_name, tc.hex_code,
cc.color_name as core_color_name, cc.hex_code as core_hex_code,
sm.ratio as splitter_main_ratio,
so.ratio as splitter_odp_ratio
FROM ftth_items i
LEFT JOIN item_types it ON i.item_type_id = it.id
LEFT JOIN tube_colors tc ON i.tube_color_id = tc.id
LEFT JOIN tube_colors cc ON i.core_color_id = cc.id
LEFT JOIN splitter_types sm ON i.splitter_main_id = sm.id
LEFT JOIN splitter_types so ON i.splitter_odp_id = so.id
ORDER BY i.created_at DESC";
it.name as item_type_name, it.icon, it.color,
tc.color_name as tube_color_name, tc.hex_code,
cc.color_name as core_color_name, cc.hex_code as core_hex_code,
sm.ratio as splitter_main_ratio,
so.ratio as splitter_odp_ratio
FROM ftth_items i
LEFT JOIN item_types it ON i.item_type_id = it.id
LEFT JOIN tube_colors tc ON i.tube_color_id = tc.id
LEFT JOIN tube_colors cc ON i.core_color_id = cc.id
LEFT JOIN splitter_types sm ON i.splitter_main_id = sm.id
LEFT JOIN splitter_types so ON i.splitter_odp_id = so.id";
if ($role !== 'engineer') {
$query .= " WHERE i.user_id = :user_id";
}
$query .= " ORDER BY i.created_at DESC";
$stmt = $db->prepare($query);
if ($role !== 'engineer') {
$stmt->bindParam(':user_id', $user_id);
}
$stmt->execute();
$items = $stmt->fetchAll(PDO::FETCH_ASSOC);
@ -148,81 +143,91 @@ try {
break;
case 'POST':
// Create new item
$item_type_id = $_POST['item_type'] ?? null;
$name = $_POST['name'] ?? null;
$description = $_POST['description'] ?? null;
$latitude = $_POST['latitude'] ?? null;
$longitude = $_POST['longitude'] ?? null;
$address = $_POST['address'] ?? null;
$serial_number = $_POST['serial_number'] ?? null; // <--- Tambahan
// Handle foreign key fields - convert empty strings to NULL
$tube_color_id = (!empty($_POST['tube_color_id']) && $_POST['tube_color_id'] !== '0') ? $_POST['tube_color_id'] : null;
$core_used = (!empty($_POST['core_used']) && $_POST['core_used'] !== '0') ? $_POST['core_used'] : null;
$core_color_id = (!empty($_POST['core_color_id']) && $_POST['core_color_id'] !== '0') ? $_POST['core_color_id'] : null;
$item_cable_type = $_POST['item_cable_type'] ?? 'distribution';
$total_core_capacity = $_POST['total_core_capacity'] ?? 24;
$splitter_main_id = (!empty($_POST['splitter_main_id']) && $_POST['splitter_main_id'] !== '0') ? $_POST['splitter_main_id'] : null;
$splitter_odp_id = (!empty($_POST['splitter_odp_id']) && $_POST['splitter_odp_id'] !== '0') ? $_POST['splitter_odp_id'] : null;
$status = $_POST['status'] ?? 'active';
if (!$item_type_id || !$name || !$latitude || !$longitude) {
$response['message'] = 'Required fields missing';
break;
}
$query = "INSERT INTO ftth_items (
item_type_id, name, description, latitude, longitude, address, serial_number,
tube_color_id, core_used, core_color_id, item_cable_type, total_core_capacity,
splitter_main_id, splitter_odp_id, status
) VALUES (
:item_type_id, :name, :description, :latitude, :longitude, :address, :serial_number,
:tube_color_id, :core_used, :core_color_id, :item_cable_type, :total_core_capacity,
:splitter_main_id, :splitter_odp_id, :status
)";
$stmt = $db->prepare($query);
$stmt->bindParam(':item_type_id', $item_type_id);
$stmt->bindParam(':name', $name);
$stmt->bindParam(':description', $description);
$stmt->bindParam(':latitude', $latitude);
$stmt->bindParam(':longitude', $longitude);
$stmt->bindParam(':address', $address);
$stmt->bindParam(':serial_number', $serial_number); // <--- Bind
$stmt->bindParam(':tube_color_id', $tube_color_id);
$stmt->bindParam(':core_used', $core_used);
$stmt->bindParam(':core_color_id', $core_color_id);
$stmt->bindParam(':item_cable_type', $item_cable_type);
$stmt->bindParam(':total_core_capacity', $total_core_capacity);
$stmt->bindParam(':splitter_main_id', $splitter_main_id);
$stmt->bindParam(':splitter_odp_id', $splitter_odp_id);
$stmt->bindParam(':status', $status);
$item_type_id = $_POST['item_type'] ?? null;
$name = $_POST['name'] ?? null;
$description = $_POST['description'] ?? null;
$latitude = $_POST['latitude'] ?? null;
$longitude = $_POST['longitude'] ?? null;
$address = $_POST['address'] ?? null;
$serial_number = $_POST['serial_number'] ?? null;
$sn_onu = $_POST['sn_onu'] ?? null;
$customer_phone = $_POST['customer_phone'] ?? null;
$tube_color_id = (!empty($_POST['tube_color_id']) && $_POST['tube_color_id'] !== '0') ? $_POST['tube_color_id'] : null;
$core_used = (!empty($_POST['core_used']) && $_POST['core_used'] !== '0') ? $_POST['core_used'] : null;
$core_color_id = (!empty($_POST['core_color_id']) && $_POST['core_color_id'] !== '0') ? $_POST['core_color_id'] : null;
$item_cable_type = (!empty($_POST['item_cable_type'])) ? $_POST['item_cable_type'] : null;
$total_core_capacity = $_POST['total_core_capacity'] ?? 24;
$splitter_main_id = (!empty($_POST['splitter_main_id']) && $_POST['splitter_main_id'] !== '0') ? $_POST['splitter_main_id'] : null;
$splitter_odp_id = (!empty($_POST['splitter_odp_id']) && $_POST['splitter_odp_id'] !== '0') ? $_POST['splitter_odp_id'] : null;
$status = $_POST['status'] ?? 'active';
if (!$item_type_id || !$name || !$latitude || !$longitude) {
$response['message'] = 'Required fields missing';
break;
}
if ($role === 'engineer') {
$query = "INSERT INTO ftth_items (
item_type_id, name, description, latitude, longitude, address, serial_number, sn_onu, customer_phone,
tube_color_id, core_used, core_color_id, item_cable_type, total_core_capacity,
splitter_main_id, splitter_odp_id, status
) VALUES (
:item_type_id, :name, :description, :latitude, :longitude, :address, :serial_number, :sn_onu, :customer_phone,
:tube_color_id, :core_used, :core_color_id, :item_cable_type, :total_core_capacity,
:splitter_main_id, :splitter_odp_id, :status
)";
$stmt = $db->prepare($query);
} else {
$query = "INSERT INTO ftth_items (
user_id, item_type_id, name, description, latitude, longitude, address, serial_number, sn_onu, customer_phone,
tube_color_id, core_used, core_color_id, item_cable_type, total_core_capacity,
splitter_main_id, splitter_odp_id, status
) VALUES (
:user_id, :item_type_id, :name, :description, :latitude, :longitude, :address, :serial_number, :sn_onu, :customer_phone,
:tube_color_id, :core_used, :core_color_id, :item_cable_type, :total_core_capacity,
:splitter_main_id, :splitter_odp_id, :status
)";
$stmt = $db->prepare($query);
$stmt->bindParam(':user_id', $user_id);
}
$stmt->bindParam(':item_type_id', $item_type_id);
$stmt->bindParam(':name', $name);
$stmt->bindParam(':description', $description);
$stmt->bindParam(':latitude', $latitude);
$stmt->bindParam(':longitude', $longitude);
$stmt->bindParam(':address', $address);
$stmt->bindParam(':serial_number', $serial_number);
$stmt->bindParam(':tube_color_id', $tube_color_id);
$stmt->bindParam(':core_used', $core_used);
$stmt->bindParam(':core_color_id', $core_color_id);
$stmt->bindParam(':item_cable_type', $item_cable_type);
$stmt->bindParam(':total_core_capacity', $total_core_capacity);
$stmt->bindParam(':splitter_main_id', $splitter_main_id);
$stmt->bindParam(':splitter_odp_id', $splitter_odp_id);
$stmt->bindParam(':status', $status);
$stmt->bindParam(':sn_onu', $sn_onu);
$stmt->bindParam(':customer_phone', $customer_phone);
if ($stmt->execute()) {
$item_id = $db->lastInsertId();
// Get the created item with joins
$query = "SELECT i.*, i.serial_number,
it.name as item_type_name, it.icon, it.color,
tc.color_name as tube_color_name, tc.hex_code,
cc.color_name as core_color_name, cc.hex_code as core_hex_code,
sm.ratio as splitter_main_ratio,
so.ratio as splitter_odp_ratio
FROM ftth_items i
LEFT JOIN item_types it ON i.item_type_id = it.id
LEFT JOIN tube_colors tc ON i.tube_color_id = tc.id
LEFT JOIN tube_colors cc ON i.core_color_id = cc.id
LEFT JOIN splitter_types sm ON i.splitter_main_id = sm.id
LEFT JOIN splitter_types so ON i.splitter_odp_id = so.id
WHERE i.id = :id";
it.name as item_type_name, it.icon, it.color,
tc.color_name as tube_color_name, tc.hex_code,
cc.color_name as core_color_name, cc.hex_code as core_hex_code,
sm.ratio as splitter_main_ratio,
so.ratio as splitter_odp_ratio
FROM ftth_items i
LEFT JOIN item_types it ON i.item_type_id = it.id
LEFT JOIN tube_colors tc ON i.tube_color_id = tc.id
LEFT JOIN tube_colors cc ON i.core_color_id = cc.id
LEFT JOIN splitter_types sm ON i.splitter_main_id = sm.id
LEFT JOIN splitter_types so ON i.splitter_odp_id = so.id
WHERE i.id = :id";
$stmt = $db->prepare($query);
$stmt->bindParam(':id', $item_id);
$stmt->execute();
$response['success'] = true;
$response['message'] = 'Item created successfully';
$response['data'] = $stmt->fetch(PDO::FETCH_ASSOC);
@ -232,67 +237,53 @@ try {
break;
case 'PUT':
// Update item - now $_POST should be properly populated
$put_data = $_POST;
$id = $put_data['id'] ?? null;
if (!$id) {
$response['message'] = 'ID required for update';
break;
}
// Build dynamic update query
$update_fields = array();
$params = array(':id' => $id);
$allowed_fields = ['item_type', 'name', 'description', 'latitude', 'longitude', 'address', 'serial_number','tube_color_id', 'core_used', 'core_color_id', 'item_cable_type', 'total_core_capacity', 'splitter_main_id', 'splitter_odp_id', 'status'];
$allowed_fields = ['item_type', 'name', 'description', 'latitude', 'longitude', 'address', 'serial_number','tube_color_id', 'core_used', 'core_color_id', 'item_cable_type', 'total_core_capacity', 'splitter_main_id', 'splitter_odp_id', 'sn_onu', 'customer_phone','status'];
foreach ($allowed_fields as $field) {
if (isset($put_data[$field])) {
$db_field = $field === 'item_type' ? 'item_type_id' : $field;
$update_fields[] = "$db_field = :$field";
// Handle empty values for foreign key fields - convert to NULL
$value = $put_data[$field];
if (in_array($field, ['tube_color_id', 'core_color_id', 'splitter_main_id', 'splitter_odp_id']) &&
($value === '' || $value === '0' || $value === 0)) {
if (in_array($field, ['tube_color_id', 'core_color_id', 'splitter_main_id', 'splitter_odp_id']) && ($value === '' || $value === '0' || $value === 0)) {
$value = null;
}
$params[":$field"] = $value;
}
}
if (empty($update_fields)) {
$response['message'] = 'No fields to update';
break;
}
$query = "UPDATE ftth_items SET " . implode(', ', $update_fields) . " WHERE id = :id";
if ($role !== 'engineer') {
$query .= " AND user_id = :user_id";
$params[':user_id'] = $user_id;
}
$stmt = $db->prepare($query);
if ($stmt->execute($params)) {
// Get updated item
$query = "SELECT i.*, i.serial_number,
it.name as item_type_name, it.icon, it.color,
tc.color_name as tube_color_name, tc.hex_code,
cc.color_name as core_color_name, cc.hex_code as core_hex_code,
sm.ratio as splitter_main_ratio,
so.ratio as splitter_odp_ratio
FROM ftth_items i
LEFT JOIN item_types it ON i.item_type_id = it.id
LEFT JOIN tube_colors tc ON i.tube_color_id = tc.id
LEFT JOIN tube_colors cc ON i.core_color_id = cc.id
LEFT JOIN splitter_types sm ON i.splitter_main_id = sm.id
LEFT JOIN splitter_types so ON i.splitter_odp_id = so.id
WHERE i.id = :id";
$query = "SELECT i.*, i.serial_number, i.sn_onu, i.customer_phone,
it.name as item_type_name, it.icon, it.color,
tc.color_name as tube_color_name, tc.hex_code,
cc.color_name as core_color_name, cc.hex_code as core_hex_code,
sm.ratio as splitter_main_ratio,
so.ratio as splitter_odp_ratio
FROM ftth_items i
LEFT JOIN item_types it ON i.item_type_id = it.id
LEFT JOIN tube_colors tc ON i.tube_color_id = tc.id
LEFT JOIN tube_colors cc ON i.core_color_id = cc.id
LEFT JOIN splitter_types sm ON i.splitter_main_id = sm.id
LEFT JOIN splitter_types so ON i.splitter_odp_id = so.id
WHERE i.id = :id";
$stmt = $db->prepare($query);
$stmt->bindParam(':id', $id);
$stmt->execute();
$response['success'] = true;
$response['message'] = 'Item updated successfully';
$response['data'] = $stmt->fetch(PDO::FETCH_ASSOC);
@ -302,41 +293,35 @@ try {
break;
case 'DELETE':
// Delete item
$input = file_get_contents("php://input");
$delete_data = array();
// Try to parse JSON first, then form data
$json_data = json_decode($input, true);
if ($json_data) {
$delete_data = $json_data;
} else {
parse_str($input, $delete_data);
}
// Also check for regular POST data (for compatibility)
if (empty($delete_data)) {
$delete_data = $_POST;
}
$id = $delete_data['id'] ?? null;
if (!$id) {
$response['message'] = 'ID required for deletion';
break;
}
// Delete related routes first
$query = "DELETE FROM cable_routes WHERE from_item_id = :id OR to_item_id = :id";
$stmt = $db->prepare($query);
$stmt->bindParam(':id', $id);
$stmt->execute();
// Delete the item
$query = "DELETE FROM ftth_items WHERE id = :id";
if ($role !== 'engineer') {
$query .= " AND user_id = :user_id";
}
$stmt = $db->prepare($query);
$stmt->bindParam(':id', $id);
if ($role !== 'engineer') {
$stmt->bindParam(':user_id', $user_id);
}
if ($stmt->execute()) {
$response['success'] = true;
$response['message'] = 'Item deleted successfully';
@ -355,4 +340,4 @@ try {
}
echo json_encode($response);
?>
?>

314
api/laporan_odp.php Normal file
View File

@ -0,0 +1,314 @@
<?php
require_once __DIR__ . '/../config/database.php';
header('Content-Type: application/json');
$db = new Database();
$conn = $db->getConnection();
if (isset($_GET['kirim']) && isset($_GET['id'])) {
function kirimWA($pesan) {
$token = "XjC9oSr4jECWG93JQRWf";
$data = [
"target" => "120363407747513164@g.us",
"message" => $pesan,
];
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.fonnte.com/send",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => http_build_query($data),
CURLOPT_HTTPHEADER => [
"Authorization: $token"
],
]);
curl_exec($curl);
curl_close($curl);
}
$id = $_GET['id'];
// 🔥 ambil ODP
$stmt = $conn->prepare("
SELECT i.id, i.name, i.latitude, i.longitude
FROM ftth_items i
WHERE i.id = ?
");
$stmt->execute([$id]);
$odp = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$odp) {
echo json_encode(['success' => false]);
exit;
}
// 🔥 ambil client
$stmtClient = $conn->prepare("
SELECT fi.id, fi.name, fi.serial_number
FROM cable_routes cr
JOIN ftth_items fi
ON (cr.to_item_id = fi.id OR cr.from_item_id = fi.id)
WHERE (cr.from_item_id = :id OR cr.to_item_id = :id)
AND fi.id != :id
");
$stmtClient->bindParam(':id', $id);
$stmtClient->execute();
$clients = $stmtClient->fetchAll(PDO::FETCH_ASSOC);
$rxValues = [];
$clientBad = [];
foreach ($clients as $c) {
if (empty($c['serial_number'])) continue;
$query = json_encode(["_id" => $c['serial_number']]);
$url = "http://rmtstb.megadataisp.net:7557/devices/?query=" . urlencode($query);
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_TIMEOUT, 5);
$res = curl_exec($ch);
curl_close($ch);
if (!$res) continue;
$json = json_decode($res, true);
if (
isset($json[0]['VirtualParameters']['RXPower']['_value']) &&
is_numeric($json[0]['VirtualParameters']['RXPower']['_value'])
) {
$rx = floatval($json[0]['VirtualParameters']['RXPower']['_value']);
$rxValues[] = $rx;
if ($rx < -23) {
$clientBad[] = $c['name'] . " (" . $rx . " dBm)";
}
}
}
// 🔥 HITUNG
$total = count($rxValues);
$bad = count($clientBad);
$good = $total - $bad;
$avg = $total > 0 ? round(array_sum($rxValues) / $total, 2) : '-';
$min = $total > 0 ? min($rxValues) : '-';
// 🔥 STATUS
$status = "BAIK";
if ($total == 0) {
$status = "NO DATA";
} elseif ($min < -27) {
$status = "KRITIS";
} elseif ($avg < -25 || $bad > ($total * 0.5)) {
$status = "BURUK";
} elseif ($avg < -23 || $bad > ($total * 0.3)) {
$status = "WARNING";
}
// 🔥 FORMAT WA
$maps = "https://www.google.com/maps?q={$odp['latitude']},{$odp['longitude']}";
$pesan = "🚨 *LAPORAN ODP*\n\n";
$pesan .= "📍 *ODP:* {$odp['name']}\n";
$pesan .= "📊 *Status:* {$status}\n\n";
$pesan .= "👥 *Total Client:* {$total}\n";
$pesan .= "🟢 Client Baik: {$good}\n";
$pesan .= "🔴 Client Buruk: {$bad}\n\n";
$pesan .= "📶 *AVG RX:* {$avg} dBm\n";
$pesan .= "📉 *MIN RX:* {$min} dBm\n\n";
if (!empty($clientBad)) {
$pesan .= "⚠ *Client Bermasalah:*\n";
$pesan .= implode("\n", array_slice($clientBad, 0, 10));
$pesan .= "\n\n";
}
$pesan .= "📌 Lokasi:\n$maps\n\n";
$pesan .= "🔎 Silakan cek detail di dashboard.";
kirimWA($pesan);
echo json_encode(['success' => true]);
exit;
}
// ✅ Ambil semua ODP
$stmt = $conn->query("
SELECT i.id, i.name, i.latitude, i.longitude
FROM ftth_items i
LEFT JOIN item_types it ON i.item_type_id = it.id
WHERE LOWER(it.name) = 'odp'
AND i.user_id = 22
");
$odps = $stmt->fetchAll(PDO::FETCH_ASSOC);
$result = [];
foreach ($odps as $odp) {
// ✅ ambil client
$stmtClient = $conn->prepare("
SELECT fi.id, fi.name, fi.serial_number
FROM cable_routes cr
JOIN ftth_items fi
ON (cr.to_item_id = fi.id OR cr.from_item_id = fi.id)
WHERE (cr.from_item_id = :id OR cr.to_item_id = :id)
AND fi.id != :id
");
$stmtClient->bindParam(':id', $odp['id']);
$stmtClient->execute();
$clients = $stmtClient->fetchAll(PDO::FETCH_ASSOC);
$rxValues = [];
$rxList = [];
$rxBadList = [];
$clientBad = [];
foreach ($clients as $c) {
if (empty($c['serial_number'])) continue;
$query = json_encode(["_id" => $c['serial_number']]);
$url = "http://rmtstb.megadataisp.net:7557/devices/?query=" . urlencode($query);
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_TIMEOUT, 5);
$res = curl_exec($ch);
curl_close($ch);
if (!$res) continue;
$json = json_decode($res, true);
if (
isset($json[0]['VirtualParameters']['RXPower']['_value']) &&
is_numeric($json[0]['VirtualParameters']['RXPower']['_value'])
) {
$rx = floatval($json[0]['VirtualParameters']['RXPower']['_value']);
$rxValues[] = $rx;
$rxList[] = $rx;
if ($rx < -23) {
$rxBadList[] = $rx;
$clientBad[] = $c['name'] . " (" . $rx . " dBm)";
}
}
}
// 🔴 Tidak ada data
if (count($rxValues) == 0) {
$result[] = [
'id' => $odp['id'],
'nama' => $odp['name'],
'lat' => $odp['latitude'],
'lng' => $odp['longitude'],
'total' => 0,
'avg_rx' => null,
'bad_count' => 0,
'min_rx' => null,
'status' => 'NO DATA',
'status_color' => 'secondary',
'analisa' => 'Tidak ada data pelanggan / ONU offline semua'
];
continue;
}
$total = count($rxValues);
$avg = array_sum($rxValues) / $total;
$min = min($rxValues);
$bad = count($rxBadList);
// 🔥 DETEKSI SELISIH
$selisih = abs($avg - $min);
// 🔥 STATUS
$status = "BAIK";
$color = "success";
if ($selisih > 1.5) {
$status = "WARNING";
$color = "warning";
}
if ($avg < -23 || $bad > ($total * 0.3)) {
$status = "WARNING";
$color = "warning";
}
if ($avg < -25 || $bad > ($total * 0.5)) {
$status = "BURUK";
$color = "danger";
}
if ($min < -27) {
$status = "KRITIS";
$color = "dark";
}
// 🔥 ANALISA + TAMBAHAN INFORMASI CLIENT
if ($min < ($avg - 1.5)) {
$analisa = "Ada client lebih jelek dari yang lain → kemungkinan IKR/dropcore bermasalah";
}
elseif ($status === "KRITIS") {
$analisa = "Banyak RX < -27 dBm → kemungkinan feeder/ODP bermasalah";
}
elseif ($status === "BURUK") {
$analisa = "Lebih dari 50% client jelek → indikasi splitter ODP / konektor kotor";
}
elseif ($status === "WARNING") {
$analisa = "Sebagian client jelek → kemungkinan jalur ke rumah";
}
else {
$analisa = "Semua pelanggan normal";
}
// 🔥 TAMBAHKAN DETAIL RX & CLIENT BURUK KE ANALISA
$analisa .= " | RX: " . implode(", ", $rxList);
if (!empty($clientBad)) {
$analisa .= " | Client bermasalah: " . implode(", ", $clientBad);
}
$result[] = [
'id' => $odp['id'],
'nama' => $odp['name'],
'lat' => $odp['latitude'],
'lng' => $odp['longitude'],
'total' => $total,
'avg_rx' => round($avg, 2),
'bad_count' => $bad,
'min_rx' => $min,
'status' => $status,
'status_color' => $color,
'analisa' => $analisa
];
}
echo json_encode([
'success' => true,
'data' => $result
]);

167
api/log.php Normal file
View File

@ -0,0 +1,167 @@
<?php
require_once __DIR__ . '/../config/database.php';
$db = new Database();
$conn = $db->getConnection();
$login_url = "http://10.255.254.21/login";
$cookie = __DIR__ . "/cookie.txt";
/*
|--------------------------------------------------------------------------
| LOGIN
|--------------------------------------------------------------------------
*/
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $login_url,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => http_build_query([
"username" => "root",
"password" => "@lokal234"
]),
CURLOPT_RETURNTRANSFER => true,
CURLOPT_COOKIEJAR => $cookie,
CURLOPT_COOKIEFILE => $cookie,
]);
$login = curl_exec($ch);
echo "<h3>LOGIN</h3>";
echo htmlspecialchars($login);
curl_close($ch);
/*
|--------------------------------------------------------------------------
| PREPARE INSERT
|--------------------------------------------------------------------------
*/
$stmt = $conn->prepare("
INSERT INTO onu_daily_history
(
sn,
ont_name,
rx_power,
voltage,
min_rx,
max_rx,
min_voltage,
max_voltage,
sample_count,
log_date
)
VALUES
(
:sn,
:ont_name,
:rx_power,
:voltage,
:min_rx,
:max_rx,
:min_voltage,
:max_voltage,
1,
CURDATE()
)
ON DUPLICATE KEY UPDATE
ont_name = VALUES(ont_name),
rx_power = VALUES(rx_power),
voltage = VALUES(voltage),
min_rx = LEAST(min_rx, VALUES(rx_power)),
max_rx = GREATEST(max_rx, VALUES(rx_power)),
min_voltage = LEAST(min_voltage, VALUES(voltage)),
max_voltage = GREATEST(max_voltage, VALUES(voltage)),
sample_count = sample_count + 1,
updated_at = NOW()
");
/*
|--------------------------------------------------------------------------
| PORT GPON
|--------------------------------------------------------------------------
*/
$ports = [1, 2];
foreach ($ports as $port_id) {
$url = "http://10.255.254.21/gponmgmt?form=optical_onu&port_id=" . $port_id;
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
//ini token akan selalu berubah kalo beda web
"x-token: eb5d9d8f66242c63dc353342d3896a95"
]
]);
$response = curl_exec($ch);
curl_close($ch);
$json = json_decode($response, true);
echo "<h3>PON {$port_id}</h3>";
if (!isset($json['data']) || empty($json['data'])) {
echo "Tidak ada ONU ditemukan<br>";
continue;
}
foreach ($json['data'] as $onu) {
$sn = trim($onu['ont_sn'] ?? '');
if (empty($sn)) {
continue;
}
$name = trim($onu['ont_name'] ?? '');
$rx = (float) preg_replace(
'/[^0-9\.\-]/',
'',
$onu['receive_power'] ?? '0'
);
$voltage = (float) preg_replace(
'/[^0-9\.]/',
'',
$onu['work_voltage'] ?? '0'
);
try {
$stmt->execute([
':sn' => $sn,
':ont_name' => $name,
':rx_power' => $rx,
':voltage' => $voltage,
':min_rx' => $rx,
':max_rx' => $rx,
':min_voltage' => $voltage,
':max_voltage' => $voltage
]);
echo "SAVE : {$sn} | RX={$rx} | V={$voltage}<br>";
} catch (PDOException $e) {
echo "ERROR {$sn} : " . $e->getMessage() . "<br>";
}
}
}
echo "<hr>";
echo "<b>SELESAI</b>";

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,6 @@
<?php
session_start(); // ✅ ambil session login user
header('Content-Type: application/json');
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS');
@ -16,6 +18,10 @@ require_once '../config/database.php';
$database = new Database();
$db = $database->getConnection();
// Ambil user login
$user_id = $_SESSION['user_id'] ?? null;
$user_role = $_SESSION['role'] ?? null;
// Function to parse multipart/form-data for PUT requests
function parseMultipartFormData($input, $contentType) {
$data = array();
@ -77,9 +83,16 @@ try {
LEFT JOIN ftth_items fi ON r.from_item_id = fi.id
LEFT JOIN ftth_items ti ON r.to_item_id = ti.id
WHERE r.id = :id";
if ($user_role !== 'engineer') {
$query .= " AND r.user_id = :user_id";
}
$stmt = $db->prepare($query);
$stmt->bindParam(':id', $_GET['id']);
if ($user_role !== 'engineer') {
$stmt->bindParam(':user_id', $user_id);
}
$stmt->execute();
$route = $stmt->fetch(PDO::FETCH_ASSOC);
@ -96,10 +109,18 @@ try {
ti.name as to_item_name, ti.latitude as to_lat, ti.longitude as to_lng
FROM cable_routes r
LEFT JOIN ftth_items fi ON r.from_item_id = fi.id
LEFT JOIN ftth_items ti ON r.to_item_id = ti.id
ORDER BY r.created_at DESC";
LEFT JOIN ftth_items ti ON r.to_item_id = ti.id";
if ($user_role !== 'engineer') {
$query .= " WHERE r.user_id = :user_id";
}
$query .= " ORDER BY r.created_at DESC";
$stmt = $db->prepare($query);
if ($user_role !== 'engineer') {
$stmt->bindParam(':user_id', $user_id);
}
$stmt->execute();
$routes = $stmt->fetchAll(PDO::FETCH_ASSOC);
@ -111,12 +132,12 @@ try {
case 'POST':
// Create new route
$from_item_id = $_POST['from_item_id'] ?? null;
$to_item_id = $_POST['to_item_id'] ?? null;
$to_item_id = $_POST['to_item_id'] ?? null;
$route_coordinates = $_POST['route_coordinates'] ?? null;
$distance = $_POST['distance'] ?? null;
$cable_type = $_POST['cable_type'] ?? 'Fiber Optic';
$core_count = $_POST['core_count'] ?? 24;
$status = $_POST['status'] ?? 'planned';
$distance = $_POST['distance'] ?? null;
$cable_type = $_POST['cable_type'] ?? 'Fiber Optic';
$core_count = $_POST['core_count'] ?? 24;
$status = $_POST['status'] ?? 'planned';
if (!$from_item_id || !$to_item_id) {
$response['message'] = 'From and To items are required';
@ -136,11 +157,12 @@ try {
$response['message'] = 'Route already exists between these items';
break;
}
$query = "INSERT INTO cable_routes (from_item_id, to_item_id, route_coordinates, distance, cable_type, core_count, status)
VALUES (:from_item_id, :to_item_id, :route_coordinates, :distance, :cable_type, :core_count, :status)";
$query = "INSERT INTO cable_routes
(from_item_id, to_item_id, route_coordinates, distance, cable_type, core_count, status, user_id)
VALUES
(:from_item_id, :to_item_id, :route_coordinates, :distance, :cable_type, :core_count, :status, :user_id)";
$stmt = $db->prepare($query);
$stmt->bindParam(':from_item_id', $from_item_id);
$stmt->bindParam(':to_item_id', $to_item_id);
@ -149,6 +171,7 @@ try {
$stmt->bindParam(':cable_type', $cable_type);
$stmt->bindParam(':core_count', $core_count);
$stmt->bindParam(':status', $status);
$stmt->bindParam(':user_id', $user_id);
if ($stmt->execute()) {
$route_id = $db->lastInsertId();
@ -161,25 +184,26 @@ try {
break;
case 'PUT':
// Update route - now using $_POST populated by parser
$put_data = $_POST;
// Debug logging
error_log('🔧 Route PUT Request - Method: ' . $method);
error_log('🔧 POST data: ' . print_r($put_data, true));
$id = $put_data['id'] ?? null;
if (!$id) {
error_log('❌ Route update failed: No ID provided');
$response['message'] = 'ID required for update';
break;
}
// Pastikan teknisi hanya update kabel miliknya
if ($user_role !== 'engineer') {
$check = $db->prepare("SELECT id FROM cable_routes WHERE id = :id AND user_id = :user_id");
$check->execute([':id'=>$id, ':user_id'=>$user_id]);
if (!$check->fetch()) {
$response['message'] = 'Not authorized to update this route';
break;
}
}
// Build dynamic update query
$update_fields = array();
$params = array(':id' => $id);
$update_fields = [];
$params = [':id' => $id];
$allowed_fields = ['cable_type', 'core_count', 'status', 'distance', 'route_coordinates'];
foreach ($allowed_fields as $field) {
@ -195,7 +219,6 @@ try {
}
$query = "UPDATE cable_routes SET " . implode(', ', $update_fields) . " WHERE id = :id";
$stmt = $db->prepare($query);
if ($stmt->execute($params)) {
@ -208,24 +231,29 @@ try {
case 'DELETE':
$input = file_get_contents("php://input");
$delete_data = array();
// Try to parse JSON first, then form data
$delete_data = [];
$json_data = json_decode($input, true);
if ($json_data) {
$delete_data = $json_data;
} else {
parse_str($input, $delete_data);
}
// Also check for regular POST data (for compatibility)
if (empty($delete_data)) {
$delete_data = $_POST;
}
if (isset($delete_data['id'])) {
// Delete single route
$id = $delete_data['id'];
// Pastikan teknisi hanya hapus kabel miliknya
if ($user_role !== 'engineer') {
$check = $db->prepare("SELECT id FROM cable_routes WHERE id = :id AND user_id = :user_id");
$check->execute([':id'=>$id, ':user_id'=>$user_id]);
if (!$check->fetch()) {
$response['message'] = 'Not authorized to delete this route';
break;
}
}
$query = "DELETE FROM cable_routes WHERE id = :id";
$stmt = $db->prepare($query);
@ -238,25 +266,34 @@ try {
$response['message'] = 'Failed to delete route';
}
} else if (isset($delete_data['item_id'])) {
// Delete all routes connected to an item
$item_id = $delete_data['item_id'];
// Get route IDs first
$query = "SELECT id FROM cable_routes WHERE from_item_id = :item_id OR to_item_id = :item_id";
$stmt = $db->prepare($query);
$stmt->bindParam(':item_id', $item_id);
$stmt->execute();
$route_ids = $stmt->fetchAll(PDO::FETCH_COLUMN);
// Delete routes
// Pastikan teknisi hanya hapus rute yg terkait item miliknya
if ($user_role !== 'engineer') {
$queryCheck = "SELECT id FROM cable_routes WHERE (from_item_id = :item_id OR to_item_id = :item_id) AND user_id = :user_id";
$stmtCheck = $db->prepare($queryCheck);
$stmtCheck->execute([':item_id'=>$item_id, ':user_id'=>$user_id]);
$route_ids = $stmtCheck->fetchAll(PDO::FETCH_COLUMN);
if (empty($route_ids)) {
$response['message'] = 'Not authorized to delete these routes';
break;
}
}
$query = "DELETE FROM cable_routes WHERE from_item_id = :item_id OR to_item_id = :item_id";
if ($user_role !== 'engineer') {
$query .= " AND user_id = :user_id";
}
$stmt = $db->prepare($query);
$stmt->bindParam(':item_id', $item_id);
if ($user_role !== 'engineer') {
$stmt->bindParam(':user_id', $user_id);
}
if ($stmt->execute()) {
$response['success'] = true;
$response['message'] = 'Routes deleted successfully';
$response['deleted_routes'] = $route_ids;
} else {
$response['message'] = 'Failed to delete routes';
}
@ -275,4 +312,3 @@ try {
}
echo json_encode($response);
?>

View File

@ -24,6 +24,7 @@
margin-bottom: 5px;
}
.popup-info .info-label {
font-weight: bold;
color: #333;
@ -453,6 +454,22 @@
color: #666;
}
/* ==== Animasi kelap-kelip redaman ==== */
@keyframes blink {
0%, 50%, 100% { opacity: 1; }
25%, 75% { opacity: 0; }
}
.red-blink {
color: red;
animation: blink 1s infinite;
}
.green-stable {
color: green;
}
/* Enhanced table styling for detail modal */
.modal-body .table td {
border-top: none;
@ -483,4 +500,48 @@
.badge {
font-size: 0.85em;
padding: 0.4em 0.6em;
}
/* untuk animasi kabel*/
.leaflet-interactive.animated-route {
stroke-dasharray: 15, 10;
stroke-dashoffset: 1000;
animation: move-dash 20s linear infinite;
}
@keyframes move-dash {
to {
stroke-dashoffset: 0;
}
}
/* ONLINE (normal kuning) */
.status-online {
filter: none;
}
/* POWER FAIL (abu) */
.status-power {
filter: grayscale(100%) brightness(0.7);
}
/* LOS → MERAH + BLINK */
.status-los {
/* Mengubah kuning ke merah, menurunkan kecerahan untuk efek 'merah hati' */
filter: brightness(0.5) sepia(1) saturate(500%) hue-rotate(330deg) contrast(120%);
animation: blink 1s infinite;
}
/*
.status-los {
filter: brightness(0) saturate(100%) invert(19%) sepia(96%) saturate(7487%) hue-rotate(357deg) brightness(97%) contrast(119%);
animation: blink 1s infinite;
}
*/
@keyframes blink {
0% { opacity: 1; }
50% { opacity: 0.3; }
100% { opacity: 1; }
}

View File

@ -4,8 +4,10 @@
let editingItemId = null;
let tempClickLatLng = null;
// Initialize application
$(document).ready(function() {
//kirimNotifOdp(id)
loadFormData();
initializeEventListeners();
@ -123,6 +125,7 @@ function getItemTypeId(typeName) {
}
// Edit existing item
//editan saya
function editItem(itemId) {
editingItemId = itemId;
@ -150,10 +153,15 @@ function editItem(itemId) {
$('#splitterMain').val(item.splitter_main_id);
$('#splitterOdp').val(item.splitter_odp_id);
$('#itemStatus').val(item.status);
// Calculate and display core available
$('#serial_number').val(item.serial_number);
$('#rx_power').val(item.rx_power || '');
// === Tambahan penting ===
toggleFields();
// Hitung core tersedia setelah isi coreUsed
setTimeout(() => calculateCoreAvailable(), 100);
updateColorPreview();
$('#itemModal').modal('show');
}
@ -164,6 +172,7 @@ function editItem(itemId) {
});
}
// Save item (create or update)
function saveItem() {
let method = editingItemId ? 'PUT' : 'POST';
@ -356,11 +365,146 @@ function showItemList() {
});
}
function showLaporanOdp() {
$.ajax({
url: 'api/laporan_odp.php',
method: 'GET',
success: function(response) {
if (response.data) {
let html = generateLaporanOdpHtml(response.data);
showModal('Laporan Kualitas ODP', html, 'modal-xl');
} else {
showNotification('Data tidak tersedia', 'error');
}
},
error: function() {
showNotification('Gagal memuat laporan ODP', 'error');
}
});
}
function generateLaporanOdpHtml(data) {
let html = `
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>Nama ODP</th>
<th>Total Client</th>
<th>AVG RX</th>
<th>Client Buruk</th>
<th>Status</th>
<th>Map</th>
<th>Notif</th> <!-- 🔥 TAMBAHAN -->
</tr>
</thead>
<tbody>
`;
data.forEach(item => {
let iconColor = 'green';
if (item.status === 'WARNING') iconColor = 'orange';
if (item.status === 'BURUK') iconColor = 'red';
if (item.status === 'KRITIS') iconColor = 'darkred';
let rxText = '';
let badClientText = '';
if (item.analisa.includes('RX:')) {
let parts = item.analisa.split('|');
parts.forEach(p => {
if (p.includes('RX:')) {
rxText = p.replace('RX:', '').trim();
}
if (p.includes('Client bermasalah')) {
badClientText = p.replace('Client bermasalah:', '').trim();
}
});
}
html += `
<tr>
<td>
<b>${item.nama}</b><br>
<small style="color:gray">
${item.analisa.split('|')[0]}
</small>
${rxText ? `<br><small>RX: ${rxText}</small>` : ''}
${badClientText ? `
<br><small style="color:red">
${badClientText}
</small>` : ''}
</td>
<td>${item.total}</td>
<td>${item.avg_rx ?? '-'} dBm</td>
<td>${item.bad_count}</td>
<td>
<span class="badge badge-${item.status_color}">
${item.status}
</span>
</td>
<td style="text-align:center;">
<i class="fas fa-map-marker-alt"
style="color:${iconColor}; cursor:pointer; font-size:18px;"
onclick="focusOnItem(${item.id})"
title="Lihat lokasi ODP">
</i>
</td>
<!-- 🔥 TOMBOL KIRIM WA -->
<td style="text-align:center;">
<button class="btn btn-sm btn-success"
onclick="kirimNotifOdp(${item.id})"
title="Kirim notifikasi ke teknisi">
<i class="fas fa-paper-plane"></i>
</button>
</td>
</tr>
`;
});
html += `</tbody></table>`;
return html;
}
function kirimNotifOdp(id) {
console.log("Kirim ODP ID:", id);
fetch(`api/laporan_odp.php?kirim=1&id=${id}`)
.then(res => res.json())
.then(res => {
if (res.success) {
alert("✅ Notif terkirim ke group");
} else {
alert("❌ Gagal kirim");
}
})
.catch(err => {
console.error(err);
alert("❌ Error koneksi");
});
}
// Generate item list HTML
// Generate item list HTML
function generateItemListHtml(items) {
let html = `
<div class="mb-3">
<input type="text" id="searchInput" class="form-control" placeholder="Cari data...">
</div>
<div class="table-responsive">
<table class="table table-striped table-hover">
<table class="table table-striped table-hover" id="itemTable">
<thead class="table-dark">
<tr>
<th>Jenis</th>
@ -408,11 +552,23 @@ function generateItemListHtml(items) {
</tbody>
</table>
</div>
<script>
document.getElementById("searchInput").addEventListener("keyup", function() {
let filter = this.value.toLowerCase();
let rows = document.querySelectorAll("#itemTable tbody tr");
rows.forEach(row => {
let text = row.innerText.toLowerCase();
row.style.display = text.includes(filter) ? "" : "none";
});
});
</script>
`;
return html;
}
// Get item color
function getItemColor(typeName) {
switch(typeName) {
@ -454,8 +610,11 @@ function showRouteList() {
// Generate route list HTML
function generateRouteListHtml(routes) {
let html = `
<div class="mb-3">
<input type="text" id="searchRouteInput" class="form-control" placeholder="Cari data route...">
</div>
<div class="table-responsive">
<table class="table table-striped table-hover">
<table class="table table-striped table-hover" id="routeTable">
<thead class="table-dark">
<tr>
<th>Dari</th>
@ -504,11 +663,23 @@ function generateRouteListHtml(routes) {
</tbody>
</table>
</div>
<script>
document.getElementById("searchRouteInput").addEventListener("keyup", function() {
let filter = this.value.toLowerCase();
let rows = document.querySelectorAll("#routeTable tbody tr");
rows.forEach(row => {
let text = row.innerText.toLowerCase();
row.style.display = text.includes(filter) ? "" : "none";
});
});
</script>
`;
return html;
}
// Focus on route in map
function focusOnRoute(routeId) {
if (routes[routeId]) {
@ -1139,6 +1310,414 @@ function showRedamanDetail(item) {
$('#redamanDetailModalBody').html(modalHtml);
$('#redamanDetailModal').modal('show');
}
function showHistoriOnu(data, sn = '') {
let rows = '';
let labels = [];
let rxData = [];
data.forEach((item, index) => {
let rx = parseFloat(item.rx_power);
// ======================
// STATUS COLOR
// ======================
let statusColor = '';
let statusText = item.status;
if (item.status === 'LASER OUT') {
statusColor = 'red';
} else if (item.status === 'POWER FAIL') {
statusColor = 'orange';
} else {
statusColor = 'gray';
}
labels.push(item.created_at);
rxData.push(rx);
rows += `
<tr>
<td>${index + 1}</td>
<td>${item.created_at}</td>
<td style="color:${statusColor}; font-weight:bold;">
${item.status}
</td>
<td>${item.rx_power} dBm</td>
</tr>
`;
});
let modalHtml = `
<div class="row">
<div class="col-md-12">
<!-- CHART -->
<div class="card mb-2">
<div class="card-header bg-dark text-white">
Histori Gangguan ONU - ${sn}
</div>
<div class="card-body" style="padding:10px;">
<canvas id="chartHistoriOnu" height="100"></canvas>
</div>
</div>
<!-- TABLE -->
<div class="card">
<div class="card-header bg-danger text-white">
Histori Gangguan ONU
</div>
<div class="card-body table-responsive" style="max-height:400px; overflow:auto;">
<table class="table table-sm table-bordered">
<thead>
<tr>
<th>No</th>
<th>Waktu</th>
<th>Status</th>
<th>RX Power</th>
</tr>
</thead>
<tbody>
${rows}
</tbody>
</table>
</div>
<div class="card-footer text-right">
<button class="btn btn-secondary btn-sm"
onclick="$('#historiOnuModal').modal('hide')">
Tutup
</button>
</div>
</div>
</div>
</div>
`;
// ======================
// MODAL INIT
// ======================
if (!$('#historiOnuModal').length) {
$('body').append(`
<div class="modal fade" id="historiOnuModal" tabindex="-1">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-body" id="historiOnuBody"></div>
</div>
</div>
</div>
`);
}
$('#historiOnuBody').html(modalHtml);
$('#historiOnuModal').modal('show');
// ======================
// CHART
// ======================
setTimeout(() => {
if (window.historiOnuChart) {
window.historiOnuChart.destroy();
}
const ctx = document.getElementById('chartHistoriOnu');
window.historiOnuChart = new Chart(ctx, {
type: 'line',
data: {
labels: labels.reverse(),
datasets: [
{
label: 'RX Power (dBm)',
data: rxData.reverse(),
borderColor: 'red',
borderWidth: 2,
pointRadius: 3,
fill: false
}
]
},
options: {
responsive: true,
plugins: {
legend: {
labels: {
font: {
size: 10
}
}
}
},
scales: {
y: {
beginAtZero: false
}
}
}
});
}, 300);
}
function loadHistoriOnu(id) {
if (!id) {
console.warn('ID tidak valid:', id);
alert('ID tidak valid');
return;
}
$.ajax({
url: 'https://10.208.176.251/ftthplanner/api/histori_onu.php',
method: 'GET',
data: { id: id },
dataType: 'json',
success: function(response) {
if (response && response.success && response.data) {
showHistoriOnu(response.data, response.sn || '');
} else {
console.warn('Response tidak sesuai:', response);
alert('Data histori ONU tidak ditemukan');
}
},
error: function(xhr, status, error) {
console.error('AJAX Error:', error, xhr.responseText);
alert('Gagal memuat histori ONU');
}
});
}
function showHistoriRedaman(data, sn = '') {
let rows = '';
let labels = [];
let rxData = [];
let voltData = [];
data.forEach((item, index) => {
let rx = parseFloat(item.rx_power);
let volt = parseFloat(item.voltage);
// ======================
// 🔥 RX STATUS (FIX LOGIC)
// ======================
let rxStatus = '';
let rxColor = '';
if (rx < -25.99) {
rxStatus = 'TROUBLE (LOW REDAMAN)';
rxColor = 'red';
} else if (rx >= -19.99) {
rxStatus = 'OVER POWER (TOO STRONG)';
rxColor = 'orange';
} else {
rxStatus = 'NORMAL';
rxColor = 'green';
}
// ======================
// 🔥 VOLT STATUS
// ======================
let voltStatus = '';
let voltColor = '';
if (volt === null || isNaN(volt)) {
voltStatus = 'NO DATA';
voltColor = 'gray';
} else if (volt > 3.29) {
voltStatus = 'OVER VOLTAGE (TERLALU TINGGI)';
voltColor = 'red';
} else if (volt < 3.2) {
voltStatus = 'UNDER VOLTAGE (TERLALU RENDAH)';
voltColor = 'orange';
} else {
voltStatus = 'NORMAL (STABLE)';
voltColor = 'green';
}
labels.push(item.updated_at);
rxData.push(rx);
voltData.push(volt);
rows += `
<tr>
<td>${index + 1}</td>
<td>${item.updated_at}</td>
<td style="color:${rxColor}; font-weight:bold;">
${item.rx_power}
<br><small>${rxStatus}</small>
</td>
<td style="color:${voltColor}; font-weight:bold;">
${item.voltage}
<br><small>${voltStatus}</small>
</td>
</tr>
`;
});
let modalHtml = `
<div class="row">
<div class="col-md-12">
<!-- CHART -->
<div class="card mb-2">
<div class="card-header bg-dark text-white">
Histori Redaman & Voltage - ${sn}
</div>
<div class="card-body" style="padding:10px;">
<canvas id="chartHistoriRedaman" height="80"></canvas>
</div>
</div>
<!-- TABLE -->
<div class="card">
<div class="card-header bg-info text-white">
Histori Detail
</div>
<div class="card-body table-responsive" style="max-height:400px; overflow:auto;">
<table class="table table-sm table-bordered">
<thead>
<tr>
<th>No</th>
<th>Waktu</th>
<th>RX Power</th>
<th>Voltage</th>
</tr>
</thead>
<tbody>
${rows}
</tbody>
</table>
</div>
<div class="card-footer text-right">
<button class="btn btn-secondary btn-sm"
onclick="$('#historiRedamanModal').modal('hide')">
Tutup
</button>
</div>
</div>
</div>
</div>
`;
// modal init
if (!$('#historiRedamanModal').length) {
$('body').append(`
<div class="modal fade" id="historiRedamanModal" tabindex="-1">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-body" id="historiRedamanBody"></div>
</div>
</div>
</div>
`);
}
$('#historiRedamanBody').html(modalHtml);
$('#historiRedamanModal').modal('show');
// ======================
// 🔥 CHART
// ======================
setTimeout(() => {
if (window.historiChart) {
window.historiChart.destroy();
}
const ctx = document.getElementById('chartHistoriRedaman');
window.historiChart = new Chart(ctx, {
type: 'line',
data: {
labels: labels.reverse(),
datasets: [
{
label: 'RX Power (dBm)',
data: rxData.reverse(),
borderColor: 'red',
borderWidth: 2,
pointRadius: 3,
fill: false
},
{
label: 'Voltage (V)',
data: voltData.reverse(),
borderColor: 'blue',
borderWidth: 2,
pointRadius: 3,
fill: false
},
// NORMAL ZONE LINE (-20 s/d -25)
{
label: 'Normal Zone (-20 s/d -25)',
data: labels.map(() => -22.5),
borderColor: 'green',
borderDash: [5, 5],
pointRadius: 0,
fill: false
},
// LIMIT LOSS LINE
{
label: 'Limit Loss (-25 dBm)',
data: labels.map(() => -25.99),
borderColor: 'red',
borderDash: [5, 5],
pointRadius: 0,
fill: false
}
]
},
options: {
responsive: true,
plugins: {
legend: {
labels: {
font: { size: 10 }
}
}
},
scales: {
y: {
beginAtZero: false
}
}
}
});
}, 300);
}
// Contoh memanggil data dari API redaman
function loadRedamanDetail(id) {
if (!id) {
@ -1148,12 +1727,13 @@ function loadRedamanDetail(id) {
}
//redaman url bisa
$.ajax({
url: 'https://172.16.96.10/ftthplanner/api/detail_redaman.php', // path sesuai lokasi HTML/JS
url: 'https://10.208.176.251/ftthplanner/api/detail_redaman.php', // path sesuai lokasi HTML/JS
method: 'GET',
data: { id: id }, // kirim 'id' sesuai PHP
dataType: 'json',
success: function(response) {
if (response && response.success && response.data) {
let data_rx_js = response
showRedamanDetail(response.data);
} else {
console.warn('Response tidak sesuai harapan:', response);
@ -1167,8 +1747,34 @@ function loadRedamanDetail(id) {
});
}
function loadHistoriRedaman(id) {
if (!id) {
console.warn('ID tidak valid:', id);
alert('ID tidak valid');
return;
}
$.ajax({
url: 'https://10.208.176.251/ftthplanner/api/histori_redaman.php',
method: 'GET',
data: { id: id },
dataType: 'json',
success: function(response) {
if (response && response.success && response.data) {
showHistoriRedaman(response.data);
} else {
console.warn('Response tidak sesuai:', response);
alert('Data histori tidak ditemukan');
}
},
error: function(xhr, status, error) {
console.error('AJAX Error:', error, xhr.responseText);
alert('Gagal memuat histori redaman');
}
});
}
// Helper function to copy text to clipboard
function copyToClipboard(text) {
@ -1200,6 +1806,7 @@ window.editItemEnhanced = editItemEnhanced;
window.deleteItem = deleteItem;
window.showItemDetail = showItemDetail;
window.showItemList = showItemList;
window.showLaporanOdp = showLaporanOdp;
window.showRouteList = showRouteList;
window.focusOnItem = focusOnItem;
window.focusOnRoute = focusOnRoute;

File diff suppressed because it is too large Load Diff

View File

@ -2,7 +2,7 @@
// auto_login_genieacs.php
// Konfigurasi login GenieACS
$genieacs_host = 'http://172.16.96.10:3000';
$genieacs_host = 'http://rmtstb.megadataisp.net:3000';
$username = 'dwi';
$password = 'jalo';

View File

@ -2,7 +2,7 @@
// auto_login_observium.php
// Konfigurasi login Observium
$observium_host = 'http://172.16.96.6'; // ganti sesuai IP Observium
$observium_host = 'http://rmtstb.megadataisp.net:8131'; // ganti sesuai IP Observium
$username = 'megadata';
$password = 'megadata';

35
auto_olt.py Normal file
View File

@ -0,0 +1,35 @@
import requests
import threading
import time
from datetime import datetime
URLS = [
"http://192.168.1.13/ftthplanner/api/ambil_dataOLT.php",
"http://10.255.254.21/#/status"
]
def hit_url(url):
try:
r = requests.get(url, timeout=300)
print(
f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] "
f"{url} -> Status: {r.status_code}"
)
except Exception as e:
print(
f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] "
f"{url} -> ERROR: {e}"
)
while True:
threads = []
for url in URLS:
t = threading.Thread(target=hit_url, args=(url,))
t.start()
threads.append(t)
for t in threads:
t.join()
time.sleep(30)

35
auto_refresh.py Normal file
View File

@ -0,0 +1,35 @@
import requests
import threading
import time
from datetime import datetime
URLS = [
"http://192.168.1.13/ftthplanner/api/auto_notif_device.php",
"http://192.168.1.13/ftthplanner/api/auto_notif_onu.php"
]
def hit_url(url):
try:
r = requests.get(url, timeout=300)
print(
f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] "
f"{url} -> Status: {r.status_code}"
)
except Exception as e:
print(
f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] "
f"{url} -> ERROR: {e}"
)
while True:
threads = []
for url in URLS:
t = threading.Thread(target=hit_url, args=(url,))
t.start()
threads.append(t)
for t in threads:
t.join()
time.sleep(30)

40
bc html.txt Normal file
View File

@ -0,0 +1,40 @@
let html = `
<div>
<h5><i class="${getItemIcon(item.item_type_name)}"></i> ${item.name}</h5>
<div class="popup-info">
<div class="info-row"><span class="info-label">Jenis:</span> ${item.item_type_name}</div>
${item.description ? `<div class="info-row"><span class="info-label">Deskripsi:</span> ${item.description}</div>` : ''}
${item.address ? `<div class="info-row"><span class="info-label">Alamat:</span> ${item.address}</div>` : ''}
<div class="info-row"><span class="info-label">Warna Tube:</span> ${tubeColorName}</div>
${item.core_used ? `<div class="info-row"><span class="info-label">Core Digunakan:</span> ${item.core_used}</div>` : ''}
<div class="info-row"><span class="info-label">Splitter Utama:</span> ${splitterMain}</div>
<div class="info-row"><span class="info-label">Splitter ODP:</span> ${splitterOdp}</div>
<div class="info-row"><span class="info-label">Status:</span>
<span class="badge badge-${getStatusBadgeClass(item.status)}">${getStatusText(item.status)}</span>
</div>
<!-- Redaman hanya untuk item selain ODP, ODC, Tiang Tumpu, OLT -->
${!['odp', 'odc', 'tiang tumpu', 'olt'].includes(item.item_type_name.toLowerCase())
? `
<div class="info-row" id="redaman-${item.id}">
<span class="info-label">Redaman :</span> <em>Memuat...</em>
</div>
<div class="info-row" id="voltage-${item.id}">
<span class="info-label">Voltage :</span> <em>Memuat...</em>
</div>
<div class="info-row" id="onu-status-${item.id}">
<span class="info-label">ONU Status :</span> <em>Memuat...</em>
</div>
<div class="info-row" id="last-update-${item.id}">
<span class="info-label">Last Update :</span> <em>Memuat...</em>
</div>
<div class="info-row">
<span class="info-label">Customer Phone :</span>
<span>${item.customer_phone ? item.customer_phone : '-'}</span>
</div>
`
: ''}

View File

@ -4,7 +4,7 @@ class Database {
private $host = "localhost";
private $db_name = "ftth_planner";
private $username = "root";
private $password = "";
private $password = "megadata";
public $conn;
public function getConnection() {

0
debug_onu.html Normal file
View File

1194
debug_onu.json Normal file

File diff suppressed because it is too large Load Diff

View File

@ -15,7 +15,7 @@ function getRxPowerFromGenieACS($serialNumber) {
$query = json_encode(["_id" => $serialNumber]);
$encodedQuery = urlencode($query); // penting untuk URL-safe
$url = "http://localhost:7557/devices/?query={$encodedQuery}";
$url = "http://rmtstb.megadataisp.net:7557/devices/?query={$encodedQuery}";
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);

626
index.php
View File

@ -5,8 +5,50 @@ if (!isset($_SESSION['user'])) {
exit;
}
require_once __DIR__ . '/config/database.php';
// Akses teknisi tidak diarahkan ke map.php, tetap di dashboard
$database = new Database();
$db = $database->getConnection();
$role = $_SESSION['role'] ?? 'teknisi1';
$user_id = $_SESSION['user_id'] ?? null;
// Helper function buat hitung statistik
function getCount($db, $item_type_id, $role, $user_id) {
$query = "SELECT COUNT(*) as total FROM ftth_items WHERE item_type_id = :item_type_id";
if ($role !== 'engineer') {
$query .= " AND user_id = :user_id";
}
$stmt = $db->prepare($query);
$stmt->bindParam(':item_type_id', $item_type_id, PDO::PARAM_INT);
if ($role !== 'engineer') {
$stmt->bindParam(':user_id', $user_id, PDO::PARAM_INT);
}
$stmt->execute();
return $stmt->fetch(PDO::FETCH_ASSOC)['total'] ?? 0;
}
// Mapping item_type_id sesuai database kamu
$olt = getCount($db, 1, $role, $user_id);
$tiang = getCount($db, 2, $role, $user_id);
$odp = getCount($db, 3, $role, $user_id);
$odc = getCount($db, 4, $role, $user_id);
$pelanggan = getCount($db, 5, $role, $user_id);
// Routes (butuh join ke ftth_items)
$query = "SELECT COUNT(*) as total
FROM cable_routes r
JOIN ftth_items i ON r.from_item_id = i.id
WHERE 1=1";
if ($role !== 'engineer') {
$query .= " AND i.user_id = :user_id";
}
$stmt = $db->prepare($query);
if ($role !== 'engineer') {
$stmt->bindParam(':user_id', $user_id, PDO::PARAM_INT);
}
$stmt->execute();
$routes = $stmt->fetch(PDO::FETCH_ASSOC)['total'] ?? 0;
?>
<!DOCTYPE html>
<html lang="id">
@ -114,6 +156,12 @@ if (!isset($_SESSION['user'])) {
<p>Routing Kabel</p>
</a>
</li>
<li class="nav-item">
<a href="#" class="nav-link" onclick="showLaporanOdp()">
<i class="nav-icon fas fa-chart-bar"></i>
<p>Laporan ODP</p>
</a>
</li>
<li class="nav-header">NAVIGASI PETA</li>
<li class="nav-item">
<a href="#" class="nav-link" onclick="zoomToItems()">
@ -132,7 +180,7 @@ if (!isset($_SESSION['user'])) {
<ul class="nav nav-treeview">
<li class="nav-item">
<a href="#" class="nav-link" onclick="zoomToItemType('OLT')">
<i class="fas fa-server nav-icon" style="color: #FF6B6B;"></i>
<i class="fas fa-cloud nav-icon" style="color: #FF6B6B;"></i>
<p>Zoom ke OLT</p>
</a>
</li>
@ -178,7 +226,7 @@ if (!isset($_SESSION['user'])) {
<li class="nav-header">ITEM FTTH</li>
<li class="nav-item">
<a href="#" class="nav-link" onclick="addNewItem('OLT')">
<i class="nav-icon fas fa-server" style="color: #FF6B6B;"></i>
<i class="nav-icon fas fa-cloud" style="color: #FF6B6B;"></i>
<p>Tambah OLT</p>
</a>
</li>
@ -202,7 +250,7 @@ if (!isset($_SESSION['user'])) {
</li>
<li class="nav-item">
<a href="#" class="nav-link" onclick="addNewItem('Pelanggan')">
<i class="nav-icon fas fa-home" style="color: #FFA500;"></i>
<i class="nav-icon fas fa-wifi" style="color: #FFA500;"></i>
<p>Tambah Pelanggan</p>
</a>
</li>
@ -243,22 +291,19 @@ if (!isset($_SESSION['user'])) {
Peta Infrastruktur FTTH
</h3>
<div class="card-tools d-flex justify-content-between">
<!-- Kanan: tombol utama -->
<!-- Kanan: tombol utama/Revisi Tombol -->
<div class="btn-group">
<?php if ($_SESSION["role"] === "engineer") : ?>
<button type="button" class="btn btn-primary btn-sm" onclick="showAddItemModal()">
<?php else: ?>
<button type="button" class="btn btn-primary btn-sm" disabled style="opacity:0.5;cursor:not-allowed;">
<?php endif; ?>
<button type="button" class="btn btn-primary btn-sm" onclick="showAddItemModal()">
<i class="fas fa-plus"></i> Tambah Item
</button>
<?php if ($_SESSION["role"] === "engineer") : ?>
<button type="button" class="btn btn-info btn-sm" onclick="showRoutingMode()">
<?php else: ?>
<button type="button" class="btn btn-info btn-sm" disabled style="opacity:0.5;cursor:not-allowed;">
<?php endif; ?>
<i class="fas fa-route"></i> Mode Routing
<button type="button" class="btn btn-info btn-sm" onclick="showRoutingMode()">
<i class="fas fa-route"></i> Route Otomatis
</button>
<button type="button" class="btn btn-info btn-sm" onclick="showRoutingMode()">
<i class="fas fa-map"></i> Route manual
</button>
<button type="button" class="btn btn-success btn-sm" onclick="exportToKMZ()">
@ -269,7 +314,7 @@ if (!isset($_SESSION['user'])) {
<!-- Tombol GenieACS & Observium di kanan -->
<div class="btn-group ml-2">
<button type="button" class="btn btn-warning btn-sm" onclick="window.open('auto_login_genieacs.php', '_blank')">
<i class="fas fa-server"></i> ACS
<i class="fas fa-cloud"></i> ACS
</button>
<button type="button" class="btn btn-secondary btn-sm" onclick="window.open('auto_login_observium.php', '_blank')">
<i class="fas fa-chart-line"></i> Observium
@ -288,7 +333,7 @@ if (!isset($_SESSION['user'])) {
<i class="fas fa-location-arrow"></i>
</button>
<button type="button" class="btn btn-outline-secondary btn-sm" onclick="map.setView([-2.5, 118], 5)" title="Zoom ke Indonesia">
<i class="fas fa-home"></i>
<i class="fas fa-wifi"></i>
</button>
<button type="button" class="btn btn-outline-secondary btn-sm" onclick="map.zoomIn()" title="Zoom In (+)">
<i class="fas fa-plus"></i>
@ -307,74 +352,74 @@ if (!isset($_SESSION['user'])) {
</div>
<!-- Statistics Cards -->
<div class="row">
<div class="col-lg-2 col-6">
<div class="small-box bg-info">
<div class="inner">
<h3 id="stat-olt">0</h3>
<p>OLT</p>
</div>
<div class="icon">
<i class="fas fa-server"></i>
</div>
</div>
</div>
<div class="col-lg-2 col-6">
<div class="small-box bg-success">
<div class="inner">
<h3 id="stat-tiang">0</h3>
<p>Tiang</p>
</div>
<div class="icon">
<i class="fas fa-tower-broadcast"></i>
</div>
</div>
</div>
<div class="col-lg-2 col-6">
<div class="small-box bg-warning">
<div class="inner">
<h3 id="stat-odp">0</h3>
<p>ODP</p>
</div>
<div class="icon">
<i class="fas fa-project-diagram"></i>
</div>
</div>
</div>
<div class="col-lg-2 col-6">
<div class="small-box bg-danger">
<div class="inner">
<h3 id="stat-odc">0</h3>
<p>ODC</p>
</div>
<div class="icon">
<i class="fas fa-network-wired"></i>
</div>
</div>
</div>
<div class="col-lg-2 col-6">
<div class="small-box bg-primary">
<div class="inner">
<h3 id="stat-pelanggan">0</h3>
<p>Pelanggan</p>
</div>
<div class="icon">
<i class="fas fa-home"></i>
</div>
</div>
</div>
<div class="col-lg-2 col-6">
<div class="small-box bg-secondary">
<div class="inner">
<h3 id="stat-routes">0</h3>
<p>Routes</p>
</div>
<div class="icon">
<i class="fas fa-route"></i>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-lg-2 col-6">
<div class="small-box bg-info">
<div class="inner">
<h3><?= $olt ?></h3>
<p>OLT</p>
</div>
<div class="icon">
<i class="fas fa-cloud"></i>
</div>
</div>
</div>
<div class="col-lg-2 col-6">
<div class="small-box bg-success">
<div class="inner">
<h3><?= $tiang ?></h3>
<p>Tiang</p>
</div>
<div class="icon">
<i class="fas fa-tower-broadcast"></i>
</div>
</div>
</div>
<div class="col-lg-2 col-6">
<div class="small-box bg-warning">
<div class="inner">
<h3><?= $odp ?></h3>
<p>ODP</p>
</div>
<div class="icon">
<i class="fas fa-project-diagram"></i>
</div>
</div>
</div>
<div class="col-lg-2 col-6">
<div class="small-box bg-danger">
<div class="inner">
<h3><?= $odc ?></h3>
<p>ODC</p>
</div>
<div class="icon">
<i class="fas fa-network-wired"></i>
</div>
</div>
</div>
<div class="col-lg-2 col-6">
<div class="small-box bg-primary">
<div class="inner">
<h3><?= $pelanggan ?></h3>
<p>Pelanggan</p>
</div>
<div class="icon">
<i class="fas fa-wifi"></i>
</div>
</div>
</div>
<div class="col-lg-2 col-6">
<div class="small-box bg-secondary">
<div class="inner">
<h3><?= $routes ?></h3>
<p>Routes</p>
</div>
<div class="icon">
<i class="fas fa-route"></i>
</div>
</div>
</div>
</div>
</div>
</section>
</div>
@ -402,7 +447,8 @@ if (!isset($_SESSION['user'])) {
<form id="itemForm">
<div class="modal-body">
<input type="hidden" id="itemId" name="id">
<!-- Pilihan Jenis Item -->
<div class="row">
<div class="col-md-6">
<div class="form-group">
@ -425,195 +471,168 @@ if (!isset($_SESSION['user'])) {
</div>
</div>
<div class="form-group">
<label for="itemDescription">Deskripsi</label>
<textarea class="form-control" id="itemDescription" name="description" rows="3"></textarea>
</div>
<div class="form-group">
<label for="itemAddress">Alamat</label>
<textarea class="form-control" id="itemAddress" name="address" rows="2"></textarea>
</div>
<div class="row">
<div class="col-md-6">
<div class="form-group">
<label for="itemLat">Latitude <span class="text-danger">*</span></label>
<input type="number" step="any" class="form-control" id="itemLat" name="latitude" placeholder="Contoh: -6.2088">
<small class="form-text text-muted">Koordinat Lintang (Latitude)</small>
</div>
<!-- Form Umum -->
<div id="generalFields">
<div class="form-group">
<label for="itemDescription">Deskripsi</label>
<textarea class="form-control" id="itemDescription" name="description" rows="3"></textarea>
</div>
<div class="col-md-6">
<div class="form-group">
<label for="itemLng">Longitude <span class="text-danger">*</span></label>
<input type="number" step="any" class="form-control" id="itemLng" name="longitude" placeholder="Contoh: 106.8456">
<small class="form-text text-muted">Koordinat Bujur (Longitude)</small>
<div class="form-group">
<label for="itemAddress">Alamat</label>
<textarea class="form-control" id="itemAddress" name="address" rows="2"></textarea>
</div>
<div class="row">
<div class="col-md-6">
<div class="form-group">
<label for="itemLat">Latitude <span class="text-danger">*</span></label>
<input type="number" step="any" class="form-control" id="itemLat" name="latitude" placeholder="Contoh: -6.2088">
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label for="itemLng">Longitude <span class="text-danger">*</span></label>
<input type="number" step="any" class="form-control" id="itemLng" name="longitude" placeholder="Contoh: 106.8456">
</div>
</div>
</div>
</div>
<div class="alert alert-info" role="alert">
<i class="fas fa-info-circle"></i>
<strong>Cara menentukan lokasi:</strong>
<br> Klik tombol "Pilih di Peta" di bawah ini
<br> Atau tutup dialog ini dan klik langsung di peta, lalu pilih "Tambah Item"
</div>
<div class="text-center mb-3">
<button type="button" class="btn btn-outline-primary" onclick="selectLocationOnMap()">
<i class="fas fa-map-marker-alt"></i> Pilih Lokasi di Peta
</button>
</div>
<div class="row">
<div class="col-md-6">
<div class="form-group">
<label for="tubeColor">Warna Tube</label>
<select class="form-control" id="tubeColor" name="tube_color_id">
<option value="">Pilih Warna Tube</option>
</select>
<!-- Form Warna Tube & Core -->
<div id="colorFields">
<div class="row">
<div class="col-md-6">
<div class="form-group">
<label for="tubeColor">Warna Tube</label>
<select class="form-control" id="tubeColor" name="tube_color_id">
<option value="">Pilih Warna Tube</option>
</select>
</div>
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label for="coreColor">Warna Core</label>
<select class="form-control" id="coreColor" name="core_color_id">
<option value="">Pilih Warna Core</option>
</select>
<div class="col-md-6">
<div class="form-group">
<label for="coreColor">Warna Core</label>
<select class="form-control" id="coreColor" name="core_color_id">
<option value="">Pilih Warna Core</option>
</select>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<div class="form-group">
<label for="cableType">Jenis Kabel</label>
<select class="form-control" id="cableType" name="item_cable_type">
<option value="">Pilih Jenis Kabel</option>
<option value="backbone">Backbone</option>
<option value="distribution">Distribution</option>
<option value="drop_core">Drop Core</option>
<option value="feeder">Feeder</option>
<option value="branch">Branch</option>
</select>
<!-- Form Kabel -->
<div id="cableFields">
<div class="row">
<div class="col-md-6">
<div class="form-group">
<label for="cableType">Jenis Kabel</label>
<select class="form-control" id="cableType" name="item_cable_type">
<option value="">Pilih Jenis Kabel</option>
<option value="backbone">Backbone</option>
<option value="distribution">Distribution</option>
<option value="drop_core">Drop Core</option>
<option value="feeder">Feeder</option>
<option value="branch">Branch</option>
</select>
</div>
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label for="totalCoreCapacity">Kapasitas Core Total</label>
<select class="form-control" id="totalCoreCapacity" name="total_core_capacity">
<option value="2">2 Core</option>
<option value="4">4 Core</option>
<option value="6">6 Core</option>
<option value="8">8 Core</option>
<option value="12">12 Core</option>
<option value="24" selected>24 Core</option>
<option value="48">48 Core</option>
<option value="72">72 Core</option>
<option value="96">96 Core</option>
<option value="144">144 Core</option>
<option value="216">216 Core</option>
<option value="288">288 Core</option>
</select>
<div class="col-md-6">
<div class="form-group">
<label for="totalCoreCapacity">Kapasitas Core Total</label>
<select class="form-control" id="totalCoreCapacity" name="total_core_capacity">
<option value="1">1 Core</option>
<option value="2">2 Core</option>
<option value="4">4 Core</option>
<option value="6">6 Core</option>
<option value="8">8 Core</option>
<option value="12">12 Core</option>
<option value="24" selected>24 Core</option>
<option value="48">48 Core</option>
<option value="72">72 Core</option>
<option value="96">96 Core</option>
<option value="144">144 Core</option>
<option value="216">216 Core</option>
<option value="288">288 Core</option>
</select>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<div class="form-group">
<label for="coreUsed">Core yang Digunakan</label>
<input type="number" class="form-control" id="coreUsed" name="core_used" min="0" max="288">
<small class="form-text text-muted">Otomatis terisi dari perhitungan routing</small>
<!-- Form Core -->
<div id="coreFields">
<div class="row">
<div class="col-md-6">
<div class="form-group">
<label for="coreUsed">Core yang Digunakan</label>
<input type="number" class="form-control" id="coreUsed" name="core_used" min="0" max="288">
</div>
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label>Core Tersedia</label>
<input type="text" class="form-control" id="coreAvailable" readonly>
<small class="form-text text-muted">Kapasitas Total - Core Digunakan</small>
<div class="col-md-6">
<div class="form-group">
<label>Core Tersedia</label>
<input type="text" class="form-control" id="coreAvailable" readonly>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<div class="form-group">
<label for="splitterMain">Splitter Jaringan Utama</label>
<select class="form-control" id="splitterMain" name="splitter_main_id">
<option value="">Pilih Splitter Utama</option>
</select>
<!-- Form Splitter -->
<div id="splitterFields">
<div class="row">
<div class="col-md-6">
<div class="form-group">
<label for="splitterMain">Splitter Jaringan Utama</label>
<select class="form-control" id="splitterMain" name="splitter_main_id">
<option value="">Pilih Splitter Utama</option>
</select>
</div>
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label for="splitterOdp">Splitter ODP</label>
<select class="form-control" id="splitterOdp" name="splitter_odp_id">
<option value="">Pilih Splitter ODP</option>
</select>
<div class="col-md-6">
<div class="form-group">
<label for="splitterOdp">Splitter ODP</label>
<select class="form-control" id="splitterOdp" name="splitter_odp_id">
<option value="">Pilih Splitter ODP</option>
</select>
</div>
</div>
</div>
</div>
<div class="form-group">
<label for="serial_number">Serial Number ONT</label>
<input type="text" class="form-control" id="serial_number" name="serial_number" placeholder="Contoh: ZTEGC1234567" required>
</div>
<div class="form-group">
<label for="rx_power">RX Power (dBm)</label>
<!-- Form Serial Number & RX Power -->
<div id="ontFields">
<div class="form-group">
<label for="serial_number">ID GenieACS TR069</label>
<input type="text" class="form-control" id="serial_number" name="serial_number" placeholder="Contoh: 00259E-HG8546M-485754431A8E6A9C">
</div>
<div class="form-group">
<label for="rx_power">RX Power (dBm)</label>
<input type="number" step="0.01" class="form-control" id="rx_power" name="rx_power" placeholder="Otomatis dari GenieACS" readonly>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
const snInput = document.getElementById('serial_number');
const rxPowerInput = document.getElementById('rx_power');
snInput.addEventListener('change', function() {
const sn = this.value.trim();
if (!sn) return;
fetch('get_rx_power.php?sn=' + encodeURIComponent(sn))
.then(response => {
if (!response.ok) {
throw new Error('Network response was not OK');
}
return response.json();
})
.then(data => {
// console.log(data);
if (data.rx_power !== null) {
rxPowerInput.value = data.rx_power;
} else {
alert('⚠ ' + (data.error || 'RX Power tidak ditemukan'));
rxPowerInput.value = '';
}
})
.catch(error => {
// console.error('Fetch error:', error);
alert('❌ Gagal mengambil RX Power dari GenieACS');
rxPowerInput.value = '';
});
});
});
</script>
<script>
</script>
<!-- Form Status -->
<div id="statusFields">
<div class="form-group">
<label for="itemStatus">Status</label>
<select class="form-control" id="itemStatus" name="status">
<option value="active">Aktif</option>
<option value="inactive">Tidak Aktif</option>
<option value="maintenance">Maintenance</option>
</select>
</div>
</div>
<!-- Form isi data ONU -->
<div class="form-group">
<label for="itemStatus">Status</label>
<select class="form-control" id="itemStatus" name="status">
<option value="active">Aktif</option>
<option value="inactive">Tidak Aktif</option>
<option value="maintenance">Maintenance</option>
</select>
</div>
<label for="sn_onu">SN ONU (OLT)</label>
<input type="text" class="form-control" id="sn_onu" name="sn_onu" placeholder="Contoh: HWTC6B926A9C">
</div>
<!-- Form isi nomor telfon -->
<div class="form-group">
<label>Nomor HP Pelanggan</label>
<input type="text" name="customer_phone" class="form-control" placeholder="628xxxxxxxxxx">
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Batal</button>
@ -624,6 +643,109 @@ if (!isset($_SESSION['user'])) {
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
const itemType = document.getElementById('itemType');
const ontFields = document.getElementById('ontFields');
const generalFields = document.getElementById('generalFields');
const colorFields = document.getElementById('colorFields');
const cableFields = document.getElementById('cableFields');
const coreFields = document.getElementById('coreFields');
const splitterFields = document.getElementById('splitterFields');
const statusFields = document.getElementById('statusFields');
const addressField = document.getElementById('itemAddress');
const addressWrapper = addressField.closest('.form-group');
const latField = document.getElementById('itemLat').closest('.col-md-6');
const lngField = document.getElementById('itemLng').closest('.col-md-6');
const descriptionField = document.getElementById('description')?.closest('.form-group'); // Deskripsi
//editan saya
window.toggleFields = function () {
const value = itemType.value;
// Semua field default tampil
generalFields.style.display = '';
colorFields.style.display = '';
cableFields.style.display = '';
coreFields.style.display = '';
splitterFields.style.display = '';
statusFields.style.display = '';
addressWrapper.style.display = '';
if (descriptionField) descriptionField.style.display = '';
latField.style.display = '';
lngField.style.display = '';
ontFields.style.display = 'none';
if (value === '5') {
// === Pelanggan ===
// Semua field tampil termasuk ONT & RX Power
ontFields.style.display = '';
}
else if (value === '2') {
// === Tiang Tumpu ===
// Hanya tampilkan Nama Item, Jenis Item, Latitude & Longitude
generalFields.style.display = ''; // Nama & Jenis item tetap tampil
latField.style.display = '';
lngField.style.display = '';
// Sembunyikan deskripsi, alamat, dan field lainnya
if (descriptionField) descriptionField.style.display = 'none';
addressWrapper.style.display = 'none';
colorFields.style.display = 'none';
cableFields.style.display = 'none';
coreFields.style.display = 'none';
splitterFields.style.display = 'none';
statusFields.style.display = 'none';
ontFields.style.display = 'none';
}
else if (value === '3' || value === '4') {
// === ODP & ODC ===
addressWrapper.style.display = 'none';
ontFields.style.display = 'none';
}
else {
// === OLT ===
// Semua field tampil kecuali ONT
ontFields.style.display = 'none';
}
}
// Jalankan toggle pertama kali
itemType.addEventListener('change', toggleFields);
toggleFields();
// === Script Otomatis Mengambil RX Power ===
const snInput = document.getElementById('serial_number');
const rxPowerInput = document.getElementById('rx_power');
snInput.addEventListener('change', function() {
const sn = this.value.trim();
if (!sn) return;
fetch('get_rx_power.php?sn=' + encodeURIComponent(sn))
.then(response => {
if (!response.ok) throw new Error('Network response was not OK');
return response.json();
})
.then(data => {
if (data.rx_power !== null) {
rxPowerInput.value = data.rx_power;
} else {
alert('⚠ ' + (data.error || 'RX Power tidak ditemukan'));
rxPowerInput.value = '';
}
})
.catch(error => {
alert('❌ Gagal mengambil RX Power dari GenieACS');
rxPowerInput.value = '';
});
});
});
</script>
<!-- jQuery -->
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<!-- Bootstrap 4 -->
@ -641,11 +763,19 @@ if (!isset($_SESSION['user'])) {
<!-- FileSaver for download -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/2.0.5/FileSaver.min.js"></script>
<!-- Custom JS -->
<!--
<script>
$.document().ready(function() {
alert('dasd')
})
</script>
-->
<script src="assets/js/map.js"></script>
<script src="assets/js/app.js"></script>
<script src="assets/js/kmz-export.js"></script>
<link rel="stylesheet" href="https://unpkg.com/leaflet-control-geocoder/dist/Control.Geocoder.css" />
<script src="https://unpkg.com/leaflet-control-geocoder/dist/Control.Geocoder.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
</body>
</html>

View File

@ -16,8 +16,11 @@ if ($_SERVER['REQUEST_METHOD'] == 'POST') {
$user = $stmt->fetch(PDO::FETCH_ASSOC);
if ($user && password_verify($password, $user['password'])) {
$_SESSION['user'] = $user['username'];
$_SESSION['role'] = $user['role'];
// Simpan data penting ke session
$_SESSION['user_id'] = $user['id']; // <-- ditambahkan
$_SESSION['user'] = $user['username'];
$_SESSION['role'] = $user['role'];
header("Location: index.php");
exit;
} else {

View File

@ -6,17 +6,31 @@ require 'config/database.php';
$database = new Database();
$conn = $database->getConnection();
$success = "";
$error = "";
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
$username = trim($_POST['username']);
$password = password_hash(trim($_POST['password']), PASSWORD_DEFAULT);
$password_plain = trim($_POST['password']);
$role = $_POST['role'];
try {
$stmt = $conn->prepare("INSERT INTO users (username, password, role) VALUES (?, ?, ?)");
$stmt->execute([$username, $password, $role]);
$success = "Pendaftaran berhasil! Silakan login.";
} catch (PDOException $e) {
$error = "Username sudah digunakan atau terjadi kesalahan.";
// Validasi sederhana
if (empty($username) || empty($password_plain) || empty($role)) {
$error = "Semua field wajib diisi.";
} else {
$password = password_hash($password_plain, PASSWORD_DEFAULT);
try {
$stmt = $conn->prepare("INSERT INTO users (username, password, role) VALUES (?, ?, ?)");
$stmt->execute([$username, $password, $role]);
$success = "Pendaftaran berhasil! Silakan login.";
} catch (PDOException $e) {
if ($e->getCode() == 23000) { // duplicate entry
$error = "Username sudah digunakan.";
} else {
$error = "Terjadi kesalahan: " . $e->getMessage();
}
}
}
}
?>
@ -97,8 +111,6 @@ if ($_SERVER['REQUEST_METHOD'] == 'POST') {
color: red;
margin-bottom: 10px;
}
/* Responsif untuk layar kecil */
@media (max-width: 480px) {
.register-container {
padding: 25px;
@ -113,17 +125,25 @@ if ($_SERVER['REQUEST_METHOD'] == 'POST') {
<div class="register-container">
<img src="megadata.jpeg" alt="Logo">
<h2>Daftar Akun</h2>
<?php
if (!empty($success)) echo "<p class='success'>$success</p>";
if (!empty($error)) echo "<p class='error'>$error</p>";
?>
<?php if (!empty($success)) echo "<p class='success'>$success</p>"; ?>
<?php if (!empty($error)) echo "<p class='error'>$error</p>"; ?>
<form method="POST">
<input type="text" name="username" placeholder="Username" required>
<input type="password" name="password" placeholder="Password" required>
<select name="role" required>
<option value="">Pilih Role</option>
<option value="engineer">Administrator</option>
<option value="teknisi">Teknisi</option>
<option value="engineer">Superadmin</option>
<option value="teknisi1">Site Bungatan</option>
<option value="teknisi2">Site Besuki</option>
<option value="teknisi3">Site jatisari</option>
<option value="teknisi4">Site Kendit</option>
<option value="teknisi5">Site Gending</option>
<option value="teknisi6">Site Bangkalan</option>
<option value="teknisi7">Site Bondowoso</option>
<option value="teknisi8">Site Arjasa</option>
<option value="teknisi9">Site Lamongan</option>
<option value="teknisi10">Site Asembagus</option>
</select>
<button type="submit">Daftar</button>
</form>