IP Erişim ve Alarm Paneli

IP Erişim ve Alarm Paneli adını taşıyan, yerel ağdaki veya erişilebilir durumdaki IP adreslerini periyodik olarak tarayan, durumlarını görsel kartlar ve iframe önizlemeleriyle gösteren, erişim sağlandığında ise sesli alarm ve yanıp sönen görsel uyarılar üreten tek dosyalı bir web uygulamasıdır.

IP Adresi Takibi: Alt alta yazılan IP adreslerini satır satır okur ve her biri için dinamik, 5x5 cm kartlarda özel izleme kartları oluşturur.

Periyodik Tarama (Monitoring): "Takibi ve Alarmı Başlat" butonuna basıldığında süreç hemen başlar ve her 20 saniyede bir otomatik olarak yenilenir. Tarama döngüsü sayacı arayüzde anlık olarak takip edilir.

Güvenli Yönetim: İstendiğinde "Durdur" butonuyla aktif zamanlayıcılar ve animasyonlar iptal edilebilir, "Temizle" butonuyla liste sıfırlanabilir. Sayfa kapatılırken veya yenilenirken arka plandaki işlemler otomatik olarak sonlandırılır.

Excel, Kod içinde daimi olarak kullanılan ip adresleri ile değiştirebilir veya silebilir, Ekran üzerinde IP adresleri ekleyebilirsiniz.

Bu koddan amaç, adresi belli olan IP adreslerinin aktif olduğunda haberdar olmaktır. Sistem duraklatıncaya kadar taramaya devam eder. İhtiyaçtan kaynaklı tasarlanmıştır. (IP adresleri belli olan kameralar aktif olunca haberdar olmak için) 


Erişilebilen IP kartları yeşil renkte yanıp sönmeye başlar. Bip bip olarak, Ses çıkarır


Durdur butonuna basınca tarama işi durur. Mevcut kaç tekrar olduysa, bilgisi de formda yer almaktadır.


HTML Kod:
<!DOCTYPE html>
<html lang="tr">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <title>IP Erişim ve Alarm Paneli</title>
    <!-- Excel Okuma Kütüphanesi -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js"></script>
    <style>
        :root {
            --m: #0b4f8a;
            --bg: #ffffff;
            --shadow: 0 3px 10px rgba(0,0,0,.12);
            --radius: 8px;
        }
        * { margin: 0; padding: 0; box-sizing: border-box; font-family: "Segoe UI", Arial, sans-serif; }
        
        /* Sayfa asla dışarı taşmaz */
        html, body { height: 100vh; overflow: hidden; }
        body { background: var(--bg); padding: 12px; display: flex; flex-direction: column; }
        
        /* Başlık Alanı */
        .header { flex-shrink: 0; display: flex; justify-content: space-between; align-items: center; padding: 10px 15px; background: #fff; border: 2px solid #000; border-radius: var(--radius); box-shadow: var(--shadow); margin-bottom: 15px; gap: 15px; }
        .left { display: flex; align-items: center; gap: 12px; width: 30%; }
        .logo img { height: 46px; }
        .title1 { font-size: 20px; font-weight: 700; color: #000; }
        .title2 { font-size: 11px; color: #555; font-weight: 600; }
        
        /* Orta Alan - Butonlar */
        .center-buttons { flex-grow: 1; display: flex; justify-content: center; gap: 15px; }
        .right { width: 30%; font-size: 13px; font-weight: 700; white-space: nowrap; text-align: right; color: #333; }
        
        /* Buton Tasarımları */
        .btn { padding: 8px 25px; font-size: 14px; font-weight: bold; cursor: pointer; border-radius: 4px; border: 1px solid; transition: all 0.2s ease; box-shadow: 0 2px 4px rgba(0,0,0,0.05); }
        .btn:active { transform: translateY(1px); }
        .btn-blue { background-color: #e0f2fe; color: #0284c7; border-color: #bae6fd; }
        .btn-blue:hover { background-color: #bae6fd; }
        .btn-green { background-color: #dcfce7; color: #16a34a; border-color: #bbf7d0; }
        .btn-green:hover { background-color: #bbf7d0; }
        .btn-stop { background-color: #ffedd5; color: #c2410c; border-color: #fed7aa; }
        .btn-stop:hover { background-color: #fed7aa; }
        .btn-red { background-color: #fee2e2; color: #dc2626; border-color: #fecaca; }
        .btn-red:hover { background-color: #fecaca; }

        /* Panel ve İçerik Alanları */
        .panel { 
            background: #fff; border: 1px solid var(--m); border-radius: 10px; 
            padding: 15px; box-shadow: var(--shadow); display: flex; 
            flex-direction: column; flex-grow: 1; overflow: hidden; 
        }
        
        .msg { font-size: 13px; font-weight: 600; color: var(--m); flex-shrink: 0; }
        #durumMesaji { font-size: 13px; font-weight: bold; color: #0369a1; }
        
        /* 1) GİRİŞ ALANI (Textarea) - Tam 10 satırlık yükseklik (~332px) */
        .ip-input { 
            width: 100%; 
            height: 332px; 
            min-height: 332px;
            max-height: 332px;
            background: #fff; color: #000; border: 1px solid var(--m); 
            padding: 10px; font-family: monospace; border-radius: 6px; 
            font-size: 14px; margin-bottom: 12px; outline: none; resize: none; flex-shrink: 0;
            overflow-y: auto;
        }

        /* 2) GÖSTERGE ALANI WRAPPER - Kesin 10 satırlık sabit çerçeve, üst kısımda yer alır */
        .gosterge-wrapper {
            display: none; 
            width: 100%;
            height: 332px; /* Kesin 10 satırlık alan hesabı */
            overflow-x: auto; /* Sütunlar sağa taştığında yatay kaydırma çubuğu çıkar */
            overflow-y: hidden;
            margin-bottom: 12px;
            border: 1px solid var(--m);
            border-radius: 6px;
            background: #f8fafc;
            padding: 10px;
            flex-shrink: 0;
        }

        /* YATAY AKIŞ Kapsayıcısı */
        .ip-grid-gosterge {
            height: 100%;
            display: flex;
            gap: 15px; /* Sütunlar arası boşluk */
            width: 100%;
        }

        /* Her bir 10'lu Sütun Grubu: Esnek ve eşit paylaşımlı yapı */
        .liste-sutun {
            flex: 1; /* Yatayda alanı eşit olarak paylaşır, birbirini ezmez */
            min-width: 200px;
            max-width: 280px;
            flex-shrink: 0;
            display: flex;
            flex-direction: column;
            gap: 4px; /* Satırlar arası boşluk */
            border-right: 1px dashed #cbd5e1;
            padding-right: 15px;
        }
        .liste-sutun:last-child {
            border-right: none;
            padding-right: 0;
        }

        .list-item {
            width: 100%;
            flex-shrink: 0; /* Asla ezilmez, orijinal boyutunu korur */
            height: 28px; /* Sabit satır yüksekliği */
            display: flex;
            align-items: center;
            white-space: nowrap; /* İkinci satıra inmez */
            overflow: hidden; text-overflow: ellipsis; /* Taşan uzun adresleri ... yapar */
            padding: 0 8px;
            background: #fff; border: 1px solid #cbd5e1; border-radius: 4px;
            font-family: monospace; font-size: 13px; transition: all 0.2s;
        }

        /* 5x5 cm Dinamik Ekranlar Grid Yapısı (Alt Bölüm) */
        .ekranlar-grid {
            display: flex; flex-wrap: wrap; gap: 15px; margin-top: 5px;
            flex-grow: 1; overflow-y: auto; align-content: flex-start; padding-bottom: 15px;
        }

        /* 5x5 cm Ekran Kartı Tasarımı */
        .ip-karti {
            width: 189px; height: 189px; border: 2px solid var(--m); border-radius: 8px;
            background: #f8fafc; padding: 6px; display: flex; flex-direction: column;
            justify-content: space-between; align-items: center; text-align: center;
            box-shadow: 0 2px 5px rgba(0,0,0,0.1); transition: background-color 0.2s ease; overflow: hidden;
        }

        .ip-karti .kart-ust { font-size: 10px; font-weight: bold; color: var(--m); width: 100%; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
        .ip-karti iframe { width: 100%; height: 110px; border: 1px solid #cbd5e1; background: #fff; border-radius: 4px; }
        .ip-karti .kart-durum { font-size: 10px; font-weight: 600; color: #555; width: 100%; }

        /* Erişim sağlanınca yanıp sönecek YEŞİL sınıf */
        .yanip-son-yesil { 
            background-color: #22c55e !important; 
            border-color: #15803d !important;
            color: white !important; 
        }
        .yanip-son-yesil .kart-ust, .yanip-son-yesil .kart-durum { color: white !important; }

        .empty { padding: 25px; text-align: center; color: #666; font-size: 13px; width: 100%; }

        /* Tarama Sayacı Kutusu */
        .tarama-sayac-kutusu {
            flex-shrink: 0; margin-top: 10px; padding: 10px 15px; background: #f1f5f9;
            border: 1px solid var(--m); border-radius: 6px; font-size: 13px; font-weight: bold;
            color: #0b4f8a; text-align: center; display: none;
        }
    </style>
</head>
<body>

<div class="header">
    <div class="left">
        <div class="logo"><img src="logo.jpg" onerror="this.style.display='none'"></div>
        <div>
            <div class="title1">IP Erişim ve Alarm Paneli</div>
            <div class="title2">Kullanıcı Destek Koordinatörlüğü</div>
        </div>
    </div>
    
    <div class="center-buttons">
        <input type="file" id="fileInput" accept=".xlsx, .xls, .txt" style="display: none;" onchange="importData(event)">
        <button class="btn btn-blue" onclick="document.getElementById('fileInput').click()">Dosya Seç</button>
        <button class="btn btn-green" onclick="takibiBaslat()">Çalıştır</button>
        <button class="btn btn-stop" onclick="takiBiDurdur()">Durdur</button>
        <button class="btn btn-red" onclick="ipListesiniTemizle()">Temizle</button>
    </div>
    
    <div class="right">MYO BAŞKANLIĞI</div>
</div>

<div class="panel">
    <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px;">
        <div class="msg">Her satıra bir IP adresi yazarak veya dosya seçerek takibi başlatabilirsiniz:</div>
        <span id="durumMesaji">Durum: Bekliyor</span>
    </div>
    
    <!-- Giriş Alanı (Metin girildiğinde gizlenip üst kısımda sütunlu liste aktif olur) -->
    <textarea id="ipListesiInput" class="ip-input" placeholder="IP veya Web adreslerini alt alta buraya girin..." oninput="listeGorunumunuGuncelle()"></textarea>
    
    <!-- Dinamik Gösterge Alanı (Üst Kısımdaki Sütunlu Liste Çerçevesi) -->
    <div id="gostergeWrapper" class="gosterge-wrapper">
        <div id="ipListesiGosterge" class="ip-grid-gosterge"></div>
    </div>
    
    <!-- Alt Bölüm: İzleme Kartları -->
    <div id="ekranlarKonteyner" class="ekranlar-grid">
        <div class="empty">Henüz tarama yapılmadı. Adres girip Çalıştır butonuna basınız.</div>
    </div>

    <div id="taramaSayacKutusu" class="tarama-sayac-kutusu">
        Aktif Tarama Döngüsü: <span id="sayacDegeri">0</span>. Tarama Gerçekleştirildi
    </div>
</div>

<script>
    let audioCtx = null;
    let taramaZamanlayici = null;
    let aktifAnimasyonlar = [];
    let taramaSayaci = 0;

    function listeGorunumunuGuncelle() {
        const inputText = document.getElementById('ipListesiInput').value;
        const ips = inputText.split('\n').map(ip => ip.trim()).filter(ip => ip !== '');
        
        let wrapper = document.getElementById('gostergeWrapper');
        let gosterge = document.getElementById('ipListesiGosterge');
        let textarea = document.getElementById('ipListesiInput');

        if (ips.length === 0) {
            wrapper.style.display = 'none';
            textarea.style.display = 'block';
            gosterge.innerHTML = '';
            document.getElementById('durumMesaji').innerText = "Durum: Bekliyor";
            return;
        }

        let htmlGrup = "";
        for (let i = 0; i < ips.length; i += 10) {
            let chunk = ips.slice(i, i + 10);
            htmlGrup += `<div class="liste-sutun">`;
            chunk.forEach((ip, idx) => {
                let absoluteIndex = i + idx;
                let safeId = ip.replace(/[^a-zA-Z0-9]/g, '_') + '_' + absoluteIndex;
                htmlGrup += `<div id="list_item_${safeId}" class="list-item" title="${ip}">${absoluteIndex + 1}. ${ip}</div>`;
            });
            htmlGrup += `</div>`;
        }

        gosterge.innerHTML = htmlGrup;
        textarea.style.display = 'none';
        wrapper.style.display = 'block';
        document.getElementById('durumMesaji').innerText = `Durum: ${ips.length} Kayıt Hazır`;
    }

    function importData(event) {
        const file = event.target.files[0];
        if (!file) return;

        const ext = file.name.split('.').pop().toLowerCase();
        
        if (ext === 'txt') {
            const reader = new FileReader();
            reader.onload = function(e) {
                const lines = e.target.result.split(/\r?\n/).map(line => line.trim()).filter(line => line !== '');
                document.getElementById('ipListesiInput').value = lines.join('\n');
                listeGorunumunuGuncelle();
            };
            reader.readAsText(file);
        } 
        else if (ext === 'xlsx' || ext === 'xls') {
            const reader = new FileReader();
            reader.onload = function(e) {
                const data = new Uint8Array(e.target.result);
                const workbook = XLSX.read(data, {type: 'array'});
                const firstSheet = workbook.Sheets[workbook.SheetNames[0]];
                const jsonRows = XLSX.utils.sheet_to_json(firstSheet, {header: 1});
                const flatData = jsonRows.flat().map(line => String(line).trim()).filter(line => line !== '');
                document.getElementById('ipListesiInput').value = flatData.join('\n');
                listeGorunumunuGuncelle();
            };
            reader.readAsArrayBuffer(file);
        } 
        else {
            alert('HATA: Yalnızca .txt ve .xlsx/.xls formatları desteklenir.');
        }
        event.target.value = ''; 
    }

    function sesMotorunuHazirla() {
        if (!audioCtx) { audioCtx = new (window.AudioContext || window.webkitAudioContext)(); }
        if (audioCtx.state === 'suspended') { audioCtx.resume(); }
    }

    function bipCal() {
        try {
            sesMotorunuHazirla();
            const osc = audioCtx.createOscillator();
            const gain = audioCtx.createGain();
            osc.type = 'sine'; osc.frequency.setValueAtTime(1000, audioCtx.currentTime);
            gain.gain.setValueAtTime(0.5, audioCtx.currentTime);
            osc.connect(gain); gain.connect(audioCtx.destination);
            osc.start(); osc.stop(audioCtx.currentTime + 0.25);
        } catch(e) { console.log("Ses çalınamadı: ", e); }
    }

    function takiBiDurdur() {
        if (taramaZamanlayici) { clearInterval(taramaZamanlayici); taramaZamanlayici = null; }
        aktifAnimasyonlar.forEach(intervalId => clearInterval(intervalId));
        aktifAnimasyonlar = [];
        document.getElementById('durumMesaji').innerText = "Durum: Durduruldu";

        document.querySelectorAll('.yanip-son-yesil').forEach(el => el.classList.remove('yanip-son-yesil'));
    }

    function ipListesiniTemizle() {
        takiBiDurdur();
        
        let textarea = document.getElementById('ipListesiInput');
        textarea.value = ''; 
        textarea.style.display = 'block';
        
        document.getElementById('gostergeWrapper').style.display = 'none';
        document.getElementById('ipListesiGosterge').innerHTML = '';
        
        document.getElementById('ekranlarKonteyner').innerHTML = '<div class="empty">IP listesi temizlendi.</div>';
        document.getElementById('durumMesaji').innerText = "Durum: Temizlendi";
        document.getElementById('taramaSayacKutusu').style.display = 'none';
        taramaSayaci = 0;
    }

    // YENİDEN DÜZENLENMİŞ VE GELİŞTİRİLMİŞ PING FONKSİYONU
    async function ipErisimKontrol(ip) {
        let url = ip.trim();
        // Protokol yoksa http ekle
        if (!url.startsWith('http://') && !url.startsWith('https://')) {
            url = 'http://' + url;
        }

        // Cache Buster: Tarayıcının cevabı önbelleğe almasını önleyerek anlık ping atılmasını sağlar
        const cacheBuster = `?_cb=${new Date().getTime()}`;
        const checkUrl = url.includes('?') ? (url + '&_cb=' + new Date().getTime()) : (url + cacheBuster);

        try {
            const controller = new AbortController();
            // Zaman aşımını 3 saniyeye çıkartarak daha güvenilir sonuç alındı
            const timeoutId = setTimeout(() => controller.abort(), 3000); 
            
            await fetch(checkUrl, { 
                mode: 'no-cors', 
                signal: controller.signal, 
                cache: 'no-store' 
            });
            
            clearTimeout(timeoutId);
            return true; // İstek hedefe ulaştı (bağlantı kuruldu)
        } catch (error) {
            // Sunucu kapalıysa, erişilemiyorsa veya 3 saniyede cevap vermezse buraya düşer
            return false; 
        }
    }

    async function ipTarasiniCalistir(ips) {
        const konteyner = document.getElementById('ekranlarKonteyner');
        
        taramaSayaci++;
        document.getElementById('sayacDegeri').innerText = taramaSayaci;
        document.getElementById('taramaSayacKutusu').style.display = 'block';

        if (konteyner.querySelector('.empty') || konteyner.children.length !== ips.length) {
            konteyner.innerHTML = '';
            ips.forEach((ip, index) => {
                let safeId = ip.replace(/[^a-zA-Z0-9]/g, '_') + '_' + index;
                // IFRAME SANDBOX BURADA EKLENDİ (Sayfanın dışarı kaçmasını engeller)
                konteyner.innerHTML += `
                    <div class="ip-karti" id="kart_${safeId}">
                        <div class="kart-ust">${index + 1} - ${ip}</div>
                        <iframe id="frame_${safeId}" src="about:blank" sandbox="allow-same-origin allow-scripts allow-forms"></iframe>
                        <div class="kart-durum" id="status_${safeId}">Bekliyor...</div>
                    </div>
                `;
            });
        }

        document.getElementById('durumMesaji').innerText = "Durum: Taranıyor...";

        for (let i = 0; i < ips.length; i++) {
            let ip = ips[i];
            let safeId = ip.replace(/[^a-zA-Z0-9]/g, '_') + '_' + i;
            let kart = document.getElementById(`kart_${safeId}`);
            let listSatiri = document.getElementById(`list_item_${safeId}`);
            let iframe = document.getElementById(`frame_${safeId}`);
            let durumEtiket = document.getElementById(`status_${safeId}`);

            if (!kart) continue;

            durumEtiket.innerText = "Test ediliyor...";
            let erisildiMi = await ipErisimKontrol(ip);

            if (erisildiMi) {
                durumEtiket.innerText = "Erişildi! Alarm çalıyor...";
                
                let finalUrl = ip;
                if (!finalUrl.startsWith('http://') && !finalUrl.startsWith('https://')) {
                    finalUrl = 'http://' + ip;
                }

                if (!iframe.src.includes(ip) || iframe.src === "about:blank") {
                    iframe.src = finalUrl;
                }
                
                let tekrar = 0;
                let interval = setInterval(() => {
                    kart.classList.toggle('yanip-son-yesil');
                    if(listSatiri) listSatiri.classList.toggle('yanip-son-yesil');
                    
                    bipCal();
                    tekrar++;
                    
                    if (tekrar >= 10) {
                        clearInterval(interval);
                        kart.classList.remove('yanip-son-yesil');
                        if(listSatiri) listSatiri.classList.remove('yanip-son-yesil');
                        durumEtiket.innerText = "Bağlantı Başarılı (Tamamlandı)";
                    }
                }, 400);
                aktifAnimasyonlar.push(interval);

            } else {
                durumEtiket.innerText = "Erişilemedi";
                iframe.srcdoc = "<div style='font-family:sans-serif; font-size:10px; padding:10px; color:red; text-align:center;'>Erişim Yok</div>";
            }
        }
    }

    function takibiBaslat() {
        sesMotorunuHazirla();
        takiBiDurdur(); 

        const inputText = document.getElementById('ipListesiInput').value.trim() || 
                          Array.from(document.querySelectorAll('#ipListesiGosterge .list-item')).map(el => el.title).join('\n');
        
        if(!inputText) {
            alert('Başlatmak için adres girmelisiniz veya dosya seçmelisiniz.');
            return;
        }

        const ips = inputText.split('\n').map(ip => ip.trim()).filter(ip => ip);
        
        taramaSayaci = 0;
        ipTarasiniCalistir(ips);
        taramaZamanlayici = setInterval(() => ipTarasiniCalistir(ips), 20000);
    }

    window.addEventListener('beforeunload', () => {
        takiBiDurdur();
    });
</script>

</body>
</html>