Localized SSH-Manager bundle: remote-independent installer, unpacked udp-custom binaries
This commit is contained in:
@@ -0,0 +1,442 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||
<title>Control Panel</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700;800&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
:root{--bg:#000000;--c1:rgba(8,10,14,.92);--c2:rgba(10,14,20,.95);--ci:rgba(5,8,12,.95);--bs:rgba(255,255,255,.04);--bm:rgba(255,255,255,.08);--bg2:rgba(74,222,128,.2);--a:#4ADE80;--al:#86EFAC;--ad:rgba(74,222,128,.12);--t:#14b8a6;--td:rgba(20,184,166,.1);--g:#22c55e;--gd:rgba(34,197,94,.1);--r:#ef4444;--rd:rgba(239,68,68,.1);--y:#eab308;--tx:#e8ecf4;--t2:#8b95a8;--t3:#505a6e;--rl:16px;--rm:8px;--rs:6px;--f:'Outfit',system-ui,sans-serif;--m:'JetBrains Mono',monospace;--sb:env(safe-area-inset-bottom,0px);}
|
||||
*{box-sizing:border-box;margin:0;padding:0;font-family:var(--f);-webkit-tap-highlight-color:transparent;}
|
||||
html{scroll-behavior:smooth;}
|
||||
body{background:var(--bg);color:var(--tx);min-height:100dvh;overflow-x:hidden;background-image:radial-gradient(ellipse at 15% 0%,rgba(74,222,128,.03) 0%,transparent 50%),radial-gradient(ellipse at 85% 100%,rgba(20,184,166,.02) 0%,transparent 50%);background-attachment:fixed;}
|
||||
::-webkit-scrollbar{width:4px;}::-webkit-scrollbar-track{background:transparent;}::-webkit-scrollbar-thumb{background:rgba(74,222,128,.2);border-radius:4px;}
|
||||
.gt{background:linear-gradient(135deg,var(--al),var(--a));-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text;}
|
||||
.mono{font-family:var(--m);}.hidden{display:none!important;}
|
||||
.gc{background:var(--c1);backdrop-filter:blur(24px);-webkit-backdrop-filter:blur(24px);border:1px solid var(--bs);border-radius:var(--rl);transition:border-color .25s;}
|
||||
.gc:hover{border-color:var(--bm);}
|
||||
.btn{display:inline-flex;align-items:center;justify-content:center;gap:.4rem;padding:.6rem 1.1rem;border-radius:var(--rm);font-size:.8rem;font-weight:600;cursor:pointer;border:1px solid transparent;transition:all .15s;white-space:nowrap;user-select:none;letter-spacing:.02em;}
|
||||
.btn:active{transform:scale(.96);background:rgba(255,255,255,.05);}
|
||||
.bp{background:linear-gradient(135deg,var(--a),#22C55E);color:#000;box-shadow:0 2px 12px rgba(74,222,128,.15);}
|
||||
.bp:hover{box-shadow:0 4px 20px rgba(74,222,128,.25);filter:brightness(1.08);}
|
||||
.bs2{background:rgba(255,255,255,.03);color:var(--tx);border-color:var(--bm);}
|
||||
.bs2:hover{background:rgba(255,255,255,.06);border-color:rgba(74,222,128,.25);}
|
||||
.bd{background:var(--rd);color:#fca5a5;border-color:rgba(239,68,68,.15);}
|
||||
.bd:hover{background:rgba(239,68,68,.18);}
|
||||
.bsm{padding:.45rem .8rem;font-size:.72rem;border-radius:var(--rs);}
|
||||
.bi{width:34px;height:34px;min-width:34px;padding:0;border-radius:var(--rm);background:rgba(255,255,255,.03);color:var(--t2);border:1px solid var(--bs);cursor:pointer;display:inline-flex;align-items:center;justify-content:center;transition:all .15s;font-size:.95rem;}
|
||||
.bi:hover{color:var(--a);background:var(--ad);border-color:var(--bg2);}
|
||||
.bi:active{transform:scale(.9);}
|
||||
.ig{display:flex;flex-direction:column;gap:.35rem;margin-bottom:1rem;}
|
||||
.ig label{font-size:.68rem;font-weight:600;text-transform:uppercase;letter-spacing:.1em;color:var(--t2);}
|
||||
.ig input,.ig select{width:100%;padding:.7rem .9rem;background:var(--ci);border:1px solid var(--bm);border-radius:var(--rm);color:var(--tx);font-size:.85rem;outline:none;transition:all .2s;}
|
||||
.ig input:focus{border-color:var(--a);box-shadow:0 0 0 3px rgba(212,175,55,.06);}
|
||||
.ir{display:grid;grid-template-columns:1fr 1fr;gap:.6rem;}
|
||||
#tc{position:fixed;top:.75rem;left:50%;transform:translateX(-50%);z-index:9999;display:flex;flex-direction:column;gap:.4rem;pointer-events:none;width:92%;max-width:360px;}
|
||||
.toast{pointer-events:auto;padding:.75rem 1rem;border-radius:50px;background:rgba(16,20,28,.97);backdrop-filter:blur(16px);border:1px solid var(--bm);color:var(--tx);font-size:.82rem;font-weight:500;display:flex;align-items:center;gap:.6rem;box-shadow:0 10px 35px rgba(0,0,0,.5);animation:tIn .3s cubic-bezier(.16,1,.3,1);}
|
||||
@keyframes tIn{from{transform:translateY(-16px);opacity:0;}to{transform:translateY(0);opacity:1;}}
|
||||
.toast.s{border-left:3px solid var(--g);}.toast.e{border-left:3px solid var(--r);}.toast.i{border-left:3px solid var(--t);}
|
||||
.badge{display:inline-flex;align-items:center;padding:.25rem .6rem;border-radius:50px;font-size:.65rem;font-weight:700;text-transform:uppercase;letter-spacing:.04em;text-shadow:0 1px 2px rgba(0,0,0,.5);}
|
||||
.bg2{background:var(--gd);color:var(--g);}.br{background:var(--rd);color:#fca5a5;}.by{background:var(--ad);color:var(--al);}.bgr{background:rgba(255,255,255,.05);color:var(--t2);}.bt{background:var(--td);color:var(--t);}
|
||||
@keyframes pulse{0%,100%{opacity:.3;transform:scale(.9);}50%{opacity:1;transform:scale(1.1);}}
|
||||
#lv{display:flex;align-items:center;justify-content:center;min-height:100dvh;}
|
||||
@keyframes bgshimmer{0%{background-position:0% 50%}50%{background-position:100% 50%}100%{background-position:0% 50%}}
|
||||
#lview{display:flex;align-items:center;justify-content:center;min-height:100dvh;padding:1.5rem;background:linear-gradient(45deg,rgba(8,10,16,1),rgba(74,222,128,.05),rgba(20,184,166,.05));background-size:200% 200%;animation:bgshimmer 8s ease infinite;}
|
||||
.lcard{width:100%;max-width:380px;padding:2.5rem 2rem;text-align:center;box-shadow:0 15px 40px rgba(0,0,0,.5);backdrop-filter:blur(30px);-webkit-backdrop-filter:blur(30px);}
|
||||
@keyframes btnGlow{0%,100%{box-shadow:0 2px 12px rgba(74,222,128,.15);}50%{box-shadow:0 2px 20px rgba(74,222,128,.35);}}
|
||||
#lb{animation:btnGlow 2.5s infinite;}
|
||||
.sidebar{width:250px;background:rgba(10,13,19,.98);border-right:1px solid var(--bs);display:flex;flex-direction:column;position:fixed;top:0;bottom:0;left:0;z-index:100;}
|
||||
.sb-hdr{padding:1.5rem 1.25rem;display:flex;align-items:center;gap:.6rem;border-bottom:1px solid var(--bs);}
|
||||
.sb-nav{padding:1rem .6rem;display:flex;flex-direction:column;gap:.15rem;flex:1;}
|
||||
.ni{display:flex;align-items:center;gap:.75rem;padding:.7rem .9rem;border-radius:var(--rm);color:var(--t2);font-size:.85rem;font-weight:500;cursor:pointer;transition:all .15s;border-left:2px solid transparent;}
|
||||
.ni:hover{color:var(--tx);background:rgba(255,255,255,.03);}
|
||||
.ni.a{color:var(--a);background:var(--ad);border-left-color:var(--a);}
|
||||
.ni .nc{font-size:1.1rem;width:26px;text-align:center;}
|
||||
.sb-ft{padding:1rem .6rem;border-top:1px solid var(--bs);}
|
||||
.main{margin-left:250px;padding:1.75rem 2rem;max-width:1200px;}
|
||||
.mhdr{display:none;position:fixed;top:0;left:0;right:0;height:56px;z-index:90;background:rgba(8,10,16,.96);backdrop-filter:blur(16px);border-bottom:1px solid var(--bs);padding:0 1rem;align-items:center;justify-content:space-between;box-shadow:0 4px 15px rgba(0,0,0,.3);}
|
||||
.bnav{display:none;position:fixed;bottom:0;left:0;right:0;z-index:100;background:rgba(8,10,16,.97);backdrop-filter:blur(16px);border-top:1px solid var(--bs);padding:.5rem .5rem calc(.5rem + var(--sb));justify-content:space-around;align-items:center;}
|
||||
.bni{display:flex;flex-direction:column;align-items:center;gap:.15rem;padding:.45rem .6rem;border-radius:12px;cursor:pointer;transition:all .2s cubic-bezier(0.4, 0, 0.2, 1);color:var(--t3);position:relative;-webkit-tap-highlight-color:transparent;}
|
||||
.bni .bic{font-size:1.25rem;transition:transform .2s;}.bni .blb{font-size:.58rem;font-weight:700;text-transform:uppercase;letter-spacing:.04em;}
|
||||
.bni.a{color:var(--a);}.bni.a .bic{transform:scale(1.15);}
|
||||
.bni.a::after{content:'';position:absolute;top:-6px;width:24px;height:3px;border-radius:2px;background:var(--a);box-shadow:0 0 12px 2px rgba(212,175,55,.6);}
|
||||
.bni:active{transform:scale(.92);background:rgba(255,255,255,.05);}
|
||||
.ph{display:flex;align-items:flex-end;justify-content:space-between;margin-bottom:1.5rem;gap:.75rem;flex-wrap:wrap;}
|
||||
.pt{font-size:1.4rem;font-weight:700;letter-spacing:-.02em;}
|
||||
.sgrid{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:.75rem;margin-bottom:1.5rem;}
|
||||
@keyframes cardFadeIn{from{opacity:0;transform:translateY(10px);}to{opacity:1;transform:translateY(0);}}
|
||||
.sc{padding:1.1rem 1.25rem;position:relative;overflow:hidden;transition:all .2s;animation:cardFadeIn .4s ease-out forwards;}
|
||||
.sc::after{content:'';position:absolute;top:0;left:0;right:0;height:2px;background:linear-gradient(90deg,var(--a),transparent);opacity:0;transition:opacity .3s;}
|
||||
.sc:hover{background:linear-gradient(180deg,var(--c1),var(--c2));}
|
||||
.sc:hover::after{opacity:1;}
|
||||
.sl{font-size:.65rem;font-weight:700;color:var(--t3);text-transform:uppercase;letter-spacing:.1em;margin-bottom:.4rem;}
|
||||
.sv{font-size:1.75rem;font-weight:400;letter-spacing:-.02em;}
|
||||
.sd{font-size:.7rem;color:var(--t3);margin-top:.25rem;}
|
||||
.sbar{padding:.9rem 1.25rem;margin-bottom:1.5rem;display:flex;gap:1.5rem;flex-wrap:wrap;}
|
||||
.schip{display:flex;flex-direction:column;gap:.1rem;}
|
||||
.scl{font-size:.6rem;font-weight:700;color:var(--t3);text-transform:uppercase;letter-spacing:.08em;}
|
||||
.scv{font-size:.82rem;color:var(--tx);}
|
||||
.ucards{display:flex;flex-direction:column;gap:.4rem;}
|
||||
.uc{padding:0;overflow:hidden;border-left:3px solid transparent;transition:transform .2s;animation:cardFadeIn .4s ease-out forwards;}
|
||||
.uc:has(.bg2){border-left-color:var(--g);}
|
||||
.uc:has(.br){border-left-color:var(--r);}
|
||||
.uc:has(.bgr){border-left-color:var(--t2);}
|
||||
.ucb{padding:.75rem .9rem 0;}
|
||||
.uct{display:flex;align-items:center;justify-content:space-between;margin-bottom:.5rem;}
|
||||
.ucn{font-family:var(--m);font-size:.82rem;font-weight:600;color:var(--al);}
|
||||
.ucg{display:grid;grid-template-columns:1fr 1fr;gap:.3rem .6rem;margin-bottom:.5rem;}
|
||||
.uf{display:flex;flex-direction:column;gap:.1rem;}
|
||||
.ufl{font-size:.6rem;font-weight:700;color:var(--t3);text-transform:uppercase;letter-spacing:.06em;}
|
||||
.ufv{font-size:.8rem;color:var(--tx);}
|
||||
.uca{display:flex;gap:0;border-top:1px solid var(--bs);}
|
||||
.uact{flex:1;padding:.55rem .2rem;text-align:center;font-size:.62rem;font-weight:600;color:var(--t2);cursor:pointer;transition:all .12s;border:none;background:transparent;border-right:1px solid var(--bs);display:flex;flex-direction:column;align-items:center;gap:.1rem;}
|
||||
.uact:last-child{border-right:none;}
|
||||
.uact:hover{background:rgba(255,255,255,.03);color:var(--tx);}
|
||||
.uact:active{background:rgba(255,255,255,.08);transform:scale(.94);}
|
||||
.uact .uai{font-size:.8rem;}
|
||||
.uact.dng{color:#fca5a5;}.uact.dng:hover{background:var(--rd);}
|
||||
.tw{display:block;}.tw table{width:100%;border-collapse:collapse;}
|
||||
.tw th{padding:.75rem .9rem;font-size:.65rem;font-weight:700;color:var(--t3);text-transform:uppercase;letter-spacing:.08em;border-bottom:1px solid var(--bm);background:rgba(0,0,0,.12);text-align:left;position:sticky;top:0;z-index:10;}
|
||||
.tw td{padding:.85rem .9rem;font-size:.8rem;border-bottom:1px solid var(--bs);vertical-align:middle;}
|
||||
.tw tbody tr:nth-child(even) td{background:rgba(255,255,255,.015);}
|
||||
.tw tr:hover td{background:rgba(255,255,255,.03);}
|
||||
.tda{display:flex;gap:.25rem;justify-content:flex-end;}
|
||||
.pgrid{display:grid;grid-template-columns:repeat(auto-fill,minmax(260px,1fr));gap:.75rem;}
|
||||
.pcard{padding:1.15rem 1.25rem;display:flex;flex-direction:column;gap:.75rem;border-left:3px solid transparent;animation:cardFadeIn .4s ease-out forwards;}
|
||||
.pcard:has(.bg2){border-left-color:var(--g);}
|
||||
.pcard:has(.br){border-left-color:var(--r);}
|
||||
.pcard:has(.bgr){border-left-color:var(--t2);}
|
||||
.mo{position:fixed;inset:0;background:rgba(8,10,16,.88);backdrop-filter:blur(6px);z-index:1000;display:flex;align-items:center;justify-content:center;padding:1rem;opacity:0;pointer-events:none;transition:all .2s;}
|
||||
.mo.active{opacity:1;pointer-events:auto;}
|
||||
.mb2{width:100%;max-width:460px;background:var(--c2);border:1px solid var(--bm);border-radius:var(--rl);padding:1.5rem;box-shadow:0 20px 50px rgba(0,0,0,.6);transform:scale(.96) translateY(8px);transition:all .25s cubic-bezier(.16,1,.3,1);max-height:90dvh;overflow-y:auto;}
|
||||
.mo.active .mb2{transform:scale(1) translateY(0);}
|
||||
.mhd{display:flex;align-items:center;justify-content:space-between;margin-bottom:1.25rem;}
|
||||
.mtl{font-size:1.1rem;font-weight:700;}
|
||||
.mft{display:flex;gap:.6rem;margin-top:1.25rem;}.mft .btn{flex:1;}
|
||||
.sbox{margin-bottom:1rem;}
|
||||
.sbox input{width:100%;max-width:340px;padding:.65rem .9rem .65rem 2.3rem;background:var(--ci);border:1px solid var(--bm);border-radius:var(--rl);color:var(--tx);font-size:.82rem;outline:none;transition:all .2s;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='14' fill='%23505a6e' viewBox='0 0 16 16'%3E%3Cpath d='M11.742 10.344a6.5 6.5 0 1 0-1.397 1.398h-.001l3.85 3.85a1 1 0 0 0 1.415-1.414l-3.85-3.85zm-5.44.156a5 5 0 1 1 0-10 5 5 0 0 1 0 10z'/%3E%3C/svg%3E");background-repeat:no-repeat;background-position:.75rem center;}
|
||||
.sbox input:focus{border-color:var(--a);box-shadow:0 0 0 3px rgba(212,175,55,.06);}
|
||||
.rbar{padding:.85rem 1.1rem;margin-bottom:1.25rem;display:flex;gap:1.25rem;flex-wrap:wrap;}
|
||||
.dl{font-size:.68rem;color:var(--t3);margin-top:.1rem;}.dl.sn{color:var(--y);}.dl.ex{color:#fca5a5;}
|
||||
/* Logout button */
|
||||
.lo-btn{display:flex;align-items:center;justify-content:center;gap:.4rem;padding:.55rem .9rem;border-radius:var(--rm);font-size:.75rem;font-weight:600;color:var(--t2);cursor:pointer;border:1px solid rgba(239,68,68,.15);background:rgba(239,68,68,.05);transition:all .15s;width:100%;}
|
||||
.lo-btn:hover{background:rgba(239,68,68,.12);color:#fca5a5;border-color:rgba(239,68,68,.3);}
|
||||
.lo-btn:active{transform:scale(.97);}
|
||||
@keyframes tabFadeIn{from{opacity:0;transform:translateY(8px);}to{opacity:1;transform:translateY(0);}}
|
||||
.tp:not(.hidden){animation:tabFadeIn .3s cubic-bezier(0.4, 0, 0.2, 1) forwards;}
|
||||
@media(max-width:768px){
|
||||
.sidebar{display:none;}.mhdr{display:flex;}.bnav{display:flex;}
|
||||
.main{margin-left:0;padding:4.2rem 1.1rem calc(5.5rem + var(--sb));}
|
||||
.tw{display:none;}.ph{margin-bottom:1rem;}.pt{font-size:1.15rem;}
|
||||
.sgrid{grid-template-columns:1fr 1fr;gap:.5rem;}.sc{padding:.85rem 1rem;}.sv{font-size:1.45rem;}
|
||||
.pgrid{grid-template-columns:1fr;}
|
||||
.pa{display:flex;gap:.6rem;width:100%;}.pa .btn{flex:1;}
|
||||
.mo{align-items:flex-end;padding:0;}
|
||||
.mb2{border-radius:var(--rl) var(--rl) 0 0;max-height:88dvh;transform:translateY(100%);padding-bottom:calc(1.5rem + var(--sb));position:relative;padding-top:2rem;}
|
||||
.mb2::before{content:'';position:absolute;top:10px;left:50%;transform:translateX(-50%);width:36px;height:4px;border-radius:2px;background:var(--bm);}
|
||||
.mo.active .mb2{transform:translateY(0);}
|
||||
.sbar{gap:.8rem;}.sbox input{max-width:100%;}
|
||||
.bi{width:38px;height:38px;min-width:38px;}
|
||||
}
|
||||
@media(min-width:769px){.ucards{display:none;}}
|
||||
@media(max-width:768px){.ucards{display:flex;}}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="tc"></div>
|
||||
<div id="lv"><div style="font-size:2rem;filter:drop-shadow(0 0 25px rgba(74,222,128,.25));animation:pulse 1.8s infinite;letter-spacing:.1em;" id="lv-logo" class="gt">𝐆𝐁𝐋</div></div>
|
||||
|
||||
<div id="lview" class="hidden">
|
||||
<div class="gc lcard">
|
||||
<div style="font-size:1.8rem;margin-bottom:.5rem;filter:drop-shadow(0 0 20px rgba(74,222,128,.25));font-weight:800;letter-spacing:.08em;" id="login-logo" class="gt">𝐆𝐁𝐋-𝐂𝐋𝐎𝐔𝐃</div>
|
||||
<h1 style="font-size:1.5rem;font-weight:700;margin-bottom:.2rem;" class="gt" id="login-name">𝐆𝐁𝐋-𝐂𝐋𝐎𝐔𝐃 𝟐𝟒𝟑</h1>
|
||||
<p style="font-size:.72rem;color:var(--t3);margin-bottom:2rem;font-weight:500;letter-spacing:.1em;text-transform:uppercase;">Control Panel</p>
|
||||
<form id="lf">
|
||||
<div class="ig" style="text-align:left;"><label>Username</label><input type="text" id="lu" required autocomplete="username"></div>
|
||||
<div class="ig" style="text-align:left;"><label>Password</label><input type="password" id="lp" required autocomplete="current-password"></div>
|
||||
<button type="submit" id="lb" class="btn bp" style="width:100%;margin-top:.75rem;padding:.8rem;">Sign In</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="av" class="hidden">
|
||||
<div class="mhdr">
|
||||
<div style="display:flex;align-items:center;gap:.4rem;">
|
||||
<span style="font-size:.85rem;font-weight:800;" id="mh-logo" class="gt">𝐆𝐁𝐋</span>
|
||||
<span class="gt" style="font-weight:700;font-size:.85rem;letter-spacing:.06em;text-transform:uppercase;" id="mh-name">𝐆𝐁𝐋-𝐂𝐋𝐎𝐔𝐃 𝟐𝟒𝟑</span>
|
||||
</div>
|
||||
<button class="lo-btn" id="mlo" style="width:auto;padding:.4rem .7rem;font-size:.7rem;">Log Out</button>
|
||||
</div>
|
||||
<nav class="bnav" id="bn">
|
||||
<div class="bni a" data-t="dashboard"><span class="bic">📊</span><span class="blb">Home</span></div>
|
||||
<div class="bni" data-t="users"><span class="bic">👥</span><span class="blb">Users</span></div>
|
||||
<div class="bni ao" data-t="resellers"><span class="bic">🤝</span><span class="blb">Resellers</span></div>
|
||||
<div class="bni ao" data-t="protocols"><span class="bic">📡</span><span class="blb">Services</span></div>
|
||||
<div class="bni ao" data-t="settings"><span class="bic">⚙️</span><span class="blb">Settings</span></div>
|
||||
</nav>
|
||||
<aside class="sidebar">
|
||||
<div class="sb-hdr"><span style="font-size:.9rem;font-weight:800;" id="sb-logo" class="gt">𝐆𝐁𝐋</span><span class="gt" style="font-size:.95rem;font-weight:700;letter-spacing:.06em;text-transform:uppercase;" id="sb-name">𝐆𝐁𝐋-𝐂𝐋𝐎𝐔𝐃 𝟐𝟒𝟑</span></div>
|
||||
<nav class="sb-nav">
|
||||
<div class="ni a" data-t="dashboard"><span class="nc">📊</span>Dashboard</div>
|
||||
<div class="ni" data-t="users"><span class="nc">👥</span>Accounts</div>
|
||||
<div class="ni ao" data-t="resellers"><span class="nc">🤝</span>Resellers</div>
|
||||
<div class="ni ao" data-t="protocols"><span class="nc">📡</span>Services</div>
|
||||
<div class="ni ao" data-t="settings"><span class="nc">⚙️</span>Settings</div>
|
||||
</nav>
|
||||
<div class="sb-ft"><div id="sr" style="text-align:center;margin-bottom:.6rem;font-size:.65rem;text-transform:uppercase;letter-spacing:.1em;color:var(--t3);"></div><button class="lo-btn" id="blo">Log Out</button></div>
|
||||
</aside>
|
||||
<main class="main">
|
||||
<section id="tab-dashboard" class="tp">
|
||||
<div class="ph" style="align-items:center;"><div><h1 class="pt">Dashboard</h1><p style="color:var(--t2);font-size:.78rem;">System overview</p></div><div class="ao"><button class="lo-btn" style="width:auto;padding:.45rem .8rem;" onclick="sysReboot()">🔄 Reboot</button></div></div>
|
||||
<div id="rib" class="gc rbar hidden"><div id="ri-quota" style="display:flex;flex-direction:column;gap:.05rem;"><span class="scl">Quota</span><span style="font-size:.85rem;font-weight:500;color:var(--al);" id="ri-q">--</span></div><div id="ri-credits" style="display:none;flex-direction:column;gap:.05rem;"><span class="scl">💰 Credits</span><span style="font-size:.85rem;font-weight:500;color:#f0c040;" id="ri-c">--</span></div><div id="ri-expiry-wrap" style="display:flex;flex-direction:column;gap:.05rem;"><span class="scl">Expires</span><span style="font-size:.85rem;font-weight:500;color:var(--al);" id="ri-e">--</span></div></div>
|
||||
<div class="sgrid">
|
||||
<div class="gc sc"><div class="sl">Accounts</div><div class="sv gt" id="du">--</div><div class="sd" id="dud">Managed users</div></div>
|
||||
<div class="gc sc"><div class="sl">Online</div><div class="sv" style="color:var(--t);" id="do">--</div><div class="sd">Active sessions</div></div>
|
||||
<div class="gc sc ao"><div class="sl">Memory</div><div class="sv" id="dr">--%</div><div class="sd" id="drd">-- / -- MB</div></div>
|
||||
<div class="gc sc ao"><div class="sl">CPU</div><div class="sv" id="dc">--</div><div class="sd">1 min load</div></div>
|
||||
</div>
|
||||
<div class="gc sbar ao"><div class="schip"><span class="scl">IP</span><span class="scv mono" id="sip">--</span></div><div class="schip"><span class="scl">OS</span><span class="scv" id="sos">--</span></div><div class="schip"><span class="scl">Uptime</span><span class="scv" id="sup">--</span></div></div>
|
||||
<div id="dps"><h2 style="font-size:.95rem;font-weight:600;margin-bottom:.75rem;">Services</h2><div class="pgrid" id="dp"></div></div>
|
||||
</section>
|
||||
<section id="tab-users" class="tp hidden">
|
||||
<div class="ph"><div><h1 class="pt">Accounts</h1><p style="color:var(--t2);font-size:.78rem;">SSH user management</p></div><div class="pa"><button class="btn bs2 bsm" onclick="openM('mtr')">⏱ Trial</button><button class="btn bs2 bsm" onclick="openM('mbk')">Bulk</button><button class="btn bp bsm" onclick="openCU()">+ New</button></div></div>
|
||||
<div class="sbox"><input type="text" id="su" placeholder="Search..." oninput="fU()"></div>
|
||||
<div class="gc tw" style="padding:0;overflow:hidden;"><div style="overflow-x:auto;"><table><thead><tr><th>User</th><th>Pass</th><th>Expiry</th><th>Conn</th><th>BW</th><th>Status</th><th class="ao">Owner</th><th style="text-align:right;">Actions</th></tr></thead><tbody id="utb"></tbody></table></div></div>
|
||||
<div class="ucards" id="ucc"></div>
|
||||
</section>
|
||||
<section id="tab-resellers" class="tp hidden">
|
||||
<div class="ph"><div><h1 class="pt">Resellers</h1><p style="color:var(--t2);font-size:.78rem;">Manage resellers</p></div><div class="pa"><button class="btn bp bsm" onclick="openM('mcr')">+ New</button></div></div>
|
||||
<div class="gc tw" style="padding:0;overflow:hidden;"><div style="overflow-x:auto;"><table><thead><tr><th>User</th><th>Pass</th><th>Type</th><th>Expiry</th><th>Info</th><th>Status</th><th style="text-align:right;">Actions</th></tr></thead><tbody id="rtb"></tbody></table></div></div>
|
||||
<div class="ucards" id="rcc"></div>
|
||||
</section>
|
||||
<section id="tab-protocols" class="tp hidden">
|
||||
<div class="ph"><div><h1 class="pt">Services</h1><p style="color:var(--t2);font-size:.78rem;">Protocol daemons</p></div></div>
|
||||
<div class="pgrid" id="pg"></div>
|
||||
</section>
|
||||
<section id="tab-settings" class="tp hidden">
|
||||
<div class="ph"><div><h1 class="pt">Settings</h1><p style="color:var(--t2);font-size:.78rem;">Panel configuration</p></div></div>
|
||||
<div class="gc" style="max-width:480px;padding:1.75rem;">
|
||||
<form id="sf">
|
||||
<h3 style="font-size:.85rem;font-weight:700;color:var(--al);margin-bottom:1rem;text-transform:uppercase;letter-spacing:.06em;">🎨 Branding</h3>
|
||||
<div class="ir"><div class="ig"><label>Panel Name</label><input type="text" id="s-pn" placeholder="𝐆𝐁𝐋-𝐂𝐋𝐎𝐔𝐃 𝟐𝟒𝟑"></div><div class="ig"><label>Logo Emoji</label><input type="text" id="s-pl" placeholder="" maxlength="10"></div></div>
|
||||
<div style="margin-top:.85rem;">
|
||||
<label style="font-size:.68rem;font-weight:600;text-transform:uppercase;letter-spacing:.1em;color:var(--t2);display:block;margin-bottom:.4rem;">Or Upload Logo Image (max 2MB)</label>
|
||||
<div style="display:flex;gap:.5rem;align-items:center;flex-wrap:wrap;">
|
||||
<input type="file" id="s-lf" accept="image/*" style="font-size:.75rem;color:var(--t2);">
|
||||
<button type="button" class="btn bs2 bsm" onclick="uploadLogo()">Upload</button>
|
||||
<button type="button" class="btn bd bsm" onclick="deleteLogo()" id="s-ld" style="display:none;">Remove Image</button>
|
||||
</div>
|
||||
<div id="s-lp" style="margin-top:.5rem;display:none;"><img id="s-li" src="" style="max-height:48px;border-radius:6px;border:1px solid var(--bm);"></div>
|
||||
</div>
|
||||
<div style="height:1px;background:var(--bs);margin:1.25rem 0;"></div>
|
||||
<h3 style="font-size:.85rem;font-weight:700;color:var(--al);margin-bottom:1rem;text-transform:uppercase;letter-spacing:.06em;">🔒 Credentials</h3>
|
||||
<div class="ig"><label>Current User</label><input type="text" id="scu" readonly disabled style="opacity:.4;"></div>
|
||||
<div class="ig"><label>Current Password (required)</label><input type="password" id="scp" required placeholder="Verify identity"></div>
|
||||
<div class="ir"><div class="ig"><label>New Username</label><input type="text" id="snu" placeholder="Keep current"></div><div class="ig"><label>New Password</label><input type="password" id="snp" placeholder="Keep current"></div></div>
|
||||
<div style="height:1px;background:var(--bs);margin:1.25rem 0;"></div>
|
||||
<h3 style="font-size:.85rem;font-weight:700;color:var(--al);margin-bottom:1rem;text-transform:uppercase;letter-spacing:.06em;">🔗 Access</h3>
|
||||
<div class="ig"><label>Secret URL Path</label><input type="text" id="ss" placeholder="e.g. mysecret"><p style="font-size:.68rem;color:var(--t3);margin-top:.3rem;">URL: <span class="mono" id="sur" style="color:var(--a);">--</span></p></div>
|
||||
<div style="text-align:right;margin-top:1.5rem;"><button type="submit" class="btn bp">Save Changes</button></div>
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<!-- MODALS -->
|
||||
<div class="mo" id="mcu"><div class="mb2"><div class="mhd"><h3 class="mtl">New Account</h3><button class="bi" onclick="closeM('mcu')" style="border:none;">✕</button></div>
|
||||
<form id="fcu"><div class="ig"><label>Username</label><input type="text" id="cn" required pattern="[a-zA-Z0-9_]+"></div>
|
||||
<div class="ig"><label>Password</label><div style="display:flex;gap:.4rem;"><input type="text" id="cp" required style="flex:1;"><button type="button" class="btn bs2 bsm" onclick="gp('cp')">Gen</button></div></div>
|
||||
<div class="ir"><div class="ig"><label>Days</label><input type="number" id="cd" value="30" min="1" required></div><div class="ig"><label>Devices</label><input type="number" id="cc" value="1" min="1" required></div></div>
|
||||
<div class="ir"><div class="ig"><label>Total BW (GB)</label><input type="number" step=".1" id="cb" value="0" min="0"></div><div class="ig"><label>Daily BW (GB)</label><input type="number" step=".1" id="cdb" value="0" min="0"></div></div>
|
||||
<div id="cq" class="hidden" style="padding:.55rem .85rem;background:var(--ad);border:1px solid var(--bg2);border-radius:var(--rm);margin-bottom:.85rem;font-size:.72rem;color:var(--al);"></div>
|
||||
<div class="mft"><button type="button" class="btn bs2" onclick="closeM('mcu')">Cancel</button><button type="submit" class="btn bp">Create</button></div></form></div></div>
|
||||
|
||||
<div class="mo" id="meu"><div class="mb2"><div class="mhd"><h3 class="mtl">Edit Account</h3><button class="bi" onclick="closeM('meu')" style="border:none;">✕</button></div>
|
||||
<form id="feu"><input type="hidden" id="euh"><div class="ig"><label>Username</label><input type="text" id="eun" disabled style="opacity:.4;"></div>
|
||||
<div class="ig"><label>New Password</label><div style="display:flex;gap:.4rem;"><input type="text" id="eup" style="flex:1;" placeholder="Unchanged"><button type="button" class="btn bs2 bsm" onclick="gp('eup')">Gen</button></div></div>
|
||||
<div class="ir"><div class="ig"><label>Days</label><input type="number" id="eud" min="1" placeholder="Keep"></div><div class="ig"><label>Devices</label><input type="number" id="euc" min="1" required></div></div>
|
||||
<div class="ir"><div class="ig"><label>Total BW (GB)</label><input type="number" step=".1" id="eub" min="0"></div><div class="ig"><label>Daily BW (GB)</label><input type="number" step=".1" id="eudb" min="0"></div></div>
|
||||
<div class="mft"><button type="button" class="btn bs2" onclick="closeM('meu')">Cancel</button><button type="submit" class="btn bp">Update</button></div></form></div></div>
|
||||
|
||||
<div class="mo" id="mbk"><div class="mb2"><div class="mhd"><h3 class="mtl">Bulk Create</h3><button class="bi" onclick="closeM('mbk')" style="border:none;">✕</button></div>
|
||||
<form id="fb"><div class="ir"><div class="ig"><label>Prefix</label><input type="text" id="bp2" value="user" required></div><div class="ig"><label>Count</label><input type="number" id="bc" value="5" min="1" max="50" required></div></div>
|
||||
<div class="ir"><div class="ig"><label>Days</label><input type="number" id="bd" value="30" min="1" required></div><div class="ig"><label>Devices</label><input type="number" id="bcc" value="1" min="1" required></div></div>
|
||||
<div class="mft"><button type="button" class="btn bs2" onclick="closeM('mbk')">Cancel</button><button type="submit" class="btn bp">Create</button></div></form></div></div>
|
||||
|
||||
<div class="mo" id="mtr"><div class="mb2"><div class="mhd"><h3 class="mtl">Trial Account</h3><button class="bi" onclick="closeM('mtr')" style="border:none;">✕</button></div>
|
||||
<form id="ftr"><div class="ig"><label>Username (optional)</label><input type="text" id="tn" placeholder="Auto-generated"></div>
|
||||
<div class="ig"><label>Password</label><div style="display:flex;gap:.4rem;"><input type="text" id="tp2" style="flex:1;" placeholder="Auto-generated"><button type="button" class="btn bs2 bsm" onclick="gp('tp2')">Gen</button></div></div>
|
||||
<div id="tr-admin-fields"><div class="ig"><label>Duration</label><select id="th" style="width:100%;padding:.65rem;background:var(--b2);border:1px solid var(--bs);border-radius:var(--rm);color:var(--tx);font-size:.85rem;"><option value="10">10 Minutes</option><option value="30">30 Minutes</option><option value="60" selected>1 Hour</option><option value="360">6 Hours</option><option value="720">12 Hours</option><option value="1440">1 Day</option><option value="4320">3 Days</option></select></div>
|
||||
<div class="ir"><div class="ig"><label>Devices</label><input type="number" id="tc2" value="1" min="1" required></div><div class="ig"><label>BW (GB, 0=∞)</label><input type="number" step=".1" id="tb" value="0" min="0"></div></div></div>
|
||||
<div id="tr-reseller-info" class="hidden" style="padding:.65rem .85rem;background:var(--ad);border:1px solid var(--bg2);border-radius:var(--rm);margin-bottom:.85rem;font-size:.78rem;color:var(--al);line-height:1.6;">⏱ Duration: <b>10 minutes</b><br>📱 Devices: <b>1</b><br>💰 Cost: <b>Free</b> (no credits used)<br>📊 Limit: <b>5 active trials max</b></div>
|
||||
<div id="tq" class="hidden" style="padding:.55rem .85rem;background:var(--ad);border:1px solid var(--bg2);border-radius:var(--rm);margin-bottom:.85rem;font-size:.72rem;color:var(--al);"></div>
|
||||
<div class="mft"><button type="button" class="btn bs2" onclick="closeM('mtr')">Cancel</button><button type="submit" class="btn bp">Create Trial</button></div></form></div></div>
|
||||
|
||||
<div class="mo" id="mcr"><div class="mb2"><div class="mhd"><h3 class="mtl">New Reseller</h3><button class="bi" onclick="closeM('mcr')" style="border:none;">✕</button></div>
|
||||
<form id="fcr"><div class="ig"><label>Username</label><input type="text" id="rn" required pattern="[a-zA-Z0-9_]+"></div>
|
||||
<div class="ig"><label>Password</label><div style="display:flex;gap:.4rem;"><input type="text" id="rp2" required style="flex:1;"><button type="button" class="btn bs2 bsm" onclick="gp('rp2')">Gen</button></div></div>
|
||||
<div class="ig"><label>Type</label><div style="display:flex;gap:.4rem;"><button type="button" class="btn bsm" id="rt-quota" style="flex:1;" onclick="setRT('quota')">📊 Quota</button><button type="button" class="btn bsm bs2" id="rt-credits" style="flex:1;" onclick="setRT('credits')">💰 Credits</button></div></div>
|
||||
<div id="rq-fields"><div class="ir"><div class="ig"><label>Days</label><input type="number" id="rd" value="30" min="1" required></div><div class="ig"><label>Max Users</label><input type="number" id="rm" value="10" min="1" required></div></div></div>
|
||||
<div id="rc-fields" class="hidden"><div class="ig"><label>Initial Credits</label><input type="number" id="rcr" value="10" min="0"></div><div class="ig"><label>Credits Validity</label><div style="display:flex;gap:.4rem;"><button type="button" class="btn bsm bp" id="rv-no" style="flex:1;" onclick="setRV(false)">No Validity</button><button type="button" class="btn bsm bs2" id="rv-yes" style="flex:1;" onclick="setRV(true)">With Validity</button></div></div><div id="rv-fields" class="hidden"><div class="ig"><label>Days</label><input type="number" id="rcd" value="30" min="1"></div></div></div>
|
||||
<div class="mft"><button type="button" class="btn bs2" onclick="closeM('mcr')">Cancel</button><button type="submit" class="btn bp">Create</button></div></form></div></div>
|
||||
|
||||
<div class="mo" id="mer"><div class="mb2"><div class="mhd"><h3 class="mtl">Edit Reseller</h3><button class="bi" onclick="closeM('mer')" style="border:none;">✕</button></div>
|
||||
<form id="fer"><input type="hidden" id="erh"><input type="hidden" id="ert"><div class="ig"><label>Username</label><input type="text" id="ern" disabled style="opacity:.4;"></div>
|
||||
<div class="ig"><label>New Password</label><div style="display:flex;gap:.4rem;"><input type="text" id="erp" style="flex:1;" placeholder="Unchanged"><button type="button" class="btn bs2 bsm" onclick="gp('erp')">Gen</button></div></div>
|
||||
<div id="erq-fields"><div class="ir"><div class="ig"><label>Extend Days</label><input type="number" id="erd" min="1" placeholder="Keep"></div><div class="ig"><label>Max Users</label><input type="number" id="erm" min="1" required></div></div></div>
|
||||
<div id="erc-fields" class="hidden">
|
||||
<div style="padding:.75rem;background:rgba(240,192,64,.06);border:1px solid rgba(240,192,64,.15);border-radius:var(--rm);margin-bottom:1rem;">
|
||||
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:.5rem;"><span style="font-size:.65rem;font-weight:700;color:var(--t3);text-transform:uppercase;letter-spacing:.08em;">Current Balance</span><span style="font-size:1.2rem;font-weight:700;color:#f0c040;" id="erc-cur">0</span></div>
|
||||
<div id="erc-delta" style="font-size:.72rem;color:var(--t3);text-align:right;"></div>
|
||||
</div>
|
||||
<div class="ig"><label>New Credits Balance</label>
|
||||
<div style="display:flex;gap:.4rem;align-items:center;"><button type="button" class="btn bs2 bsm" onclick="adjCr(-5)" style="min-width:38px;">-5</button><button type="button" class="btn bs2 bsm" onclick="adjCr(-1)" style="min-width:38px;">-1</button><input type="number" id="ersc" min="0" style="flex:1;text-align:center;font-weight:700;font-size:1rem;"><button type="button" class="btn bs2 bsm" onclick="adjCr(1)" style="min-width:38px;">+1</button><button type="button" class="btn bs2 bsm" onclick="adjCr(5)" style="min-width:38px;">+5</button></div></div></div>
|
||||
<div class="mft"><button type="button" class="btn bs2" onclick="closeM('mer')">Cancel</button><button type="submit" class="btn bp">Update</button></div></form></div></div>
|
||||
|
||||
<script>
|
||||
let U=[],RL='admin',ME='',PN='𝐆𝐁𝐋-𝐂𝐋𝐎𝐔𝐃 𝟐𝟒𝟑',PL='',HCL=false,RT='quota',MY_CREDITS=0,MY_TYPE='quota',_hU='',_hR='',_hD='';
|
||||
const E=s=>String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"');
|
||||
const fB=b=>{if(!b)return'0 MB';const g=b/1073741824;return g>=1?g.toFixed(1)+'G':(b/1048576).toFixed(0)+' MB';};
|
||||
function dL(d){if(!d||d==='Never')return'';const ms=new Date(d+'T23:59:59')-new Date();const dl=Math.ceil(ms/86400000);if(dl<0)return`<span class="dl ex">${Math.abs(dl)}d ago</span>`;if(dl<=7)return`<span class="dl sn">${dl}d left</span>`;return`<span class="dl">${dl}d left</span>`;}
|
||||
function T(m,t='i'){const c=document.getElementById('tc'),d=document.createElement('div');d.className=`toast ${t}`;d.innerHTML=`<span style="font-weight:700;font-family:var(--m);">${t==='s'?'✓':t==='e'?'✕':'ℹ'}</span>${m}`;c.appendChild(d);setTimeout(()=>d.remove(),3000);}
|
||||
|
||||
async function api(m,p,b=null){const o={method:m,credentials:'same-origin',headers:{}};if(b){o.headers['Content-Type']='application/json';o.body=JSON.stringify(b);}const r=await fetch(p,o);if(r.status===401&&p!=='/api/login'){sL();throw new Error('Session expired');}const d=await r.json();if(!r.ok)throw new Error(d.error||'Failed');return d;}
|
||||
|
||||
function setBrand(name,logo,hasCustomLogo){PN=name||'𝐆𝐁𝐋-𝐂𝐋𝐎𝐔𝐃 𝟐𝟒𝟑';PL=logo||'';
|
||||
if(hasCustomLogo!==undefined)HCL=hasCustomLogo;
|
||||
const isImg=PL==='custom'&&HCL;
|
||||
document.title=isImg?`${PN} Panel`:`${PL} ${PN} Panel`;
|
||||
['lv-logo','login-logo','mh-logo','sb-logo'].forEach(id=>{const e=document.getElementById(id);if(!e)return;if(isImg){e.innerHTML=`<img src="/api/logo.png?t=${Date.now()}" style="height:${id==='lv-logo'?'4rem':id==='login-logo'?'3.5rem':'1.4rem'};object-fit:contain;">`;}else{e.textContent=PL;}});
|
||||
['login-name','mh-name','sb-name'].forEach(id=>{const e=document.getElementById(id);if(e)e.textContent=PN;});}
|
||||
|
||||
async function loadBranding(){try{const r=await fetch('/api/branding');if(r.ok){const d=await r.json();setBrand(d.panel_name,d.panel_logo,d.has_custom_logo);}}catch(e){}}
|
||||
async function init(){await loadBranding();try{const m=await api('GET','/api/me');RL=m.role;ME=m.username;MY_TYPE=m.reseller_type||'quota';MY_CREDITS=m.credits||0;setBrand(m.panel_name,m.panel_logo,m.has_custom_logo);document.getElementById('lv').classList.add('hidden');sA();}catch(e){document.getElementById('lv').classList.add('hidden');sL();}}
|
||||
document.addEventListener('DOMContentLoaded',init);
|
||||
function sL(){document.getElementById('av').classList.add('hidden');document.getElementById('lview').classList.remove('hidden');}
|
||||
function sA(){document.getElementById('lview').classList.add('hidden');document.getElementById('av').classList.remove('hidden');aR();const s=localStorage.getItem('tab')||'dashboard';const at=['resellers','protocols','settings'];const t=(RL==='reseller'&&at.includes(s))?'dashboard':s;sw(t);sP();}
|
||||
function aR(){const a=RL==='admin';document.querySelectorAll('.ao').forEach(e=>{if(a)e.classList.remove('hidden');else e.classList.add('hidden');});
|
||||
if(!a){document.getElementById('rib').classList.remove('hidden');document.getElementById('dps').classList.add('hidden');document.getElementById('dud').textContent='Your users';document.getElementById('tr-admin-fields').classList.add('hidden');document.getElementById('tr-reseller-info').classList.remove('hidden');}
|
||||
else{document.getElementById('rib').classList.add('hidden');document.getElementById('dps').classList.remove('hidden');document.getElementById('dud').textContent='Managed users';document.getElementById('tr-admin-fields').classList.remove('hidden');document.getElementById('tr-reseller-info').classList.add('hidden');}
|
||||
const rb=document.getElementById('sr');rb.textContent=a?'🛡️ Administrator':`🤝 ${ME}`;rb.style.color=a?'var(--a)':'var(--t)';}
|
||||
function sw(t){localStorage.setItem('tab',t);document.querySelectorAll('.tp').forEach(p=>p.classList.add('hidden'));document.getElementById('tab-'+t).classList.remove('hidden');document.querySelectorAll('.ni,.bni').forEach(i=>i.classList.toggle('a',i.dataset.t===t));if(t==='dashboard')lD();else if(t==='users')lU();else if(t==='resellers')lR();else if(t==='protocols')lP();else if(t==='settings')lS();}
|
||||
document.querySelectorAll('.ni,.bni').forEach(i=>i.addEventListener('click',()=>sw(i.dataset.t)));
|
||||
document.getElementById('blo').addEventListener('click',async()=>{try{await api('POST','/api/logout');}catch(e){}sL();});
|
||||
document.getElementById('mlo').addEventListener('click',async()=>{try{await api('POST','/api/logout');}catch(e){}sL();});
|
||||
let poll;function sP(){if(poll)clearInterval(poll);poll=setInterval(()=>{if(document.getElementById('av').classList.contains('hidden'))return;const t=localStorage.getItem('tab')||'dashboard';if(t==='dashboard')lD(1);else if(t==='users')lU(1);else if(t==='resellers')lR(1);},5000);}
|
||||
document.getElementById('lf').addEventListener('submit',async e=>{e.preventDefault();const b=document.getElementById('lb');b.disabled=true;b.textContent='...';try{await api('POST','/api/login',{username:document.getElementById('lu').value,password:document.getElementById('lp').value});const m=await api('GET','/api/me');RL=m.role;ME=m.username;setBrand(m.panel_name,m.panel_logo);T('Welcome','s');sA();}catch(e){T(e.message,'e');}finally{b.disabled=false;b.textContent='Sign In';}});
|
||||
|
||||
async function lD(s){try{const d=await api('GET','/api/dashboard');const h=JSON.stringify(d);if(s&&h===_hD)return;_hD=h;document.getElementById('du').textContent=d.user_count;document.getElementById('do').textContent=d.online_sessions;if(d.ram_percent!==undefined){document.getElementById('dr').textContent=d.ram_percent+'%';document.getElementById('drd').textContent=`${d.ram_used_mb}/${d.ram_total_mb} MB`;document.getElementById('dc').textContent=d.cpu_load_1m;document.getElementById('sip').textContent=d.server_ip;document.getElementById('sos').textContent=d.os_name;document.getElementById('sup').textContent=d.uptime;}if(d.reseller_info){const ri=d.reseller_info;if(ri.type==='credits'){document.getElementById('ri-quota').style.display='none';document.getElementById('ri-credits').style.display='flex';document.getElementById('ri-c').textContent=ri.credits;document.getElementById('ri-expiry-wrap').style.display='none';MY_CREDITS=ri.credits;MY_TYPE='credits';}else{document.getElementById('ri-quota').style.display='flex';document.getElementById('ri-credits').style.display='none';document.getElementById('ri-q').textContent=`${ri.created_users}/${ri.max_users}`;document.getElementById('ri-expiry-wrap').style.display='flex';document.getElementById('ri-e').textContent=ri.expire_date;MY_TYPE='quota';}}if(d.protocols&&d.protocols.length)rP(d.protocols,'dp');}catch(e){if(!s)T('Error','e');}}
|
||||
|
||||
async function lU(s){try{const d=await api('GET','/api/users');const h=JSON.stringify(d);if(s&&h===_hU)return;_hU=h;U=d.users||[];const si=document.getElementById('su');if(document.activeElement!==si||!si.value)fU();}catch(e){if(!s)T('Failed','e');}}
|
||||
function fU(){const q=document.getElementById('su').value.toLowerCase();rU(U.filter(u=>u.username.toLowerCase().includes(q)));}
|
||||
function rU(list){
|
||||
const tb=document.getElementById('utb'),cards=document.getElementById('ucc');tb.innerHTML='';cards.innerHTML='';const isA=RL==='admin';
|
||||
if(!list.length){tb.innerHTML=`<tr><td colspan="${isA?8:7}" style="text-align:center;color:var(--t3);padding:2rem;">No accounts</td></tr>`;cards.innerHTML='<div class="gc" style="text-align:center;color:var(--t3);padding:2rem;">No accounts</div>';return;}
|
||||
list.forEach(u=>{
|
||||
const bg=u.is_expired?'<span class="badge br">Expired</span>':u.is_locked?'<span class="badge by">Locked</span>':'<span class="badge bg2">Active</span>';
|
||||
const bw=u.bandwidth_gb>0?`${fB(u.total_used_bytes)}/${u.bandwidth_gb}G`:`${fB(u.total_used_bytes)}/∞`;
|
||||
const dl2=dL(u.expire_date);
|
||||
const tr=document.createElement('tr');
|
||||
tr.innerHTML=`<td class="mono" style="font-weight:600;color:var(--al);">${E(u.username)}</td>
|
||||
<td><span class="mono" style="color:var(--t2);font-size:.78rem;" id="p-${u.username}">••••••</span> <span style="cursor:pointer;font-size:.7rem;opacity:.6;" onclick="tp('p-${u.username}','${E(u.password)}')">👁</span></td>
|
||||
<td><span class="mono" style="font-size:.75rem;">${u.expire_date}</span>${dl2?'<br>'+dl2:''}</td>
|
||||
<td><span style="color:${u.online_sessions?'var(--t)':'inherit'};font-weight:600;">${u.online_sessions}</span>/${u.conn_limit}</td>
|
||||
<td class="mono" style="font-size:.75rem;">${bw}</td><td>${bg}</td>
|
||||
${isA?`<td><span class="badge ${u.owner==='admin'?'by':'bt'}">${E(u.owner||'admin')}</span></td>`:''}
|
||||
<td><div class="tda">
|
||||
<button class="bi" title="Copy" onclick="cA('${E(u.username)}','${E(u.password)}','${u.expire_date}',${u.bandwidth_gb},${u.daily_bandwidth_gb},${u.conn_limit})">📋</button>
|
||||
<button class="bi" title="Edit" onclick="eU('${E(u.username)}',${u.conn_limit},${u.bandwidth_gb},${u.daily_bandwidth_gb})">✏️</button>
|
||||
<button class="bi" title="Renew" onclick="rnU('${E(u.username)}')">🔄</button>
|
||||
${u.is_locked?`<button class="bi" title="Unlock" onclick="lkU('${E(u.username)}',0)">🔓</button>`:`<button class="bi" title="Lock" onclick="lkU('${E(u.username)}',1)">🔒</button>`}
|
||||
<button class="bi" title="Delete" style="color:var(--r);" onclick="dU('${E(u.username)}')">🗑</button>
|
||||
</div></td>`;tb.appendChild(tr);
|
||||
const c=document.createElement('div');c.className='gc uc';
|
||||
c.innerHTML=`<div class="ucb"><div class="uct"><span class="ucn">${E(u.username)}</span>${bg}</div>
|
||||
<div class="ucg">
|
||||
<div class="uf"><span class="ufl">Password</span><span class="ufv"><span class="mono" id="mp-${u.username}">••••••</span> <span style="cursor:pointer;opacity:.5;" onclick="tp('mp-${u.username}','${E(u.password)}')">👁</span></span></div>
|
||||
<div class="uf"><span class="ufl">Expiry</span><span class="ufv mono" style="font-size:.78rem;">${u.expire_date}</span>${dl2||''}</div>
|
||||
<div class="uf"><span class="ufl">Sessions</span><span class="ufv"><b style="color:${u.online_sessions?'var(--t)':'inherit'}">${u.online_sessions}</b> / ${u.conn_limit}</span></div>
|
||||
<div class="uf"><span class="ufl">Bandwidth</span><span class="ufv mono" style="font-size:.78rem;">${bw}</span></div>
|
||||
${isA?`<div class="uf"><span class="ufl">Owner</span><span class="badge ${u.owner==='admin'?'by':'bt'}" style="width:fit-content;margin-top:.1rem;">${E(u.owner||'admin')}</span></div>`:''}</div></div>
|
||||
<div class="uca">
|
||||
<button class="uact" onclick="cA('${E(u.username)}','${E(u.password)}','${u.expire_date}',${u.bandwidth_gb},${u.daily_bandwidth_gb},${u.conn_limit})"><span class="uai">📋</span>Copy</button>
|
||||
<button class="uact" onclick="eU('${E(u.username)}',${u.conn_limit},${u.bandwidth_gb},${u.daily_bandwidth_gb})"><span class="uai">✏️</span>Edit</button>
|
||||
<button class="uact" onclick="rnU('${E(u.username)}')"><span class="uai">🔄</span>Renew</button>
|
||||
${u.is_locked?`<button class="uact" onclick="lkU('${E(u.username)}',0)"><span class="uai">🔓</span>Unlock</button>`:`<button class="uact" onclick="lkU('${E(u.username)}',1)"><span class="uai">🔒</span>Lock</button>`}
|
||||
<button class="uact dng" onclick="dU('${E(u.username)}')"><span class="uai">🗑</span>Del</button>
|
||||
</div>`;cards.appendChild(c);});}
|
||||
|
||||
async function lR(s){try{const d=await api('GET','/api/resellers');const h=JSON.stringify(d);if(s&&h===_hR)return;_hR=h;rR(d.resellers||[]);}catch(e){if(!s)T('Failed','e');}}
|
||||
function rR(list){
|
||||
const tb=document.getElementById('rtb'),cards=document.getElementById('rcc');tb.innerHTML='';cards.innerHTML='';
|
||||
if(!list.length){tb.innerHTML='<tr><td colspan="7" style="text-align:center;color:var(--t3);padding:2rem;">No resellers</td></tr>';cards.innerHTML='<div class="gc" style="text-align:center;color:var(--t3);padding:2rem;">No resellers</div>';return;}
|
||||
list.forEach(r=>{
|
||||
const isCr=r.type==='credits';
|
||||
const bg=r.is_expired&&!isCr?'<span class="badge br">Expired</span>':!r.enabled?'<span class="badge bgr">Disabled</span>':'<span class="badge bg2">Active</span>';
|
||||
const tb2=isCr?'<span class="badge" style="background:linear-gradient(135deg,#f0c040,#e6a000);color:#000;font-weight:600;">Credits</span>':'<span class="badge" style="background:var(--bg2);color:var(--t2);">Quota</span>';
|
||||
const dl2=isCr?'':dL(r.expire_date);
|
||||
const infoCol=isCr?`<span style="color:#f0c040;font-weight:600;">💰 ${r.credits}</span>`:`<span style="color:var(--a);font-weight:600;">${r.created_users}</span>/${r.max_users}`;
|
||||
const tr=document.createElement('tr');
|
||||
tr.innerHTML=`<td class="mono" style="font-weight:600;color:var(--t);">${E(r.username)}</td>
|
||||
<td><span class="mono" style="color:var(--t2);font-size:.78rem;" id="rp-${r.username}">••••••</span> <span style="cursor:pointer;font-size:.7rem;opacity:.6;" onclick="tp('rp-${r.username}','${E(r.password)}')">👁</span></td>
|
||||
<td>${tb2}</td>
|
||||
<td><span class="mono" style="font-size:.75rem;">${isCr?'Never':r.expire_date}</span>${dl2?'<br>'+dl2:''}</td>
|
||||
<td>${infoCol}</td><td>${bg}</td>
|
||||
<td><div class="tda"><button class="bi" title="Copy" onclick="cR2('${E(r.username)}','${E(r.password)}','${isCr?'Never':r.expire_date}','${isCr?'credits':'quota'}',${r.created_users},${r.max_users},${r.credits||0})">📋</button><button class="bi" title="Edit" onclick="eR('${E(r.username)}',${r.max_users},'${isCr?'credits':'quota'}',${r.credits||0})">✏️</button><button class="bi" title="${r.enabled?'Disable':'Enable'}" onclick="tR('${E(r.username)}')">${r.enabled?'🔒':'🔓'}</button><button class="bi" title="Delete" style="color:var(--r);" onclick="dR('${E(r.username)}',${r.created_users})">🗑</button></div></td>`;
|
||||
tb.appendChild(tr);
|
||||
const c=document.createElement('div');c.className='gc uc';
|
||||
c.innerHTML=`<div class="ucb"><div class="uct"><span class="ucn" style="color:var(--t);">${E(r.username)}</span>${tb2} ${bg}</div>
|
||||
<div class="ucg"><div class="uf"><span class="ufl">Password</span><span class="ufv"><span class="mono" id="mrp-${r.username}">••••••</span> <span style="cursor:pointer;opacity:.5;" onclick="tp('mrp-${r.username}','${E(r.password)}')">👁</span></span></div>
|
||||
${isCr?`<div class="uf"><span class="ufl">Credits</span><span class="ufv" style="color:#f0c040;font-weight:600;">💰 ${r.credits}</span></div>`:`<div class="uf"><span class="ufl">Expiry</span><span class="ufv mono" style="font-size:.78rem;">${r.expire_date}</span>${dl2||''}</div>
|
||||
<div class="uf"><span class="ufl">Users</span><span class="ufv"><b style="color:var(--a);">${r.created_users}</b> / ${r.max_users}</span></div>`}</div></div>
|
||||
<div class="uca"><button class="uact" onclick="eR('${E(r.username)}',${r.max_users},'${isCr?'credits':'quota'}',${r.credits||0})"><span class="uai">✏️</span>Edit</button>
|
||||
<button class="uact" onclick="tR('${E(r.username)}')"><span class="uai">${r.enabled?'🔒':'🔓'}</span>${r.enabled?'Disable':'Enable'}</button>
|
||||
<button class="uact dng" onclick="dR('${E(r.username)}',${r.created_users})"><span class="uai">🗑</span>Delete</button>
|
||||
<button class="uact" onclick="cR2('${E(r.username)}','${E(r.password)}','${isCr?'Never':r.expire_date}','${isCr?'credits':'quota'}',${r.created_users},${r.max_users},${r.credits||0})"><span class="uai">📋</span>Copy</button></div>`;
|
||||
cards.appendChild(c);});}
|
||||
|
||||
async function lP(s){try{const d=await api('GET','/api/protocols');rP(d,'pg');}catch(e){if(!s)T('Failed','e');}}
|
||||
function rP(list,cid){const c=document.getElementById(cid);c.innerHTML='';list.forEach(p=>{const b=!p.installed?'<span class="badge bgr">N/A</span>':p.running?'<span class="badge bg2">Running</span>':'<span class="badge br">Stopped</span>';const d=document.createElement('div');d.className='gc pcard';d.innerHTML=`<div style="display:flex;align-items:center;justify-content:space-between;"><span style="font-weight:600;">${p.name}</span>${b}</div><div style="font-size:.75rem;color:var(--t3);">Port: <span class="mono" style="color:var(--tx);">${p.port}</span></div>${p.installed?`<button class="btn bs2 bsm" style="width:100%;" onclick="rpP('${p.service}')">Restart</button>`:''}`;c.appendChild(d);});}
|
||||
async function rpP(s){try{await api('POST',`/api/protocols/${s}/restart`);T('Restarted','s');lP();}catch(e){T('Failed','e');}}
|
||||
|
||||
async function lS(){try{const d=await api('GET','/api/settings');document.getElementById('scu').value=d.username||'';document.getElementById('ss').value=d.secret||'';document.getElementById('s-pn').value=d.panel_name||'𝐆𝐁𝐋-𝐂𝐋𝐎𝐔𝐃 𝟐𝟒𝟑';const lg=d.panel_logo||'';document.getElementById('s-pl').value=lg==='custom'?'':lg;if(d.has_custom_logo){document.getElementById('s-lp').style.display='block';document.getElementById('s-li').src='/api/logo.png?t='+Date.now();document.getElementById('s-ld').style.display='inline-flex';}else{document.getElementById('s-lp').style.display='none';document.getElementById('s-ld').style.display='none';}uPr(d.secret||'');}catch(e){}}
|
||||
document.getElementById('ss').addEventListener('input',e=>uPr(e.target.value));
|
||||
function uPr(s){const h=location.host;const p=s.trim().replace(/^\/+/,'');document.getElementById('sur').textContent=p?`http://${h}/${p}`:`http://${h}/`;}
|
||||
document.getElementById('sf').addEventListener('submit',async e=>{e.preventDefault();try{const r=await api('PUT','/api/settings',{current_password:document.getElementById('scp').value,new_username:document.getElementById('snu').value,new_password:document.getElementById('snp').value,new_secret:document.getElementById('ss').value,panel_name:document.getElementById('s-pn').value,panel_logo:document.getElementById('s-pl').value});setBrand(r.panel_name,r.panel_logo);T('Saved','s');if(r.secret){const u=`http://${location.host}/${r.secret}`;alert(`New URL: ${u}`);location.href=u;}else lS();}catch(e){T(e.message,'e');}});
|
||||
|
||||
async function lkU(u,l){try{await api('POST',`/api/users/${u}/${l?'lock':'unlock'}`);T(`${l?'Locked':'Unlocked'}`,'s');lU();}catch(e){T(e.message,'e');}}
|
||||
async function dU(u){if(!confirm(`Delete "${u}"?`))return;try{await api('DELETE',`/api/users/${u}`);T('Deleted','s');lU();}catch(e){T(e.message,'e');}}
|
||||
async function rnU(u){const d=prompt(`Renew ${u} — days?`,'30');if(!d||isNaN(d))return;try{await api('POST',`/api/users/${u}/renew`,{days:+d});T('Renewed','s');lU();}catch(e){T(e.message,'e');}}
|
||||
async function tR(u){try{const r=await api('POST',`/api/resellers/${u}/toggle`);T(`${r.enabled?'Enabled':'Disabled'}`,'s');lR();}catch(e){T(e.message,'e');}}
|
||||
async function dR(u,n){let del=false;if(n>0)del=confirm(`Also delete ${n} SSH accounts?`);if(!confirm(`Delete reseller "${u}"?`))return;try{await api('DELETE',`/api/resellers/${u}?delete_users=${del?'1':'0'}`);T('Deleted','s');lR();}catch(e){T(e.message,'e');}}
|
||||
async function sysReboot(){if(!confirm('Reboot the server? This will disconnect all users temporarily.'))return;try{await api('POST','/api/system/reboot');T('Rebooting...','s');setTimeout(()=>sL(),2000);}catch(e){T(e.message,'e');}}
|
||||
|
||||
function openM(id){document.getElementById(id).classList.add('active');}
|
||||
function closeM(id){document.getElementById(id).classList.remove('active');}
|
||||
function openCU(){if(RL==='reseller'){api('GET','/api/me').then(m=>{MY_TYPE=m.reseller_type||'quota';MY_CREDITS=m.credits||0;if(MY_TYPE==='credits'){const q=document.getElementById('cq');q.innerHTML=`💰 <b style="color:#f0c040;">${MY_CREDITS}</b> credits remaining (1 credit = 30 days)`;q.classList.remove('hidden');}else{if(m.created_users>=m.max_users){T(`Limit (${m.max_users})`,'e');return;}const q=document.getElementById('cq');q.textContent=`${m.created_users}/${m.max_users} quota used`;q.classList.remove('hidden');}openM('mcu');}).catch(e=>T(e.message,'e'));}else{document.getElementById('cq').classList.add('hidden');openM('mcu');}}
|
||||
function eU(u,c,b,d){document.getElementById('euh').value=u;document.getElementById('eun').value=u;document.getElementById('euc').value=c;document.getElementById('eub').value=b;document.getElementById('eudb').value=d;openM('meu');}
|
||||
let _origCr=0;function eR(u,m,t,cr){document.getElementById('erh').value=u;document.getElementById('ern').value=u;document.getElementById('ert').value=t||'quota';if(t==='credits'){document.getElementById('erq-fields').classList.add('hidden');document.getElementById('erc-fields').classList.remove('hidden');_origCr=cr||0;document.getElementById('erc-cur').textContent=_origCr;document.getElementById('ersc').value=_origCr;document.getElementById('erc-delta').innerHTML='';document.getElementById('erm').removeAttribute('required');document.getElementById('ersc').oninput=updCrDelta;}else{document.getElementById('erq-fields').classList.remove('hidden');document.getElementById('erc-fields').classList.add('hidden');document.getElementById('erm').value=m;document.getElementById('erm').setAttribute('required','');}openM('mer');}
|
||||
function adjCr(n){const el=document.getElementById('ersc');el.value=Math.max(0,(+el.value||0)+n);updCrDelta();}
|
||||
function updCrDelta(){const nv=+(document.getElementById('ersc').value||0);const d=nv-_origCr;const el=document.getElementById('erc-delta');if(d===0){el.innerHTML='';}else if(d>0){el.innerHTML=`<span style="color:var(--g);">▲ +${d} credits</span>`;}else{el.innerHTML=`<span style="color:var(--r);">▼ ${d} credits</span>`;}}
|
||||
let CRT='quota',CRV=false;function setRT(t){CRT=t;CRV=false;document.getElementById('rt-quota').className=t==='quota'?'btn bsm bp':'btn bsm bs2';document.getElementById('rt-credits').className=t==='credits'?'btn bsm bp':'btn bsm bs2';if(t==='credits'){document.getElementById('rq-fields').classList.add('hidden');document.getElementById('rc-fields').classList.remove('hidden');document.getElementById('rd').removeAttribute('required');document.getElementById('rm').removeAttribute('required');setRV(false);}else{document.getElementById('rq-fields').classList.remove('hidden');document.getElementById('rc-fields').classList.add('hidden');document.getElementById('rd').setAttribute('required','');document.getElementById('rm').setAttribute('required','');}}
|
||||
function setRV(v){CRV=v;document.getElementById('rv-no').className=v?'btn bsm bs2':'btn bsm bp';document.getElementById('rv-yes').className=v?'btn bsm bp':'btn bsm bs2';if(v){document.getElementById('rv-fields').classList.remove('hidden');}else{document.getElementById('rv-fields').classList.add('hidden');}}
|
||||
|
||||
document.getElementById('fcu').addEventListener('submit',async e=>{e.preventDefault();try{await api('POST','/api/users',{username:document.getElementById('cn').value,password:document.getElementById('cp').value,days:+document.getElementById('cd').value,conn_limit:+document.getElementById('cc').value,bandwidth_gb:+(document.getElementById('cb').value||0),daily_bandwidth_gb:+(document.getElementById('cdb').value||0)});T('Created','s');closeM('mcu');lU();}catch(e){T(e.message,'e');}});
|
||||
document.getElementById('feu').addEventListener('submit',async e=>{e.preventDefault();const u=document.getElementById('euh').value,b={conn_limit:+document.getElementById('euc').value,bandwidth_gb:+(document.getElementById('eub').value||0),daily_bandwidth_gb:+(document.getElementById('eudb').value||0)};const p=document.getElementById('eup').value;if(p)b.password=p;const d=document.getElementById('eud').value;if(d)b.days=+d;try{await api('PUT',`/api/users/${u}`,b);T('Updated','s');closeM('meu');lU();}catch(e){T(e.message,'e');}});
|
||||
document.getElementById('fb').addEventListener('submit',async e=>{e.preventDefault();try{const r=await api('POST','/api/users/bulk',{prefix:document.getElementById('bp2').value,count:+document.getElementById('bc').value,days:+document.getElementById('bd').value,conn_limit:+document.getElementById('bcc').value});T(`${r.users?r.users.length:0} created`,'s');closeM('mbk');lU();}catch(e){T(e.message,'e');}});
|
||||
document.getElementById('fcr').addEventListener('submit',async e=>{e.preventDefault();try{const b={username:document.getElementById('rn').value,password:document.getElementById('rp2').value,type:CRT};if(CRT==='credits'){b.credits=+(document.getElementById('rcr').value||0);if(CRV){b.days=+(document.getElementById('rcd').value||30);}}else{b.days=+document.getElementById('rd').value;b.max_users=+document.getElementById('rm').value;}await api('POST','/api/resellers',b);T('Created','s');closeM('mcr');lR();}catch(e){T(e.message,'e');}});
|
||||
document.getElementById('fer').addEventListener('submit',async e=>{e.preventDefault();const u=document.getElementById('erh').value,t=document.getElementById('ert').value,b={};const p=document.getElementById('erp').value;if(p)b.password=p;if(t==='credits'){const sc=document.getElementById('ersc').value;if(sc!=='')b.set_credits=+sc;}else{b.max_users=+document.getElementById('erm').value;const d=document.getElementById('erd').value;if(d)b.days=+d;}try{await api('PUT',`/api/resellers/${u}`,b);T('Updated','s');closeM('mer');lR();}catch(e){T(e.message,'e');}});
|
||||
document.getElementById('ftr').addEventListener('submit',async e=>{e.preventDefault();try{const b={username:document.getElementById('tn').value,password:document.getElementById('tp2').value};if(RL!=='reseller'){b.minutes=+document.getElementById('th').value;b.conn_limit=+document.getElementById('tc2').value;b.bandwidth_gb=+(document.getElementById('tb').value||0);}const r=await api('POST','/api/users/trial',b);const m=r.minutes;let exp;if(m<60)exp=m+' min';else if(m<1440)exp=Math.floor(m/60)+'h';else exp=Math.floor(m/1440)+'d';T(`Trial ${r.username} created (expires in ${exp})`,'s');closeM('mtr');lU();}catch(e){T(e.message,'e');}});
|
||||
|
||||
function gp(id){const c='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';let r='';for(let i=0;i<10;i++)r+=c[Math.floor(Math.random()*c.length)];document.getElementById(id).value=r;}
|
||||
function tp(id,p){const e=document.getElementById(id);e.textContent=e.textContent==='••••••'?p:'••••••';}
|
||||
function cT(t){if(navigator.clipboard&&window.isSecureContext){navigator.clipboard.writeText(t);T('Copied','i');}else{const a=document.createElement('textarea');a.value=t;a.style.position='fixed';a.style.left='-9999px';document.body.appendChild(a);a.select();try{document.execCommand('copy');T('Copied','i');}catch(e){T('Failed','e');}document.body.removeChild(a);}}
|
||||
function cA(u,p,e,b,d,c){let t=`👤 ${u}\n🔑 ${p}\n📅 ${e}\n🔌 ${c} devices\n`;if(b>0||d>0){t+='📊 ';if(b>0)t+=`${b}GB total `;if(d>0)t+=`${d}GB/day`;t+='\n';}cT(t);}
|
||||
function cR(u,p,e,c,m){const url=location.origin;cT(`👤 Reseller: ${u}\n🔑 ${p}\n📅 ${e}\n👥 Users: ${c} / ${m}\n🌐 Panel: ${url}\n`);}
|
||||
function cR2(u,p,e,t,c,m,cr){const url=location.origin;let txt=`👤 Reseller: ${u}\n🔑 ${p}\n`;if(t==='credits'){txt+=`💰 Credits: ${cr}\n`;}else{txt+=`📅 ${e}\n👥 Users: ${c} / ${m}\n`;}txt+=`🌐 Panel: ${url}\n`;cT(txt);}
|
||||
async function uploadLogo(){const f=document.getElementById('s-lf').files[0];if(!f){T('Select an image file','e');return;}if(f.size>2097152){T('Max 2MB','e');return;}try{const r=await fetch('/api/logo/upload',{method:'POST',credentials:'same-origin',body:f});const d=await r.json();if(!r.ok)throw new Error(d.error||'Failed');T('Logo uploaded','s');setBrand(null,d.panel_logo,true);lS();}catch(e){T(e.message,'e');}}
|
||||
async function deleteLogo(){if(!confirm('Remove custom logo?'))return;try{const d=await api('POST','/api/logo/delete');T('Logo removed','s');setBrand(null,d.panel_logo,false);lS();}catch(e){T(e.message,'e');}}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user