File size: 9,331 Bytes
bcb31ef 1308519 bcb31ef 1308519 bcb31ef 1308519 bcb31ef 1308519 bcb31ef 1308519 bcb31ef 1308519 bcb31ef 1308519 bcb31ef 1308519 bcb31ef 1308519 bcb31ef 1308519 bcb31ef 1308519 bcb31ef 1308519 bcb31ef 37df93f bcb31ef 1308519 bcb31ef 1308519 bcb31ef 37df93f | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 | <!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> | <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> |