Download index.html from nopoh22/pythonany: direct link, hf CLI and curl.
- Browser
- Download file 9.33 kB
-
https://huggingface.co/spaces/nopoh22/pythonany/resolve/main/index.html
- Command line
-
hf download hf://spaces/nopoh22/pythonany/index.html
-
curl -L -o index.html https://huggingface.co/spaces/nopoh22/pythonany/resolve/main/index.html
9.33 kB
| <html lang="ar" dir="rtl"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>PythonAnywhere Manager</title> | |
| <style> | |
| * { box-sizing: border-box; margin: 0; padding: 0; } | |
| body { font-family: 'Segoe UI', Tahoma, sans-serif; background: #0f172a; color: #e2e8f0; min-height: 100vh; } | |
| header { background: linear-gradient(135deg, #1e3a5f, #0f172a); padding: 20px 30px; border-bottom: 1px solid #334155; } | |
| header h1 { font-size: 1.6rem; color: #38bdf8; } | |
| header p { color: #94a3b8; font-size: 0.9rem; margin-top: 4px; } | |
| .container { max-width: 960px; margin: 30px auto; padding: 0 20px; } | |
| .card { background: #1e293b; border: 1px solid #334155; border-radius: 12px; padding: 24px; margin-bottom: 24px; } | |
| .card h2 { color: #38bdf8; margin-bottom: 18px; font-size: 1.05rem; display:flex; align-items:center; gap:8px; } | |
| .info-box { | |
| background: #0f172a; border: 1px solid #1e3a5f; border-radius: 8px; | |
| padding: 14px 18px; font-size: 0.88rem; color: #7dd3fc; line-height: 1.8; | |
| } | |
| .info-box code { background: #1e293b; padding: 2px 8px; border-radius: 4px; color: #fbbf24; font-size: 0.85rem; } | |
| input[type=text] { | |
| flex: 1; min-width: 200px; padding: 10px 14px; | |
| background: #0f172a; border: 1px solid #475569; border-radius: 8px; | |
| color: #e2e8f0; font-size: 0.95rem; | |
| } | |
| input:focus { outline: none; border-color: #38bdf8; } | |
| input::placeholder { color: #475569; } | |
| .form-row { display: flex; gap: 12px; flex-wrap: wrap; margin-bottom: 14px; } | |
| button { | |
| padding: 10px 22px; border: none; border-radius: 8px; cursor: pointer; | |
| font-size: 0.9rem; font-weight: 600; transition: all .15s; | |
| } | |
| button:hover { transform: translateY(-1px); opacity: .9; } | |
| .btn-primary { background: #2563eb; color: #fff; } | |
| .btn-warning { background: #d97706; color: #fff; } | |
| .btn-success { background: #16a34a; color: #fff; } | |
| .btn-gray { background: #475569; color: #fff; } | |
| table { width: 100%; border-collapse: collapse; font-size: 0.88rem; } | |
| th { background: #0f172a; color: #94a3b8; padding: 10px 14px; text-align: right; font-weight: 600; } | |
| td { padding: 10px 14px; border-top: 1px solid #1e293b; vertical-align: middle; } | |
| tr:hover td { background: #1a2d47; } | |
| .badge { display:inline-block; padding:3px 10px; border-radius:20px; font-size:0.78rem; font-weight:600; } | |
| .badge-ok { background:#14532d; color:#86efac; } | |
| .badge-na { background:#334155; color:#94a3b8; } | |
| #log { | |
| background: #020617; border: 1px solid #1e3a5f; border-radius: 8px; | |
| padding: 14px; height: 260px; overflow-y: auto; | |
| font-family: 'Courier New', monospace; font-size: 0.8rem; color: #7dd3fc; | |
| direction: ltr; text-align: left; | |
| } | |
| #log .line.err { color: #f87171; } | |
| #log .line.ok { color: #86efac; } | |
| .sch-badge { display:inline-flex; align-items:center; gap:6px; padding:6px 16px; border-radius:20px; font-size:0.85rem; font-weight:600; margin-left:12px; } | |
| .sch-on { background:#14532d; color:#86efac; } | |
| .sch-off { background:#3f1818; color:#fca5a5; } | |
| .toast { position:fixed; bottom:24px; left:50%; transform:translateX(-50%); | |
| padding:12px 28px; border-radius:10px; font-size:0.92rem; font-weight:600; | |
| z-index:9999; display:none; } | |
| .toast-ok { background:#14532d; color:#bbf7d0; border:1px solid #16a34a; } | |
| .toast-err { background:#7f1d1d; color:#fca5a5; border:1px solid #dc2626; } | |
| .ctrl-row { display:flex; align-items:center; flex-wrap:wrap; gap:10px; margin-bottom:14px; } | |
| .empty { color:#475569; padding:16px 0; text-align:center; } | |
| </style> | |
| </head> | |
| <body> | |
| <header> | |
| <h1>🐍 PythonAnywhere Auto-Renewer</h1> | |
| <p>تجديد حسابات PythonAnywhere تلقائياً كل 10 أيام</p> | |
| </header> | |
| <div class="container"> | |
| <!-- تعليمات الإعداد --> | |
| <div class="card"> | |
| <h2>⚙️ طريقة إضافة الحسابات</h2> | |
| <div class="info-box"> | |
| الحسابات تُضاف من <b>Hugging Face → Settings → Variables and secrets</b><br><br> | |
| أضف secret باسم: <code>ACCOUNTS</code><br> | |
| القيمة: <code>username1:password1,username2:password2</code><br><br> | |
| مثال: <code>nopoh55678:mypass123,mahmoud:pass456</code><br><br> | |
| أو حسابات منفردة: <code>ACCOUNT_1</code> = <code>user1:pass1</code> | <code>ACCOUNT_2</code> = <code>user2:pass2</code> | |
| </div> | |
| </div> | |
| <!-- الحسابات --> | |
| <div class="card"> | |
| <h2>📋 الحسابات المحملة من Secrets</h2> | |
| <div id="accounts-wrap"><div class="empty">جاري التحميل...</div></div> | |
| <button class="btn-gray" style="margin-top:14px" onclick="loadAccounts()">🔄 تحديث</button> | |
| </div> | |
| <!-- التحكم --> | |
| <div class="card"> | |
| <h2>🎮 التحكم والجدولة</h2> | |
| <div class="ctrl-row"> | |
| <span id="sch-badge" class="sch-badge sch-off">⏹ المجدول متوقف</span> | |
| <button class="btn-success" id="sch-btn" onclick="toggleScheduler()">▶️ تشغيل المجدول التلقائي</button> | |
| </div> | |
| <div style="margin-bottom:14px"> | |
| <button class="btn-primary" onclick="renewAll()">🚀 تجديد كل الحسابات الآن</button> | |
| </div> | |
| <div class="form-row"> | |
| <input type="text" id="single-user" placeholder="تجديد حساب معين — اسم المستخدم"> | |
| <button class="btn-warning" onclick="renewOne()">🔄 تجديد</button> | |
| </div> | |
| </div> | |
| <!-- السجل --> | |
| <div class="card"> | |
| <h2>📜 السجل المباشر</h2> | |
| <div id="log"></div> | |
| <button class="btn-gray" style="margin-top:10px" onclick="loadLog()">🔄 تحديث</button> | |
| </div> | |
| </div> | |
| <div id="toast" class="toast"></div> | |
| <script> | |
| function toast(msg, ok=true) { | |
| const el = document.getElementById('toast'); | |
| el.textContent = msg; | |
| el.className = 'toast ' + (ok ? 'toast-ok' : 'toast-err'); | |
| el.style.display = 'block'; | |
| clearTimeout(el._t); | |
| el._t = setTimeout(() => el.style.display='none', 3500); | |
| } | |
| async function api(path, method='GET', body=null) { | |
| try { | |
| const opts = { method, headers:{'Content-Type':'application/json'} }; | |
| if (body) opts.body = JSON.stringify(body); | |
| const r = await fetch(path, opts); | |
| return await r.json(); | |
| } catch(e) { | |
| return { ok: false, message: '❌ خطأ في الاتصال' }; | |
| } | |
| } | |
| async function loadAccounts() { | |
| const data = await api('/api/accounts'); | |
| const el = document.getElementById('accounts-wrap'); | |
| if (!Array.isArray(data) || !data.length) { | |
| el.innerHTML = '<div class="empty">⚠️ لا توجد حسابات — أضف ACCOUNTS في Secrets وأعد تشغيل الـ Space.</div>'; | |
| return; | |
| } | |
| let html = `<table> | |
| <tr><th>المستخدم</th><th>الخادم</th><th>آخر تجديد</th><th>التجديد القادم</th><th>الحالة</th></tr>`; | |
| for (const a of data) { | |
| const last = a.last_renewed ? a.last_renewed.slice(0,16).replace('T',' ') : '—'; | |
| const nxt = a.next_renewal ? a.next_renewal.slice(0,16).replace('T',' ') : '—'; | |
| const res = (a.last_results||[]).map(r=>`${r.domain}: ${r.status}`).join('<br>') || '—'; | |
| const badge = a.last_renewed | |
| ? '<span class="badge badge-ok">✅ مجدَّد</span>' | |
| : '<span class="badge badge-na">⏳ لم يُجدَّد</span>'; | |
| html += `<tr> | |
| <td><b>${a.username}</b></td> | |
| <td style="font-size:.8rem;color:#94a3b8">${a.host||'—'}</td> | |
| <td style="font-size:.82rem">${last}</td> | |
| <td style="font-size:.82rem">${nxt}</td> | |
| <td>${badge}<br><span style="font-size:.78rem;color:#64748b">${res}</span></td> | |
| </tr>`; | |
| } | |
| el.innerHTML = html + '</table>'; | |
| } | |
| async function renewAll() { | |
| toast('🚀 جاري إرسال طلب التجديد...'); | |
| await api('/api/renew','POST'); | |
| setTimeout(() => { loadLog(); loadAccounts(); }, 2000); | |
| } | |
| async function renewOne() { | |
| const user = document.getElementById('single-user').value.trim(); | |
| if (!user) { toast('❌ أدخل اسم المستخدم', false); return; } | |
| toast(`🔄 جاري تجديد ${user}...`); | |
| await api('/api/renew/'+user,'POST'); | |
| setTimeout(() => { loadLog(); loadAccounts(); }, 2000); | |
| } | |
| async function toggleScheduler() { | |
| const r = await api('/api/scheduler','POST'); | |
| const on = r.running; | |
| document.getElementById('sch-badge').className = 'sch-badge ' + (on?'sch-on':'sch-off'); | |
| document.getElementById('sch-badge').textContent = on ? '🟢 المجدول شغّال' : '⏹ المجدول متوقف'; | |
| document.getElementById('sch-btn').textContent = on ? '⏹ إيقاف المجدول' : '▶️ تشغيل المجدول التلقائي'; | |
| toast(on ? '✅ المجدول شغّال' : '⏹ تم إيقاف المجدول'); | |
| } | |
| function colorLine(l) { | |
| if (l.includes('❌') || l.includes('فشل')) return 'err'; | |
| if (l.includes('✅') || l.includes('تم')) return 'ok'; | |
| return ''; | |
| } | |
| async function loadLog() { | |
| const data = await api('/api/log'); | |
| const el = document.getElementById('log'); | |
| if (!data.lines) return; | |
| el.innerHTML = data.lines.map(l=>`<div class="line ${colorLine(l)}">${l}</div>`).join(''); | |
| el.scrollTop = el.scrollHeight; | |
| } | |
| loadAccounts(); | |
| loadLog(); | |
| setInterval(loadLog, 10000); | |
| setInterval(loadAccounts, 30000); | |
| </script> | |
| </body> | |
| </html> |