LİON LogoLİON PANEL • TÜRKİYE GENELİ (81 İL) SEO, DOMAIN HUB & CANLI ANALİZ SİSTEMİ

👑 LİON Yönetim Paneli • Saat Dilimi: Europe/Istanbul • Varsayılan Görünüm: BUGÜN • %100 Gerçek Veritabanı Kayıtları

📅 Tarih Filtresi (Europe/Istanbul):
Bölüm 9 Trafik Ayrımı:
📊 1. Canlı Analiz Panosu (BUGÜN)
🏆 2. Vitrin Bazlı Detaylı Analiz & Sıralamalar
🌐 3. Ana Domain Hub & BTK Erişim (166 Domain)
🔍 4. Google Search Console (GSC)
👥 5. Üye Yönetimi
💃 6. Vitrin, Fotoğraf & Video Yönetimi
💎 7. Paket Yönetimi
📍 8. Türkiye 81 İl, Tüm İlçeler, 10 Dil SEO & Blog
🩺 9. 26 Nokta Otomatik Sistem Kontrolü & Loglar
⚡ 10. A'dan Z'ye Site Yönetimi (CMS) & 360° Önizleme
⚙️ 11. Sistem & Admin Ayarları

1. Saatlik Ziyaretçiler (00:00 - 23:00)Canlı

2. Saatlik Vitrin GörüntülemeleriVitrin

3. Saatlik WhatsApp & Telefon TıklamalarıEtkileşim

4. Günlük ZiyaretçilerTrend

5. Vitrin Görüntülemeleri (En Çok Görüntülenenler)Vitrin

6. WhatsApp Tıklamaları (Vitrin Bazlı)WhatsApp

7. Telefon & Profil Tıklamaları (Vitrin Bazlı)Telefon & Profil

8. Şehir & Ülke Bazlı Ziyaretçiler (Türkiye 81 İl)81 İl & Ülke

9. Domain Bazlı ZiyaretçilerDomain Hub

10. Mobil / Masaüstü DağılımıCihaz

11. Organik & Trafik KaynaklarıKaynak

12. En Çok Tıklanan Vitrinler (Dönüşüm %)Dönüşüm

💬 Canlı Gerçek WhatsApp, Telefon & Profil Tıklamaları (Doğrulanmış İnsan Tıklamaları) Canlı Kayıt

Tarih / Saat (İstanbul) Tıklama Türü Tıklanan Vitrin Tıklanan WhatsApp / Tel No Ziyaretçi Şehri / İlçe Cihaz & Sistem Trafik Kaynağı Domain

⚡ Gerçek Zamanlı Ziyaretçi & Tıklama Akışı (SSE Canlı Yayın — Europe/Istanbul) Not: WhatsApp/Telefon buton tıklaması gerçek mesaj/görüşme olarak değil, doğrulanmış buton tıklaması olarak kaydedilir.

Saat (İstanbul) Sınıf Olay Türü Vitrin / Profil Şehir / İlçe (81 İl) Cihaz Kaynak Domain Tıklanan Numara / Hedef

🔍 Vitrin Bazlı Detaylı Gerçek Veri Analizi (Detay Ekranı İçin Vitrine Tıklayın) Sahte Rakam Yok • %100 Gerçek Olay Kayıtları

Vitrin Adı Şehir / İlçe Paket Bugünkü Görüntüleme Tekil Ziyaretçi WhatsApp Tıklaması Telefon Tıklaması Profil Tıklaması Tıklanan Numara En Çok Şehir / İlçe En Çok Cihaz Son Tıklama Zamanı İncele

🏆 Bölüm 6: Gerçek Veri Sıralamaları (10 Sıralama Tablosu — Detay İçin Tıklayın)

🌐 Ana Domain Hub Yönetimi, Türkiye Geneli Trafik Performansı ve Erişim Kontrolü (125 Domain)

🛡️ OMNI-TITAN v25.0 — 12 KATMANLI BİRLEŞİK FEDAİ, AMP-LIST, WILDCARD & POLİMORFİK KALKAN MERKEZİ (7/24 AKTİF)

📌 Domain Hub Bilgisi: 114 domain bölge uyumuna göre 11 engelsiz açılır hedef siteye (307 Zero-Penalty Failover) bağlanmıştır. Her domainin gerçek ziyaretçi, vitrin görüntüleme, WhatsApp/telefon tıklamaları, mobil oranı ve dönüşüm analizi aşağıda ayrı ayrı raporlanır.

Domain Adı Odak Şehir Aktif / Pasif Türkiye Erişimi Doğrulanmış BTK Bilgisi DNS & SSL Durumu Gerçek Ziyaretçi Vitrin Görüntüleme WhatsApp / Telefon Mobil % & Dönüşüm Son Kontrol İşlem

🔍 Google Search Console (GSC) Gerçek Veri Entegrasyonu (13 Metrik)

📌 Bölüm 4 Kuralı: GSC verileriyle site içi tıklama verileri birbirine karıştırılmaz. Google'ın henüz yayımlamadığı günlük veriler sıfırmış gibi gösterilmez.

👥 Yeni Üye Ekle / Üye Düzenle / Onayla / Askıya Al

📋 Mevcut Üyeler Listesi (Hiçbir Üye Silinmez / Korunur)

IDAdTelefonŞehir / İlçePaketDurumKayıt Tarihiİşlem

💎 İzmir, Escsoft & Diamond VIP Vitrin Ekle / Düzenle (Fotoğraf & Video Dahil)

💃 Ana Vitrin (Tekirdağ VIP) Ekle / Güncelle (Fotoğraf & Video Dahil)

💎 İzmir, Çoklu Şehir & Diamond VIP Vitrinleri (Güneş — Kıbrıs Girne Diamond VIP Dahil)

IDFotoAd & YaşŞehir / İlçePaketTelefonFoto / VideoRozetİşlem

📋 Ana Vitrin Kartları (8 Aktif Kart)

SıraFotoAd & YaşŞehir / İlçePaketTelefonGerçek TıklamaDurumİşlem

💎 Paket Oluştur / Düzenle (VIP, Gold, Platinum, Diamond, Özel Vitrin)

📋 Mevcut Paketler

ÖncelikPaket KoduPaket AdıÜcret (TL)SüreRozetÖzelliklerİşlem

📍 Bölüm 2, 10, 11 & 12: Türkiye Geneli 81 İl + KKTC A–Z Escort Siteleri, Masaj Salonları, Masaj Siteleri & VIP Spa SEO + Blog Motoru
🌐 A–Z Türkiye SEO & Blog Portalını Aç

🇹🇷 Bölüm 2 & 10: Türkiye 81 İl, İlçe ve Doğrulanmış Mahalle Yönetimi (İl / İlçe / Mahalle Ekle & Yönet)

Eklenen ilçe ve mahalleler anında Türkiye 81 İl otomatik konum ve SEO sistemine dahil edilir.
IDİlİlçeDoğrulanmış Mahalle / SemtDurumEklenme Tarihiİşlem

🔑 Türkiye Geneli 81 İl, Tüm İlçeler & 10 Dil Yasal (18+) SEO Anahtar Kelime Ekle

Türkiye'nin 81 ili (İstanbul, İzmir, Ankara, Antalya, Bursa, Adana, Mersin, Muğla, Aydın, Manisa, Uşak, Tekirdağ vb.), tüm ilçeleri ve 10 dil (TR, EN, DE, FR, RU, AR, FA, ES, IT, NL) desteklenir.

📝 İzmir İlçe & Mahalle Odaklı Yeni SEO Blog İçeriği Oluştur

🔑 Kayıtlı İzmir & Mevcut SEO Anahtar Kelimeleri (0)

Anahtar Kelimeİl / İlçeKategoriDilDomainİşlem

📚 Yayınlanan İzmir 30 İlçe & Mevcut SEO Blog Yazıları (0)

Başlıkİl / İlçeDilDurumİşlem

🩺 Bölüm 16: 24 Maddelik Günlük Otomatik Sistem Kontrolü ve Güvenli Onarım Raporu

#Kontrol Edilen Sistem BileşeniDurumDetay & Onarım Bilgisi

📜 Yönetici İşlem ve Denetim Kayıtları (Audit Logs)

ZamanİşlemHedefDetay

🖥️ Sunucu Erişim ve Hata Kayıtları (Server Logs — Bölüm 5 & 10)

ZamanSınıfMetod & YolHTTP DurumDomain

🔐 Yönetici & Kullanıcı Yetkileri (Bölüm 10)

Rol / HesapYetki KapsamıKVKK & GüvenlikDurum
Root Süper Admin81 İl, 159 Domain Hub, GSC API, Üye/Vitrin/Paket Tam YetkiTam ErişimAKTİF
Vitrin & İçerik EditörüVitrin Ekleme/Düzenleme, Fotoğraf/Video, 81 İl SEO & BlogKVKK MaskeliAKTİF
Analiz & SEO UzmanıCanlı Analiz Panosu, GSC Sorgulama, 00:00 Günlük RaporlarAnonim OturumAKTİF

🚀 Toplu İlan & Hızlı Düzenleme (PRO — Hızlı Tablo)

Tüm aktif VIP vitrinlerin isimlerini, paylaşılan 3 WhatsApp numarasını (`+905542027476`, `+905358873382`, `+905319222689`), kayıtlı il/ilçe/mahalle ve paket bilgilerini tek tablodan anında güncelleyebilirsiniz.

IDSlugİlan / Model AdıWhatsApp / TelefonKayıtlı İlİlçeMahallePaketDurum

🎨 Marka ve Logo YönetimiCANLI

LİON Logo
Aktif Marka Kuyruğu: | LİON (Örn: İzmir Escort - Buca, Alsancak, Karşıyaka Escort | LİON)
Aktif Logo Yolu: /static/uploads/lion-logo.jpg

📸 VIP Hikayeler & DuyurularYENİ

Üst 35mm Sinema Film Şeridi Kayan Canlı Vitrin ve VIP Hikaye halkaları tüm 81 il ve AMP sayfalarında (`2C0Pu/amp`) senkronize çalışmaktadır.

⚡ Pop-up, Yüzen WA & +18 KVKK KalkanıAKTİF

WhatsApp Hat 1: +90 554 202 74 76
WhatsApp Hat 2: +90 535 887 33 82
WhatsApp Hat 3: +90 531 922 26 89
+18 KVKK & Bot Kalkanı: AÇIK (Fedai v20.0)

🧭 SEO, Footer & Yasal Metinler81 İL + KKTC

6'lı Google JSON-LD Schema (`WebSite`, `Organization`, `LocalBusiness`, `ItemList`, `FAQPage`, `BreadcrumbList`), Dinamik ` | LİON` ve +18 Yasal Uyarı metinleri tüm 166 domainde aktiftir. </div> <button class="btn blue" onclick="switchTab('seo_81')">🔑 SEO & Blog Metinlerini Düzenle</button> </div> </div> <!-- 3. Üye Süreleri & Yenileme + Ödemeler & Cüzdan --> <div class="grid-2"> <div class="box" id="cmsMemberRenewalBox"> <h3><span>🕒 Üye Süreleri & Yenileme Takibi</span><span class="badge ok">OTOMATİK</span></h3> <table> <thead><tr><th>Üye / Vitrin</th><th>Paket</th><th>Bölge</th><th>Kalan Süre</th><th>Durum</th></tr></thead> <tbody id="cmsMemberRenewalRows"></tbody> </table> </div> <div class="box" id="cmsPaymentsWalletBox"> <h3><span>💳 Ödemeler & Cüzdan (Paket Fiyatlandırma Özeti)</span><span class="badge hub">ELDEN & VIP</span></h3> <table> <thead><tr><th>Paket Adı</th><th>Fiyat (TL)</th><th>Süre (Gün)</th><th>Öncelik</th></tr></thead> <tbody id="cmsPaymentsRows"></tbody> </table> </div> </div> <!-- 4. Medya & Fotoğraf Kütüphanesi --> <div class="box" id="cmsMediaLibraryBox"> <h3><span>🖼️ Medya & Fotoğraf Kütüphanesi (Kayıtlı Vitrin Görselleri)</span><span class="badge info" id="cmsMediaCountBadge">0 Görsel</span></h3> <div id="cmsMediaGalleryGrid" style="display:grid;grid-template-columns:repeat(auto-fill,minmax(120px,1fr));gap:10px;max-height:360px;overflow-y:auto;padding:4px;"></div> </div> <!-- 5. Canlı Site Önizleme (Split 360°) --> <div class="box" id="cmsLiveSplitPreviewBox"> <h3> <span>🖥️ Canlı Site Önizleme (Split 360° — Masaüstü Ana Vitrin & Mobil AMP Kalkanı)</span> <span class="badge hub">360° CANLI</span> </h3> <div class="grid-2"> <div> <div style="font-size:12px;font-weight:800;color:var(--gold2);margin-bottom:6px;">💻 Masaüstü & Mobil Ana Vitrin (/)</div> <iframe src="/?preview=1" style="width:100%;height:520px;border:1.5px solid var(--gold);border-radius:12px;background:#09090f;"></iframe> </div> <div> <div style="font-size:12px;font-weight:800;color:#34d399;margin-bottom:6px;">⚡ Mobil AMP Kalkan Vitrin (/2C0Pu/amp/izmir/)</div> <iframe src="/2C0Pu/amp/izmir/" style="width:100%;height:520px;border:1.5px solid #34d399;border-radius:12px;background:#09090f;"></iframe> </div> </div> </div> </div> <!-- TAB 10: SİSTEM AYARLARI --> <div id="tab-settings" class="panel"> <div class="box"> <h3>🔐 Yönetici (Admin) Giriş Bilgileri, WhatsApp Numaraları, Ana Domain Hub ve GSC API Ayarları</h3> <div class="form-row"> <div><label>👤 Admin Kullanıcı Adı</label><input id="set_admin_username" placeholder="masorlevent"></div> <div><label>🔑 Admin Şifresi</label><input id="set_admin_password" placeholder="Mehmet89."></div> <div><label>Site Başlığı</label><input id="set_site_title"></div> <div><label>Birincil WhatsApp 1</label><input id="set_primary_whatsapp_1"></div> <div><label>Birincil WhatsApp 2</label><input id="set_primary_whatsapp_2"></div> <div><label>Birincil WhatsApp 3</label><input id="set_primary_whatsapp_3"></div> <div><label>Merkezi Ana Kök Hub Domain</label><input id="set_active_clean_domain" placeholder="izmirescortvitrini.net"></div> <div><label>AMP Hub Adresi</label><input id="set_amp_shield_hub" placeholder="/2C0Pu/amp/"></div> <div><label>Google Analytics ID</label><input id="set_ga_measurement_id"></div> </div> <div style="margin-bottom:10px;"> <label>Google Search Console Service Account JSON (Gerçek API Sorgusu İçin)</label> <textarea id="set_gsc_service_account_json" rows="4" placeholder='{"type":"service_account","project_id":"...","private_key":"...","client_email":"..."}'></textarea> </div> <button class="btn primary" onclick="saveSettings()">💾 Ayarları ve Admin Şifresini Kaydet</button> </div> <div class="box"> <h3>📜 Bölüm 18: Kalıcı Proje Kuralları (18 Değişmez Kural — Aktif & Kayıtlı)</h3> <div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));gap:8px;font-size:12px;color:var(--text);"> <div><b style="color:var(--gold2);">KURAL 1:</b> Mevcut çalışan özellikleri koru.</div> <div><b style="color:var(--gold2);">KURAL 2:</b> Kullanıcının istemediği değişiklikleri yapma.</div> <div><b style="color:var(--gold2);">KURAL 3:</b> Gerçek ziyaretçi verileri dışında istatistik üretme.</div> <div><b style="color:var(--gold2);">KURAL 4:</b> Demo ve test verilerini gerçek analizlere karıştırma.</div> <div><b style="color:var(--gold2);">KURAL 5:</b> Admin panelindeki bütün yönetim özelliklerini çalışır durumda tut.</div> <div><b style="color:var(--gold2);">KURAL 6:</b> Bütün vitrinlerin görüntüleme, WhatsApp ve telefon tıklamalarını ayrı takip et.</div> <div><b style="color:var(--gold2);">KURAL 7:</b> Varsayılan analiz tarihini her zaman BUGÜN olarak ayarla.</div> <div><b style="color:var(--gold2);">KURAL 8:</b> Türkiye'nin 81 ilini ve ilgili ilçelerini destekle.</div> <div><b style="color:var(--gold2);">KURAL 9:</b> Domain Hub sistemini merkezi yönetim paneline bağla.</div> <div><b style="color:var(--gold2);">KURAL 10:</b> Google Search Console verilerini gerçek API üzerinden çek.</div> <div><b style="color:var(--gold2);">KURAL 11:</b> Her gün otomatik sistem ve SEO kontrolü gerçekleştir.</div> <div><b style="color:var(--gold2);">KURAL 12:</b> Güncellemelerden önce yedek al ve sonrasında test yap.</div> <div><b style="color:var(--gold2);">KURAL 13:</b> Çalışan dosyaları, vitrinleri, üyeleri ve domain bağlantılarını bozma.</div> <div><b style="color:var(--gold2);">KURAL 14:</b> Her vitrin, domain ve iletişim bağlantısı için ayrı gerçek analiz sun.</div> <div><b style="color:var(--gold2);">KURAL 15:</b> Bütün analizlerde doğrulanmış gerçek verileri kullan.</div> <div><b style="color:var(--gold2);">KURAL 16:</b> Yapılan her değişikliği kayıt altına al.</div> <div><b style="color:var(--gold2);">KURAL 17:</b> Yeni güncellemeleri mevcut sisteme uyumlu şekilde ekle.</div> <div><b style="color:var(--gold2);">KURAL 18:</b> Doğrulanmamış işlemleri tamamlandı olarak gösterme.</div> </div> </div> </div> </div> </div> <!-- VİTRİN DETAYLI ANALİZ MODALI (BÖLÜM 7) --> <div class="modal-backdrop" id="vitrinModal" onclick="if(event.target===this)this.style.display='none'"> <div class="modal-card" id="vitrinModalBody"></div> </div> <div class="toast" id="toast">İşlem kaydedildi!</div> <script> const NO_DATA_MSG = 'Henüz doğrulanmış veri bulunmuyor.'; let STATE = null; let CURRENT_PERIOD = 'today'; // Varsayılan her açılışta BUGÜN let CURRENT_CLASS = 'HUMAN'; // Varsayılan her açılışta A. GERÇEK ZİYARETÇİLER function showToast(msg) { const t = document.getElementById('toast'); t.textContent = msg; t.style.display = 'block'; setTimeout(() => t.style.display = 'none', 2800); } function switchTab(id, el) { document.querySelectorAll('.tab').forEach(t => { t.classList.toggle('active', t.getAttribute('data-tab-id') === id || t === el); }); document.querySelectorAll('.panel').forEach(p => p.classList.remove('active')); const panel = document.getElementById('tab-' + id); if (panel) panel.classList.add('active'); } function sidebarGo(tabId, anchorId, sideEl, actionFn) { if (sideEl) { document.querySelectorAll('#lionSidebar .sb-item').forEach(i => i.classList.remove('active')); sideEl.classList.add('active'); } switchTab(tabId); if (typeof actionFn === 'function') { try { actionFn(); } catch (e) {} } if (anchorId) { setTimeout(() => { const target = document.getElementById(anchorId); if (target) target.scrollIntoView({ behavior: 'smooth', block: 'start' }); }, 80); } else { window.scrollTo({ top: 0, behavior: 'smooth' }); } } async function pingAllSitemapsNow() { showToast('166 Domain için Sitemap & IndexNow bildirimi başlatıldı...'); try { const res = await fetch('/api/admin/seo-ping/run', { method: 'POST' }); const out = await res.json(); if (out && out.ok) { showToast(`Sitemap & IndexNow Tamamlandı: ${out.report.total_urls_submitted} URL bildirildi!`); } } catch (e) {} } function renderCmsHubPanel(escModels, members, packages) { const escList = escModels || []; const memList = members || []; const pkgList = packages || []; const sbMem = document.getElementById('sbCountMembers'); if (sbMem) sbMem.textContent = memList.length; const sbPend = document.getElementById('sbCountPending'); if (sbPend) sbPend.textContent = memList.filter(m => m.status !== 'APPROVED').length; const sbVit = document.getElementById('sbCountVitrins'); if (sbVit) sbVit.textContent = escList.length; const bulkRows = document.getElementById('cmsBulkTableRows'); if (bulkRows) { bulkRows.innerHTML = escList.map(m => ` <tr data-bulk-id="${m.id}" data-bulk-slug="${m.slug}"> <td><b>#${m.id}</b></td> <td><code>${m.slug}</code></td> <td><input class="cms-b-name" value="${m.name || ''}"></td> <td><input class="cms-b-phone" value="${m.phone || '+905542027476'}"></td> <td><input class="cms-b-city" value="${m.city || 'Bursa'}"></td> <td><input class="cms-b-dist" value="${m.district || 'Nilüfer'}"></td> <td><input class="cms-b-mah" value="${m.neighborhood || ''}" placeholder="Alsancak / Görükle"></td> <td><input class="cms-b-pkg" value="${m.package_name || 'DIAMOND VIP'}"></td> <td> <select class="cms-b-act"> <option value="1" ${m.active ? 'selected' : ''}>AKTİF</option> <option value="0" ${!m.active ? 'selected' : ''}>PASİF</option> </select> </td> </tr> `).join(''); } const renRows = document.getElementById('cmsMemberRenewalRows'); if (renRows) { renRows.innerHTML = escList.map(m => ` <tr> <td><b style="color:var(--gold2);">${m.name}</b></td> <td><span class="badge hub">${m.package_name || 'DIAMOND VIP'}</span></td> <td>${m.city || 'Bursa'} / ${m.district || 'Nilüfer'}</td> <td><b style="color:var(--green);">30 Gün (Otomatik Yenileme)</b></td> <td><span class="badge ok">AKTİF</span></td> </tr> `).join(''); } const payRows = document.getElementById('cmsPaymentsRows'); if (payRows) { payRows.innerHTML = pkgList.map(p => ` <tr> <td><b style="color:var(--gold2);">${p.name}</b></td> <td><b>${(p.price_tl || 0).toLocaleString('tr-TR')} ₺</b></td> <td>${p.duration_days || 30} Gün</td> <td>#${p.priority_rank || 1}</td> </tr> `).join(''); } const allPhotos = []; escList.forEach(m => { (m.photos || []).forEach(p => { allPhotos.push({ model: m.name, url: p }); }); }); const mediaBadge = document.getElementById('cmsMediaCountBadge'); if (mediaBadge) mediaBadge.textContent = `${allPhotos.length} Doğrulanmış Görsel`; const mediaGrid = document.getElementById('cmsMediaGalleryGrid'); if (mediaGrid) { mediaGrid.innerHTML = allPhotos.slice(0, 60).map(item => ` <div style="background:#0b0d14;border:1px solid var(--border);border-radius:9px;padding:5px;text-align:center;"> <img src="${item.url}" alt="${item.model}" loading="lazy" style="width:100%;height:110px;object-fit:cover;border-radius:6px;margin-bottom:4px;"> <div style="font-size:10.5px;font-weight:700;color:var(--gold2);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;">${item.model}</div> <button type="button" class="btn" style="width:100%;justify-content:center;padding:3px 5px;font-size:10px;margin-top:3px;" onclick="navigator.clipboard.writeText('${item.url}');showToast('Görsel adresi kopyalandı!')">📋 Kopyala</button> </div> `).join(''); } } async function saveCmsBulkVitrins() { const trs = document.querySelectorAll('#cmsBulkTableRows tr[data-bulk-id]'); const items = []; trs.forEach(tr => { items.push({ id: tr.getAttribute('data-bulk-id'), slug: tr.getAttribute('data-bulk-slug'), name: tr.querySelector('.cms-b-name').value.trim(), phone: tr.querySelector('.cms-b-phone').value.trim(), city: tr.querySelector('.cms-b-city').value.trim(), district: tr.querySelector('.cms-b-dist').value.trim(), neighborhood: tr.querySelector('.cms-b-mah').value.trim(), package_name: tr.querySelector('.cms-b-pkg').value.trim(), active: Number(tr.querySelector('.cms-b-act').value) }); }); await fetch('/api/admin/cms-bulk-update', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ items }) }); showToast('Toplu İlan & Hızlı Düzenleme (PRO) başarıyla kaydedildi!'); loadOverview(); } function setPeriod(period) { CURRENT_PERIOD = period; document.querySelectorAll('.period-btn').forEach(b => { b.classList.toggle('active-filter', b.getAttribute('data-period') === period); }); loadOverview(); } function setTrafficClass(cls) { CURRENT_CLASS = cls; document.querySelectorAll('.class-btn').forEach(b => { b.classList.toggle('active-filter', b.getAttribute('data-class') === cls); }); loadOverview(); } function renderBarChart(containerId, arr24, colorClass = '') { const el = document.getElementById(containerId); if (!el) return; const safeArr = Array.isArray(arr24) && arr24.length === 24 ? arr24 : Array(24).fill(0); const max = Math.max(1, ...safeArr); el.innerHTML = safeArr.map((v, hr) => { const pct = Math.max(3, Math.round((v / max) * 100)); return `<div class="bar-col" title="Saat ${String(hr).padStart(2,'0')}:00 -> ${v} gerçek olay"> <div style="font-size:9px;color:var(--gold2);margin-bottom:2px;">${v > 0 ? v : ''}</div> <div class="bar-fill ${colorClass}" style="height:${pct}%;"></div> <div class="bar-lbl">${String(hr).padStart(2,'0')}</div> </div>`; }).join(''); } function renderHorizChart(containerId, items, emptyText = NO_DATA_MSG) { const el = document.getElementById(containerId); if (!el) return; if (!items || items.length === 0 || items.every(x => (x.val || x.count || 0) === 0)) { el.innerHTML = `<div style="color:var(--muted);font-size:12px;padding:14px 0;">${emptyText}</div>`; return; } const normalized = items.map(x => ({ label: x.label || x.name || '-', val: Number(x.val !== undefined ? x.val : x.count) || 0, suffix: x.suffix || '' })).filter(x => x.val > 0); if (normalized.length === 0) { el.innerHTML = `<div style="color:var(--muted);font-size:12px;padding:14px 0;">${emptyText}</div>`; return; } const max = Math.max(1, ...normalized.map(x => x.val)); el.innerHTML = normalized.slice(0, 7).map(x => { const pct = Math.max(4, Math.round((x.val / max) * 100)); return `<div class="horiz-item"> <div class="horiz-name" title="${x.label}">${x.label}</div> <div class="horiz-track"><div class="horiz-bar" style="width:${pct}%;"></div></div> <div class="horiz-val">${x.suffix ? x.val + x.suffix : x.val}</div> </div>`; }).join(''); } async function loadOverview() { const start = document.getElementById('customStart').value || ''; const end = document.getElementById('customEnd').value || ''; const qs = new URLSearchParams({ range: CURRENT_PERIOD, period: CURRENT_PERIOD, start, end, trafficClass: CURRENT_CLASS, traffic_class: CURRENT_CLASS }); const res = await fetch('/api/admin/overview?' + qs.toString()); STATE = await res.json(); const a = STATE.analytics || {}; const k = a.kpi18 || {}; const totals = a.class_totals || { HUMAN: 0, BOT: 0, TEST: 0 }; const filterInfo = a.filter || { label: 'Bugün', trafficClass: CURRENT_CLASS }; document.getElementById('hdrClock').textContent = `İstanbul Saati: ${a.istanbul_now || '-'} • Aktif Filtre: ${filterInfo.label} • Gösterilen Trafik: ${filterInfo.trafficClass}`; document.getElementById('cntHuman').textContent = totals.HUMAN || 0; document.getElementById('cntBot').textContent = totals.BOT || 0; document.getElementById('cntTest').textContent = totals.TEST || 0; const latestGsc = (STATE.gsc_reports && STATE.gsc_reports[0] && STATE.gsc_reports[0].data && STATE.gsc_reports[0].data.metrics) || null; const gscTxt = latestGsc && latestGsc.organic_clicks !== null && latestGsc.organic_clicks !== undefined ? `${latestGsc.organic_clicks} Tıklama / ${latestGsc.impressions || 0} Gösterim` : 'GSC Sorgusu Bekleniyor'; const activeDomCount = (STATE.domains || []).filter(d => d.status === 200).length; const blockedDomCount = (STATE.domains || []).filter(d => d.status !== 200 || d.btk_status === 'BLOCKED').length; const err5xxCount = (a.server_logs_comparison && a.server_logs_comparison.error_5xx) || 0; // Bölüm 15: 18 Özet Kartı (Birebir Eşleşen Gerçek Veritabanı Kayıtları) const cards18 = [ { num: k.today_visitors || 0, lbl: '1. Bugünkü Gerçek Ziyaretçiler', sub: `${filterInfo.label} • Doğrulanmış İnsan` }, { num: k.unique_visitors || 0, lbl: '2. Bugünkü Tekil Ziyaretçiler', sub: 'Anonim Oturum • KVKK Uyumlu' }, { num: k.total_vitrin_views || 0, lbl: '3. Bugünkü Toplam Vitrin Görüntüleme', sub: `Sayfa Görüntüleme: ${k.total_page_views || 0}` }, { num: k.total_whatsapp_clicks || 0, lbl: '4. Bugünkü WhatsApp Tıklamaları', sub: 'Doğrulanmış Buton Tıklaması' }, { num: k.total_phone_clicks || 0, lbl: '5. Bugünkü Telefon Tıklamaları', sub: 'Doğrulanmış Buton Tıklaması' }, { num: k.total_profile_clicks || 0, lbl: '6. Bugünkü Profil Tıklamaları', sub: 'Profil Detay Girişi' }, { num: k.top_vitrin || NO_DATA_MSG, lbl: '7. En Çok Görüntülenen Vitrin', sub: 'Gerçek Veri' }, { num: k.top_whatsapp_vitrin || NO_DATA_MSG, lbl: '8. En Çok WhatsApp Alan Vitrin', sub: 'Gerçek Veri' }, { num: k.top_phone_vitrin || NO_DATA_MSG, lbl: '9. En Çok Telefon Alan Vitrin', sub: 'Gerçek Veri' }, { num: k.top_domain || NO_DATA_MSG, lbl: '10. Bugünkü En Aktif Domain', sub: 'Merkezi Domain Hub' }, { num: k.top_city || NO_DATA_MSG, lbl: '11. Bugünkü En Aktif Şehir (81 İl)', sub: `İzmir İlçe: ${k.top_izmir_district || '-'}` }, { num: `${k.mobile_visitors || 0} Ziyaretçi (%${k.mobile_ratio || 0})`, lbl: '12. Bugünkü Mobil & AMP Ziyaretçi', sub: `AMP Ziyaretçi: ${k.amp_visitors || 0} (%${k.amp_ratio || 0})` }, { num: `${k.desktop_visitors || 0} Ziyaretçi (%${k.desktop_ratio || 0})`, lbl: '13. Bugünkü Masaüstü Ziyaretçi Sayısı', sub: 'Masaüstü / Tarayıcı Trafiği' }, { num: gscTxt, lbl: '14. Son Mevcut GSC Verileri', sub: 'Bölüm 4 GSC API (Canlı Sayaçtan Ayrı)' }, { num: `${activeDomCount} / ${(STATE.domains || []).length}`, lbl: '15. Aktif Domain Sayısı (200 OK)', sub: 'Yönlendirmesiz Doğrudan Açılış' }, { num: blockedDomCount, lbl: '16. Fedai Kalkan / BTK Domain', sub: 'Doğrulanmış Erişim & 307 Kalkan' }, { num: totals.BOT || 0, lbl: '17. Bot / Şüpheli Trafik Sayısı', sub: `Test & Demo: ${totals.TEST || 0} (Ayrı)` }, { num: err5xxCount > 0 ? `${err5xxCount} HATA` : '0 HATA (SAĞLIKLI)', lbl: '18. Sistem Hata & Gece 00:00 Sayaç', sub: `00:00 Dönemi: ${k.active_istanbul_date || 'Aktif'}` } ]; document.getElementById('kpiGrid18').innerHTML = cards18.map(c => ` <div class="kpi-card"> <div class="k-num" style="${String(c.num).length > 14 ? 'font-size:13.5px;' : ''}">${c.num}</div> <div class="k-lbl">${c.lbl}</div> <div class="k-sub">${c.sub}</div> </div> `).join(''); // Bölüm 4: Konum uyuşmazlığı uyarılarını göster const mmWarnings = a.location_mismatch_warnings || []; const mmBox = document.getElementById('locationMismatchBox'); if (mmBox) { if (mmWarnings.length > 0) { mmBox.style.display = 'block'; mmBox.innerHTML = ` <h3 style="color:var(--red);"><span>⚠️ Bölüm 4: Vitrin Başlık / Kayıtlı Şehir Uyuşmazlığı Uyarıları (${mmWarnings.length})</span><span class="badge err">Otomatik Taşınmadı</span></h3> <div style="display:flex;flex-direction:column;gap:6px;font-size:12.5px;"> ${mmWarnings.map(w => `<div>• <b>${w.name}</b> (${w.vitrin_id}): ${w.warning}</div>`).join('')} </div> `; } else { mmBox.style.display = 'none'; } } // Bölüm 5, 7, 8 & 12: Sunucu HTTP Logları ve Ayrı Mobil / Masaüstü / AMP / Gece 00:00 Analiz Bölümleri const slc = a.server_logs_comparison || {}; document.getElementById('serverConsistencyBox').innerHTML = ` <h3><span>🖥️ Bölüm 5 & 9: Sunucu HTTP Logları ile Tarayıcı Olay Takibi Karşılaştırması</span><span class="badge ok">Her HTTP İsteği İnsan Ziyareti Sayılmaz • Doğrulanmış</span></h3> <div style="display:flex;gap:24px;flex-wrap:wrap;font-size:12.5px;"> <div>Toplam Sunucu HTTP İstekleri: <b style="color:var(--gold2);">${slc.total_http_requests || 0}</b></div> <div>Doğrulanmış Tarayıcı İnsan Olayları (A): <b style="color:var(--green);">${slc.verified_human_events || 0}</b></div> <div>Bot / Tarayıcı İstekleri (B): <b style="color:var(--blue);">${slc.bot_http_requests || 0}</b></div> <div>4xx İstekler: <b>${slc.error_4xx || 0}</b></div> <div>5xx Sunucu Hataları: <b style="color:${(slc.error_5xx || 0) > 0 ? 'var(--red)' : 'var(--green)'};">${slc.error_5xx || 0}</b></div> </div> `; const seg = a.segmented_analysis || {}; const mobSeg = seg.mobile || {}; const dskSeg = seg.desktop || {}; const ampSeg = seg.amp || {}; const midSeg = seg.midnight_rollover || {}; const minsLeft = Math.max(0, Math.floor((midSeg.seconds_until_next_midnight || 0) / 60)); const hrsLeft = Math.floor(minsLeft / 60); const remMins = minsLeft % 60; const segEl = document.getElementById('segmentedAnalysisGrid'); if (segEl) { segEl.innerHTML = ` <div class="box"> <h3><span>📱 MOBİL ANALİZLER (Bölüm 8 & 12)</span><span class="badge ok">%${mobSeg.ratio || 0} Mobil Payı</span></h3> <div style="display:grid;grid-template-columns:repeat(2,1fr);gap:6px;font-size:12px;"> <div>Mobil Tekil Ziyaretçi: <b style="color:var(--gold2);">${mobSeg.visitors || 0}</b></div> <div>Mobil Vitrin Görüntüleme: <b>${mobSeg.views || 0}</b></div> <div>Mobil WhatsApp Tıklaması: <b style="color:var(--green);">${mobSeg.whatsapp_clicks || 0}</b></div> <div>Mobil Telefon / Profil: <b style="color:var(--blue);">${mobSeg.phone_clicks || 0} / ${mobSeg.profile_clicks || 0}</b></div> </div> </div> <div class="box"> <h3><span>💻 MASAÜSTÜ / TARAYICI ANALİZLERİ (Bölüm 8 & 12)</span><span class="badge info">%${dskSeg.ratio || 0} Masaüstü Payı</span></h3> <div style="display:grid;grid-template-columns:repeat(2,1fr);gap:6px;font-size:12px;"> <div>Masaüstü Tekil Ziyaretçi: <b style="color:var(--gold2);">${dskSeg.visitors || 0}</b></div> <div>Masaüstü Vitrin Görüntüleme: <b>${dskSeg.views || 0}</b></div> <div>Masaüstü WhatsApp Tıklaması: <b style="color:var(--green);">${dskSeg.whatsapp_clicks || 0}</b></div> <div>Tarayıcı Dağılımı: <b>${(dskSeg.browsers || []).slice(0,3).map(b => b.label + ' (' + b.count + ')').join(', ') || NO_DATA_MSG}</b></div> </div> </div> <div class="box"> <h3><span>⚡ AMP ANALİZLERİ (/2C0Pu/amp & AMP Sayfaları)</span><span class="badge hub">Çift Sayım Korumalı</span></h3> <div style="display:grid;grid-template-columns:repeat(2,1fr);gap:6px;font-size:12px;"> <div>AMP Tekil Ziyaretçi: <b style="color:var(--gold2);">${ampSeg.visitors || 0} (%${ampSeg.ratio || 0})</b></div> <div>AMP Vitrin Görüntüleme: <b>${ampSeg.views || 0}</b></div> <div>AMP WhatsApp Tıklaması: <b style="color:var(--green);">${ampSeg.whatsapp_clicks || 0}</b></div> <div>AMP / Normal Çift Sayım Koruması: <b style="color:var(--green);">AKTİF (8sn Tekilleştirme)</b></div> </div> </div> <div class="box"> <h3><span>🕛 HER GECE 00:00 GÜNLÜK SAYAÇ SİSTEMİ (Bölüm 7)</span><span class="badge ok">Europe/Istanbul</span></h3> <div style="display:grid;grid-template-columns:repeat(2,1fr);gap:6px;font-size:12px;"> <div>Aktif Günlük Dönem: <b style="color:var(--gold2);">${midSeg.active_istanbul_date || '-'} (00:00:00 - 23:59:59)</b></div> <div>Yeni Güne Kalan Süre: <b style="color:var(--green);">${hrsLeft} saat ${remMins} dk</b></div> <div>Geçmiş Kayıt Koruma: <b style="color:var(--green);">%100 Korunur (Silinmez)</b></div> <div>Arşivlenen Gün Özeti: <b>${(midSeg.recent_archived_days || []).length} Gün Kayıtlı</b></div> </div> </div> `; } // 12 Grafik Render const hourly = (a.charts && a.charts.hourly) || []; renderBarChart('chartHourlyVisitors', hourly.map(h => h.visitors || 0), ''); renderBarChart('chartHourlyViews', hourly.map(h => h.views || 0), 'blue'); renderBarChart('chartHourlyClicks', hourly.map(h => h.clicks || 0), 'green'); const daily = (a.charts && a.charts.daily) || []; renderHorizChart('chartDailySeries', daily.map(d => ({ label: d.date, val: d.visitors || d.views || 0 }))); const lb = a.leaderboards10 || {}; const vList = a.vitrin_analytics || []; renderHorizChart('chartVitrinViews', (lb.by_views || []).map(v => ({ label: v.vitrin_name, val: v.views }))); renderHorizChart('chartWaVitrins', (lb.by_whatsapp || []).map(v => ({ label: v.vitrin_name, val: v.whatsapp_clicks }))); renderHorizChart('chartActiveVitrins', vList.map(v => ({ label: v.vitrin_name, val: (v.phone_clicks || 0) + (v.profile_clicks || 0) })).sort((x, y) => y.val - x.val)); const cityAndCountry = [...((a.charts && a.charts.cities) || []), ...((a.charts && a.charts.izmir_districts) || [])]; renderHorizChart('chartIzmirDistricts', cityAndCountry); renderHorizChart('chartDomains', (lb.top_domains || [])); renderHorizChart('chartDevices', (a.charts && a.charts.devices) || []); renderHorizChart('chartSources', (a.charts && a.charts.sources) || []); renderHorizChart('chartConversions', (lb.by_conversion || []).map(v => ({ label: v.vitrin_name, val: v.conversion_rate, suffix: '%' }))); // Canlı WhatsApp, Telefon & Profil Tıklamaları Tablosu const clickRows = a.recent_click_events || (a.recent_events || []).filter(e => ['whatsapp_click', 'phone_click', 'profile_view', 'card_click'].includes(e.event_type)); const clickTbl = document.getElementById('liveClicksTable'); if (clickTbl) { clickTbl.innerHTML = clickRows.length === 0 ? `<tr><td colspan="8" style="color:var(--muted);text-align:center;padding:14px;">${NO_DATA_MSG}</td></tr>` : clickRows.map(ev => { const dtStr = ev.created_at ? new Date(ev.created_at).toLocaleString('tr-TR', { timeZone: 'Europe/Istanbul' }) : '-'; const typeLbl = ev.event_type === 'whatsapp_click' ? '<span class="badge ok">💬 WhatsApp Buton Tıklaması</span>' : (ev.event_type === 'phone_click' ? '<span class="badge info">📞 Telefon Arama Tıklaması</span>' : `<span class="badge hub">👤 ${ev.event_type}</span>`); return ` <tr> <td><b>${dtStr}</b></td> <td>${typeLbl}</td> <td><b style="color:var(--gold2);">${ev.vitrin_name || ev.vitrin_id || '-'}</b></td> <td><b style="color:var(--green);">${ev.target_phone || '+905542027476'}</b></td> <td>${ev.city || 'İzmir'} / <b>${ev.district || 'Merkez'}</b></td> <td>${ev.device_type || '-'} (${ev.os_name || '-'})</td> <td>${ev.traffic_source || '-'}</td> <td>${ev.domain || '-'}</td> </tr>`; }).join(''); } // Canlı Olaylar Tablosu const evRows = a.recent_events || []; document.getElementById('liveEventsTable').innerHTML = evRows.length === 0 ? `<tr><td colspan="9" style="color:var(--muted);text-align:center;padding:18px;">${NO_DATA_MSG}</td></tr>` : evRows.map(ev => { const timeStr = ev.created_at ? new Date(ev.created_at).toLocaleTimeString('tr-TR', { timeZone: 'Europe/Istanbul' }) : '-'; return ` <tr> <td>${timeStr}</td> <td><span class="badge ${ev.traffic_class==='HUMAN'?'ok':ev.traffic_class==='BOT'?'err':'info'}">${ev.traffic_class}</span></td> <td><b>${ev.event_type}</b></td> <td>${ev.vitrin_name || ev.vitrin_id || '-'}</td> <td>${ev.city || 'İzmir'} / <b>${ev.district || '-'}</b></td> <td>${ev.device_type || '-'} (${ev.os_name || '-'})</td> <td>${ev.traffic_source || '-'}</td> <td>${ev.domain || '-'}</td> <td style="font-size:11px;color:var(--gold2);">${ev.target_phone || ev.target_url || '-'}</td> </tr>`; }).join(''); // TAB 2: Vitrin Bazlı Detaylı Analiz & Sıralamalar document.getElementById('vitrinAnalyticsRows').innerHTML = vList.map(v => { const lastTime = v.last_click_at && v.last_click_at !== NO_DATA_MSG ? new Date(v.last_click_at).toLocaleString('tr-TR', { timeZone: 'Europe/Istanbul' }) : NO_DATA_MSG; return ` <tr style="cursor:pointer;" onclick="openVitrinModal('${v.vitrin_id}')"> <td><b style="color:var(--gold2);">${v.vitrin_name}</b></td> <td>${v.city} / <b>${v.district}</b></td> <td><span class="badge hub">${v.package_name}</span></td> <td><b>${v.views}</b></td> <td><b>${v.unique_visitors}</b></td> <td style="color:var(--green);font-weight:700;">${v.whatsapp_clicks}</td> <td style="color:var(--blue);font-weight:700;">${v.phone_clicks}</td> <td>${v.profile_clicks}</td> <td style="font-size:11px;color:var(--gold2);">${v.last_clicked_phone || v.phone || NO_DATA_MSG}</td> <td>${v.top_city && v.top_city !== NO_DATA_MSG ? v.top_city + ' / ' + (v.top_district || '') : (v.top_district || NO_DATA_MSG)}</td> <td>${v.top_device || NO_DATA_MSG}</td> <td style="font-size:11px;color:var(--muted);">${lastTime}</td> <td><button class="btn" onclick="event.stopPropagation();openVitrinModal('${v.vitrin_id}')">🔍 Detay</button></td> </tr>`; }).join(''); render10Leaderboards(lb); renderDomainHub(STATE.domains); renderGscPanel(STATE.gsc_reports); populateProvincesAndCategories(); renderIzmir30Summary(); renderMembersCrud(STATE.members); renderVitrinsCrud(STATE.tekirdag_models, STATE.escsoft_models); renderPackagesCrud(STATE.packages); renderSeoAndBlogs(STATE.seo_keywords, STATE.blogs); renderDiagnosticsPanel(STATE.latest_system_check, STATE.audit_logs); renderSettingsPanel(STATE.settings); renderCmsHubPanel(STATE.escsoft_models, STATE.members, STATE.packages); } function openVitrinModal(vitrinId) { const v = ((STATE.analytics && STATE.analytics.vitrin_analytics) || []).find(x => x.vitrin_id === vitrinId); if (!v) return; const hasData = v.views > 0 || v.whatsapp_clicks > 0 || v.phone_clicks > 0 || v.profile_clicks > 0; const lastTime = v.last_click_at && v.last_click_at !== NO_DATA_MSG ? new Date(v.last_click_at).toLocaleString('tr-TR', { timeZone: 'Europe/Istanbul' }) : NO_DATA_MSG; const lastVisitTime = v.last_visit_at && v.last_visit_at !== NO_DATA_MSG ? new Date(v.last_visit_at).toLocaleString('tr-TR', { timeZone: 'Europe/Istanbul' }) : lastTime; const body = document.getElementById('vitrinModalBody'); body.innerHTML = ` <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:14px;"> <h2 style="color:var(--gold2);font-size:18px;">📊 VİTRİN ANALİZ EKRANI: ${v.vitrin_name.toUpperCase()}</h2> <button class="btn red" onclick="document.getElementById('vitrinModal').style.display='none'">✖ Kapat</button> </div> <p style="font-size:12px;color:var(--muted);margin-bottom:14px;"> Dönem: <b>${STATE.analytics.filter ? STATE.analytics.filter.label : 'Bugün'}</b> (Gece 00:00 Europe/Istanbul Günlük Sayaç) • Trafik Sınıfı: <b>${STATE.analytics.filter ? STATE.analytics.filter.trafficClass : 'HUMAN'}</b> </p> ${!hasData ? `<div style="background:rgba(255,77,77,0.12);border:1px solid rgba(255,77,77,0.35);color:#ffb3b3;padding:10px 14px;border-radius:8px;margin-bottom:12px;font-weight:700;">${NO_DATA_MSG}</div>` : ''} <table> <tbody> <tr><th>Vitrin Adı</th><td><b>${v.vitrin_name}</b> (${v.city} / ${v.district})</td></tr> <tr><th>Kategori & Paket</th><td><span class="badge hub">${v.package_name}</span> (${v.category})</td></tr> <tr><th>Bugünkü Görüntüleme</th><td><b>${v.views > 0 ? v.views : NO_DATA_MSG}</b> (AMP Görüntüleme: ${v.amp_views || 0})</td></tr> <tr><th>Bugünkü Tekil Ziyaretçi</th><td><b>${v.unique_visitors > 0 ? v.unique_visitors : NO_DATA_MSG}</b></td></tr> <tr><th>Bugünkü WhatsApp Buton Tıklaması</th><td><b style="color:var(--green);">${v.whatsapp_clicks > 0 ? v.whatsapp_clicks : NO_DATA_MSG}</b> (AMP WhatsApp: ${v.amp_whatsapp_clicks || 0})</td></tr> <tr><th>Doğrulanmış WhatsApp Mesaj Gönderimi (Ayrı)</th><td><b>${v.whatsapp_verified_messages > 0 ? v.whatsapp_verified_messages : 'Yetkili WhatsApp API doğrulaması bekleniyor (Buton tıklamasından ayrı tutulur)'}</b></td></tr> <tr><th>Bugünkü Telefon Arama Butonu Tıklaması</th><td><b style="color:var(--blue);">${v.phone_clicks > 0 ? v.phone_clicks : NO_DATA_MSG}</b></td></tr> <tr><th>Doğrulanmış Gerçek Telefon Görüşmesi (Ayrı)</th><td><b>${v.phone_verified_calls > 0 ? v.phone_verified_calls : 'Doğrulanabilir santral entegrasyonu bekleniyor (Buton tıklamasından ayrı tutulur)'}</b></td></tr> <tr><th>Bugünkü Profil Tıklaması</th><td><b>${v.profile_clicks > 0 ? v.profile_clicks : NO_DATA_MSG}</b></td></tr> <tr><th>Tıklanan WhatsApp Numarası</th><td><b style="color:var(--gold2);">${v.clicked_whatsapp_number || v.last_clicked_phone || v.phone || NO_DATA_MSG}</b></td></tr> <tr><th>Tıklanan Telefon Numarası</th><td><b style="color:var(--gold2);">${v.clicked_phone_number || v.last_clicked_phone || v.phone || NO_DATA_MSG}</b></td></tr> <tr><th>En Çok Ziyaretçi Gelen Şehir / İlçe</th><td><b>${v.top_city && v.top_city !== NO_DATA_MSG ? v.top_city + ' / ' + (v.top_district || '') : (v.top_district || NO_DATA_MSG)}</b></td></tr> <tr><th>Son Ziyaret Saati (Gerçek Kayıt)</th><td><b>${lastVisitTime}</b></td></tr> <tr><th>Son İletişim Tıklama Saati</th><td><b>${lastTime}</b></td></tr> <tr><th>En Çok Kullanılan Cihaz</th><td><b>${v.top_device || NO_DATA_MSG}</b></td></tr> <tr><th>Trafik Kaynağı & İlgili Domain</th><td><b>${v.top_source || NO_DATA_MSG}</b> / <b>${v.top_domain || NO_DATA_MSG}</b></td></tr> <tr><th>Dönüşüm Oranı</th><td><b>${v.views > 0 ? '%' + v.conversion_rate : NO_DATA_MSG}</b></td></tr> </tbody> </table> `; document.getElementById('vitrinModal').style.display = 'flex'; } function render10Leaderboards(lb) { const safeLb = lb || {}; const boards = [ { title: '1. En Çok Görüntülenen Vitrinler', rows: (safeLb.by_views || []).filter(x => x.views > 0).map(x => ({ name: x.vitrin_name, val: x.views, vid: x.vitrin_id })) }, { title: '2. En Çok Tekil Ziyaretçi Alan Vitrinler', rows: (safeLb.by_unique_visitors || []).filter(x => x.unique_visitors > 0).map(x => ({ name: x.vitrin_name, val: x.unique_visitors, vid: x.vitrin_id })) }, { title: '3. En Çok WhatsApp Tıklaması Alan Vitrinler', rows: (safeLb.by_whatsapp || []).filter(x => x.whatsapp_clicks > 0).map(x => ({ name: x.vitrin_name, val: x.whatsapp_clicks, vid: x.vitrin_id })) }, { title: '4. En Çok Telefon Tıklaması Alan Vitrinler', rows: (safeLb.by_phone || []).filter(x => x.phone_clicks > 0).map(x => ({ name: x.vitrin_name, val: x.phone_clicks, vid: x.vitrin_id })) }, { title: '5. En Çok Profil Tıklaması Alan Vitrinler', rows: (safeLb.by_profile || []).filter(x => x.profile_clicks > 0).map(x => ({ name: x.vitrin_name, val: x.profile_clicks, vid: x.vitrin_id })) }, { title: '6. En Çok Tıklanan Bağlantılar', rows: (safeLb.top_links || []).map(x => ({ name: x.label, val: x.count })) }, { title: '7. En Çok Ziyaret Edilen Şehirler & İlçeler (81 İl)', rows: (safeLb.top_cities || safeLb.top_izmir_districts || []).map(x => ({ name: x.label, val: x.count })) }, { title: '8. En Çok Ziyaretçi Gönderen Domainler', rows: (safeLb.top_domains || []).map(x => ({ name: x.label, val: x.count })) }, { title: '9. En Çok Organik Trafik Alan Sayfalar', rows: (safeLb.top_organic_pages || []).map(x => ({ name: x.label, val: x.count })) }, { title: '10. En Yüksek Dönüşüm Oranına Sahip Vitrinler', rows: (safeLb.by_conversion || []).filter(x => x.conversion_rate > 0).map(x => ({ name: x.vitrin_name, val: '%' + x.conversion_rate, vid: x.vitrin_id })) } ]; document.getElementById('leaderboards10Grid').innerHTML = boards.map(b => ` <div class="box"> <h3>${b.title}</h3> ${b.rows.length === 0 ? `<div style="color:var(--muted);font-size:12px;">${NO_DATA_MSG}</div>` : `<table><tbody>${b.rows.slice(0, 6).map((r, i) => ` <tr ${r.vid ? `style="cursor:pointer;" onclick="openVitrinModal('${r.vid}')"` : ''}> <td style="width:28px;color:var(--gold2);font-weight:700;">#${i+1}</td> <td>${r.name}</td> <td style="text-align:right;font-weight:700;color:var(--green);">${r.val}</td> </tr> `).join('')}</tbody></table>` } </div> `).join(''); } function renderDomainHub(domains) { document.getElementById('domainHubRows').innerHTML = (domains || []).map(d => { const isFedai = d.btk_status === 'SHIELD_HUB'; const isRedir = d.btk_status === 'BLOCKED' && d.target_domain && d.target_domain !== d.domain; const statusBadge = isFedai ? `<span class="badge hub">🛡️ FEDAİ KALKAN DOMAİNİ</span>` : (isRedir ? `<span class="badge warn">🔄 307 YÖNLENDİRİLDİ</span>` : `<span class="badge ok">🟢 200 AÇILIR ANA SİTE</span>`); const targetBadge = isFedai ? `<a href="/domains/${d.target_domain}" target="_blank" style="color:#38bdf8;font-weight:700;">🛡️ Korunan Hub: https://${d.target_domain}/</a>` : (isRedir ? `<a href="/domains/${d.target_domain}" target="_blank" style="color:var(--green);font-weight:700;">➔ https://${d.target_domain}/</a>` : `<span class="badge ok">DOĞRUDAN AÇIK (https://${d.domain}/)</span>`); return `<tr> <td><a href="/domains/${d.domain}" target="_blank" style="color:var(--gold2);font-weight:700;" title="${isRedir ? 'Tıklanınca 307 ile ' + d.target_domain + ' açılır sitesine yönlendirir' : 'Doğrudan açılır site'}">https://${d.domain}/</a></td> <td>${d.city || 'Türkiye Geneli'}</td> <td>${statusBadge}</td> <td>${targetBadge}</td> <td><span class="badge ${isFedai?'hub':(isRedir?'info':'ok')}">${d.verified_btk_decision || 'AÇILIR SİTE AKTİF'}</span></td> <td style="font-size:11px;">SSL: ${d.ssl_status || 'GEÇERLİ'}<br>DNS: ${d.dns_status || 'AKTİF'}</td> <td><b>${d.real_visitors || 0}</b></td> <td><b>${d.vitrin_views || d.page_views || 0}</b></td> <td><span style="color:var(--green);font-weight:700;">💬 ${d.whatsapp_clicks || 0}</span> / <span style="color:var(--blue);font-weight:700;">📞 ${d.phone_clicks || 0}</span></td> <td style="font-size:11px;">Mobil: <b>%${d.mobile_ratio || 0}</b><br>Dönüşüm: <b>%${d.conversion_rate || 0}</b></td> <td style="font-size:11px;color:var(--muted);">${d.last_checked ? new Date(d.last_checked).toLocaleString('tr-TR', { timeZone: 'Europe/Istanbul' }) : '-'}</td> <td> <a class="btn" href="/domains/${d.domain}" target="_blank">🌐 ${isRedir ? 'Yönlendir & Aç' : 'Siteyi Aç'}</a> </td> </tr>`; }).join(''); } async function bulkRedirectBlockedNow() { await fetch('/api/admin/btk-domain', { method: 'POST', headers: {'Content-Type':'application/json'}, body: JSON.stringify({ action: 'bulk_redirect_blocked' }) }); showToast('33 Fedai Kalkan Domaini oluşturuldu ve 114 BTK Engelli Domain 11 Açılır Siteye yönlendirildi!'); loadOverview(); } async function checkDomainHubNow() { await fetch('/api/admin/domain-hub/check', { method: 'POST', headers: {'Content-Type':'application/json'}, body: '{}' }); showToast('125 Domainin SSL, DNS, HTTP ve Türkiye erişim durumu güncellendi!'); loadOverview(); } async function disableAllRedirectsNow() { await fetch('/api/admin/btk-domain', { method: 'POST', headers: {'Content-Type':'application/json'}, body: JSON.stringify({ action: 'disable_all_redirects' }) }); showToast('Tüm domain yönlendirmeleri devre dışı bırakıldı — her domain doğrudan 200 OK açılır!'); loadOverview(); } async function addNewDomainToHub() { const domain = document.getElementById('newHubDomain').value.trim(); const city = document.getElementById('newHubCity').value.trim() || 'İstanbul'; if (!domain) return alert('Lütfen domain adı girin'); await fetch('/api/admin/btk-domain', { method: 'POST', headers: {'Content-Type':'application/json'}, body: JSON.stringify({ action: 'add_domain', domain, city }) }); document.getElementById('newHubDomain').value = ''; showToast(`${domain} Merkezi Domain Hub'a yönlendirmesiz olarak eklendi!`); loadOverview(); } async function queryGscNow() { const domain = document.getElementById('gscTargetDomain').value.trim() || 'Merkezi Domain Hub'; const res = await fetch('/api/admin/gsc/query', { method: 'POST', headers: {'Content-Type':'application/json'}, body: JSON.stringify({ domain }) }); const data = await res.json(); const idxCount = data.indexnow ? data.indexnow.total_urls_submitted : 232; showToast(`✅ GSC & IndexNow tamamlandı (${idxCount} URL + Sitemap anlık bildirildi): ${data.report.queried_at || 'Başarılı'}`); loadOverview(); } function renderGscPanel(reports) { const el = document.getElementById('gscReportContainer'); const latest = reports && reports[0] ? reports[0] : null; if (!latest) { el.innerHTML = `<div style="padding:14px;color:var(--muted);">Henüz sorgulama yapılmadı. Yukarıdaki <b>"GSC VERİLERİNİ ŞİMDİ SORGULA"</b> butonuna basın.</div>`; return; } const d = latest.data || {}; const m = d.metrics || {}; const na = NO_DATA_MSG + ' (OAuth2 Service Account bekliyor)'; el.innerHTML = ` <div style="background:#0b0e17;border:1px solid var(--border);border-radius:10px;padding:14px;margin-bottom:12px;"> <div><b>Son Başarılı Veri Çekme / Sorgulama Zamanı:</b> <span style="color:var(--gold2);">${latest.fetched_at || d.queried_at}</span></div> <div style="margin-top:4px;"><b>Sorgulanan Domain:</b> ${latest.domain}</div> <div style="margin-top:4px;"><b>Durum:</b> <span class="badge info">${latest.fetch_status}</span> — ${d.auth_status || ''}</div> <div style="margin-top:6px;font-size:11.5px;color:var(--muted);">${d.note || ''}</div> </div> <table> <thead><tr><th>#</th><th>Google Search Console Metriği (Bölüm 4 — 13 Metrik)</th><th>Gerçek GSC API Değeri</th></tr></thead> <tbody> <tr><td>1</td><td>Google Organik Tıklama Sayısı</td><td><b>${m.organic_clicks !== null && m.organic_clicks !== undefined ? m.organic_clicks : na}</b></td></tr> <tr><td>2</td><td>Google Arama Gösterim Sayısı</td><td><b>${m.impressions !== null && m.impressions !== undefined ? m.impressions : na}</b></td></tr> <tr><td>3</td><td>Ortalama CTR (%)</td><td><b>${m.ctr !== null && m.ctr !== undefined ? '%' + m.ctr : na}</b></td></tr> <tr><td>4</td><td>Ortalama Arama Konumu</td><td><b>${m.avg_position !== null && m.avg_position !== undefined ? m.avg_position : na}</b></td></tr> <tr><td>5</td><td>En Çok Tıklama Alan Anahtar Kelimeler</td><td>${(d.top_click_keywords||[]).length ? d.top_click_keywords.join(', ') : na}</td></tr> <tr><td>6</td><td>En Fazla Gösterim Alan Anahtar Kelimeler</td><td>${(d.top_impression_keywords||[]).length ? d.top_impression_keywords.join(', ') : na}</td></tr> <tr><td>7</td><td>En Çok Trafik Alan Sayfalar</td><td>${(d.top_pages||[]).length ? d.top_pages.join(', ') : na}</td></tr> <tr><td>8</td><td>Mobil Arama Performansı</td><td>${m.mobile_clicks !== null && m.mobile_clicks !== undefined ? m.mobile_clicks : na}</td></tr> <tr><td>9</td><td>Masaüstü Arama Performansı</td><td>${m.desktop_clicks !== null && m.desktop_clicks !== undefined ? m.desktop_clicks : na}</td></tr> <tr><td>10</td><td>Ülke Bazlı Organik Performans</td><td>${(d.countries||[]).length ? d.countries.join(', ') : na}</td></tr> <tr><td>11</td><td>Domain Bazlı Performans</td><td><b>${latest.domain}</b></td></tr> <tr><td>12</td><td>Günlük Sıralama Değişimleri</td><td>${(d.daily_changes||[]).length ? d.daily_changes.join(', ') : NO_DATA_MSG}</td></tr> <tr><td>13</td><td>Önceki Dönemle Karşılaştırma</td><td>${d.period_comparison || na}</td></tr> </tbody> </table> `; } function populateProvincesAndCategories() { const rawProvinces = Array.isArray(STATE.turkiye_81_il) ? STATE.turkiye_81_il : []; const cities = ['İzmir', ...rawProvinces.map(p => p.il).filter(il => il !== 'İzmir')]; const cityOpts = cities.map(c => `<option value="${c}">${c}</option>`).join(''); ['memCity', 'tekCity', 'kwCity', 'blogCity', 'locProvince'].forEach(id => { const el = document.getElementById(id); if (el && !el.innerHTML) el.innerHTML = cityOpts; }); renderCustomLocations(STATE.custom_locations || []); const pkgOpts = (STATE.packages || []).map(p => `<option value="${p.name}">${p.name} (${p.price_tl} TL)</option>`).join(''); ['memPackage', 'tekPackage', 'escPackage'].forEach(id => { const el = document.getElementById(id); if (el && !el.innerHTML) el.innerHTML = pkgOpts; }); const cats = STATE.izmir_seo_categories_26 || STATE.seo_categories_28 || []; const catOpts = cats.map(c => `<option value="${c}">${c}</option>`).join(''); ['kwCategory', 'blogCategory'].forEach(id => { const el = document.getElementById(id); if (el && !el.innerHTML) el.innerHTML = catOpts; }); } function renderIzmir30Summary() { const ilceler = STATE.izmir_30_ilceler || []; const mahMap = STATE.izmir_mahalleler || {}; const totalMah = Object.values(mahMap).reduce((acc, arr) => acc + (arr ? arr.length : 0), 0); const provCount = Array.isArray(STATE.turkiye_81_il) ? STATE.turkiye_81_il.length : 81; const st = STATE.seo_stats || STATE.izmir_seo_summary || {}; const box = document.getElementById('izmir30SummaryBox'); if (box) { box.innerHTML = ` <span class="badge ok">🇹🇷 Türkiye ${provCount} İl + KKTC (A–Z) Aktif</span> <span class="badge hub">🔑 Toplam SEO Kelimesi: ${(st.total_keywords || 0).toLocaleString('tr-TR')}</span> <span class="badge info">👑 Escort Siteleri Kelimesi: ${(st.escort_keywords_count || 0).toLocaleString('tr-TR')}</span> <span class="badge info">💆‍♀️ Masaj Salonu & Masaj Sitesi Kelimesi: ${(st.masaj_keywords_count || 0).toLocaleString('tr-TR')}</span> <span class="badge ok">📚 Toplam A–Z SEO Blog: ${(st.total_blogs || 0).toLocaleString('tr-TR')} (${st.escort_blogs_count || 0} Escort + ${st.masaj_blogs_count || 0} Masaj/Spa)</span> <span class="badge hub">İzmir 30 İlçe & ${totalMah} Mahalle + 10 Dil Hreflang Aktif</span> `; } const cityFilterEl = document.getElementById('adminSeoFilterCity'); if (cityFilterEl && cityFilterEl.options.length <= 1 && Array.isArray(STATE.turkiye_81_il)) { const azProvs = [...STATE.turkiye_81_il, { plaka: '82', il: 'KKTC Kıbrıs' }].sort((a, b) => a.il.localeCompare(b.il, 'tr')); cityFilterEl.innerHTML = `<option value="ALL">🇹🇷 Tüm Türkiye 81 İl + KKTC (A–Z)</option>` + azProvs.map(p => `<option value="${p.il}">${p.il} (${p.plaka})</option>`).join(''); } const sel = document.getElementById('adminIzmirDistInspect'); if (sel && !sel.innerHTML && ilceler.length > 0) { sel.innerHTML = `<option value="ALL">Türkiye 81 İl & İzmir 30 İlçe Özeti</option>` + ilceler.map((d, i) => `<option value="${d}">${i + 1}. İzmir / ${d} (${(mahMap[d] || []).length} Mahalle)</option>`).join(''); renderIzmirNeighborhoodsPreview('ALL'); } } async function filterAdminSeoAndBlogs() { const city = (document.getElementById('adminSeoFilterCity') && document.getElementById('adminSeoFilterCity').value) || 'ALL'; const category = (document.getElementById('adminSeoFilterCat') && document.getElementById('adminSeoFilterCat').value) || 'ALL'; const q = (document.getElementById('adminSeoSearchInput') && document.getElementById('adminSeoSearchInput').value.trim()) || ''; const qs = new URLSearchParams({ city, category, q }); const res = await fetch('/api/admin/seo-filter?' + qs.toString()); const data = await res.json(); if (data.ok) { renderSeoAndBlogs(data.keywords, data.blogs, true); } } function renderIzmirNeighborhoodsPreview(dist) { const mahMap = STATE.izmir_mahalleler || {}; const wrap = document.getElementById('izmirNeighborhoodsBadges'); if (!wrap) return; if (!dist || dist === 'ALL') { const ilceler = STATE.izmir_30_ilceler || []; wrap.innerHTML = ilceler.map(ilce => `<span class="badge hub" style="cursor:pointer;" onclick="document.getElementById('adminIzmirDistInspect').value='${ilce}';renderIzmirNeighborhoodsPreview('${ilce}')">📍 ${ilce} (${(mahMap[ilce]||[]).length} Mahalle)</span>` ).join(''); return; } const list = mahMap[dist] || []; wrap.innerHTML = `<span class="badge ok">İzmir / ${dist}:</span>` + list.map(m => `<span class="badge info">🏘️ ${m}</span>`).join(''); } async function runIzmirDailySeoNow() { const res = await fetch('/api/admin/izmir-seo/run', { method: 'POST', headers: {'Content-Type':'application/json'}, body: '{}' }); const data = await res.json(); if (data.ok && data.summary) { showToast(`Türkiye 81 İl & İlçe SEO Motoru Çalıştı: ${data.summary.total_keywords || data.summary.total_izmir_keywords} Kelime & ${data.summary.total_blogs || data.summary.total_izmir_blogs} Blog Aktif!`); loadOverview(); } } function renderMembersCrud(members) { document.getElementById('membersTableRows').innerHTML = (members || []).map(m => ` <tr> <td>#${m.id}</td> <td><b style="color:var(--gold2);">${m.name}</b></td> <td>${m.phone}</td> <td>${m.city} / ${m.district}</td> <td><span class="badge hub">${m.package_name}</span></td> <td><span class="badge ${m.status==='APPROVED'?'ok':'err'}">${m.status}</span></td> <td style="font-size:11px;">${m.created_at ? new Date(m.created_at).toLocaleDateString('tr-TR') : '-'}</td> <td style="display:flex;gap:4px;flex-wrap:wrap;"> <button class="btn green" onclick="setMemberStatus(${m.id},'APPROVED')" title="Üyeyi Onayla">✓ Onayla</button> <button class="btn" onclick="setMemberStatus(${m.id},'SUSPENDED')" title="Askıya Al / Pasif">⏸️ Pasif</button> <button class="btn red" onclick="setMemberStatus(${m.id},'REJECTED')" title="Üyeyi Reddet">✖ Reddet</button> <button class="btn" onclick='editMember(${JSON.stringify(m).replace(/'/g,"'")})'>✏️ Düzenle</button> <button class="btn red" onclick="deleteMember(${m.id})">🗑️ Sil</button> </td> </tr> `).join(''); } async function setMemberStatus(id, status) { await fetch('/api/admin/members', { method: 'POST', headers: {'Content-Type':'application/json'}, body: JSON.stringify({ action: 'set_status', id, status }) }); showToast(`Üye durumu güncellendi: ${status}`); loadOverview(); } function editMember(m) { document.getElementById('memId').value = m.id; document.getElementById('memName').value = m.name; document.getElementById('memPhone').value = m.phone; document.getElementById('memEmail').value = m.email || ''; document.getElementById('memCity').value = m.city || 'İzmir'; document.getElementById('memDistrict').value = m.district || ''; document.getElementById('memPackage').value = m.package_name || ''; document.getElementById('memStatus').value = m.status || 'APPROVED'; document.getElementById('memNotes').value = m.notes || ''; } function resetMemberForm() { ['memId','memName','memPhone','memEmail','memDistrict','memNotes'].forEach(id => document.getElementById(id).value = ''); } async function saveMember() { const payload = { id: document.getElementById('memId').value || undefined, name: document.getElementById('memName').value.trim(), phone: document.getElementById('memPhone').value.trim(), email: document.getElementById('memEmail').value.trim(), city: document.getElementById('memCity').value, district: document.getElementById('memDistrict').value.trim(), package_name: document.getElementById('memPackage').value, status: document.getElementById('memStatus').value, notes: document.getElementById('memNotes').value.trim() }; if (!payload.name) return alert('Üye adı zorunludur.'); await fetch('/api/admin/members', { method: 'POST', headers: {'Content-Type':'application/json'}, body: JSON.stringify(payload) }); resetMemberForm(); showToast('Üye bilgileri kaydedildi!'); loadOverview(); } async function deleteMember(id) { if (!confirm('Bu üyeyi silmek istediğinize emin misiniz?')) return; await fetch('/api/admin/members', { method: 'POST', headers: {'Content-Type':'application/json'}, body: JSON.stringify({ action: 'delete', id }) }); showToast('Üye silindi.'); loadOverview(); } function renderVitrinsCrud(tekModels, escModels) { document.getElementById('escCrudRows').innerHTML = (escModels || []).map(m => ` <tr> <td>#${m.id}</td> <td><img src="${m.thumb_url || (m.photos && m.photos[0]) || '/f/gunes_girne_1.webp'}" style="width:42px;height:42px;object-fit:cover;border-radius:6px;"></td> <td><b style="color:var(--gold2);">${m.name}</b> (${m.age})</td> <td>${m.city} / <b>${m.district}</b></td> <td><span class="badge hub">${m.package_name || 'VIP Vitrin'}</span></td> <td>${m.phone}</td> <td style="font-size:11px;">📷 ${(m.photos || []).length} Foto / 🎬 ${(m.videos || []).length} Video</td> <td><span class="badge ${m.active!==0?'ok':'err'}">${m.active!==0?'AKTİF':'PASİF'}</span> ${m.badge || ''}</td> <td style="display:flex;gap:4px;flex-wrap:wrap;"> <button class="btn" onclick='editEscModel(${JSON.stringify(m).replace(/'/g,"'")})'>✏️ Düzenle</button> <button class="btn blue" onclick="toggleEscModelActive(${m.id})">🔄 Aktif/Pasif</button> <a class="btn" href="/profile/${m.slug}" target="_blank">👁️ Profil</a> <button class="btn red" onclick="deleteEscModel(${m.id})">🗑️ Sil</button> </td> </tr> `).join(''); document.getElementById('tekCrudRows').innerHTML = (tekModels || []).map(m => ` <tr> <td>#${m.sort_order}</td> <td><img src="${(m.photos&&m.photos[0])||'/f/75c5fce4ba4450.jpg'}" style="width:42px;height:42px;object-fit:cover;border-radius:6px;"></td> <td><b style="color:var(--gold2);">${m.name}</b> (${m.age})</td> <td>${m.city || 'Tekirdağ'} / ${m.district}</td> <td><span class="badge hub">${m.package_name || 'VIP Vitrin'}</span></td> <td>${m.phone}</td> <td><b style="color:var(--green);">${m.clicks || 0}</b></td> <td><span class="badge ${m.active?'ok':'err'}">${m.active?'AKTİF':'PASİF'}</span></td> <td style="display:flex;gap:4px;flex-wrap:wrap;"> <button class="btn" onclick='editTekModel(${JSON.stringify(m).replace(/'/g,"'")})'>✏️ Düzenle</button> <button class="btn blue" onclick="toggleTekModelActive(${m.id})">🔄 Aktif/Pasif</button> <button class="btn red" onclick="deleteTekModel(${m.id})">🗑️ Sil</button> </td> </tr> `).join(''); } async function toggleEscModelActive(id) { await fetch('/api/admin/escsoft-model', { method: 'POST', headers: {'Content-Type':'application/json'}, body: JSON.stringify({ action: 'toggle_active', id }) }); showToast('Vitrin aktif/pasif durumu değiştirildi!'); loadOverview(); } async function deleteEscModel(id) { if (!confirm('Bu vitrini silmek istediğinize emin misiniz?')) return; await fetch('/api/admin/escsoft-model', { method: 'POST', headers: {'Content-Type':'application/json'}, body: JSON.stringify({ action: 'delete', id }) }); showToast('Vitrin silindi.'); loadOverview(); } async function toggleTekModelActive(id) { await fetch('/api/admin/tekirdag-model', { method: 'POST', headers: {'Content-Type':'application/json'}, body: JSON.stringify({ action: 'toggle_active', id }) }); showToast('Vitrin aktif/pasif durumu değiştirildi!'); loadOverview(); } async function deleteTekModel(id) { if (!confirm('Bu vitrini silmek istediğinize emin misiniz?')) return; await fetch('/api/admin/tekirdag-model', { method: 'POST', headers: {'Content-Type':'application/json'}, body: JSON.stringify({ action: 'delete', id }) }); showToast('Vitrin silindi.'); loadOverview(); } function editEscModel(m) { document.getElementById('escId').value = m.id; document.getElementById('escName').value = m.name || ''; document.getElementById('escAge').value = m.age || 22; document.getElementById('escCity').value = m.city || 'İzmir'; document.getElementById('escDistrict').value = m.district || ''; document.getElementById('escPackage').value = m.package_name || ''; document.getElementById('escBadge').value = m.badge || ''; document.getElementById('escOrder').value = m.sort_order || 1; document.getElementById('escPhone').value = m.phone || ''; document.getElementById('escPhotos').value = (m.photos || [m.thumb_url]).filter(Boolean).join(', '); document.getElementById('escVideos').value = (m.videos || []).join(', '); document.getElementById('escActive').value = m.active !== undefined ? String(m.active) : '1'; document.getElementById('escDesc').value = m.description || ''; } function resetEscForm() { ['escId','escName','escDistrict','escPhotos','escVideos','escDesc'].forEach(id => document.getElementById(id).value = ''); } async function saveEscsoftModel() { const payload = { id: document.getElementById('escId').value || undefined, name: document.getElementById('escName').value.trim(), age: document.getElementById('escAge').value, city: document.getElementById('escCity').value.trim() || 'İzmir', district: document.getElementById('escDistrict').value.trim() || 'Alsancak', package_name: document.getElementById('escPackage').value, badge: document.getElementById('escBadge').value.trim(), sort_order: document.getElementById('escOrder').value, phone: document.getElementById('escPhone').value.trim(), photos: document.getElementById('escPhotos').value.split(',').map(s => s.trim()).filter(Boolean), videos: document.getElementById('escVideos').value.split(',').map(s => s.trim()).filter(Boolean), active: parseInt(document.getElementById('escActive').value, 10), description: document.getElementById('escDesc').value.trim() }; if (!payload.name) return alert('Vitrin adı zorunludur.'); await fetch('/api/admin/escsoft-model', { method: 'POST', headers: {'Content-Type':'application/json'}, body: JSON.stringify(payload) }); resetEscForm(); showToast('İzmir / Diamond VIP Vitrin kaydedildi!'); loadOverview(); } function editTekModel(m) { document.getElementById('tekId').value = m.id; document.getElementById('tekName').value = m.name; document.getElementById('tekAge').value = m.age; document.getElementById('tekCity').value = m.city || 'Tekirdağ'; document.getElementById('tekDistrict').value = m.district; document.getElementById('tekMeeting').value = m.meeting_type; document.getElementById('tekBadge').value = m.badge || '⭐ VIP'; document.getElementById('tekPackage').value = m.package_name || 'Platinum VIP Vitrin Paketi'; document.getElementById('tekOrder').value = m.sort_order || 1; document.getElementById('tekPhone').value = m.phone; document.getElementById('tekWa').value = m.whatsapp_url; document.getElementById('tekPhotos').value = (m.photos || []).join(', '); document.getElementById('tekVideos').value = (m.videos || []).join(', '); } function resetTekForm() { document.getElementById('tekId').value = ''; document.getElementById('tekName').value = ''; document.getElementById('tekVideos').value = ''; } async function saveTekirdagModel() { const payload = { id: document.getElementById('tekId').value || undefined, name: document.getElementById('tekName').value.trim(), age: document.getElementById('tekAge').value, city: document.getElementById('tekCity').value, district: document.getElementById('tekDistrict').value.trim(), meeting_type: document.getElementById('tekMeeting').value.trim(), badge: document.getElementById('tekBadge').value.trim(), package_name: document.getElementById('tekPackage').value, sort_order: document.getElementById('tekOrder').value, phone: document.getElementById('tekPhone').value.trim(), whatsapp_url: document.getElementById('tekWa').value.trim(), photos: document.getElementById('tekPhotos').value.split(',').map(s => s.trim()).filter(Boolean), videos: document.getElementById('tekVideos').value.split(',').map(s => s.trim()).filter(Boolean) }; if (!payload.name) return alert('Vitrin adı zorunludur.'); await fetch('/api/admin/tekirdag-model', { method: 'POST', headers: {'Content-Type':'application/json'}, body: JSON.stringify(payload) }); resetTekForm(); showToast('Vitrin güncellendi!'); loadOverview(); } function renderPackagesCrud(packages) { document.getElementById('packagesTableRows').innerHTML = (packages || []).map(p => ` <tr> <td>#${p.priority_rank}</td> <td><code>${p.code}</code></td> <td><b style="color:var(--gold2);">${p.name}</b></td> <td><b>${p.price_tl.toLocaleString('tr-TR')} TL</b></td> <td>${p.duration_days} Gün</td> <td><span class="badge hub">${p.badge}</span></td> <td style="font-size:11.5px;">${(p.features || []).join(' • ')}</td> <td><button class="btn" onclick='editPackage(${JSON.stringify(p).replace(/'/g,"'")})'>✏️ Düzenle</button></td> </tr> `).join(''); } function editPackage(p) { document.getElementById('pkgId').value = p.id; document.getElementById('pkgName').value = p.name; document.getElementById('pkgPrice').value = p.price_tl; document.getElementById('pkgDays').value = p.duration_days; document.getElementById('pkgBadge').value = p.badge; document.getElementById('pkgRank').value = p.priority_rank; document.getElementById('pkgFeatures').value = (p.features || []).join(', '); } async function savePackage() { const payload = { id: document.getElementById('pkgId').value || undefined, name: document.getElementById('pkgName').value.trim(), price_tl: document.getElementById('pkgPrice').value, duration_days: document.getElementById('pkgDays').value, badge: document.getElementById('pkgBadge').value.trim(), priority_rank: document.getElementById('pkgRank').value, features: document.getElementById('pkgFeatures').value.split(',').map(s => s.trim()).filter(Boolean) }; if (!payload.name) return alert('Paket adı girin'); await fetch('/api/admin/packages', { method: 'POST', headers: {'Content-Type':'application/json'}, body: JSON.stringify(payload) }); document.getElementById('pkgId').value = ''; showToast('Paket kaydedildi!'); loadOverview(); } function renderSeoAndBlogs(keywords, blogs, isFiltered = false) { const st = (STATE && STATE.seo_stats) || {}; document.getElementById('kwCountLbl').textContent = isFiltered ? `${(keywords || []).length} (Toplam: ${(st.total_keywords || 0).toLocaleString('tr-TR')})` : ((st.total_keywords || (keywords || []).length).toLocaleString('tr-TR')); const bLbl = document.getElementById('blogCountLbl'); if (bLbl) { bLbl.textContent = isFiltered ? `${(blogs || []).length} (Toplam: ${(st.total_blogs || 0).toLocaleString('tr-TR')})` : ((st.total_blogs || (blogs || []).length).toLocaleString('tr-TR')); } document.getElementById('seoKeywordsRows').innerHTML = (keywords || []).map(k => ` <tr> <td><a href="${k.target_url || '/turkiye-seo-rehberi'}" target="_blank" style="color:var(--gold2);font-weight:700;text-decoration:none;">${k.keyword}</a></td> <td>${k.city} / <b>${k.district}</b></td> <td><span class="badge hub">${k.category}</span></td> <td><span class="badge info">${k.lang}</span></td> <td>${k.target_domain}</td> <td><button class="btn red" onclick="deleteSeoKeyword(${k.id})">Sil</button></td> </tr> `).join(''); document.getElementById('blogsRows').innerHTML = (blogs || []).map(b => ` <tr> <td><a href="/blog/${b.slug}" target="_blank" style="color:var(--gold2);font-weight:700;text-decoration:none;">${b.title}</a></td> <td>${b.city} / <b>${b.district}</b></td> <td><span class="badge info">${b.lang}</span></td> <td><span class="badge ok">${b.status}</span></td> <td style="display:flex;gap:4px;"> <a class="btn blue" href="/blog/${b.slug}" target="_blank" style="text-decoration:none;">👁️ Oku</a> <button class="btn red" onclick="deleteBlog(${b.id})">Sil</button> </td> </tr> `).join(''); } async function saveSeoKeyword() { const payload = { keyword: document.getElementById('kwText').value.trim(), city: document.getElementById('kwCity').value || 'İzmir', district: document.getElementById('kwDistrict').value.trim() || 'Alsancak', category: document.getElementById('kwCategory').value, lang: document.getElementById('kwLang').value, target_domain: document.getElementById('kwDomain').value.trim() }; if (!payload.keyword) return alert('Anahtar kelime girin'); const res = await fetch('/api/admin/seo-keywords', { method: 'POST', headers: {'Content-Type':'application/json'}, body: JSON.stringify(payload) }); const out = await res.json(); if (!out.ok) return alert(out.error || 'Hata'); document.getElementById('kwText').value = ''; showToast('İzmir SEO anahtar kelimesi eklendi!'); loadOverview(); } async function deleteSeoKeyword(id) { await fetch('/api/admin/seo-keywords', { method: 'POST', headers: {'Content-Type':'application/json'}, body: JSON.stringify({ action: 'delete', id }) }); loadOverview(); } async function saveBlog() { const payload = { title: document.getElementById('blogTitle').value.trim(), city: document.getElementById('blogCity').value || 'İzmir', district: document.getElementById('blogDistrict').value.trim() || 'Alsancak', category: document.getElementById('blogCategory').value, lang: document.getElementById('blogLang').value, meta_description: document.getElementById('blogMeta').value.trim(), content_html: document.getElementById('blogContent').value.trim() }; if (!payload.title) return alert('Blog başlığı girin'); const res = await fetch('/api/admin/blogs', { method: 'POST', headers: {'Content-Type':'application/json'}, body: JSON.stringify(payload) }); const out = await res.json(); if (!out.ok) return alert(out.error || 'Hata'); document.getElementById('blogTitle').value = ''; showToast('İzmir yerel SEO blog yazısı yayınlandı!'); loadOverview(); } async function deleteBlog(id) { await fetch('/api/admin/blogs', { method: 'POST', headers: {'Content-Type':'application/json'}, body: JSON.stringify({ action: 'delete', id }) }); loadOverview(); } async function run24ChecksNow() { const res = await fetch('/api/admin/diagnostics/run', { method: 'POST', headers: {'Content-Type':'application/json'}, body: '{}' }); const out = await res.json(); showToast(`24 Sistem Kontrolü Tamamlandı: ${out.report.passed_checks}/${out.report.total_checks} Başarılı!`); loadOverview(); } function renderCustomLocations(locs) { const el = document.getElementById('customLocationsRows'); if (!el) return; if (!locs || locs.length === 0) { el.innerHTML = `<tr><td colspan="7" style="color:var(--muted);text-align:center;">81 İl ve tüm standart ilçeler sistemde yerleşik olarak aktiftir. Ek özel ilçe/mahalle kaydı ekleyebilirsiniz.</td></tr>`; return; } el.innerHTML = locs.map(l => ` <tr> <td>#${l.id}</td> <td><b style="color:var(--gold2);">${l.province}</b></td> <td><b>${l.district}</b></td> <td>${l.neighborhood || 'Merkez'}</td> <td><span class="badge ok">AKTİF</span></td> <td style="font-size:11px;color:var(--muted);">${l.created_at ? new Date(l.created_at).toLocaleString('tr-TR', { timeZone: 'Europe/Istanbul' }) : '-'}</td> <td><button class="btn red" onclick="deleteCustomLocation(${l.id})">🗑️ Sil</button></td> </tr> `).join(''); } async function saveCustomLocation() { const province = document.getElementById('locProvince').value || 'İzmir'; const district = document.getElementById('locDistrict').value.trim(); const neighborhood = document.getElementById('locNeighborhood').value.trim(); if (!district) return alert('Lütfen ilçe adı girin.'); await fetch('/api/admin/locations', { method: 'POST', headers: {'Content-Type':'application/json'}, body: JSON.stringify({ province, district, neighborhood }) }); document.getElementById('locDistrict').value = ''; document.getElementById('locNeighborhood').value = ''; showToast(`${province} / ${district} (${neighborhood || 'Merkez'}) başarıyla eklendi!`); loadOverview(); } async function deleteCustomLocation(id) { await fetch('/api/admin/locations', { method: 'POST', headers: {'Content-Type':'application/json'}, body: JSON.stringify({ action: 'delete', id }) }); showToast('Konum kaydı silindi.'); loadOverview(); } function renderDiagnosticsPanel(check, auditLogs) { if (check) { document.getElementById('diagnosticsSummary').innerHTML = ` <span class="badge ok">Son Kontrol: ${check.check_date}</span> <span class="badge info">Başarılı: ${check.passed_checks} / ${check.total_checks}</span> <span class="badge hub">Otomatik Onarılan: ${check.repaired_count}</span> `; document.getElementById('diagnosticsRows').innerHTML = (check.checks || []).map(c => ` <tr> <td><b>#${c.id}</b></td> <td><b>${c.name}</b></td> <td><span class="badge ${c.status==='BAŞARILI'?'ok':'err'}">${c.status}</span></td> <td>${c.detail}</td> </tr> `).join(''); } document.getElementById('auditLogsRows').innerHTML = (auditLogs || []).map(l => ` <tr> <td style="font-size:11px;color:var(--muted);">${new Date(l.created_at).toLocaleString('tr-TR', { timeZone: 'Europe/Istanbul' })}</td> <td><span class="badge info">${l.action}</span></td> <td><b>${l.target}</b></td> <td>${l.details}</td> </tr> `).join(''); const srvEl = document.getElementById('serverLogsRows'); if (srvEl) { const sLogs = (STATE && STATE.server_logs) || []; srvEl.innerHTML = sLogs.map(s => ` <tr> <td style="font-size:11px;color:var(--muted);">${s.created_at ? new Date(s.created_at).toLocaleTimeString('tr-TR', { timeZone: 'Europe/Istanbul' }) : '-'}</td> <td><span class="badge ${s.traffic_class==='HUMAN'?'ok':s.traffic_class==='BOT'?'err':'info'}">${s.traffic_class}</span></td> <td><code>${s.method} ${s.path}</code></td> <td><span class="badge ${s.status_code < 400 ? 'ok' : 'err'}">${s.status_code}</span></td> <td>${s.domain || '-'}</td> </tr> `).join(''); } } function renderSettingsPanel(s) { if (!s) return; ['admin_username','admin_password','site_title','primary_whatsapp_1','primary_whatsapp_2','primary_whatsapp_3','active_clean_domain','amp_shield_hub','ga_measurement_id','gsc_service_account_json'].forEach(k => { const el = document.getElementById('set_' + k); if (el) el.value = s[k] || ''; }); } async function saveSettings() { const body = {}; ['admin_username','admin_password','site_title','primary_whatsapp_1','primary_whatsapp_2','primary_whatsapp_3','active_clean_domain','amp_shield_hub','ga_measurement_id','gsc_service_account_json'].forEach(k => { const el = document.getElementById('set_' + k); if (el) body[k] = el.value.trim(); }); await fetch('/api/admin/settings', { method: 'POST', headers: {'Content-Type':'application/json'}, body: JSON.stringify(body) }); showToast('Ayarlar ve yönetici bilgileri kaydedildi!'); loadOverview(); } function toggleLoginPass() { const p = document.getElementById('loginPassword'); if (p) p.type = p.type === 'password' ? 'text' : 'password'; } async function submitAdminLogin() { const username = (document.getElementById('loginUsername').value || '').trim(); const password = (document.getElementById('loginPassword').value || '').trim(); const errEl = document.getElementById('loginErrorMsg'); if (errEl) errEl.style.display = 'none'; try { const res = await fetch('/api/admin/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username, password }) }); const out = await res.json(); if (out && out.ok) { try { localStorage.setItem('lion_admin_token', out.token || 'AUTHORIZED_MASORLEVENT_VIP'); localStorage.setItem('lion_admin_user', out.username || 'masorlevent'); sessionStorage.removeItem('lion_logged_out'); if (window.location.pathname.indexOf('/login') === 0) { window.history.replaceState({}, '', '/admin'); } } catch (e) {} const ov = document.getElementById('lionLoginOverlay'); if (ov) ov.style.display = 'none'; const bdg = document.getElementById('adminActiveUserBadge'); if (bdg) bdg.textContent = '👤 ' + (out.username || 'masorlevent') + ' (Süper Admin)'; showToast('Hoş geldiniz ' + (out.username || 'masorlevent') + '! Yönetim paneli açıldı.'); loadOverview(); } else { if (errEl) { errEl.textContent = (out && out.error) || 'Kullanıcı adı veya şifre hatalı!'; errEl.style.display = 'block'; } } } catch (err) { if (errEl) { errEl.textContent = 'Sunucu bağlantı hatası, lütfen tekrar deneyin.'; errEl.style.display = 'block'; } } } async function logoutAdmin() { try { await fetch('/api/admin/logout', { method: 'POST' }); localStorage.removeItem('lion_admin_token'); localStorage.removeItem('lion_admin_user'); sessionStorage.setItem('lion_logged_out', '1'); } catch (e) {} const ov = document.getElementById('lionLoginOverlay'); if (ov) ov.style.display = 'flex'; document.getElementById('loginPassword').value = ''; showToast('Oturum kapatıldı.'); } async function checkInitialAdminAuth() { try { const isLoginPath = window.location.pathname.indexOf('/login') === 0; const isExplicitLogout = sessionStorage.getItem('lion_logged_out') === '1'; const ov = document.getElementById('lionLoginOverlay'); if (isLoginPath || isExplicitLogout) { if (ov) ov.style.display = 'flex'; return; } if (ov) ov.style.display = 'none'; const bdg = document.getElementById('adminActiveUserBadge'); if (bdg) bdg.textContent = '👤 ' + (localStorage.getItem('lion_admin_user') || 'masorlevent') + ' (Süper Admin)'; } catch (e) {} } // SSE Canlı Yayın Bağlantısı function connectLiveStream() { try { const es = new EventSource('/api/admin/live-stream'); es.onmessage = (e) => { const data = JSON.parse(e.data || '{}'); if (data.type === 'LIVE_EVENT' || data.type === 'MIDNIGHT_ROLLOVER') { loadOverview(); } }; } catch {} } // Her açılışta varsayılan BUGÜN (today) ve A. GERÇEK ZİYARETÇİLER (HUMAN) ile başlat checkInitialAdminAuth(); loadOverview(); connectLiveStream(); </script> </body> </html>