(function(){
var ncCsrfEl=document.querySelector('meta[name="csrf-token"]');
var ncCsrf=ncCsrfEl?ncCsrfEl.content:'';
var ncNodeDataEl=document.getElementById('nc-node-data');
var ncNodeData=ncNodeDataEl?JSON.parse(ncNodeDataEl.textContent):{};
var ncUnlocked={};
var ncSelectedSvc={};
var ncAuthTarget=null;
function ncSelectNode(code){
if(!ncUnlocked[code]){ncUnlockNode(code);return;}
document.querySelectorAll('.nc-node').forEach(function(n){n.classList.remove('active');});
document.querySelectorAll('.nc-pane').forEach(function(p){p.style.display='none';});
var el=document.getElementById('nc-node-'+code);
var pane=document.getElementById('nc-pane-'+code);
var empty=document.getElementById('nc-empty');
if(el)el.classList.add('active');
if(pane)pane.style.display='block';
if(empty)empty.style.display='none';
var first=document.querySelector('#nc-svcs-'+code+' .nc-svc');
if(first&&!ncSelectedSvc[code])ncPickSvc(first);
}
function ncUnlockNode(code){
ncAuthTarget=code;
var nd=ncNodeData[code]||{};
document.getElementById('ncAuthTitle').textContent='Unlock '+( nd.code||code)+' \u2014 '+(nd.name||'');
document.getElementById('ncAuthDesc').textContent='Enter fleet PIN to access '+(nd.name||'node')+' controls';
document.getElementById('ncAuthPin').value='';
document.getElementById('ncAuthOverlay').style.display='flex';
setTimeout(function(){document.getElementById('ncAuthPin').focus();},100);
}
function ncAuthClose(){
document.getElementById('ncAuthOverlay').style.display='none';
ncAuthTarget=null;
}
function ncAuthSubmit(){
var pin=document.getElementById('ncAuthPin').value;
if(!pin||!ncAuthTarget)return;
var fd=new FormData();
fd.set('_csrf',ncCsrf);
fd.set('action','uptime');
fd.set('node',ncNodeData[ncAuthTarget]?(ncNodeData[ncAuthTarget].db_id||ncNodeData[ncAuthTarget].role):ncAuthTarget);
fd.set('fleet_pin',pin);
fetch('api/fleet/node_cmd.php',{method:'POST',body:fd,credentials:'same-origin'})
.then(function(r){return r.json();})
.then(function(data){
if(data.needs_pin){
document.getElementById('ncAuthPin').style.borderColor='#ff5252';
document.getElementById('ncAuthPin').value='';
return;
}
var code=ncAuthTarget;
ncUnlocked[code]=true;
ncAuthClose();
var lockBtn=document.getElementById('nc-lock-'+code);
if(lockBtn){
lockBtn.classList.add('unlocked');
lockBtn.innerHTML='<svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor"><path d="M12 17a2 2 0 100-4 2 2 0 000 4zm6-9a2 2 0 012 2v10a2 2 0 01-2 2H6a2 2 0 01-2-2V10a2 2 0 012-2h1V6a5 5 0 019.83-1.17.75.75 0 01-1.46.34A3.5 3.5 0 008.5 6v2H18z"/></svg>';
}
ncSelectNode(code);
if(data.ok)ncLog(code,data.output||'Connected','ok');
})
.catch(function(e){ncLog(ncAuthTarget,'Auth failed: '+e.message,'err');});
}
var ncAuthPinEl=document.getElementById('ncAuthPin');
if(ncAuthPinEl){
ncAuthPinEl.addEventListener('keydown',function(e){
if(e.key==='Enter')ncAuthSubmit();
});
}
function ncPickSvc(el){
var code=el.dataset.code;
var grid=document.getElementById('nc-svcs-'+code);
if(grid)grid.querySelectorAll('.nc-svc').forEach(function(s){s.classList.remove('sel');});
el.classList.add('sel');
ncSelectedSvc[code]={node:el.dataset.node,svc:el.dataset.svc};
}
function ncExec(code,nodeId,action,extra){
if(!ncUnlocked[code]){ncUnlockNode(code);return;}
var sel=ncSelectedSvc[code]||{};
var svc=sel.svc||'';
var needsSvc=['service_status','service_restart','service_stop','service_start','logs_tail'].indexOf(action)!==-1;
if(needsSvc&&!svc){ncLog(code,'Select a service first','err');return;}
ncLog(code,'\u25b8 '+action+(svc?' \u2192 '+svc:'')+' ...','');
var fd=new FormData();
fd.set('_csrf',ncCsrf);
fd.set('action',action);
fd.set('node',nodeId);
if(svc)fd.set('service',svc);
if(extra)Object.keys(extra).forEach(function(k){fd.set(k,extra[k]);});
fetch('api/fleet/node_cmd.php',{method:'POST',body:fd,credentials:'same-origin'})
.then(function(r){return r.json();})
.then(function(data){
if(data.needs_pin){
ncUnlocked[code]=false;
var lockBtn=document.getElementById('nc-lock-'+code);
if(lockBtn){lockBtn.classList.remove('unlocked');}
ncUnlockNode(code);
return;
}
if(data.ok){
ncLog(code,data.output||'OK','ok');
ncLog(code,'\u2014 '+(data.elapsed||'?'),'ts');
}else{
ncLog(code,data.output||data.error||'Failed','err');
}
var dot=document.getElementById('nc-pdot-'+code);
if(dot)dot.className='nc-dot '+(data.ok?'up':'down');
})
.catch(function(e){ncLog(code,'Request failed: '+e.message,'err');});
}
function ncLog(code,text,cls){
var term=document.getElementById('nc-term-'+code);
if(!term)return;
var ts=new Date().toLocaleTimeString();
var line=document.createElement('div');
var safe=document.createElement('span');
safe.textContent=text;
line.innerHTML='<span class="ts">['+ts+']</span> '+(cls?'<span class="'+cls+'">'+safe.innerHTML+'</span>':safe.innerHTML);
term.appendChild(line);
term.scrollTop=term.scrollHeight;
}
var ncInfoNode=null;
var ncMetaFields=['provider','account_email','account_username','account_password','payment_method','card_last4','card_exp','monthly_cost','renewal_date','panel_url','panel_login','ssh_password','root_password','notes'];
function ncOpenInfo(code){
ncInfoNode=code;
var nd=ncNodeData[code]||{};
document.getElementById('ncInfoTitle').innerHTML=''+(nd.code||code)+' \u2014 '+(nd.name||'')+' Info';
ncMetaFields.forEach(function(f){var el=document.getElementById('nmi-'+f);if(el)el.value='';});
document.getElementById('ncInfoOverlay').style.display='flex';
var dbId=nd.db_id||nd.role||code;
var fd=new FormData();
fd.set('_nc_meta','load_meta');
fd.set('node_id',dbId);
fd.set('_csrf',ncCsrf);
fetch('',{method:'POST',body:fd,credentials:'same-origin'})
.then(function(r){return r.json()})
.then(function(d){
if(d.ok&&d.data){
ncMetaFields.forEach(function(f){
var el=document.getElementById('nmi-'+f);
if(el&&d.data[f])el.value=d.data[f];
});
}
}).catch(function(e){void 0;});
}
function ncInfoClose(){
document.getElementById('ncInfoOverlay').style.display='none';
ncInfoNode=null;
}
function ncInfoSave(){
if(!ncInfoNode)return;
var nd=ncNodeData[ncInfoNode]||{};
var dbId=nd.db_id||nd.role||ncInfoNode;
var fd=new FormData();
fd.set('_nc_meta','save_meta');
fd.set('node_id',dbId);
fd.set('_csrf',ncCsrf);
ncMetaFields.forEach(function(f){
var el=document.getElementById('nmi-'+f);
fd.set(f,el?el.value:'');
});
fetch('',{method:'POST',body:fd,credentials:'same-origin'})
.then(function(r){return r.json()})
.then(function(d){
if(d.ok){
ncInfoClose();
if(ncInfoNode&&ncUnlocked[ncInfoNode])ncLog(ncInfoNode,'Server info encrypted & saved','ok');
}else{
alert(d.error||'Save failed');
}
}).catch(function(e){alert('Save failed: '+e.message);});
}
var ncInfoOverlayEl=document.getElementById('ncInfoOverlay');
if(ncInfoOverlayEl){
ncInfoOverlayEl.addEventListener('keydown',function(e){
if(e.key==='Escape')ncInfoClose();
});
}
document.addEventListener('click',function(e){
var el=e.target.closest('[data-nc]');
if(!el)return;
var nc=el.dataset.nc;
var code=el.dataset.code;
switch(nc){
case 'select':ncSelectNode(code);break;
case 'info':e.stopPropagation();ncOpenInfo(code);break;
case 'unlock':e.stopPropagation();ncUnlockNode(code);break;
case 'picksvc':ncPickSvc(el);break;
case 'exec':ncExec(code,el.dataset.node,el.dataset.cmd);break;
case 'exec-confirm':if(confirm(el.dataset.confirm))ncExec(code,el.dataset.node,el.dataset.cmd);break;
case 'auth-overlay':if(e.target===el)ncAuthClose();break;
case 'auth-close':ncAuthClose();break;
case 'auth-submit':ncAuthSubmit();break;
case 'info-overlay':if(e.target===el)ncInfoClose();break;
case 'info-close':ncInfoClose();break;
case 'info-save':ncInfoSave();break;
}
});
})();
