Spaces:
Running
Running
| <html lang="tr"> | |
| <head> | |
| <meta charset="utf-8" /> | |
| <meta name="viewport" content="width=device-width,initial-scale=1" /> | |
| <title>Hugging Face Bağlantı Testi</title> | |
| <meta name="description" | |
| content="Hugging Face’e bağlanamıyorum hatası için basit bağlantı testi ve DNS/Geo/IP bilgileri. Türkçe arayüz, modern ve duyarlı tasarım." /> | |
| <style> | |
| :root { | |
| --bg: #0b0f14; | |
| --bg2: #0e131a; | |
| --text: #e6edf3; | |
| --muted: #9aa5b1; | |
| --border: #1c2530; | |
| --card: #0f1620; | |
| --ok: #10b981; | |
| --warn: #f59e0b; | |
| --bad: #ef4444; | |
| --accent: #7c3aed; | |
| --accent2: #22d3ee; | |
| --shadow: 0 10px 30px rgba(0, 0, 0, .35); | |
| --radius: 16px; | |
| } | |
| @media (prefers-color-scheme: light) { | |
| :root { | |
| --bg: #f8fafc; | |
| --bg2: #f1f5f9; | |
| --text: #0b1220; | |
| --muted: #475569; | |
| --border: #e2e8f0; | |
| --card: #ffffff; | |
| --shadow: 0 8px 24px rgba(0, 0, 0, .08); | |
| } | |
| } | |
| * { | |
| box-sizing: border-box | |
| } | |
| html, | |
| body { | |
| height: 100% | |
| } | |
| body { | |
| margin: 0; | |
| background: radial-gradient(1200px 600px at 10% -10%, rgba(124, 58, 237, .15), transparent 60%), | |
| radial-gradient(1200px 600px at 110% 10%, rgba(34, 211, 238, .12), transparent 60%), | |
| var(--bg); | |
| color: var(--text); | |
| font: 16px/1.5 ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Inter, Helvetica, Arial, "Apple Color Emoji", "Segoe UI Emoji"; | |
| } | |
| .wrap { | |
| max-width: 1100px; | |
| margin: 0 auto; | |
| padding: 24px | |
| } | |
| header.hero { | |
| display: flex; | |
| gap: 16px; | |
| align-items: center; | |
| justify-content: space-between; | |
| flex-wrap: wrap; | |
| margin: 12px 0 20px; | |
| } | |
| .brand { | |
| display: flex; | |
| align-items: center; | |
| gap: 14px; | |
| } | |
| .brand .logo { | |
| width: 42px; | |
| height: 42px; | |
| border-radius: 12px; | |
| background: conic-gradient(from 200deg at 50% 50%, var(--accent), var(--accent2)); | |
| box-shadow: var(--shadow); | |
| position: relative; | |
| isolation: isolate; | |
| } | |
| .brand .logo::after { | |
| content: ""; | |
| position: absolute; | |
| inset: 2px; | |
| border-radius: 10px; | |
| background: var(--card); | |
| mix-blend-mode: screen; | |
| opacity: .65; | |
| } | |
| .brand .title { | |
| font-weight: 700; | |
| font-size: 22px; | |
| letter-spacing: .2px; | |
| } | |
| .brand .subtitle { | |
| font-size: 13px; | |
| color: var(--muted); | |
| margin-top: 2px | |
| } | |
| .actions { | |
| display: flex; | |
| gap: 10px; | |
| align-items: center; | |
| flex-wrap: wrap; | |
| } | |
| button.btn { | |
| appearance: none; | |
| border: 1px solid var(--border); | |
| background: linear-gradient(180deg, var(--bg2), var(--card)); | |
| color: var(--text); | |
| padding: 10px 14px; | |
| border-radius: 12px; | |
| cursor: pointer; | |
| font-weight: 600; | |
| letter-spacing: .2px; | |
| box-shadow: var(--shadow); | |
| transition: .2s transform ease, .2s filter ease, .2s background ease; | |
| } | |
| button.btn:hover { | |
| transform: translateY(-1px) | |
| } | |
| button.btn:active { | |
| transform: translateY(0) | |
| } | |
| button.primary { | |
| background: linear-gradient(180deg, rgba(124, 58, 237, .25), rgba(124, 58, 237, .1)); | |
| border-color: color-mix(in oklab, var(--accent) 50%, var(--border)); | |
| } | |
| .hint { | |
| font-size: 13px; | |
| color: var(--muted) | |
| } | |
| .grid { | |
| display: grid; | |
| gap: 16px; | |
| grid-template-columns: 1fr; | |
| } | |
| @media (min-width:860px) { | |
| .grid { | |
| grid-template-columns: 1.1fr .9fr | |
| } | |
| } | |
| .card { | |
| background: linear-gradient(180deg, rgba(255, 255, 255, .02), transparent 60%), var(--card); | |
| border: 1px solid var(--border); | |
| border-radius: var(--radius); | |
| box-shadow: var(--shadow); | |
| padding: 16px; | |
| } | |
| .card h3 { | |
| margin: 6px 0 10px; | |
| font-size: 18px | |
| } | |
| .status { | |
| display: flex; | |
| align-items: center; | |
| gap: 12px; | |
| padding: 12px; | |
| border: 1px dashed var(--border); | |
| border-radius: 12px; | |
| background: linear-gradient(180deg, rgba(124, 58, 237, .05), transparent 70%); | |
| } | |
| .dot { | |
| width: 14px; | |
| height: 14px; | |
| border-radius: 50%; | |
| background: var(--bad); | |
| box-shadow: 0 0 0 4px rgba(239, 68, 68, .12); | |
| } | |
| .dot.ok { | |
| background: var(--ok); | |
| box-shadow: 0 0 0 4px rgba(16, 185, 129, .12) | |
| } | |
| .dot.warn { | |
| background: var(--warn); | |
| box-shadow: 0 0 0 4px rgba(245, 158, 11, .12) | |
| } | |
| .row { | |
| display: flex; | |
| align-items: center; | |
| justify-content: space-between; | |
| gap: 10px; | |
| flex-wrap: wrap | |
| } | |
| .progress { | |
| width: 100%; | |
| height: 10px; | |
| border-radius: 999px; | |
| background: rgba(148, 163, 184, .25); | |
| overflow: hidden; | |
| margin-top: 12px | |
| } | |
| .bar { | |
| height: 100%; | |
| width: 0%; | |
| background: linear-gradient(90deg, var(--accent), var(--accent2)); | |
| transition: width .3s ease; | |
| border-radius: inherit | |
| } | |
| .kv { | |
| display: grid; | |
| grid-template-columns: 140px 1fr; | |
| gap: 10px; | |
| font-size: 14px; | |
| margin-top: 10px | |
| } | |
| .kv div { | |
| color: var(--muted) | |
| } | |
| .kv strong { | |
| color: var(--text); | |
| font-weight: 600 | |
| } | |
| .tests { | |
| display: grid; | |
| gap: 10px; | |
| margin-top: 6px | |
| } | |
| .test { | |
| display: flex; | |
| align-items: center; | |
| gap: 12px; | |
| padding: 10px; | |
| border: 1px solid var(--border); | |
| border-radius: 12px; | |
| background: linear-gradient(180deg, rgba(255, 255, 255, .02), transparent 60%) | |
| } | |
| .pill { | |
| padding: 4px 8px; | |
| border-radius: 999px; | |
| font-size: 12px; | |
| font-weight: 700; | |
| letter-spacing: .3px; | |
| border: 1px solid var(--border); | |
| color: var(--muted) | |
| } | |
| .pill.ok { | |
| color: #065f46; | |
| background: rgba(16, 185, 129, .2); | |
| border-color: rgba(16, 185, 129, .35) | |
| } | |
| .pill.bad { | |
| color: #7f1d1d; | |
| background: rgba(239, 68, 68, .18); | |
| border-color: rgba(239, 68, 68, .35) | |
| } | |
| .pill.warn { | |
| color: #78350f; | |
| background: rgba(245, 158, 11, .18); | |
| border-color: rgba(245, 158, 11, .35) | |
| } | |
| .test .name { | |
| flex: 1 1 auto | |
| } | |
| .test .meta { | |
| color: var(--muted); | |
| font-size: 12px | |
| } | |
| .footer { | |
| display: flex; | |
| align-items: center; | |
| justify-content: space-between; | |
| gap: 10px; | |
| margin-top: 18px; | |
| flex-wrap: wrap; | |
| color: var(--muted); | |
| font-size: 13px | |
| } | |
| a.inline { | |
| color: var(--accent); | |
| text-decoration: none; | |
| border-bottom: 1px dashed color-mix(in oklab, var(--accent) 60%, transparent) | |
| } | |
| a.inline:hover { | |
| opacity: .85 | |
| } | |
| .badge { | |
| display: inline-flex; | |
| align-items: center; | |
| gap: 8px; | |
| border: 1px solid var(--border); | |
| padding: 8px 10px; | |
| border-radius: 12px; | |
| background: linear-gradient(180deg, rgba(124, 58, 237, .09), rgba(124, 58, 237, .03)); | |
| } | |
| .tiny { | |
| font-size: 12px; | |
| color: var(--muted); | |
| margin-top: 8px | |
| } | |
| .mono { | |
| font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace | |
| } | |
| .kbd { | |
| font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; | |
| font-size: 12px; | |
| border: 1px solid var(--border); | |
| padding: 2px 6px; | |
| border-radius: 6px; | |
| background: var(--bg2); | |
| color: var(--muted) | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="wrap"> | |
| <header class="hero"> | |
| <div class="brand"> | |
| <div class="logo" aria-hidden="true"></div> | |
| <div> | |
| <div class="title">Hugging Face Bağlantı Testi</div> | |
| <div class="subtitle">huggingface.co bağlanmayı reddetti hatası için hızlı teşhis ve durum paneli</div> | |
| </div> | |
| </div> | |
| <div class="actions"> | |
| <button class="btn primary" id="runBtn">Yeniden dene</button> | |
| <button class="btn" id="autoBtn">Otomatik aç/kapar</button> | |
| </div> | |
| </header> | |
| <div class="grid"> | |
| <section class="card"> | |
| <div class="row"> | |
| <h3>Genel durum</h3> | |
| <div class="hint" id="lastRun">Son test: —</div> | |
| </div> | |
| <div class="status" aria-live="polite" aria-atomic="true"> | |
| <div class="dot" id="statusDot" title="Durum"></div> | |
| <div> | |
| <div id="statusTitle" style="font-weight:700">Ölçüm bekleniyor…</div> | |
| <div id="statusMsg" class="hint">Bağlantıyı doğrulamak için küçük bir istek gönderiyoruz.</div> | |
| <div class="progress" title="İlerleme"> | |
| <div class="bar" id="progressBar"></div> | |
| </div> | |
| </div> | |
| </div> | |
| <div style="margin-top:14px"> | |
| <div class="row"> | |
| <h3 style="margin:0">Ağ bilgileri</h3> | |
| <span class="badge"> | |
| <span style="width:8px;height:8px;border-radius:50%;background:var(--accent2);display:inline-block"></span> | |
| Canlı | |
| </span> | |
| </div> | |
| <div class="kv"> | |
| <div>İnternet</div> | |
| <div id="netInfo">—</div> | |
| <div>IP</div> | |
| <div id="ipInfo">Tespit ediliyor…</div> | |
| <div>DNS (1.1.1.1)</div> | |
| <div id="dnsCloudflare" class="mono">—</div> | |
| <div>DNS (8.8.8.8)</div> | |
| <div id="dnsGoogle" class="mono">—</div> | |
| </div> | |
| <div class="tiny">IP bilgisi <a class="inline" href="https://api.ipify.org?format=json" target="_blank" | |
| rel="noopener">api.ipify.org</a> üzerinden alınmaktadır.</div> | |
| </div> | |
| <div style="margin-top:16px"> | |
| <div class="row"> | |
| <h3 style="margin:0">Hızlı aç</h3> | |
| <div class="hint">Aşağıdaki bağlantılar site açıldığında çalışacaktır</div> | |
| </div> | |
| <div class="row" style="margin-top:8px; gap:8px"> | |
| <a class="btn" target="_blank" rel="noopener" href="https://huggingface.co/">Hugging Face Ana Sayfa</a> | |
| <a class="btn" target="_blank" rel="noopener" href="https://huggingface.co/models">Models</a> | |
| <a class="btn" target="_blank" rel="noopener" href="https://huggingface.co/datasets">Datasets</a> | |
| <a class="btn" target="_blank" rel="noopener" href="https://huggingface.co/spaces">Spaces</a> | |
| </div> | |
| </div> | |
| </section> | |
| <section class="card"> | |
| <div class="row"> | |
| <h3>Test detayları</h3> | |
| <div class="hint">Hedefler ve sonuçlar</div> | |
| </div> | |
| <div class="tests" id="tests"></div> | |
| <div style="margin-top:16px"> | |
| <h3 style="margin:0 0 8px">Öneriler</h3> | |
| <ul class="hint" style="margin:0 0 0 18px"> | |
| <li>İnternet bağlantınızı ve Wi‑Fi/Ethernet’in durumunu kontrol edin.</li> | |
| <li>Bir VPN kullanıyorsanız geçici olarak kapatın.</li> | |
| <li>DNS sunucularınızı 1.1.1.1 veya 8.8.8.8 olarak ayarlayın.</li> | |
| <li>Antivirüs/Güvenlik Duvarı uygulaması huggingface.co’yu engelliyor olabilir.</li> | |
| <li>Tarayıcınızın DNS önbelleğini temizleyin: <span class="kbd">ipconfig /flushdns</span> (Windows) veya | |
| <span class="kbd">sudo dscacheutil -flushcache</span> (macOS). | |
| </li> | |
| <li>Tarayıcı eklentileri (özellikle reklam engelleyicileri) baz istekleri engelleyebilir.</li> | |
| </ul> | |
| </div> | |
| </section> | |
| </div> | |
| <div class="footer"> | |
| <div> | |
| <strong>Durum özeti:</strong> | |
| <span id="summaryText">—</span> | |
| </div> | |
| <div> | |
| <span class="hint">Altyapı desteği:</span> | |
| <a class="inline" href="https://huggingface.co/" target="_blank" rel="noopener">huggingface.co</a> | |
| <span class="hint"> • </span> | |
| <a class="inline" href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" rel="noopener">Built | |
| with anycoder</a> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| // Utility: time formatting in Turkish | |
| const fmtTime = (d=new Date()) => d.toLocaleString('tr-TR', {hour:'2-digit', minute:'2-digit', second:'2-digit'}); | |
| const fmtDate = (d=new Date()) => d.toLocaleString('tr-TR', {day:'2-digit', month:'short', year:'numeric'}); | |
| const $ = (sel, parent=document) => parent.querySelector(sel); | |
| // Build a tiny, modern spinner | |
| const makeSpinner = (size=16) => { | |
| const s = document.createElement('span'); | |
| s.style.cssText = ` | |
| width:${size}px;height:${size}px;border:2px solid rgba(255,255,255,.25); | |
| border-top-color:var(--accent2); border-radius:50%; display:inline-block; | |
| animation: spin 1s linear infinite; | |
| `; | |
| const style = document.createElement('style'); | |
| style.textContent = `@keyframes spin{to{transform:rotate(360deg)}}`; | |
| document.head.append(style); | |
| return s; | |
| }; | |
| // Image test for a given URL, resolve/reject by load/error | |
| function imageTest(url, timeout=7000){ | |
| return new Promise((resolve, reject)=>{ | |
| const img = new Image(); | |
| let done = false; | |
| const tidy = ()=>{ | |
| if(done) return; | |
| done = true; | |
| clearTimeout(to); | |
| img.onload = img.onerror = null; | |
| }; | |
| const to = setTimeout(()=>{ tidy(); reject(new Error('timeout')); }, timeout); | |
| img.onload = ()=>{ tidy(); resolve({url, status:'ok'}); }; | |
| img.onerror = ()=>{ tidy(); reject(new Error('load error')); }; | |
| img.referrerPolicy = 'no-referrer'; | |
| // cache-bust to avoid accidental cached "error" | |
| img.src = url + (url.includes('?') ? '&' : '?') + 't=' + Date.now(); | |
| }); | |
| } | |
| // Quick fetch to HF APIs to see if CORS / connectivity is there | |
| async function quickFetchCheck(url){ | |
| try{ | |
| const r = await fetch(url, {mode:'cors', cache:'no-store'}); | |
| return {url, status: r.ok ? 'ok' : 'warn', http: r.status}; | |
| }catch(e){ | |
| return {url, status:'bad', error: String(e && e.message || e)}; | |
| } | |
| } | |
| // DNS resolution attempt via platform (not standard web, but modern browsers may support) | |
| async function resolveHost(host){ | |
| try{ | |
| if('resolveDNS' in window && typeof window.resolveDNS === 'function'){ | |
| const addrs = await window.resolveDNS(host); | |
| return Array.isArray(addrs) ? addrs : [String(addrs)]; | |
| } | |
| }catch(_){} | |
| try{ | |
| const res = await fetch(`https://dns.google/resolve?name=${encodeURIComponent(host)}&type=A`); | |
| const js = await res.json(); | |
| const addrs = (js && js.Answer || []).filter(a=>a.type===1).map(a=>a.data); | |
| return addrs.length ? addrs : null; | |
| }catch(_){ | |
| return null; | |
| } | |
| } | |
| // Build test list UI | |
| const targets = [ | |
| { | |
| id:'hf_home', | |
| title:'Ana Sayfa (huggingface.co)', | |
| url:'https://huggingface.co/', | |
| note:'HTTP 200', | |
| type:'fetch', | |
| checker: ()=>quickFetchCheck('https://huggingface.co/') | |
| }, | |
| { | |
| id:'hf_avatar', | |
| title:'Hesap Avatar Görseli (CDN)', | |
| url:'https://huggingface.co/avalonia.ai/resolve/main/README.png', | |
| note:'Küçük resim', | |
| type:'image', | |
| checker: ()=>imageTest('https://huggingface.co/avalonia.ai/resolve/main/README.png') | |
| }, | |
| { | |
| id:'hf_models', | |
| title:'Models Listesi', | |
| url:'https://huggingface.co/models', | |
| note:'HTTP 200', | |
| type:'fetch', | |
| checker: ()=>quickFetchCheck('https://huggingface.co/models') | |
| }, | |
| { | |
| id:'hf_spaces', | |
| title:'Spaces Listesi', | |
| url:'https://huggingface.co/spaces', | |
| note:'HTTP 200', | |
| type:'fetch', | |
| checker: ()=>quickFetchCheck('https://huggingface.co/spaces') | |
| }, | |
| { | |
| id:'hf_api_models', | |
| title:'HF API: /api/models (JSON)', | |
| url:'https://huggingface.co/api/models?sort=downloads&direction=-1&limit=1', | |
| note:'JSON CORS', | |
| type:'fetch', | |
| checker: ()=>quickFetchCheck('https://huggingface.co/api/models?limit=1') | |
| } | |
| ]; | |
| const testContainer = $('#tests'); | |
| function renderTestList(){ | |
| testContainer.innerHTML = ''; | |
| for(const t of targets){ | |
| const row = document.createElement('div'); | |
| row.className = 'test'; | |
| row.id = 'test-' + t.id; | |
| const badge = document.createElement('span'); | |
| badge.className = 'pill'; | |
| badge.textContent = t.type.toUpperCase(); | |
| badge.title = 'Test tipi'; | |
| const name = document.createElement('div'); | |
| name.className = 'name'; | |
| name.innerHTML = `<div style="font-weight:600">${t.title}</div><div class="meta">${t.note}</div>`; | |
| const url = document.createElement('div'); | |
| url.className = 'meta mono'; | |
| url.style.maxWidth = '340px'; | |
| url.style.overflow = 'hidden'; | |
| url.style.textOverflow = 'ellipsis'; | |
| url.style.whiteSpace = 'nowrap'; | |
| url.textContent = t.url; | |
| const status = document.createElement('span'); | |
| status.className = 'pill'; | |
| status.id = `status-${t.id}`; | |
| status.textContent = 'bekleniyor…'; | |
| row.appendChild(badge); | |
| row.appendChild(name); | |
| row.appendChild(url); | |
| row.appendChild(status); | |
| testContainer.appendChild(row); | |
| } | |
| } | |
| // Update a test row status | |
| function setTestStatus(id, status, extra=''){ | |
| const el = document.getElementById('status-' + id); | |
| if(!el) return; | |
| el.className = 'pill ' + (status==='ok' ? 'ok' : status==='bad' ? 'bad' : status==='warn' ? 'warn' : ''); | |
| el.textContent = status==='ok' ? 'başarılı' : status==='bad' ? 'başarısız' : status==='warn' ? 'uyarı' : 'bekleniyor…'; | |
| if(extra){ | |
| el.title = extra; | |
| } | |
| } | |
| // Aggregate status UI | |
| const dot = $('#statusDot'); | |
| const progressBar = $('#progressBar'); | |
| const statusTitle = $('#statusTitle'); | |
| const statusMsg = $('#statusMsg'); | |
| const lastRun = $('#lastRun'); | |
| const summaryText = $('#summaryText'); | |
| function setGlobalStatus(kind, title, msg, progress=null){ | |
| dot.className = 'dot ' + (kind==='ok' ? 'ok' : kind==='warn' ? 'warn' : 'bad'); | |
| statusTitle.textContent = title; | |
| statusMsg.textContent = msg; | |
| if(progress!==null) progressBar.style.width = Math.max(2, Math.min(100, progress)) + '%'; | |
| } | |
| // IP + DNS info | |
| async function refreshNetInfo(){ | |
| const netInfo = $('#netInfo'); | |
| const ipInfo = $('#ipInfo'); | |
| netInfo.textContent = navigator.onLine ? 'Çevrimiçi' : 'Çevrimdışı'; | |
| // IP from ipify | |
| try{ | |
| const res = await fetch('https://api.ipify.org?format=json', {cache:'no-store'}); | |
| const js = await res.json(); | |
| ipInfo.textContent = js.ip || '—'; | |
| }catch(_){ | |
| ipInfo.textContent = 'Alınamadı'; | |
| } | |
| // DNS resolve attempt | |
| const dnsCF = $('#dnsCloudflare'); | |
| const dnsGG = $('#dnsGoogle'); | |
| dnsCF.textContent = '…'; | |
| dnsGG.textContent = '…'; | |
| const [cf, gg] = await Promise.allSettled([ | |
| resolveHost('huggingface.co'), | |
| resolveHost('huggingface.co') | |
| ]); | |
| const toList = (p)=> p.status==='fulfilled' && Array.isArray(p.value) && p.value.length ? p.value.join(', ') : 'Çözümlenemedi'; | |
| dnsCF.textContent = toList(cf); | |
| dnsGG.textContent = toList(gg); | |
| } | |
| // Main runner | |
| let autoTimer = null; | |
| async function runTest(){ | |
| renderTestList(); | |
| const startedAt = new Date(); | |
| lastRun.textContent = `Son test: ${fmtDate(startedAt)} • ${fmtTime(startedAt)}`; | |
| let okCount = 0, badCount = 0, warnCount = 0; | |
| const total = targets.length; | |
| // Reset UI | |
| setGlobalStatus('warn', 'Test çalışıyor…', 'Lütfen bekleyin, birkaç küçük istek yapıyoruz.', 5); | |
| summaryText.textContent = 'ölçüm devam ediyor…'; | |
| // Run checks with concurrency | |
| try{ | |
| await refreshNetInfo(); | |
| }catch(_){} | |
| const tasks = targets.map(async (t, i)=>{ | |
| try{ | |
| setTestStatus(t.id, 'warn', 'kontrol ediliyor…'); | |
| const spinner = makeSpinner(14); | |
| const pill = document.getElementById('status-' + t.id); | |
| const prev = pill.textContent; | |
| pill.textContent = ''; | |
| pill.appendChild(spinner); | |
| pill.title = 'Kontrol ediliyor…'; | |
| const res = await t.checker(); | |
| // Cleanup spinner | |
| pill.removeChild(spinner); | |
| if(res.status === 'ok'){ | |
| okCount++; | |
| setTestStatus(t.id, 'ok', res.http ? `HTTP ${res.http}` : 'OK'); | |
| }else if(res.status === 'warn'){ | |
| warnCount++; | |
| setTestStatus(t.id, 'warn', res.http ? `HTTP ${res.http}` : 'Uyarı'); | |
| }else{ | |
| badCount++; | |
| setTestStatus(t.id, 'bad', res.error ? res.error : 'Başarısız'); | |
| } | |
| }catch(e){ | |
| badCount++; | |
| setTestStatus(t.id, 'bad', (e && e.message) || 'Başarısız'); | |
| }finally{ | |
| const pct = Math.round(((i+1)/total)*100); | |
| progressBar.style.width = pct + '%'; | |
| } | |
| }); | |
| await Promise.allSettled(tasks); | |
| // Summarize | |
| let kind = 'bad'; | |
| if(okCount === total) kind = 'ok'; | |
| else if(okCount >= Math.ceil(total/2)) kind = 'warn'; | |
| const title = kind==='ok' ? 'Bağlantı başarılı' | |
| : kind==='warn' ? 'Kısmi sorun olabilir' | |
| : 'Bağlantı başarısız'; | |
| const detail = `${okCount}/${total} kontrol başarılı`; | |
| setGlobalStatus(kind, title, detail, 100); | |
| summaryText.textContent = detail; | |
| // If mostly OK, open HF in a new tab (only if user pressed "Yeniden dene" recently? Keep conservative: only on auto? We won’t auto-open silently; only update UI). | |
| // Provide a hint: | |
| statusMsg.title = 'Aşağıdaki "Hızlı aç" bağlantılarını kullanabilirsiniz.'; | |
| } | |
| // Wire buttons | |
| $('#runBtn').addEventListener('click', ()=>runTest()); | |
| $('#autoBtn').addEventListener('click', (e)=>{ | |
| if(autoTimer){ | |
| clearInterval(autoTimer); | |
| autoTimer = null; | |
| e.currentTarget.textContent = 'Otomatik aç/kapar'; | |
| e.currentTarget.classList.remove('primary'); | |
| }else{ | |
| e.currentTarget.textContent = 'Otomatik açık (Durdur)'; | |
| e.currentTarget.classList.add('primary'); | |
| runTest(); | |
| autoTimer = setInterval(runTest, 15000); | |
| } | |
| }); | |
| // Initial | |
| renderTestList(); | |
| runTest(); | |
| // Keep net indicator fresh | |
| window.addEventListener('online', ()=>refreshNetInfo()); | |
| window.addEventListener('offline', ()=>refreshNetInfo()); | |
| </script> | |
| </body> | |
| </html> |