You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
const policies=[['daily_burn_priority','잔액/남은일수 우선','만료 전 써야 할 크레딧을 먼저 태움'],['balance_priority','잔액 많은 순','현재 잔액이 큰 key 우선'],['round_robin','순환 분산','요청마다 key를 순서대로 선택'],['drain_first','앞 key 소진','1번 key부터 다 쓰고 다음 key로 이동']];
49
49
const initialConfig=${scriptJson(initialConfig)};
50
50
let cfg=initialConfig, dirty=false, pendingBridgeKey=authKey(localStorage.getItem('pendingBridgeApiKey'));
51
-
const $=id=>document.getElementById(id); const esc=v=>String(v??'').replace(/[&<>"']/g,ch=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[ch])); let toastTimer,modalTimer; const toast=t=>{clearTimeout(toastTimer); $('toast').textContent=t; $('toast').classList.add('show'); toastTimer=setTimeout(()=>$('toast').classList.remove('show'),2500); }; function popup(t,ms=3000){clearTimeout(modalTimer); $('modalText').textContent=t; $('modal').classList.add('show'); modalTimer=setTimeout(()=>$('modal').classList.remove('show'),ms);}
51
+
const $=id=>document.getElementById(id); const esc=v=>String(v??'').replace(/[&<>"']/g,ch=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[ch])); let toastTimer,modalTimer; const toast=t=>{clearTimeout(toastTimer); $('toast').textContent=t; $('toast').classList.add('show'); toastTimer=setTimeout(()=>$('toast').classList.remove('show'),2500); }; function showPopup(t,ms=0){clearTimeout(modalTimer); $('modalText').textContent=t; $('modal').classList.add('show'); if(ms>0)modalTimer=setTimeout(()=>$('modal').classList.remove('show'),ms);} function hidePopup(){clearTimeout(modalTimer); $('modal').classList.remove('show');} function popup(t,ms=3000){showPopup(t,ms);}
52
52
const expiredIds=new Set();
53
53
function isRedactedSecret(value){const v=String(value||'').trim(); return !v||v==='[REDACTED]'||v==='sk-[REDACTED]'||v.includes('…')||v.includes('...');}
54
54
function fullBridgeKey(){const raw=($('bridgeApiKey')?.value||'').trim(); if(!raw||isRedactedSecret(raw))return ''; return raw.startsWith('sk-')?raw:'sk-'+raw;}
@@ -68,17 +68,17 @@ function setOnline(h){$('dot').className='dot on'; $('online').textContent='onli
68
68
async function load(){if(cfg){if(cfg.bridge) setOnline(cfg.bridge); render(); setDirty(cfg.dirty||false);} try{const h=await health(); setOnline(h);}catch{ if(!cfg){$('dot').className='dot off'; $('online').textContent='offline';} } updateRestart(); try{cfg=await api('/admin/config'); if(cfg.bridge) setOnline(cfg.bridge); try{const m=await api('/admin/commandcode/credentials?refresh=true'); const byId=new Map((m.credentials||[]).map(x=>[x.id,x])); cfg.credentials.forEach(c=>c.metrics=byId.get(c.id));}catch{} render(); setDirty(cfg.dirty||false); return {ok:true,dirty:!!cfg.dirty,restartRequired:!!cfg.restart_required};}catch(e){if(!cfg) toast('설정 로드 실패: 포트 9992 주소로 다시 열어주세요.'); return {ok:false,error:e};}}
69
69
function render(){if(!cfg)return; $('bindHost').value=cfg.server.host; $('bindPort').value=cfg.server.port; $('maxPer').value=cfg.routing.maxInFlightPerCredential||4; syncBridgeKey();
function rememberPendingBridgeKey(key){pendingBridgeKey=authKey(key); if(pendingBridgeKey)localStorage.setItem('pendingBridgeApiKey',pendingBridgeKey); else localStorage.removeItem('pendingBridgeApiKey');}
76
76
function generateBridgeKey(){const key=randomBridgeKey(); rememberPendingBridgeKey(key); const el=$('bridgeApiKey'); if(el)el.value=displayBridgeKey(key); setDirty(); toast('Random Client API key generated. Save JSON, then restart to apply.');}
77
77
$('bindHost').onchange=()=>{cfg.server.host=$('bindHost').value;syncBridgeKey();setDirty();}; $('bindPort').oninput=()=>{cfg.server.port=Number($('bindPort').value)||9992;setDirty();}; function saveBridgeKey(nextKey){const key=nextKey||fullBridgeKey(); const current=currentBridgeAuthKey(); if(key&&key!==current){rememberPendingBridgeKey(key); syncBridgeKey(); setDirty(); toast('Pending Client API key saved. Save JSON, then restart to apply.'); return;} if(key)localStorage.setItem('bridgeApiKey',key); else localStorage.removeItem('bridgeApiKey'); syncBridgeKey(); toast(key?'Current Client API key saved in this browser.':'Client API key cleared.');} async function copyBridgeKey(){const key=fullBridgeKey()||authKey(localStorage.getItem('bridgeApiKey'))||''; if(!key){toast('Client API key is empty.');return;} try{await navigator.clipboard.writeText(key);toast('Client API key copied.');}catch{toast('Copy failed. Select and copy manually.');}} $('generateBridgeKey').onclick=generateBridgeKey; $('saveBridgeKey').onclick=()=>saveBridgeKey(); $('copyBridgeKey').onclick=copyBridgeKey; $('bridgeApiKey').onkeydown=e=>{if(e.key==='Enter')saveBridgeKey();}; $('maxPer').oninput=()=>{cfg.routing.maxInFlightPerCredential=Number($('maxPer').value)||4; setDirty();}; $('refreshCreds').onclick=async()=>{try{const m=await api('/admin/commandcode/credentials?refresh=true'); const byId=new Map((m.credentials||[]).map(x=>[x.id,x])); cfg.credentials.forEach(c=>c.metrics=byId.get(c.id)); render(); toast('Credentials refreshed.');}catch(e){toast('Credential refresh failed.');}}; $('addCred').onclick=()=>{cfg.credentials.push({id:'key'+(cfg.credentials.length+1),apiKey:'',weight:1,enabled:true});render();setDirty();};
async function waitForRestart(){for(let elapsed=5;elapsed<=30;elapsed+=5){showPopup('Restart requested. Checking bridge state in '+elapsed+'s / 30s...',0); await new Promise(r=>setTimeout(r,5000)); const state=await load(); if(state?.ok&&!state.dirty&&!state.restartRequired){hidePopup(); return true;} if(elapsed<30)showPopup('Bridge is still restarting. Extending wait to '+(elapsed+5)+'s / 30s...',0);} popup('Restart did not finish cleanly within 30s. Please refresh or check the service.',5000); return false;}
0 commit comments