j49_c commited on
Commit
867199e
·
1 Parent(s): fe51cec

v0.1: Public registration, plan system, user management, remove HF references

Browse files
Files changed (4) hide show
  1. admin.html +20 -14
  2. app.py +139 -8
  3. index.html +3 -16
  4. login.html +202 -55
admin.html CHANGED
@@ -379,12 +379,11 @@
379
  <table>
380
  <thead>
381
  <tr>
382
- <th>DESIGNATION</th>
383
- <th>COMMLINK (EMAIL)</th>
384
- <th>NODES</th>
385
- <th>CLEARANCE</th>
386
- <th>ACCESS</th>
387
- <th>DIRECTIVES</th>
388
  </tr>
389
  </thead>
390
  <tbody id="usersT"></tbody>
@@ -477,23 +476,30 @@
477
  function renderUsers(arr) {
478
  const t = document.getElementById('usersT'); t.innerHTML = '';
479
  arr.forEach(u => {
 
480
  t.innerHTML += `
481
  <tr>
482
  <td style="font-weight:700">${u.username}</td>
483
- <td style="color:var(--text-dim)">${u.email || '—'}</td>
484
- <td style="font-family:var(--mono)">${u.servers}</td>
485
- <td>${u.premium ? '<span class="tag ok">LVL 5 CLEARANCE</span>' : '<span class="tag">STANDARD</span>'}</td>
486
- <td>${u.active ? '<span class="tag ok">GRANTED</span>' : '<span class="tag err">REVOKED</span>'}</td>
 
 
 
 
 
487
  <td class="nav-actions">
488
- <button class="btn ${u.active ? 'btn-danger' : ''}" onclick="setActive('${u.username}', ${u.active ? 'false' : 'true'})">${u.active ? 'REVOKE' : 'GRANT'}</button>
489
- <button class="btn" onclick="setPremium('${u.username}', ${u.premium ? 'false' : 'true'})">${u.premium ? 'DEMOTE' : 'CLEARANCE LVL 5'}</button>
490
  </td>
491
  </tr>`;
492
  });
493
  }
494
 
495
- async function setActive(u, act) { await fetch('/api/admin/user/update', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username: u, active: act }) }); refreshUsers(); refreshStats(); }
496
- async function setPremium(u, prm) { await fetch('/api/admin/user/update', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username: u, premium: prm }) }); refreshUsers(); refreshStats(); }
 
497
 
498
  function filterUsers() {
499
  const q = (document.getElementById('searchUser').value || '').toLowerCase();
 
379
  <table>
380
  <thead>
381
  <tr>
382
+ <th>USERNAME</th>
383
+ <th>PLAN</th>
384
+ <th>CREATED</th>
385
+ <th>STATUS</th>
386
+ <th>ACTIONS</th>
 
387
  </tr>
388
  </thead>
389
  <tbody id="usersT"></tbody>
 
476
  function renderUsers(arr) {
477
  const t = document.getElementById('usersT'); t.innerHTML = '';
478
  arr.forEach(u => {
479
+ const planColor = u.plan === 'pro' ? 'var(--accent)' : u.plan === 'enterprise' ? 'var(--warning)' : 'var(--text-dim)';
480
  t.innerHTML += `
481
  <tr>
482
  <td style="font-weight:700">${u.username}</td>
483
+ <td>
484
+ <select onchange="setPlan('${u.username}', this.value)" style="background:rgba(255,255,255,0.05);border:1px solid var(--border);color:var(--text);padding:6px 10px;border-radius:8px;font-size:12px;cursor:pointer">
485
+ <option value="free" ${u.plan === 'free' ? 'selected' : ''}>🟢 Free</option>
486
+ <option value="pro" ${u.plan === 'pro' ? 'selected' : ''}>🔵 Pro</option>
487
+ <option value="enterprise" ${u.plan === 'enterprise' ? 'selected' : ''}>🟡 Enterprise</option>
488
+ </select>
489
+ </td>
490
+ <td style="color:var(--text-dim);font-size:12px;font-family:var(--mono)">${u.created_at || '—'}</td>
491
+ <td>${u.active ? '<span class="tag ok">ACTIVE</span>' : '<span class="tag err">BANNED</span>'}</td>
492
  <td class="nav-actions">
493
+ <button class="btn ${u.active ? 'btn-danger' : ''}" onclick="toggleActive('${u.username}')">${u.active ? 'BAN' : 'UNBAN'}</button>
494
+ <button class="btn btn-danger" style="opacity:0.6" onclick="deleteUser('${u.username}')">DELETE</button>
495
  </td>
496
  </tr>`;
497
  });
498
  }
499
 
500
+ async function toggleActive(u) { await fetch('/api/admin/user/toggle-active', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username: u }) }); refreshUsers(); refreshStats(); }
501
+ async function setPlan(u, plan) { await fetch('/api/admin/user/set-plan', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username: u, plan }) }); refreshUsers(); }
502
+ async function deleteUser(u) { if (!confirm(`DELETE USER '${u}'? This cannot be undone.`)) return; await fetch('/api/admin/user/delete', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username: u }) }); refreshUsers(); refreshStats(); }
503
 
504
  function filterUsers() {
505
  const q = (document.getElementById('searchUser').value || '').toLowerCase();
app.py CHANGED
@@ -168,14 +168,34 @@ def current_username():
168
  return (u.get("username") or "").strip()
169
 
170
 
171
- def get_user_limit(username: str) -> int:
172
- if is_admin_session():
173
- return 999999
 
 
 
 
 
174
  db = load_users()
175
  u = find_user(db, username)
176
  if not u:
177
- return 1
178
- return 5 if u.get("premium", False) else 1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
179
 
180
 
181
  def login_required(fn):
@@ -515,9 +535,120 @@ def api_login():
515
  return jsonify({"success": True, "is_admin": False})
516
 
517
 
518
- @app.route("/api/auth/create", methods=["POST"])
519
- def api_create():
520
- return jsonify({"success": False, "message": "Registration disabled. Personal use only."}), 403
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
521
 
522
 
523
  def list_all_servers_for_admin():
 
168
  return (u.get("username") or "").strip()
169
 
170
 
171
+ PLANS = {
172
+ "free": {"label": "Free", "max_bots": 1, "max_mem_mb": 256},
173
+ "pro": {"label": "Pro", "max_bots": 5, "max_mem_mb": 512},
174
+ "enterprise": {"label": "Enterprise", "max_bots": 50, "max_mem_mb": 1024},
175
+ }
176
+
177
+
178
+ def get_user_plan(username: str) -> dict:
179
  db = load_users()
180
  u = find_user(db, username)
181
  if not u:
182
+ return PLANS["free"]
183
+ plan_name = u.get("plan", "free")
184
+ return PLANS.get(plan_name, PLANS["free"])
185
+
186
+
187
+ def get_user_limit(username: str) -> int:
188
+ if is_admin_session():
189
+ return 999999
190
+ plan = get_user_plan(username)
191
+ return plan["max_bots"]
192
+
193
+
194
+ def get_user_mem_limit(username: str) -> int:
195
+ if is_admin_session():
196
+ return MAX_PROCESS_MEMORY_MB
197
+ plan = get_user_plan(username)
198
+ return plan["max_mem_mb"]
199
 
200
 
201
  def login_required(fn):
 
535
  return jsonify({"success": True, "is_admin": False})
536
 
537
 
538
+ @app.route("/api/auth/register", methods=["POST"])
539
+ @rate_limited
540
+ def api_register():
541
+ data = request.get_json(silent=True) or {}
542
+ username = (data.get("username") or "").strip()
543
+ password = data.get("password") or ""
544
+
545
+ if len(username) < 3 or len(username) > 20:
546
+ return jsonify({"success": False, "message": "Username must be 3-20 characters"}), 400
547
+ if not re.match(r'^[A-Za-z0-9_]+$', username):
548
+ return jsonify({"success": False, "message": "Username: letters, numbers, underscore only"}), 400
549
+ if len(password) < 6:
550
+ return jsonify({"success": False, "message": "Password must be at least 6 characters"}), 400
551
+ if username.lower() == ADMIN_USERNAME.lower():
552
+ return jsonify({"success": False, "message": "Username not available"}), 400
553
+
554
+ db = load_users()
555
+ if find_user(db, username):
556
+ return jsonify({"success": False, "message": "Username already taken"}), 409
557
+
558
+ new_user = {
559
+ "username": username,
560
+ "password_hash": generate_password_hash(password),
561
+ "active": True,
562
+ "plan": "free",
563
+ "created_at": time.strftime("%Y-%m-%d %H:%M:%S")
564
+ }
565
+ db.setdefault("users", []).append(new_user)
566
+ save_users(db)
567
+
568
+ session["user"] = {"username": username, "is_admin": False}
569
+ session.permanent = True
570
+ ensure_user_dirs(username)
571
+ logger.info(f"New user registered: {username}")
572
+ return jsonify({"success": True})
573
+
574
+
575
+ @app.route("/api/user/profile")
576
+ @login_required
577
+ def api_user_profile():
578
+ username = current_username()
579
+ db = load_users()
580
+ u = find_user(db, username)
581
+ plan_name = "admin" if is_admin_session() else (u.get("plan", "free") if u else "free")
582
+ plan_info = PLANS.get(plan_name, PLANS["free"])
583
+ return jsonify({
584
+ "success": True,
585
+ "username": username,
586
+ "is_admin": is_admin_session(),
587
+ "plan": plan_name,
588
+ "plan_label": plan_info.get("label", plan_name.title()),
589
+ "max_bots": get_user_limit(username),
590
+ "max_mem_mb": get_user_mem_limit(username)
591
+ })
592
+
593
+
594
+ @app.route("/api/admin/users")
595
+ @admin_required
596
+ def api_admin_users():
597
+ db = load_users()
598
+ users = []
599
+ for u in db.get("users", []):
600
+ users.append({
601
+ "username": u.get("username"),
602
+ "active": u.get("active", True),
603
+ "plan": u.get("plan", "free"),
604
+ "created_at": u.get("created_at", "unknown")
605
+ })
606
+ return jsonify({"success": True, "users": users})
607
+
608
+
609
+ @app.route("/api/admin/user/set-plan", methods=["POST"])
610
+ @admin_required
611
+ def api_admin_set_plan():
612
+ data = request.get_json(silent=True) or {}
613
+ username = (data.get("username") or "").strip()
614
+ plan = (data.get("plan") or "free").strip()
615
+ if plan not in PLANS:
616
+ return jsonify({"success": False, "message": "Invalid plan"}), 400
617
+ db = load_users()
618
+ u = find_user(db, username)
619
+ if not u:
620
+ return jsonify({"success": False, "message": "User not found"}), 404
621
+ u["plan"] = plan
622
+ save_users(db)
623
+ return jsonify({"success": True})
624
+
625
+
626
+ @app.route("/api/admin/user/toggle-active", methods=["POST"])
627
+ @admin_required
628
+ def api_admin_toggle_active():
629
+ data = request.get_json(silent=True) or {}
630
+ username = (data.get("username") or "").strip()
631
+ db = load_users()
632
+ u = find_user(db, username)
633
+ if not u:
634
+ return jsonify({"success": False, "message": "User not found"}), 404
635
+ u["active"] = not u.get("active", True)
636
+ save_users(db)
637
+ return jsonify({"success": True, "active": u["active"]})
638
+
639
+
640
+ @app.route("/api/admin/user/delete", methods=["POST"])
641
+ @admin_required
642
+ def api_admin_delete_user():
643
+ data = request.get_json(silent=True) or {}
644
+ username = (data.get("username") or "").strip()
645
+ db = load_users()
646
+ u = find_user(db, username)
647
+ if not u:
648
+ return jsonify({"success": False, "message": "User not found"}), 404
649
+ db["users"] = [x for x in db.get("users", []) if (x.get("username") or "").strip().lower() != username.lower()]
650
+ save_users(db)
651
+ return jsonify({"success": True})
652
 
653
 
654
  def list_all_servers_for_admin():
index.html CHANGED
@@ -701,7 +701,7 @@
701
  <img src="https://f.top4top.io/p_37210bgwm1.jpg" alt="Logo" class="brand-logo">
702
  <div class="brand-info">
703
  <span class="brand-name">الذئب الابيض</span>
704
- <span class="brand-status" id="sysAddr">CONNECTING...</span>
705
  </div>
706
  </div>
707
  <div class="nav-actions">
@@ -833,20 +833,7 @@
833
  function escapeHtml(s) { return (s || '').replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#039;"); }
834
  function escapeAttr(s) { return escapeHtml(s).replace(/\\/g, "\\\\"); }
835
 
836
- async function loadPythonHost() {
837
- const addr = document.getElementById('sysAddr');
838
- try {
839
- const r = await fetch('/servers');
840
- if (r.redirected) { location.href = r.url; return; }
841
- const d = await r.json();
842
- const port = location.port || (location.protocol === 'https:' ? 443 : 80);
843
- if (!d.servers || d.servers.length === 0) { addr.textContent = `${location.hostname}:${port}`; return; }
844
- const key = d.servers[0].key || d.servers[0].folder;
845
- const s = await fetch(`/server/stats/${encodeURIComponent(key)}`);
846
- const sd = await s.json();
847
- addr.textContent = `${sd.ip || location.hostname}:${port}`;
848
- } catch (e) { addr.textContent = `${location.hostname}:EXT`; }
849
- }
850
 
851
  async function loadServers() {
852
  const r = await fetch('/servers');
@@ -1012,7 +999,7 @@
1012
  if (d.success) { closeM(); loadServers(); toast('Engine created', 'success'); } else toast(d.message, 'error');
1013
  });
1014
 
1015
- document.addEventListener('DOMContentLoaded', () => { loadServers(); loadPythonHost(); });
1016
  setInterval(() => { if (document.getElementById('homeView').style.display !== 'none') loadServers(); }, 10000);
1017
 
1018
  document.addEventListener('contextmenu', e => e.preventDefault());
 
701
  <img src="https://f.top4top.io/p_37210bgwm1.jpg" alt="Logo" class="brand-logo">
702
  <div class="brand-info">
703
  <span class="brand-name">الذئب الابيض</span>
704
+ <span class="brand-status">v0.1 · SECURE</span>
705
  </div>
706
  </div>
707
  <div class="nav-actions">
 
833
  function escapeHtml(s) { return (s || '').replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#039;"); }
834
  function escapeAttr(s) { return escapeHtml(s).replace(/\\/g, "\\\\"); }
835
 
836
+
 
 
 
 
 
 
 
 
 
 
 
 
 
837
 
838
  async function loadServers() {
839
  const r = await fetch('/servers');
 
999
  if (d.success) { closeM(); loadServers(); toast('Engine created', 'success'); } else toast(d.message, 'error');
1000
  });
1001
 
1002
+ document.addEventListener('DOMContentLoaded', () => { loadServers(); });
1003
  setInterval(() => { if (document.getElementById('homeView').style.display !== 'none') loadServers(); }, 10000);
1004
 
1005
  document.addEventListener('contextmenu', e => e.preventDefault());
login.html CHANGED
@@ -18,6 +18,7 @@
18
  --text-muted: #8E8E93;
19
  --border: rgba(255, 255, 255, 0.08);
20
  --error: #FF453A;
 
21
  }
22
 
23
  * {
@@ -38,7 +39,6 @@
38
  position: relative;
39
  }
40
 
41
- /* Animated background elements */
42
  body::before {
43
  content: '';
44
  position: absolute;
@@ -77,7 +77,7 @@
77
 
78
  .container {
79
  width: 100%;
80
- max-width: 400px;
81
  padding: 20px;
82
  position: relative;
83
  z-index: 10;
@@ -169,8 +169,50 @@
169
  letter-spacing: 0.1em;
170
  }
171
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
172
  .input-group {
173
- margin-bottom: 16px;
174
  position: relative;
175
  }
176
 
@@ -223,8 +265,18 @@
223
  transform: translateY(0);
224
  }
225
 
 
 
 
 
 
 
 
 
 
 
226
  .msg {
227
- margin-top: 24px;
228
  font-size: 13px;
229
  font-weight: 500;
230
  min-height: 20px;
@@ -232,14 +284,13 @@
232
  }
233
 
234
  .footer {
235
- margin-top: 40px;
236
  color: rgba(255, 255, 255, 0.2);
237
  font-size: 11px;
238
  font-family: 'JetBrains Mono', monospace;
239
  letter-spacing: 0.05em;
240
  }
241
 
242
- /* Fancy Loading bar */
243
  .loading-bar {
244
  position: absolute;
245
  bottom: 0;
@@ -260,6 +311,42 @@
260
  transform: translateX(100%);
261
  }
262
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
263
  </style>
264
  </head>
265
 
@@ -271,93 +358,153 @@
271
  <img src="https://f.top4top.io/p_37210bgwm1.jpg" alt="Logo" class="logo-img">
272
  </div>
273
  <h1>الذئب الابيض</h1>
274
- <div class="subtitle">System Authentication</div>
275
 
276
- <div class="input-group">
277
- <input id="u" placeholder="Designation (Username)" autocomplete="username">
 
278
  </div>
279
- <div class="input-group">
280
- <input id="p" placeholder="Passcode" type="password" autocomplete="current-password">
 
 
 
 
 
 
 
 
 
 
 
281
  </div>
282
 
283
- <button class="btn" onclick="doLogin()">
284
- <span id="btnText">AUTHENTICATE</span>
285
- <div class="loading-bar" id="loadBar"></div>
286
- </button>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
287
 
288
  <div class="msg" id="m"></div>
289
 
290
  <div class="footer">
291
- © 2026 WHITE WOLF PROTOCOL [v2.4.0]<br>
292
  PROTECTED BY @j49_c
293
  </div>
294
  </div>
295
  </div>
296
 
297
  <script>
 
 
 
 
 
 
 
298
  async function doLogin() {
299
  const m = document.getElementById('m');
300
- const lb = document.getElementById('loadBar');
301
- const bt = document.getElementById('btnText');
302
-
303
- m.textContent = '';
304
- lb.style.display = 'block';
305
- bt.textContent = 'CONNECTING...';
306
 
307
- const username = document.getElementById('u').value.trim();
308
- const password = document.getElementById('p').value;
309
 
310
  try {
311
  const r = await fetch('/api/auth/login', {
312
- method: 'POST',
313
- headers: { 'Content-Type': 'application/json' },
314
  body: JSON.stringify({ username, password })
315
  });
316
- const d = await r.json().catch(() => ({ success: false, message: 'Encryption mismatch' }));
317
-
318
  if (!r.ok || !d.success) {
319
  m.style.color = "var(--error)";
320
- m.textContent = (d.message || 'Verification Failed').toUpperCase();
321
- lb.style.display = 'none';
322
- bt.textContent = 'AUTHENTICATE';
323
  return;
324
  }
325
-
326
- m.style.color = "#32D74B";
327
- m.textContent = 'ACCESS GRANTED. INITIALIZING...';
328
  bt.textContent = 'INITIALIZING...';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
329
 
330
- setTimeout(() => {
331
- window.location.href = d.is_admin ? './admin' : './';
332
- }, 800);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
333
  } catch (e) {
334
- m.style.color = "var(--error)";
335
- m.textContent = "UPLINK ERROR";
336
- lb.style.display = 'none';
337
- bt.textContent = 'RETRY';
338
  }
339
  }
340
 
341
- // --- SECURITY PROTOCOL: ANTI-INSPECTION ---
342
  document.addEventListener('contextmenu', e => e.preventDefault());
343
  document.onkeydown = function (e) {
344
- if (e.keyCode == 123) return false; // F12
345
  if (e.ctrlKey && e.shiftKey && (e.keyCode == 'I'.charCodeAt(0) || e.keyCode == 'J'.charCodeAt(0) || e.keyCode == 'C'.charCodeAt(0))) return false;
346
- if (e.ctrlKey && e.keyCode == 'U'.charCodeAt(0)) return false; // View Source
347
  };
 
348
 
349
- // Debugger trap to hinder devtools
350
- setInterval(() => {
351
- const start = performance.now();
352
- debugger;
353
- if (performance.now() - start > 100) {
354
- window.location.href = "about:blank";
355
- }
356
- }, 500);
357
-
358
- // Hit enter to login
359
  document.addEventListener('keypress', (e) => {
360
- if (e.key === 'Enter') doLogin();
 
 
 
361
  });
362
  </script>
363
  </body>
 
18
  --text-muted: #8E8E93;
19
  --border: rgba(255, 255, 255, 0.08);
20
  --error: #FF453A;
21
+ --success: #32D74B;
22
  }
23
 
24
  * {
 
39
  position: relative;
40
  }
41
 
 
42
  body::before {
43
  content: '';
44
  position: absolute;
 
77
 
78
  .container {
79
  width: 100%;
80
+ max-width: 420px;
81
  padding: 20px;
82
  position: relative;
83
  z-index: 10;
 
169
  letter-spacing: 0.1em;
170
  }
171
 
172
+ .tabs {
173
+ display: flex;
174
+ gap: 4px;
175
+ margin-bottom: 28px;
176
+ background: rgba(255, 255, 255, 0.05);
177
+ border-radius: 14px;
178
+ padding: 4px;
179
+ }
180
+
181
+ .tab {
182
+ flex: 1;
183
+ padding: 12px;
184
+ border: none;
185
+ background: transparent;
186
+ color: var(--text-muted);
187
+ font-size: 14px;
188
+ font-weight: 700;
189
+ cursor: pointer;
190
+ border-radius: 12px;
191
+ transition: all 0.3s;
192
+ font-family: 'Plus Jakarta Sans', sans-serif;
193
+ letter-spacing: 0.05em;
194
+ }
195
+
196
+ .tab.active {
197
+ background: var(--primary);
198
+ color: white;
199
+ box-shadow: 0 4px 15px rgba(10, 132, 255, 0.3);
200
+ }
201
+
202
+ .tab:hover:not(.active) {
203
+ color: var(--text);
204
+ }
205
+
206
+ .form-section {
207
+ display: none;
208
+ }
209
+
210
+ .form-section.active {
211
+ display: block;
212
+ }
213
+
214
  .input-group {
215
+ margin-bottom: 14px;
216
  position: relative;
217
  }
218
 
 
265
  transform: translateY(0);
266
  }
267
 
268
+ .btn-secondary {
269
+ background: rgba(255, 255, 255, 0.08);
270
+ margin-top: 8px;
271
+ }
272
+
273
+ .btn-secondary:hover {
274
+ background: rgba(255, 255, 255, 0.15);
275
+ box-shadow: none;
276
+ }
277
+
278
  .msg {
279
+ margin-top: 20px;
280
  font-size: 13px;
281
  font-weight: 500;
282
  min-height: 20px;
 
284
  }
285
 
286
  .footer {
287
+ margin-top: 36px;
288
  color: rgba(255, 255, 255, 0.2);
289
  font-size: 11px;
290
  font-family: 'JetBrains Mono', monospace;
291
  letter-spacing: 0.05em;
292
  }
293
 
 
294
  .loading-bar {
295
  position: absolute;
296
  bottom: 0;
 
311
  transform: translateX(100%);
312
  }
313
  }
314
+
315
+ .plans-info {
316
+ margin-top: 16px;
317
+ text-align: left;
318
+ }
319
+
320
+ .plan-row {
321
+ display: flex;
322
+ justify-content: space-between;
323
+ align-items: center;
324
+ padding: 10px 14px;
325
+ border-radius: 12px;
326
+ margin-bottom: 6px;
327
+ background: rgba(255, 255, 255, 0.03);
328
+ border: 1px solid var(--border);
329
+ font-size: 13px;
330
+ }
331
+
332
+ .plan-name {
333
+ font-weight: 700;
334
+ color: var(--text);
335
+ }
336
+
337
+ .plan-detail {
338
+ color: var(--text-muted);
339
+ font-family: 'JetBrains Mono', monospace;
340
+ font-size: 11px;
341
+ }
342
+
343
+ .plan-free {
344
+ border-color: rgba(50, 215, 75, 0.3);
345
+ }
346
+
347
+ .plan-pro {
348
+ border-color: rgba(10, 132, 255, 0.3);
349
+ }
350
  </style>
351
  </head>
352
 
 
358
  <img src="https://f.top4top.io/p_37210bgwm1.jpg" alt="Logo" class="logo-img">
359
  </div>
360
  <h1>الذئب الابيض</h1>
361
+ <div class="subtitle">Bot Hosting Platform</div>
362
 
363
+ <div class="tabs">
364
+ <button class="tab active" onclick="switchTab('login')">LOGIN</button>
365
+ <button class="tab" onclick="switchTab('register')">REGISTER</button>
366
  </div>
367
+
368
+ <!-- LOGIN -->
369
+ <div class="form-section active" id="loginForm">
370
+ <div class="input-group">
371
+ <input id="lu" placeholder="Username" autocomplete="username">
372
+ </div>
373
+ <div class="input-group">
374
+ <input id="lp" placeholder="Password" type="password" autocomplete="current-password">
375
+ </div>
376
+ <button class="btn" onclick="doLogin()">
377
+ <span id="loginBtnText">AUTHENTICATE</span>
378
+ <div class="loading-bar" id="loginLoad"></div>
379
+ </button>
380
  </div>
381
 
382
+ <!-- REGISTER -->
383
+ <div class="form-section" id="registerForm">
384
+ <div class="input-group">
385
+ <input id="ru" placeholder="Choose a username" autocomplete="off" minlength="3" maxlength="20">
386
+ </div>
387
+ <div class="input-group">
388
+ <input id="rp" placeholder="Create a password" type="password" autocomplete="new-password" minlength="6">
389
+ </div>
390
+ <div class="input-group">
391
+ <input id="rp2" placeholder="Confirm password" type="password" autocomplete="new-password">
392
+ </div>
393
+ <button class="btn" onclick="doRegister()">
394
+ <span id="regBtnText">CREATE ACCOUNT</span>
395
+ <div class="loading-bar" id="regLoad"></div>
396
+ </button>
397
+ <div class="plans-info">
398
+ <div class="plan-row plan-free">
399
+ <span class="plan-name">🟢 Free</span>
400
+ <span class="plan-detail">1 BOT · 256MB RAM</span>
401
+ </div>
402
+ <div class="plan-row plan-pro">
403
+ <span class="plan-name">🔵 Pro</span>
404
+ <span class="plan-detail">5 BOTS · 512MB RAM</span>
405
+ </div>
406
+ </div>
407
+ </div>
408
 
409
  <div class="msg" id="m"></div>
410
 
411
  <div class="footer">
412
+ © 2026 WHITE WOLF PROTOCOL [v0.1]<br>
413
  PROTECTED BY @j49_c
414
  </div>
415
  </div>
416
  </div>
417
 
418
  <script>
419
+ function switchTab(tab) {
420
+ document.querySelectorAll('.tab').forEach((t, i) => { t.classList.toggle('active', (tab === 'login' ? i === 0 : i === 1)); });
421
+ document.getElementById('loginForm').classList.toggle('active', tab === 'login');
422
+ document.getElementById('registerForm').classList.toggle('active', tab === 'register');
423
+ document.getElementById('m').textContent = '';
424
+ }
425
+
426
  async function doLogin() {
427
  const m = document.getElementById('m');
428
+ const lb = document.getElementById('loginLoad');
429
+ const bt = document.getElementById('loginBtnText');
430
+ m.textContent = ''; lb.style.display = 'block'; bt.textContent = 'CONNECTING...';
 
 
 
431
 
432
+ const username = document.getElementById('lu').value.trim();
433
+ const password = document.getElementById('lp').value;
434
 
435
  try {
436
  const r = await fetch('/api/auth/login', {
437
+ method: 'POST', headers: { 'Content-Type': 'application/json' },
 
438
  body: JSON.stringify({ username, password })
439
  });
440
+ const d = await r.json().catch(() => ({ success: false, message: 'Connection error' }));
 
441
  if (!r.ok || !d.success) {
442
  m.style.color = "var(--error)";
443
+ m.textContent = (d.message || 'Authentication Failed').toUpperCase();
444
+ lb.style.display = 'none'; bt.textContent = 'AUTHENTICATE';
 
445
  return;
446
  }
447
+ m.style.color = "var(--success)";
448
+ m.textContent = 'ACCESS GRANTED';
 
449
  bt.textContent = 'INITIALIZING...';
450
+ setTimeout(() => { window.location.href = d.is_admin ? './admin' : './'; }, 600);
451
+ } catch (e) {
452
+ m.style.color = "var(--error)"; m.textContent = "CONNECTION ERROR";
453
+ lb.style.display = 'none'; bt.textContent = 'RETRY';
454
+ }
455
+ }
456
+
457
+ async function doRegister() {
458
+ const m = document.getElementById('m');
459
+ const lb = document.getElementById('regLoad');
460
+ const bt = document.getElementById('regBtnText');
461
+ m.textContent = '';
462
+
463
+ const username = document.getElementById('ru').value.trim();
464
+ const password = document.getElementById('rp').value;
465
+ const password2 = document.getElementById('rp2').value;
466
+
467
+ if (username.length < 3) { m.style.color = "var(--error)"; m.textContent = "USERNAME MIN 3 CHARACTERS"; return; }
468
+ if (password.length < 6) { m.style.color = "var(--error)"; m.textContent = "PASSWORD MIN 6 CHARACTERS"; return; }
469
+ if (password !== password2) { m.style.color = "var(--error)"; m.textContent = "PASSWORDS DO NOT MATCH"; return; }
470
 
471
+ lb.style.display = 'block'; bt.textContent = 'CREATING...';
472
+
473
+ try {
474
+ const r = await fetch('/api/auth/register', {
475
+ method: 'POST', headers: { 'Content-Type': 'application/json' },
476
+ body: JSON.stringify({ username, password })
477
+ });
478
+ const d = await r.json().catch(() => ({ success: false, message: 'Connection error' }));
479
+ if (!r.ok || !d.success) {
480
+ m.style.color = "var(--error)";
481
+ m.textContent = (d.message || 'Registration Failed').toUpperCase();
482
+ lb.style.display = 'none'; bt.textContent = 'CREATE ACCOUNT';
483
+ return;
484
+ }
485
+ m.style.color = "var(--success)";
486
+ m.textContent = 'ACCOUNT CREATED. REDIRECTING...';
487
+ bt.textContent = 'SUCCESS';
488
+ setTimeout(() => { window.location.href = './'; }, 600);
489
  } catch (e) {
490
+ m.style.color = "var(--error)"; m.textContent = "CONNECTION ERROR";
491
+ lb.style.display = 'none'; bt.textContent = 'RETRY';
 
 
492
  }
493
  }
494
 
 
495
  document.addEventListener('contextmenu', e => e.preventDefault());
496
  document.onkeydown = function (e) {
497
+ if (e.keyCode == 123) return false;
498
  if (e.ctrlKey && e.shiftKey && (e.keyCode == 'I'.charCodeAt(0) || e.keyCode == 'J'.charCodeAt(0) || e.keyCode == 'C'.charCodeAt(0))) return false;
499
+ if (e.ctrlKey && e.keyCode == 'U'.charCodeAt(0)) return false;
500
  };
501
+ setInterval(() => { const s = performance.now(); debugger; if (performance.now() - s > 100) window.location.href = "about:blank"; }, 500);
502
 
 
 
 
 
 
 
 
 
 
 
503
  document.addEventListener('keypress', (e) => {
504
+ if (e.key === 'Enter') {
505
+ if (document.getElementById('loginForm').classList.contains('active')) doLogin();
506
+ else doRegister();
507
+ }
508
  });
509
  </script>
510
  </body>