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로 이동']];
48
48
const initialConfig=${scriptJson(initialConfig)};
49
-
let cfg=initialConfig, dirty=false;
49
+
let cfg=initialConfig, dirty=false, pendingBridgeKey=authKey(localStorage.getItem('pendingBridgeApiKey'));
function isRedactedSecret(value){const v=String(value||'').trim(); return !v||v==='[REDACTED]'||v==='sk-[REDACTED]'||v.includes('…')||v.includes('...');}
53
53
function fullBridgeKey(){const raw=($('bridgeApiKey')?.value||'').trim(); if(!raw||isRedactedSecret(raw))return ''; return raw.startsWith('sk-')?raw:'sk-'+raw;}
54
54
function displayBridgeKey(key){return isRedactedSecret(key)?'':(key?.startsWith('sk-')?key.slice(3):(key||''))}
55
55
function authKey(value){return isRedactedSecret(value)?'':String(value||'').trim();}
56
-
function auth(){const key=authKey(cfg?.bridgeApiKey)||authKey(localStorage.getItem('bridgeApiKey'))||fullBridgeKey()||''; return key?{'authorization':'Bearer '+key,'content-type':'application/json'}:{'content-type':'application/json'}}
56
+
function currentBridgeAuthKey(){return authKey(cfg?.bridgeApiKey)||authKey(localStorage.getItem('bridgeApiKey'))||'';}
57
+
function auth(){const key=currentBridgeAuthKey()||(!pendingBridgeKey?fullBridgeKey():'')||''; return key?{'authorization':'Bearer '+key,'content-type':'application/json'}:{'content-type':'application/json'}}
57
58
function duplicateCredentialMessage(e){const text=String(e?.message||e||''); return text.includes('duplicate_commandcode_api_key')||text.includes('Duplicate CommandCode API key')?'이미 등록된 키입니다. 다른 CommandCode API key를 입력해주세요.':null;}
58
59
function preventDuplicateVisibleKey(input){const value=input.value.trim(); if(!value)return false; const dup=Array.from(document.querySelectorAll('[data-ckey]')).some(el=>el!==input&&el.value.trim()===value); if(!dup)return false; const i=+input.dataset.ckey; input.value=''; if(cfg?.credentials?.[i])cfg.credentials[i].apiKey=''; toast('이미 등록된 키입니다. 입력하지 않았습니다.'); return true;}
function setDirty(v=true){dirty=v; $('dirtyText').textContent=v?'pending changes · restart required':'no pending changes'; updateRestart();}
61
62
function updateRestart(){const online=$('online').textContent==='online'; $('restart').disabled=online&&!dirty; $('restart').classList.toggle('active',!$('restart').disabled)}
62
63
async function fetchJson(path,opt={}){const init={...opt,cache:'no-store',headers:{...auth(),...(opt.headers||{})}}; try{const r=await fetch(path,init); if(!r.ok) throw new Error(await r.text()); return r.json();}catch(e){if(location.hostname&&!location.port){const r=await fetch('http://'+location.hostname+':9992'+path,init); if(!r.ok) throw new Error(await r.text()); return r.json();}throw e;}}
function generateBridgeKey(){const key=randomBridgeKey(); const el=$('bridgeApiKey'); if(el)el.value=displayBridgeKey(key); setDirty(); toast('Random Admin API key generated. Save JSON, then restart to apply.');}
74
-
$('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(); if(key)localStorage.setItem('bridgeApiKey',key); else localStorage.removeItem('bridgeApiKey'); syncBridgeKey(); toast(key?'Admin API key saved in this browser.':'Admin API key cleared.');} async function copyBridgeKey(){const key=fullBridgeKey()||authKey(localStorage.getItem('bridgeApiKey'))||''; if(!key){toast('Admin API key is empty.');return;} try{await navigator.clipboard.writeText(key);toast('Admin 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();};
75
-
$('save').onclick=async()=>{try{if(!cfg)throw new Error('config not loaded'); const pendingBridgeKey=fullBridgeKey(); const payload={...(pendingBridgeKey?{bridgeApiKey:pendingBridgeKey}:{}),server:cfg.server,routing:cfg.routing,models:cfg.models,credentials:cfg.credentials.map(c=>({id:c.id,originalId:c.originalId,apiKey:c.apiKey||undefined,weight:c.weight||1,enabled:expiredIds.has(c.id)?undefined:c.enabled!==false,maxInFlight:c.maxInFlight,allowedModels:c.allowedModels}))}; cfg=await api('/admin/config',{method:'PUT',body:JSON.stringify(payload)}); render(); setDirty(true); toast('JSON saved. Restart required.');}catch(e){toast(duplicateCredentialMessage(e)||('Save failed: '+(e?.message||e)));}};
function rememberPendingBridgeKey(key){pendingBridgeKey=authKey(key); if(pendingBridgeKey)localStorage.setItem('pendingBridgeApiKey',pendingBridgeKey); else localStorage.removeItem('pendingBridgeApiKey');}
75
+
function generateBridgeKey(){const key=randomBridgeKey(); rememberPendingBridgeKey(key); const el=$('bridgeApiKey'); if(el)el.value=displayBridgeKey(key); setDirty(); toast('Random Admin API key generated. Save JSON, then restart to apply.');}
76
+
$('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 Admin API key saved. Save JSON, then restart to apply.'); return;} if(key)localStorage.setItem('bridgeApiKey',key); else localStorage.removeItem('bridgeApiKey'); syncBridgeKey(); toast(key?'Current Admin API key saved in this browser.':'Admin API key cleared.');} async function copyBridgeKey(){const key=fullBridgeKey()||authKey(localStorage.getItem('bridgeApiKey'))||''; if(!key){toast('Admin API key is empty.');return;} try{await navigator.clipboard.writeText(key);toast('Admin 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();};
77
+
$('save').onclick=async()=>{try{if(!cfg)throw new Error('config not loaded'); const pendingKey=pendingBridgeKey||((fullBridgeKey()&&fullBridgeKey()!==currentBridgeAuthKey())?fullBridgeKey():''); const payload={...(pendingKey?{bridgeApiKey:pendingKey}:{}),server:cfg.server,routing:cfg.routing,models:cfg.models,credentials:cfg.credentials.map(c=>({id:c.id,originalId:c.originalId,apiKey:c.apiKey||undefined,weight:c.weight||1,enabled:expiredIds.has(c.id)?undefined:c.enabled!==false,maxInFlight:c.maxInFlight,allowedModels:c.allowedModels}))}; cfg=await api('/admin/config',{method:'PUT',body:JSON.stringify(payload)}); render(); setDirty(true); toast('JSON saved. Restart required.');}catch(e){toast(duplicateCredentialMessage(e)||('Save failed: '+(e?.message||e)));}};
0 commit comments