Update index.html
Browse files- index.html +32 -102
index.html
CHANGED
|
@@ -13,29 +13,27 @@
|
|
| 13 |
.container { max-width: 960px; margin: 30px auto; padding: 0 20px; }
|
| 14 |
.card { background: #1e293b; border: 1px solid #334155; border-radius: 12px; padding: 24px; margin-bottom: 24px; }
|
| 15 |
.card h2 { color: #38bdf8; margin-bottom: 18px; font-size: 1.05rem; display:flex; align-items:center; gap:8px; }
|
| 16 |
-
.
|
| 17 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 18 |
flex: 1; min-width: 200px; padding: 10px 14px;
|
| 19 |
background: #0f172a; border: 1px solid #475569; border-radius: 8px;
|
| 20 |
color: #e2e8f0; font-size: 0.95rem;
|
| 21 |
}
|
| 22 |
input:focus { outline: none; border-color: #38bdf8; }
|
| 23 |
input::placeholder { color: #475569; }
|
| 24 |
-
.
|
| 25 |
-
.hint span { color: #fbbf24; }
|
| 26 |
-
.divider { display: flex; align-items: center; gap: 10px; margin: 10px 0; color: #475569; font-size: 0.82rem; }
|
| 27 |
-
.divider::before, .divider::after { content: ''; flex: 1; height: 1px; background: #334155; }
|
| 28 |
button {
|
| 29 |
padding: 10px 22px; border: none; border-radius: 8px; cursor: pointer;
|
| 30 |
font-size: 0.9rem; font-weight: 600; transition: all .15s;
|
| 31 |
}
|
| 32 |
button:hover { transform: translateY(-1px); opacity: .9; }
|
| 33 |
-
button:active { transform: translateY(0); }
|
| 34 |
.btn-primary { background: #2563eb; color: #fff; }
|
| 35 |
-
.btn-danger { background: #dc2626; color: #fff; }
|
| 36 |
-
.btn-success { background: #16a34a; color: #fff; }
|
| 37 |
.btn-warning { background: #d97706; color: #fff; }
|
| 38 |
-
.btn-
|
| 39 |
.btn-gray { background: #475569; color: #fff; }
|
| 40 |
table { width: 100%; border-collapse: collapse; font-size: 0.88rem; }
|
| 41 |
th { background: #0f172a; color: #94a3b8; padding: 10px 14px; text-align: right; font-weight: 600; }
|
|
@@ -44,14 +42,12 @@
|
|
| 44 |
.badge { display:inline-block; padding:3px 10px; border-radius:20px; font-size:0.78rem; font-weight:600; }
|
| 45 |
.badge-ok { background:#14532d; color:#86efac; }
|
| 46 |
.badge-na { background:#334155; color:#94a3b8; }
|
| 47 |
-
.badge-method { background:#1e3a5f; color:#7dd3fc; font-size:0.72rem; padding:2px 8px; border-radius:10px; }
|
| 48 |
#log {
|
| 49 |
background: #020617; border: 1px solid #1e3a5f; border-radius: 8px;
|
| 50 |
padding: 14px; height: 260px; overflow-y: auto;
|
| 51 |
font-family: 'Courier New', monospace; font-size: 0.8rem; color: #7dd3fc;
|
| 52 |
direction: ltr; text-align: left;
|
| 53 |
}
|
| 54 |
-
#log .line { margin: 2px 0; }
|
| 55 |
#log .line.err { color: #f87171; }
|
| 56 |
#log .line.ok { color: #86efac; }
|
| 57 |
.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; }
|
|
@@ -59,14 +55,10 @@
|
|
| 59 |
.sch-off { background:#3f1818; color:#fca5a5; }
|
| 60 |
.toast { position:fixed; bottom:24px; left:50%; transform:translateX(-50%);
|
| 61 |
padding:12px 28px; border-radius:10px; font-size:0.92rem; font-weight:600;
|
| 62 |
-
z-index:9999; display:none;
|
| 63 |
.toast-ok { background:#14532d; color:#bbf7d0; border:1px solid #16a34a; }
|
| 64 |
.toast-err { background:#7f1d1d; color:#fca5a5; border:1px solid #dc2626; }
|
| 65 |
-
@keyframes fadeup { from { opacity:0; transform:translateX(-50%) translateY(10px); } to { opacity:1; transform:translateX(-50%) translateY(0); } }
|
| 66 |
-
.spinner { display:inline-block; width:14px; height:14px; border:2px solid #fff; border-top-color:transparent; border-radius:50%; animation:spin .6s linear infinite; vertical-align:middle; margin-left:6px; }
|
| 67 |
-
@keyframes spin { to { transform:rotate(360deg); } }
|
| 68 |
.ctrl-row { display:flex; align-items:center; flex-wrap:wrap; gap:10px; margin-bottom:14px; }
|
| 69 |
-
.actions-row { display:flex; gap:10px; flex-wrap:wrap; }
|
| 70 |
.empty { color:#475569; padding:16px 0; text-align:center; }
|
| 71 |
</style>
|
| 72 |
</head>
|
|
@@ -74,51 +66,28 @@
|
|
| 74 |
|
| 75 |
<header>
|
| 76 |
<h1>🐍 PythonAnywhere Auto-Renewer</h1>
|
| 77 |
-
<p>
|
| 78 |
</header>
|
| 79 |
|
| 80 |
<div class="container">
|
| 81 |
|
| 82 |
-
<!--
|
| 83 |
<div class="card">
|
| 84 |
-
<h2>
|
| 85 |
-
<div
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
<h2>➕ إضافة حساب جديد</h2>
|
| 92 |
-
|
| 93 |
-
<div class="hint">
|
| 94 |
-
يمكنك استخدام <span>كلم�� المرور</span> (طريقة التمديد المباشر /extend) أو <span>API Token</span> (إعادة تحميل الويب آب).
|
| 95 |
-
إذا أدخلت الاثنين معاً، ستُستخدم كلمة المرور أولاً.
|
| 96 |
-
</div>
|
| 97 |
-
|
| 98 |
-
<div class="form-row">
|
| 99 |
-
<input type="text" id="new-user" placeholder="اسم المستخدم — مثال: mahmoud123">
|
| 100 |
-
</div>
|
| 101 |
-
|
| 102 |
-
<div class="divider">كلمة المرور (موصى بها)</div>
|
| 103 |
-
<div class="form-row">
|
| 104 |
-
<input type="password" id="new-pass" placeholder="كلمة مرور الحساب">
|
| 105 |
</div>
|
| 106 |
-
|
| 107 |
-
<div class="divider">أو API Token</div>
|
| 108 |
-
<div class="form-row">
|
| 109 |
-
<input type="password" id="new-token" placeholder="API Token — من /account/#api_token">
|
| 110 |
-
</div>
|
| 111 |
-
|
| 112 |
-
<button class="btn-primary" id="add-btn" onclick="addAccount()">➕ إضافة الحساب</button>
|
| 113 |
</div>
|
| 114 |
|
| 115 |
-
<!-- ح
|
| 116 |
<div class="card">
|
| 117 |
-
<h2>
|
| 118 |
-
<div
|
| 119 |
-
|
| 120 |
-
</div>
|
| 121 |
-
<button class="btn-danger" onclick="deleteAccount()">🗑️ حذف</button>
|
| 122 |
</div>
|
| 123 |
|
| 124 |
<!-- التحكم -->
|
|
@@ -128,7 +97,7 @@
|
|
| 128 |
<span id="sch-badge" class="sch-badge sch-off">⏹ المجدول متوقف</span>
|
| 129 |
<button class="btn-success" id="sch-btn" onclick="toggleScheduler()">▶️ تشغيل المجدول التلقائي</button>
|
| 130 |
</div>
|
| 131 |
-
<div
|
| 132 |
<button class="btn-primary" onclick="renewAll()">🚀 تجديد كل الحسابات الآن</button>
|
| 133 |
</div>
|
| 134 |
<div class="form-row">
|
|
@@ -149,8 +118,6 @@
|
|
| 149 |
<div id="toast" class="toast"></div>
|
| 150 |
|
| 151 |
<script>
|
| 152 |
-
let schedulerOn = false;
|
| 153 |
-
|
| 154 |
function toast(msg, ok=true) {
|
| 155 |
const el = document.getElementById('toast');
|
| 156 |
el.textContent = msg;
|
|
@@ -175,11 +142,11 @@ async function loadAccounts() {
|
|
| 175 |
const data = await api('/api/accounts');
|
| 176 |
const el = document.getElementById('accounts-wrap');
|
| 177 |
if (!Array.isArray(data) || !data.length) {
|
| 178 |
-
el.innerHTML = '<div class="empty">لا توجد حسابات
|
| 179 |
return;
|
| 180 |
}
|
| 181 |
let html = `<table>
|
| 182 |
-
<tr><th>المستخدم</th><th>الخادم</th><th>
|
| 183 |
for (const a of data) {
|
| 184 |
const last = a.last_renewed ? a.last_renewed.slice(0,16).replace('T',' ') : '—';
|
| 185 |
const nxt = a.next_renewal ? a.next_renewal.slice(0,16).replace('T',' ') : '—';
|
|
@@ -187,15 +154,9 @@ async function loadAccounts() {
|
|
| 187 |
const badge = a.last_renewed
|
| 188 |
? '<span class="badge badge-ok">✅ مجدَّد</span>'
|
| 189 |
: '<span class="badge badge-na">⏳ لم يُجدَّد</span>';
|
| 190 |
-
const method = a.auth_method === 'password'
|
| 191 |
-
? '<span class="badge-method">🔑 Password</span>'
|
| 192 |
-
: a.auth_method === 'token'
|
| 193 |
-
? '<span class="badge-method">🔐 Token</span>'
|
| 194 |
-
: '—';
|
| 195 |
html += `<tr>
|
| 196 |
<td><b>${a.username}</b></td>
|
| 197 |
<td style="font-size:.8rem;color:#94a3b8">${a.host||'—'}</td>
|
| 198 |
-
<td>${method}</td>
|
| 199 |
<td style="font-size:.82rem">${last}</td>
|
| 200 |
<td style="font-size:.82rem">${nxt}</td>
|
| 201 |
<td>${badge}<br><span style="font-size:.78rem;color:#64748b">${res}</span></td>
|
|
@@ -204,36 +165,6 @@ async function loadAccounts() {
|
|
| 204 |
el.innerHTML = html + '</table>';
|
| 205 |
}
|
| 206 |
|
| 207 |
-
async function addAccount() {
|
| 208 |
-
const user = document.getElementById('new-user').value.trim();
|
| 209 |
-
const pass = document.getElementById('new-pass').value.trim();
|
| 210 |
-
const token = document.getElementById('new-token').value.trim();
|
| 211 |
-
if (!user) { toast('❌ أدخل اسم المستخدم', false); return; }
|
| 212 |
-
if (!pass && !token) { toast('❌ أدخل كلمة المرور أو API Token', false); return; }
|
| 213 |
-
const btn = document.getElementById('add-btn');
|
| 214 |
-
btn.innerHTML = 'جاري التحقق... <span class="spinner"></span>';
|
| 215 |
-
btn.disabled = true;
|
| 216 |
-
const r = await api('/api/accounts', 'POST', {username: user, token, password: pass});
|
| 217 |
-
btn.innerHTML = '➕ إضافة الحساب';
|
| 218 |
-
btn.disabled = false;
|
| 219 |
-
toast(r.message, r.ok);
|
| 220 |
-
if (r.ok) {
|
| 221 |
-
document.getElementById('new-user').value = '';
|
| 222 |
-
document.getElementById('new-pass').value = '';
|
| 223 |
-
document.getElementById('new-token').value = '';
|
| 224 |
-
loadAccounts();
|
| 225 |
-
}
|
| 226 |
-
}
|
| 227 |
-
|
| 228 |
-
async function deleteAccount() {
|
| 229 |
-
const user = document.getElementById('del-user').value.trim();
|
| 230 |
-
if (!user) { toast('❌ أدخل اسم المستخدم', false); return; }
|
| 231 |
-
if (!confirm(`هل أنت متأكد من حذف ${user}؟`)) return;
|
| 232 |
-
const r = await api('/api/accounts/'+user,'DELETE');
|
| 233 |
-
toast(r.message, r.ok);
|
| 234 |
-
if (r.ok) { document.getElementById('del-user').value=''; loadAccounts(); }
|
| 235 |
-
}
|
| 236 |
-
|
| 237 |
async function renewAll() {
|
| 238 |
toast('🚀 جاري إرسال طلب التجديد...');
|
| 239 |
await api('/api/renew','POST');
|
|
@@ -250,17 +181,16 @@ async function renewOne() {
|
|
| 250 |
|
| 251 |
async function toggleScheduler() {
|
| 252 |
const r = await api('/api/scheduler','POST');
|
| 253 |
-
|
| 254 |
-
document.getElementById('sch-badge').className = 'sch-badge ' + (
|
| 255 |
-
document.getElementById('sch-badge').textContent =
|
| 256 |
-
document.getElementById('sch-btn').textContent =
|
| 257 |
-
toast(
|
| 258 |
-
loadLog();
|
| 259 |
}
|
| 260 |
|
| 261 |
function colorLine(l) {
|
| 262 |
-
if (l.includes('❌') || l.includes('فشل')
|
| 263 |
-
if (l.includes('✅') || l.includes('تم
|
| 264 |
return '';
|
| 265 |
}
|
| 266 |
|
|
|
|
| 13 |
.container { max-width: 960px; margin: 30px auto; padding: 0 20px; }
|
| 14 |
.card { background: #1e293b; border: 1px solid #334155; border-radius: 12px; padding: 24px; margin-bottom: 24px; }
|
| 15 |
.card h2 { color: #38bdf8; margin-bottom: 18px; font-size: 1.05rem; display:flex; align-items:center; gap:8px; }
|
| 16 |
+
.info-box {
|
| 17 |
+
background: #0f172a; border: 1px solid #1e3a5f; border-radius: 8px;
|
| 18 |
+
padding: 14px 18px; font-size: 0.88rem; color: #7dd3fc; line-height: 1.8;
|
| 19 |
+
}
|
| 20 |
+
.info-box code { background: #1e293b; padding: 2px 8px; border-radius: 4px; color: #fbbf24; font-size: 0.85rem; }
|
| 21 |
+
input[type=text] {
|
| 22 |
flex: 1; min-width: 200px; padding: 10px 14px;
|
| 23 |
background: #0f172a; border: 1px solid #475569; border-radius: 8px;
|
| 24 |
color: #e2e8f0; font-size: 0.95rem;
|
| 25 |
}
|
| 26 |
input:focus { outline: none; border-color: #38bdf8; }
|
| 27 |
input::placeholder { color: #475569; }
|
| 28 |
+
.form-row { display: flex; gap: 12px; flex-wrap: wrap; margin-bottom: 14px; }
|
|
|
|
|
|
|
|
|
|
| 29 |
button {
|
| 30 |
padding: 10px 22px; border: none; border-radius: 8px; cursor: pointer;
|
| 31 |
font-size: 0.9rem; font-weight: 600; transition: all .15s;
|
| 32 |
}
|
| 33 |
button:hover { transform: translateY(-1px); opacity: .9; }
|
|
|
|
| 34 |
.btn-primary { background: #2563eb; color: #fff; }
|
|
|
|
|
|
|
| 35 |
.btn-warning { background: #d97706; color: #fff; }
|
| 36 |
+
.btn-success { background: #16a34a; color: #fff; }
|
| 37 |
.btn-gray { background: #475569; color: #fff; }
|
| 38 |
table { width: 100%; border-collapse: collapse; font-size: 0.88rem; }
|
| 39 |
th { background: #0f172a; color: #94a3b8; padding: 10px 14px; text-align: right; font-weight: 600; }
|
|
|
|
| 42 |
.badge { display:inline-block; padding:3px 10px; border-radius:20px; font-size:0.78rem; font-weight:600; }
|
| 43 |
.badge-ok { background:#14532d; color:#86efac; }
|
| 44 |
.badge-na { background:#334155; color:#94a3b8; }
|
|
|
|
| 45 |
#log {
|
| 46 |
background: #020617; border: 1px solid #1e3a5f; border-radius: 8px;
|
| 47 |
padding: 14px; height: 260px; overflow-y: auto;
|
| 48 |
font-family: 'Courier New', monospace; font-size: 0.8rem; color: #7dd3fc;
|
| 49 |
direction: ltr; text-align: left;
|
| 50 |
}
|
|
|
|
| 51 |
#log .line.err { color: #f87171; }
|
| 52 |
#log .line.ok { color: #86efac; }
|
| 53 |
.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; }
|
|
|
|
| 55 |
.sch-off { background:#3f1818; color:#fca5a5; }
|
| 56 |
.toast { position:fixed; bottom:24px; left:50%; transform:translateX(-50%);
|
| 57 |
padding:12px 28px; border-radius:10px; font-size:0.92rem; font-weight:600;
|
| 58 |
+
z-index:9999; display:none; }
|
| 59 |
.toast-ok { background:#14532d; color:#bbf7d0; border:1px solid #16a34a; }
|
| 60 |
.toast-err { background:#7f1d1d; color:#fca5a5; border:1px solid #dc2626; }
|
|
|
|
|
|
|
|
|
|
| 61 |
.ctrl-row { display:flex; align-items:center; flex-wrap:wrap; gap:10px; margin-bottom:14px; }
|
|
|
|
| 62 |
.empty { color:#475569; padding:16px 0; text-align:center; }
|
| 63 |
</style>
|
| 64 |
</head>
|
|
|
|
| 66 |
|
| 67 |
<header>
|
| 68 |
<h1>🐍 PythonAnywhere Auto-Renewer</h1>
|
| 69 |
+
<p>تجديد حسابات PythonAnywhere تلقائياً كل 10 أيام</p>
|
| 70 |
</header>
|
| 71 |
|
| 72 |
<div class="container">
|
| 73 |
|
| 74 |
+
<!-- تعليمات الإعداد -->
|
| 75 |
<div class="card">
|
| 76 |
+
<h2>⚙️ طريقة إضافة الحسابات</h2>
|
| 77 |
+
<div class="info-box">
|
| 78 |
+
الحسابات تُضاف من <b>Hugging Face → Settings → Variables and secrets</b><br><br>
|
| 79 |
+
أضف secret باسم: <code>ACCOUNTS</code><br>
|
| 80 |
+
القيمة: <code>username1:password1,username2:password2</code><br><br>
|
| 81 |
+
مثال: <code>nopoh55678:mypass123,mahmoud:pass456</code><br><br>
|
| 82 |
+
أو حسابات منفردة: <code>ACCOUNT_1</code> = <code>user1:pass1</code> | <code>ACCOUNT_2</code> = <code>user2:pass2</code>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 83 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 84 |
</div>
|
| 85 |
|
| 86 |
+
<!-- الحسابات -->
|
| 87 |
<div class="card">
|
| 88 |
+
<h2>📋 الحسابات المحملة من Secrets</h2>
|
| 89 |
+
<div id="accounts-wrap"><div class="empty">جاري التحميل...</div></div>
|
| 90 |
+
<button class="btn-gray" style="margin-top:14px" onclick="loadAccounts()">🔄 تحديث</button>
|
|
|
|
|
|
|
| 91 |
</div>
|
| 92 |
|
| 93 |
<!-- التحكم -->
|
|
|
|
| 97 |
<span id="sch-badge" class="sch-badge sch-off">⏹ المجدول متوقف</span>
|
| 98 |
<button class="btn-success" id="sch-btn" onclick="toggleScheduler()">▶️ تشغيل المجدول التلقائي</button>
|
| 99 |
</div>
|
| 100 |
+
<div style="margin-bottom:14px">
|
| 101 |
<button class="btn-primary" onclick="renewAll()">🚀 تجديد كل الحسابات الآن</button>
|
| 102 |
</div>
|
| 103 |
<div class="form-row">
|
|
|
|
| 118 |
<div id="toast" class="toast"></div>
|
| 119 |
|
| 120 |
<script>
|
|
|
|
|
|
|
| 121 |
function toast(msg, ok=true) {
|
| 122 |
const el = document.getElementById('toast');
|
| 123 |
el.textContent = msg;
|
|
|
|
| 142 |
const data = await api('/api/accounts');
|
| 143 |
const el = document.getElementById('accounts-wrap');
|
| 144 |
if (!Array.isArray(data) || !data.length) {
|
| 145 |
+
el.innerHTML = '<div class="empty">⚠️ لا توجد حسابات — أضف ACCOUNTS في Secrets وأعد تشغيل الـ Space.</div>';
|
| 146 |
return;
|
| 147 |
}
|
| 148 |
let html = `<table>
|
| 149 |
+
<tr><th>المستخدم</th><th>الخادم</th><th>آخر تجديد</th><th>التجديد القادم</th><th>الحالة</th></tr>`;
|
| 150 |
for (const a of data) {
|
| 151 |
const last = a.last_renewed ? a.last_renewed.slice(0,16).replace('T',' ') : '—';
|
| 152 |
const nxt = a.next_renewal ? a.next_renewal.slice(0,16).replace('T',' ') : '—';
|
|
|
|
| 154 |
const badge = a.last_renewed
|
| 155 |
? '<span class="badge badge-ok">✅ مجدَّد</span>'
|
| 156 |
: '<span class="badge badge-na">⏳ لم يُجدَّد</span>';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 157 |
html += `<tr>
|
| 158 |
<td><b>${a.username}</b></td>
|
| 159 |
<td style="font-size:.8rem;color:#94a3b8">${a.host||'—'}</td>
|
|
|
|
| 160 |
<td style="font-size:.82rem">${last}</td>
|
| 161 |
<td style="font-size:.82rem">${nxt}</td>
|
| 162 |
<td>${badge}<br><span style="font-size:.78rem;color:#64748b">${res}</span></td>
|
|
|
|
| 165 |
el.innerHTML = html + '</table>';
|
| 166 |
}
|
| 167 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 168 |
async function renewAll() {
|
| 169 |
toast('🚀 جاري إرسال طلب التجديد...');
|
| 170 |
await api('/api/renew','POST');
|
|
|
|
| 181 |
|
| 182 |
async function toggleScheduler() {
|
| 183 |
const r = await api('/api/scheduler','POST');
|
| 184 |
+
const on = r.running;
|
| 185 |
+
document.getElementById('sch-badge').className = 'sch-badge ' + (on?'sch-on':'sch-off');
|
| 186 |
+
document.getElementById('sch-badge').textContent = on ? '🟢 المجدول شغّال' : '⏹ المجدول متوقف';
|
| 187 |
+
document.getElementById('sch-btn').textContent = on ? '⏹ إيقاف المجدول' : '▶️ تشغيل المجدول التلقائي';
|
| 188 |
+
toast(on ? '✅ المجدول شغّال' : '⏹ تم إيقاف المجدول');
|
|
|
|
| 189 |
}
|
| 190 |
|
| 191 |
function colorLine(l) {
|
| 192 |
+
if (l.includes('❌') || l.includes('فشل')) return 'err';
|
| 193 |
+
if (l.includes('✅') || l.includes('تم')) return 'ok';
|
| 194 |
return '';
|
| 195 |
}
|
| 196 |
|