pythonany / index.html
nopoh22's picture
Update index.html
1308519 verified
Raw History Blame Contribute Delete
9.33 kB
<!DOCTYPE html>
<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> &nbsp;|&nbsp; <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>