feat: Explorer KPI stats, enhanced views, start/stop expansion, Prompt Generator docs injection
- Explorer: KPI stats bar with cached counts (SQLite), background refresh, enhanced views for VCNs/Subnets/LBs/Buckets, start/stop for DB Systems, MySQL, Container Instances, dead state filtering across all 36 endpoints - Prompt Generator: same knowledge pipeline as Terraform Agent — resource type detection + official docs from GitHub + compact resource reference - Fix: NetworkFirewallPolicySummaryCollection not iterable (.items accessor)
This commit is contained in:
@@ -244,7 +244,7 @@ tbody tr:last-child td{border-bottom:none}
|
||||
|
||||
/* ── Explorer ── */
|
||||
.exp-wrap{display:flex;height:calc(100vh - 56px - 3rem);border:1px solid var(--bd);border-radius:var(--rl);overflow:hidden;box-shadow:var(--sh2)}
|
||||
.exp-tree{width:240px;flex-shrink:0;background:var(--bg1);border-right:none;overflow-y:auto;padding:.5rem 0;min-width:140px;max-width:50%}
|
||||
.exp-tree{width:280px;flex-shrink:0;background:var(--bg1);border-right:none;overflow-y:auto;padding:0;min-width:180px;max-width:50%}
|
||||
.exp-resize{width:5px;flex-shrink:0;cursor:col-resize;background:var(--bd);transition:background .15s;position:relative}
|
||||
.exp-resize:hover,.exp-resize.dragging{background:var(--ac);opacity:.6}
|
||||
.exp-resize::after{content:'';position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);width:3px;height:28px;border-left:1px solid var(--t4);border-right:1px solid var(--t4);opacity:.4}
|
||||
@@ -268,7 +268,7 @@ tbody tr:last-child td{border-bottom:none}
|
||||
.exp-c strong{color:var(--t1);font-size:.84rem;font-weight:600;display:block;overflow:hidden;text-overflow:ellipsis}
|
||||
.exp-c .em{font-family:var(--fm);font-size:.65rem;color:var(--t4);word-break:break-all;overflow-wrap:break-word;display:inline-block;max-width:100%}
|
||||
.exp-node{padding:.15rem 0;font-size:.74rem;color:var(--t2)}
|
||||
.exp-node-row{display:flex;align-items:center;gap:.25rem;padding:.25rem .5rem;border-radius:6px;cursor:pointer;transition:all .15s}
|
||||
.exp-node-row{display:flex;align-items:center;gap:.3rem;padding:.35rem .6rem;border-radius:6px;cursor:pointer;transition:all .15s;font-size:.76rem}
|
||||
.exp-node-row:hover{background:var(--bg2)}
|
||||
.exp-node-row.selected{background:var(--acl);color:var(--ac);font-weight:600;border-left:2px solid var(--ac)}
|
||||
.exp-node-toggle{width:16px;text-align:center;font-size:.55rem;color:var(--t4);flex-shrink:0;cursor:pointer;transition:transform .15s}
|
||||
@@ -531,7 +531,7 @@ const IC={
|
||||
const LOGO_W=`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36" width="26" height="26" style="flex-shrink:0"><rect x="2" y="5" width="32" height="26" rx="13" ry="13" fill="rgba(255,255,255,0.12)" stroke="#fff" stroke-width="2"/><rect x="11" y="11" width="14" height="14" rx="4" fill="#fff" opacity="0.95"/><circle cx="15" cy="17" r="2" fill="#C74634"/><circle cx="21" cy="17" r="2" fill="#C74634"/><circle cx="15" cy="16.7" r="0.7" fill="#fff"/><circle cx="21" cy="16.7" r="0.7" fill="#fff"/><rect x="14" y="21" width="8" height="1.5" rx="0.75" fill="#C74634" opacity="0.6"/><line x1="18" y1="11" x2="18" y2="6" stroke="#fff" stroke-width="1.2" stroke-linecap="round"/><circle cx="18" cy="5.5" r="1.5" fill="#fff" opacity="0.9"/><line x1="11" y1="18" x2="6" y2="18" stroke="#fff" stroke-width="1" stroke-linecap="round" opacity="0.7"/><line x1="25" y1="18" x2="30" y2="18" stroke="#fff" stroke-width="1" stroke-linecap="round" opacity="0.7"/><circle cx="5.5" cy="18" r="1" fill="#fff" opacity="0.7"/><circle cx="30.5" cy="18" r="1" fill="#fff" opacity="0.7"/></svg>`;
|
||||
const LOGO_R=`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36" width="52" height="52"><rect x="2" y="5" width="32" height="26" rx="13" ry="13" fill="rgba(199,70,52,0.08)" stroke="#C74634" stroke-width="1.8"/><rect x="11" y="11" width="14" height="14" rx="4" fill="#fff" stroke="#C74634" stroke-width="0.4"/><circle cx="15" cy="17" r="2" fill="#C74634"/><circle cx="21" cy="17" r="2" fill="#C74634"/><circle cx="15" cy="16.7" r="0.7" fill="#fff"/><circle cx="21" cy="16.7" r="0.7" fill="#fff"/><rect x="14" y="21" width="8" height="1.5" rx="0.75" fill="#C74634" opacity="0.6"/><line x1="18" y1="11" x2="18" y2="6" stroke="#C74634" stroke-width="1.2" stroke-linecap="round"/><circle cx="18" cy="5.5" r="1.5" fill="#C74634" opacity="0.25" stroke="#C74634" stroke-width="0.7"/><line x1="11" y1="18" x2="6" y2="18" stroke="#C74634" stroke-width="1" stroke-linecap="round" opacity="0.4"/><line x1="25" y1="18" x2="30" y2="18" stroke="#C74634" stroke-width="1" stroke-linecap="round" opacity="0.4"/><circle cx="5.5" cy="18" r="1" fill="#C74634" opacity="0.35"/><circle cx="30.5" cy="18" r="1" fill="#C74634" opacity="0.35"/></svg>`;
|
||||
|
||||
const S={user:null,token:null,tab:'chat',msgs:[],sid:null,reports:[],ociCfg:[],genaiCfg:[],adbCfg:[],mcpSvr:[],users:[],models:{},regions:[],embModels:{},expData:null,expCfg:'',expSelRegions:[],expRegDdOpen:false,expTree:[],expSelComp:'',expCat:'Compute',expResType:'instances',expTreeOpen:{},expLoading:false,expRegions:[],expCounts:{},expTreeW:null,editing:null,
|
||||
const S={user:null,token:null,tab:'chat',msgs:[],sid:null,reports:[],ociCfg:[],genaiCfg:[],adbCfg:[],mcpSvr:[],users:[],models:{},regions:[],embModels:{},expData:null,expCfg:'',expSelRegions:[],expRegDdOpen:false,expTree:[],expSelComp:'',expCat:'Compute',expResType:'instances',expTreeOpen:{},expLoading:false,expRegions:[],expCounts:{},expCountsRefreshing:false,expTreeW:null,editing:null,
|
||||
chatModel:'',chatOci:'',chatRegion:'',chatCompartment:'',chatHistOpen:false,chatHistory:[],tfHistOpen:false,tfHistory:[],
|
||||
chatParams:{temperature:1,max_tokens:6000,top_p:0.95,top_k:1,frequency_penalty:0,presence_penalty:0,reasoning_effort:'medium'},chatPanel:'',chatUseTools:true,
|
||||
chatPrompts:[],editingPrompt:null,trackingReportId:null,ociRegions:{},rptSelRegions:[],rptRegionsOpen:false,rptRegionFilter:'',
|
||||
@@ -1615,6 +1615,21 @@ async function ociAdbAction(id,action){
|
||||
if(!confirm(label+' este Autonomous Database?'))return;
|
||||
try{await $api('/oci/autonomous-databases/'+id+'/action',{method:'POST',body:{action,oci_config_id:S.expCfg,region:null}});expPollState(action==='start'?'AVAILABLE':'STOPPED')}catch(e){alert('Erro: '+e.message)}
|
||||
}
|
||||
async function ociDbSystemAction(id,action){
|
||||
const label=action==='START'?'Iniciar':'Parar';
|
||||
if(!confirm(label+' este DB System?'))return;
|
||||
try{await $api('/oci/db-systems/'+id+'/action',{method:'POST',body:{action,oci_config_id:S.expCfg,region:null}});expPollState(action==='START'?'AVAILABLE':'STOPPED')}catch(e){alert('Erro: '+e.message)}
|
||||
}
|
||||
async function ociMysqlAction(id,action){
|
||||
const label=action==='start'?'Iniciar':'Parar';
|
||||
if(!confirm(label+' este MySQL DB System?'))return;
|
||||
try{await $api('/oci/mysql-db-systems/'+id+'/action',{method:'POST',body:{action,oci_config_id:S.expCfg,region:null}});expPollState(action==='start'?'ACTIVE':'INACTIVE')}catch(e){alert('Erro: '+e.message)}
|
||||
}
|
||||
async function ociContainerAction(id,action){
|
||||
const label=action==='start'?'Iniciar':'Parar';
|
||||
if(!confirm(label+' esta Container Instance?'))return;
|
||||
try{await $api('/oci/container-instances/'+id+'/action',{method:'POST',body:{action,oci_config_id:S.expCfg,region:null}});expPollState(action==='start'?'ACTIVE':'INACTIVE')}catch(e){alert('Erro: '+e.message)}
|
||||
}
|
||||
async function ociAdbUpdateNet(id){
|
||||
try{
|
||||
const ipResp=await $api('/oci/my-ip');
|
||||
@@ -1844,8 +1859,19 @@ function rExplorer(){
|
||||
const cats=EXP_GROUPS.map(g=>`<div class="exp-cat${S.expCat===g.name?' active':''}" onclick="expSetCat('${g.name}')">${catIcons[g.name]||''} ${g.name}</div>`).join('');
|
||||
const activeGrp=EXP_GROUPS.find(g=>g.name===S.expCat)||EXP_GROUPS[0];
|
||||
const tabs=activeGrp.tabs.map(t=>{const cnt=S.expCounts[t.key];return`<div class="exp-tab${S.expResType===t.key?' active':''}" onclick="expSetTab('${t.key}')">${t.label}${cnt!=null?' <span style="font-size:.58rem;opacity:.7">('+cnt+')</span>':''}</div>`}).join('');
|
||||
// KPI stats from counts
|
||||
const _iamKeys=new Set(['iam_users','iam_groups','iam_policies','iam_dynamic_groups']);
|
||||
const totalRes=Object.entries(S.expCounts).reduce((a,[k,v])=>a+(_iamKeys.has(k)?0:(v||0)),0);
|
||||
const kpiCats=EXP_GROUPS.map(g=>{const cnt=g.tabs.reduce((a,t)=>a+(S.expCounts[t.key]||0),0);const detail=g.tabs.filter(t=>(S.expCounts[t.key]||0)>0).map(t=>t.label+': '+S.expCounts[t.key]).join('\n');return{name:g.name,icon:catIcons[g.name]||'',count:cnt,detail:detail}}).filter(k=>k.count>0);
|
||||
const selCompName=S.expTree.find(n=>n.id===S.expSelComp);
|
||||
const compLabel=selCompName?selCompName.name:'—';
|
||||
const kpiHtml=S.expSelComp?`<div style="display:flex;gap:.5rem;padding:.5rem .75rem;border-bottom:1px solid var(--bd);background:var(--bg);overflow-x:auto;align-items:stretch">
|
||||
<div class="kpi" style="padding:.5rem .7rem;min-width:120px;flex:1"><div class="kpi-label">${IC.folder} Compartment</div><div style="font-size:.82rem;font-weight:700;color:var(--t1);margin-top:.15rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap" title="${compLabel}">${compLabel}</div><div class="kpi-sub">${S.expSelRegions.length||'Todas'} região(ões)</div></div>
|
||||
<div class="kpi kpi-info" style="padding:.5rem .7rem;min-width:80px;text-align:center;flex:0 0 auto"><div class="kpi-label">${IC.activity} Total</div><div class="kpi-val v-info" style="font-size:1.3rem">${totalRes}</div><div class="kpi-sub">${S.expCountsRefreshing?'<span class="spinner" style="width:10px;height:10px;display:inline-block;vertical-align:-1px"></span> atualizando...':'recursos'}</div></div>
|
||||
${kpiCats.slice(0,6).map(k=>`<div class="kpi" style="padding:.5rem .7rem;min-width:75px;text-align:center;flex:0 0 auto;cursor:default" title="${k.detail}"><div class="kpi-label" style="justify-content:center">${k.icon}</div><div class="kpi-val" style="font-size:1.2rem">${k.count}</div><div class="kpi-sub">${k.name}</div></div>`).join('')}
|
||||
</div>`:'';
|
||||
return`<div class="exp-wrap">
|
||||
<div class="exp-tree" id="exp-tree"><div style="padding:.5rem .65rem .4rem;font-size:.74rem;font-weight:700;color:var(--t2);border-bottom:1px solid var(--bd);margin-bottom:.3rem;display:flex;align-items:center;gap:.35rem">${IC.folder} Compartments</div>${S.expTree.length?rExpTree(null,0):'<div class="ld"><span class="spinner"></span> Carregando...</div>'}</div>
|
||||
<div class="exp-tree" id="exp-tree"><div style="padding:.6rem .7rem .5rem;font-size:.78rem;font-weight:700;color:var(--t1);border-bottom:1px solid var(--bd);margin-bottom:.3rem;display:flex;align-items:center;gap:.4rem;background:var(--bg2)">${IC.folder} Compartments</div>${S.expTree.length?rExpTree(null,0):'<div class="ld"><span class="spinner"></span> Carregando...</div>'}</div>
|
||||
<div class="exp-resize" onmousedown="expStartResize(event)"></div>
|
||||
<div class="exp-main">
|
||||
<div class="exp-toolbar"><select onchange="expSetCfg(this.value)">${cfgOpts}</select>
|
||||
@@ -1857,6 +1883,7 @@ ${S.expRegDdOpen?`<div style="position:absolute;top:100%;left:0;margin-top:4px;b
|
||||
</div>`:''}</div>`:''}
|
||||
${S.expSelRegions.length?`<div style="display:flex;gap:.25rem;flex-wrap:wrap;align-items:center">${S.expSelRegions.map(r=>`<span class="tag" style="font-size:.6rem;display:inline-flex;align-items:center;gap:.2rem;background:var(--acl);color:var(--ac);border:1px solid var(--ac);border-radius:6px;padding:.1rem .35rem">${r}<span onclick="expRemoveRegion('${r}')" style="cursor:pointer;font-weight:700;font-size:.7rem">×</span></span>`).join('')}</div>`:''}
|
||||
<button class="btn" style="margin-left:auto;font-size:.7rem;padding:.25rem .6rem" onclick="expRefresh()">↻ Refresh</button></div>
|
||||
${kpiHtml}
|
||||
<div class="exp-nav"><div class="exp-cats">${cats}</div></div>
|
||||
<div class="exp-tabs">${tabs}</div>
|
||||
<div class="exp-content">${S.expLoading?'<div class="ld"><span class="spinner"></span> Consultando OCI...</div>':renderExpData(S.expData)}</div>
|
||||
@@ -1873,19 +1900,19 @@ function rExpTree(parentId,depth){
|
||||
<span class="exp-node-toggle" onclick="event.stopPropagation();expToggleNode('${n.id}')">${hasKids?(isOpen?'▼':'▶'):' '}</span>
|
||||
<span>${n.name}</span></div>
|
||||
${hasKids&&isOpen?`<div class="exp-node-children">${rExpTree(n.id,depth+1)}</div>`:''}</div>`}).join('')}
|
||||
async function expSetCfg(cid){S.expCfg=cid;S.expTree=[];S.expSelComp='';S.expData=null;S.expSelRegions=[];S.expRegions=[];S.expCounts={};S.expRegDdOpen=false;R();await expLoadTree();await expLoadRegions()}
|
||||
async function expSetCfg(cid){S.expCfg=cid;S.expTree=[];S.expSelComp='';S.expData=null;S.expSelRegions=[];S.expRegions=[];S.expCounts={};S.expRegDdOpen=false;_expCountAbort++;R();await expLoadTree();await expLoadRegions()}
|
||||
function expToggleRegion(r){const i=S.expSelRegions.indexOf(r);if(i>=0)S.expSelRegions.splice(i,1);else S.expSelRegions.push(r);R()}
|
||||
function expRemoveRegion(r){S.expSelRegions=S.expSelRegions.filter(x=>x!==r);if(S.expSelComp)expLoadRes()}
|
||||
async function expApplyRegions(){if(S.expSelComp)await expLoadRes()}
|
||||
async function expApplyRegions(){if(S.expSelComp){S.expCounts={};_expCountAbort++;await expLoadRes();expLoadCachedCounts();expRefreshCounts()}}
|
||||
function expSetCat(c){S.expCat=c;const grp=EXP_GROUPS.find(g=>g.name===c);if(grp&&grp.tabs.length){S.expResType=grp.tabs[0].key;S.expData=null;R();if(S.expSelComp)expLoadRes()}else R()}
|
||||
function expSetTab(t){S.expResType=t;S.expData=null;R();if(S.expSelComp)expLoadRes()}
|
||||
function expToggleNode(id){S.expTreeOpen[id]=!S.expTreeOpen[id];R()}
|
||||
async function expSelectComp(id){S.expSelComp=id;S.expCounts={};await expLoadRes()}
|
||||
async function expSelectComp(id){S.expSelComp=id;S.expCounts={};_expCountAbort++;expLoadCachedCounts();await expLoadRes();expRefreshCounts()}
|
||||
function expStartResize(e){e.preventDefault();const tree=document.getElementById('exp-tree');const handle=e.target;if(!tree)return;handle.classList.add('dragging');const startX=e.clientX;const startW=tree.offsetWidth;
|
||||
const onMove=ev=>{const w=Math.max(140,Math.min(startW+(ev.clientX-startX),window.innerWidth*0.5));tree.style.width=w+'px';S.expTreeW=w};
|
||||
const onUp=()=>{handle.classList.remove('dragging');document.removeEventListener('mousemove',onMove);document.removeEventListener('mouseup',onUp);document.body.style.cursor='';document.body.style.userSelect=''};
|
||||
document.body.style.cursor='col-resize';document.body.style.userSelect='none';document.addEventListener('mousemove',onMove);document.addEventListener('mouseup',onUp)}
|
||||
async function expRefresh(){if(S.expCfg){S.expTree=[];S.expCounts={};R();await expLoadTree();if(S.expSelComp)await expLoadRes()}}
|
||||
async function expRefresh(){if(S.expCfg){S.expTree=[];S.expCounts={};_expCountAbort++;R();await expLoadTree();if(S.expSelComp){await expLoadRes();expRefreshCounts()}}}
|
||||
async function expLoadTree(){
|
||||
if(!S.expCfg)return;
|
||||
try{const d=await $api('/oci/explore/'+S.expCfg+'/compartment-tree');
|
||||
@@ -1917,25 +1944,131 @@ async function expLoadRes(){
|
||||
else S.expData=[];
|
||||
S.expCounts[S.expResType]=all.length;
|
||||
S.expLoading=false;R()}
|
||||
let _expCountAbort=0;
|
||||
async function expLoadCachedCounts(){
|
||||
const comp=S.expSelComp;const cfg=S.expCfg;if(!comp||!cfg)return;
|
||||
const regKey=S.expSelRegions.length?S.expSelRegions.slice().sort().join(','):'';
|
||||
try{
|
||||
const q=new URLSearchParams({compartment_id:comp,regions:regKey});
|
||||
const d=await $api('/oci/explore/'+cfg+'/counts?'+q);
|
||||
if(d&&!d.error){for(const[k,v]of Object.entries(d)){S.expCounts[k]=v.count}R()}
|
||||
}catch(e){console.warn('Cache counts error:',e)}
|
||||
}
|
||||
async function expRefreshCounts(){
|
||||
const comp=S.expSelComp;const cfg=S.expCfg;if(!comp||!cfg)return;
|
||||
const myRun=++_expCountAbort;
|
||||
S.expCountsRefreshing=true;R();
|
||||
try{
|
||||
await $api('/oci/explore/'+cfg+'/counts/refresh',{method:'POST',body:{compartment_id:comp,regions:S.expSelRegions}});
|
||||
const poll=async(attempts)=>{
|
||||
if(_expCountAbort!==myRun||S.expSelComp!==comp){S.expCountsRefreshing=false;R();return}
|
||||
if(attempts<=0){S.expCountsRefreshing=false;R();return}
|
||||
await new Promise(r=>setTimeout(r,3000));
|
||||
if(_expCountAbort!==myRun||S.expSelComp!==comp){S.expCountsRefreshing=false;R();return}
|
||||
await expLoadCachedCounts();
|
||||
poll(attempts-1);
|
||||
};
|
||||
poll(12);
|
||||
}catch(e){S.expCountsRefreshing=false;R();console.warn('Refresh counts error:',e)}
|
||||
}
|
||||
function renderExpData(d){if(!d)return'<div class="emp"><p>Selecione um compartment na árvore.</p></div>';
|
||||
if(d.error)return`<div class="al al-e">${d.error}</div>`;if(!Array.isArray(d)||!d.length)return'<div class="emp"><p>Nenhum recurso encontrado neste compartment.</p></div>';
|
||||
if(S.expResType==='security_lists')return rExpSecLists(d);
|
||||
if(S.expResType==='nsgs')return rExpNsgs(d);
|
||||
if(S.expResType==='route_tables')return rExpRouteTables(d);
|
||||
if(S.expResType==='vcns')return rExpVcns(d);
|
||||
if(S.expResType==='subnets')return rExpSubnets(d);
|
||||
if(S.expResType==='load_balancers')return rExpLoadBalancers(d);
|
||||
if(S.expResType==='buckets')return rExpBuckets(d);
|
||||
const isInst=S.expResType==='instances';
|
||||
const isAdb=S.expResType==='databases';
|
||||
const isDbSys=S.expResType==='db_systems';
|
||||
const isMysql=S.expResType==='mysql_db_systems';
|
||||
const isCI=S.expResType==='container_instances';
|
||||
return`<div style="font-size:.74rem;color:var(--t4);margin-bottom:.5rem">${d.length} item(ns) encontrado(s)</div><div class="exp-r">${d.map(i=>{
|
||||
const keys=Object.keys(i);
|
||||
const name=i.display_name||i.name||i.db_name||i.application||'—';
|
||||
const skip=['display_name','name','db_name','lifecycle_state','application','rules','routes','whitelisted_ips'];
|
||||
const skip=['display_name','name','db_name','lifecycle_state','application','rules','routes','whitelisted_ips','listeners','backend_sets'];
|
||||
const aclHtml=isAdb&&i.whitelisted_ips&&i.whitelisted_ips.length?'<br><span style="color:var(--ac);font-size:.66rem;font-weight:600">ACL:</span> <span class="em" style="font-size:.66rem">'+i.whitelisted_ips.join(', ')+'</span>':'';
|
||||
let actionBtn='';
|
||||
if(isInst&&i.lifecycle_state==='RUNNING')actionBtn=' <button class="btn bsm" style="font-size:.6rem;padding:1px 8px;background:var(--rd);color:#fff;border-color:var(--rd);margin-left:6px" onclick="event.stopPropagation();ociInstanceAction(\''+i.id+'\',\'STOP\')">■ Stop</button>';
|
||||
else if(isInst&&i.lifecycle_state==='STOPPED')actionBtn=' <button class="btn bsm" style="font-size:.6rem;padding:1px 8px;background:var(--gn);color:#fff;border-color:var(--gn);margin-left:6px" onclick="event.stopPropagation();ociInstanceAction(\''+i.id+'\',\'START\')">▶ Start</button>';
|
||||
else if(isAdb&&i.lifecycle_state==='AVAILABLE')actionBtn=' <button class="btn bsm" style="font-size:.6rem;padding:1px 8px;background:var(--rd);color:#fff;border-color:var(--rd);margin-left:6px" onclick="event.stopPropagation();ociAdbAction(\''+i.id+'\',\'stop\')">■ Stop</button> <button class="btn bsm" style="font-size:.6rem;padding:1px 8px;background:var(--ac);color:#fff;border-color:var(--ac);margin-left:2px" onclick="event.stopPropagation();ociAdbUpdateNet(\''+i.id+'\')">'+IC.shield+' Update Network Access</button>';
|
||||
else if(isAdb&&i.lifecycle_state==='STOPPED')actionBtn=' <button class="btn bsm" style="font-size:.6rem;padding:1px 8px;background:var(--gn);color:#fff;border-color:var(--gn);margin-left:6px" onclick="event.stopPropagation();ociAdbAction(\''+i.id+'\',\'start\')">▶ Start</button> <button class="btn bsm" style="font-size:.6rem;padding:1px 8px;background:var(--ac);color:#fff;border-color:var(--ac);margin-left:2px" onclick="event.stopPropagation();ociAdbUpdateNet(\''+i.id+'\')">'+IC.shield+' Update Network Access</button>';
|
||||
else if(isDbSys&&i.lifecycle_state==='AVAILABLE')actionBtn=' <button class="btn bsm" style="font-size:.6rem;padding:1px 8px;background:var(--rd);color:#fff;border-color:var(--rd);margin-left:6px" onclick="event.stopPropagation();ociDbSystemAction(\''+i.id+'\',\'STOP\')">■ Stop</button>';
|
||||
else if(isDbSys&&i.lifecycle_state==='STOPPED')actionBtn=' <button class="btn bsm" style="font-size:.6rem;padding:1px 8px;background:var(--gn);color:#fff;border-color:var(--gn);margin-left:6px" onclick="event.stopPropagation();ociDbSystemAction(\''+i.id+'\',\'START\')">▶ Start</button>';
|
||||
else if(isMysql&&i.lifecycle_state==='ACTIVE')actionBtn=' <button class="btn bsm" style="font-size:.6rem;padding:1px 8px;background:var(--rd);color:#fff;border-color:var(--rd);margin-left:6px" onclick="event.stopPropagation();ociMysqlAction(\''+i.id+'\',\'stop\')">■ Stop</button>';
|
||||
else if(isMysql&&i.lifecycle_state==='INACTIVE')actionBtn=' <button class="btn bsm" style="font-size:.6rem;padding:1px 8px;background:var(--gn);color:#fff;border-color:var(--gn);margin-left:6px" onclick="event.stopPropagation();ociMysqlAction(\''+i.id+'\',\'start\')">▶ Start</button>';
|
||||
else if(isCI&&i.lifecycle_state==='ACTIVE')actionBtn=' <button class="btn bsm" style="font-size:.6rem;padding:1px 8px;background:var(--rd);color:#fff;border-color:var(--rd);margin-left:6px" onclick="event.stopPropagation();ociContainerAction(\''+i.id+'\',\'stop\')">■ Stop</button>';
|
||||
else if(isCI&&i.lifecycle_state==='INACTIVE')actionBtn=' <button class="btn bsm" style="font-size:.6rem;padding:1px 8px;background:var(--gn);color:#fff;border-color:var(--gn);margin-left:6px" onclick="event.stopPropagation();ociContainerAction(\''+i.id+'\',\'start\')">▶ Start</button>';
|
||||
return`<div class="exp-c"><strong>${name}</strong>${i.lifecycle_state?` <span class="badge ${i.lifecycle_state==='RUNNING'||i.lifecycle_state==='ACTIVE'||i.lifecycle_state==='AVAILABLE'?'b-pass':'b-pend'}">${i.lifecycle_state}</span>`:''}${actionBtn}${aclHtml}<br>
|
||||
${keys.filter(k=>!skip.includes(k)).map(k=>`<span style="color:var(--t4);font-size:.66rem">${k}:</span> <span class="em">${typeof i[k]==='object'?JSON.stringify(i[k]):i[k]}</span>`).join('<br>')}</div>`}).join('')}</div>`}
|
||||
function _fmtBytes(b){if(!b&&b!==0)return'—';if(b<1024)return b+' B';if(b<1048576)return(b/1024).toFixed(1)+' KB';if(b<1073741824)return(b/1048576).toFixed(1)+' MB';if(b<1099511627776)return(b/1073741824).toFixed(1)+' GB';return(b/1099511627776).toFixed(2)+' TB'}
|
||||
function rExpVcns(d){
|
||||
return`<div style="font-size:.74rem;color:var(--t4);margin-bottom:.5rem">${d.length} VCN(s)</div>${d.map((v,idx)=>{
|
||||
const expanded=S._expExpanded&&S._expExpanded['vcn'+idx];
|
||||
return`<div class="exp-c" style="max-width:100%"><div style="display:flex;justify-content:space-between;align-items:center;cursor:pointer" onclick="S._expExpanded=S._expExpanded||{};S._expExpanded['vcn'+${idx}]=!S._expExpanded['vcn'+${idx}];R()">
|
||||
<div><strong>${v.display_name}</strong> <span class="badge ${v.lifecycle_state==='AVAILABLE'?'b-pass':'b-pend'}">${v.lifecycle_state}</span></div>
|
||||
<div style="font-size:.66rem;color:var(--t4)">${expanded?'▼':'▶'} ${(v.cidr_blocks||[]).length} CIDR(s)</div></div>
|
||||
<div class="em" style="margin-top:.3rem;font-size:.64rem">id: ${v.id}</div>
|
||||
${expanded?`<div style="margin-top:.5rem">
|
||||
<table style="width:100%;font-size:.68rem;border-collapse:collapse">
|
||||
<tr style="border-top:1px solid var(--bd)"><td style="padding:.25rem .4rem;color:var(--t4);width:140px">CIDR Blocks</td><td style="padding:.25rem .4rem;font-family:var(--fm)">${(v.cidr_blocks||[]).join(', ')}</td></tr>
|
||||
<tr style="border-top:1px solid var(--bd)"><td style="padding:.25rem .4rem;color:var(--t4)">DNS Label</td><td style="padding:.25rem .4rem">${v.dns_label||'—'}</td></tr>
|
||||
<tr style="border-top:1px solid var(--bd)"><td style="padding:.25rem .4rem;color:var(--t4)">VCN Domain</td><td style="padding:.25rem .4rem;font-family:var(--fm)">${v.vcn_domain_name||'—'}</td></tr>
|
||||
<tr style="border-top:1px solid var(--bd)"><td style="padding:.25rem .4rem;color:var(--t4)">Default Route Table</td><td style="padding:.25rem .4rem;font-size:.6rem;font-family:var(--fm)">${v.default_route_table_id||'—'}</td></tr>
|
||||
<tr style="border-top:1px solid var(--bd)"><td style="padding:.25rem .4rem;color:var(--t4)">Default Security List</td><td style="padding:.25rem .4rem;font-size:.6rem;font-family:var(--fm)">${v.default_security_list_id||'—'}</td></tr>
|
||||
<tr style="border-top:1px solid var(--bd)"><td style="padding:.25rem .4rem;color:var(--t4)">Criado em</td><td style="padding:.25rem .4rem">${v.time_created||'—'}</td></tr>
|
||||
</table></div>`:''}</div>`}).join('')}`}
|
||||
function rExpSubnets(d){
|
||||
return`<div style="font-size:.74rem;color:var(--t4);margin-bottom:.5rem">${d.length} subnet(s)</div>${d.map((s,idx)=>{
|
||||
const expanded=S._expExpanded&&S._expExpanded['sub'+idx];
|
||||
return`<div class="exp-c" style="max-width:100%"><div style="display:flex;justify-content:space-between;align-items:center;cursor:pointer" onclick="S._expExpanded=S._expExpanded||{};S._expExpanded['sub'+${idx}]=!S._expExpanded['sub'+${idx}];R()">
|
||||
<div><strong>${s.display_name}</strong> <span class="badge ${s.lifecycle_state==='AVAILABLE'?'b-pass':'b-pend'}">${s.lifecycle_state}</span> <span style="font-size:.62rem;color:var(--t4);margin-left:4px">${s.cidr_block}</span></div>
|
||||
<div style="font-size:.66rem;color:var(--t4)">${expanded?'▼':'▶'} ${s.availability_domain||'Regional'}</div></div>
|
||||
<div class="em" style="margin-top:.3rem;font-size:.64rem">id: ${s.id}</div>
|
||||
${expanded?`<div style="margin-top:.5rem">
|
||||
<table style="width:100%;font-size:.68rem;border-collapse:collapse">
|
||||
<tr style="border-top:1px solid var(--bd)"><td style="padding:.25rem .4rem;color:var(--t4);width:140px">CIDR</td><td style="padding:.25rem .4rem;font-family:var(--fm)">${s.cidr_block}</td></tr>
|
||||
<tr style="border-top:1px solid var(--bd)"><td style="padding:.25rem .4rem;color:var(--t4)">Availability Domain</td><td style="padding:.25rem .4rem">${s.availability_domain||'Regional'}</td></tr>
|
||||
<tr style="border-top:1px solid var(--bd)"><td style="padding:.25rem .4rem;color:var(--t4)">DNS Label</td><td style="padding:.25rem .4rem">${s.dns_label||'—'}</td></tr>
|
||||
<tr style="border-top:1px solid var(--bd)"><td style="padding:.25rem .4rem;color:var(--t4)">Subnet Domain</td><td style="padding:.25rem .4rem;font-family:var(--fm)">${s.subnet_domain_name||'—'}</td></tr>
|
||||
<tr style="border-top:1px solid var(--bd)"><td style="padding:.25rem .4rem;color:var(--t4)">IP Público</td><td style="padding:.25rem .4rem">${s.prohibit_public_ip_on_vnic?'Proibido (privada)':'Permitido (pública)'}</td></tr>
|
||||
<tr style="border-top:1px solid var(--bd)"><td style="padding:.25rem .4rem;color:var(--t4)">Route Table</td><td style="padding:.25rem .4rem;font-size:.6rem;font-family:var(--fm)">${s.route_table_id||'—'}</td></tr>
|
||||
<tr style="border-top:1px solid var(--bd)"><td style="padding:.25rem .4rem;color:var(--t4)">Security Lists</td><td style="padding:.25rem .4rem;font-size:.6rem;font-family:var(--fm)">${(s.security_list_ids||[]).join('<br>')||'—'}</td></tr>
|
||||
<tr style="border-top:1px solid var(--bd)"><td style="padding:.25rem .4rem;color:var(--t4)">VCN</td><td style="padding:.25rem .4rem;font-size:.6rem;font-family:var(--fm)">${s.vcn_id||'—'}</td></tr>
|
||||
<tr style="border-top:1px solid var(--bd)"><td style="padding:.25rem .4rem;color:var(--t4)">Criado em</td><td style="padding:.25rem .4rem">${s.time_created||'—'}</td></tr>
|
||||
</table></div>`:''}</div>`}).join('')}`}
|
||||
function rExpLoadBalancers(d){
|
||||
return`<div style="font-size:.74rem;color:var(--t4);margin-bottom:.5rem">${d.length} load balancer(s)</div>${d.map((lb,idx)=>{
|
||||
const expanded=S._expExpanded&&S._expExpanded['lb'+idx];
|
||||
const ips=(lb.ip_addresses||[]).join(', ');
|
||||
return`<div class="exp-c" style="max-width:100%"><div style="display:flex;justify-content:space-between;align-items:center;cursor:pointer" onclick="S._expExpanded=S._expExpanded||{};S._expExpanded['lb'+${idx}]=!S._expExpanded['lb'+${idx}];R()">
|
||||
<div><strong>${lb.display_name}</strong> <span class="badge ${lb.lifecycle_state==='ACTIVE'?'b-pass':'b-pend'}">${lb.lifecycle_state}</span> <span style="font-size:.62rem;color:var(--t4);margin-left:4px">${lb.is_private?'Privado':'Público'}</span></div>
|
||||
<div style="font-size:.66rem;color:var(--t4)">${expanded?'▼':'▶'} ${lb.shape_name} · ${(lb.listeners||[]).length} listener(s)</div></div>
|
||||
<div class="em" style="margin-top:.3rem;font-size:.64rem">IPs: ${ips||'—'}</div>
|
||||
${expanded?`<div style="margin-top:.5rem">
|
||||
${(lb.listeners||[]).length?`<div style="font-size:.68rem;font-weight:600;color:var(--t2);margin:.4rem 0 .2rem">Listeners (${lb.listeners.length})</div>
|
||||
<table style="width:100%;font-size:.66rem;border-collapse:collapse"><thead><tr style="background:var(--bg3);text-align:left"><th style="padding:.25rem .4rem">Nome</th><th style="padding:.25rem .4rem">Porta</th><th style="padding:.25rem .4rem">Protocolo</th><th style="padding:.25rem .4rem">Backend Set</th></tr></thead><tbody>
|
||||
${lb.listeners.map(l=>`<tr style="border-top:1px solid var(--bd)"><td style="padding:.25rem .4rem;font-weight:600">${l.name}</td><td style="padding:.25rem .4rem">${l.port}</td><td style="padding:.25rem .4rem">${l.protocol}</td><td style="padding:.25rem .4rem;font-size:.62rem">${l.backend_set||'—'}</td></tr>`).join('')}
|
||||
</tbody></table>`:''}
|
||||
${(lb.backend_sets||[]).length?`<div style="font-size:.68rem;font-weight:600;color:var(--t2);margin:.6rem 0 .2rem">Backend Sets (${lb.backend_sets.length})</div>
|
||||
${lb.backend_sets.map(bs=>`<div style="margin-bottom:.4rem;padding:.3rem .4rem;background:var(--bg3);border-radius:6px"><strong style="font-size:.66rem">${bs.name}</strong> <span style="font-size:.62rem;color:var(--t4)">policy: ${bs.policy||'—'}</span>
|
||||
${(bs.backends||[]).length?`<table style="width:100%;font-size:.62rem;border-collapse:collapse;margin-top:.3rem"><thead><tr style="text-align:left"><th style="padding:.2rem .3rem">IP</th><th style="padding:.2rem .3rem">Porta</th><th style="padding:.2rem .3rem">Peso</th></tr></thead><tbody>
|
||||
${bs.backends.map(b=>`<tr style="border-top:1px solid var(--bd)"><td style="padding:.2rem .3rem;font-family:var(--fm)">${b.ip}</td><td style="padding:.2rem .3rem">${b.port}</td><td style="padding:.2rem .3rem">${b.weight}</td></tr>`).join('')}
|
||||
</tbody></table>`:'<div style="font-size:.62rem;color:var(--t4);margin-top:.2rem">Nenhum backend</div>'}</div>`).join('')}`:''}
|
||||
</div>`:''}</div>`}).join('')}`}
|
||||
function rExpBuckets(d){
|
||||
return`<div style="font-size:.74rem;color:var(--t4);margin-bottom:.5rem">${d.length} bucket(s)</div><div class="exp-r">${d.map(b=>{
|
||||
const vis=b.public_access_type==='NoPublicAccess'?'Privado':'Público ('+b.public_access_type+')';
|
||||
const visBadge=b.public_access_type==='NoPublicAccess'?'b-pass':'b-pend';
|
||||
return`<div class="exp-c"><strong>${b.name}</strong> <span class="badge ${visBadge}">${vis}</span>
|
||||
<br><span style="color:var(--t4);font-size:.66rem">Objetos:</span> <span class="em">${b.approximate_count!=null?b.approximate_count.toLocaleString():'—'}</span>
|
||||
<span style="color:var(--t4);font-size:.66rem;margin-left:8px">Tamanho:</span> <span class="em">${_fmtBytes(b.approximate_size)}</span>
|
||||
<br><span style="color:var(--t4);font-size:.66rem">Storage Tier:</span> <span class="em">${b.storage_tier||'—'}</span>
|
||||
<span style="color:var(--t4);font-size:.66rem;margin-left:8px">Versionamento:</span> <span class="em">${b.versioning||'—'}</span>
|
||||
<br><span style="color:var(--t4);font-size:.66rem">Namespace:</span> <span class="em">${b.namespace}</span>
|
||||
<span style="color:var(--t4);font-size:.66rem;margin-left:8px">Criado:</span> <span class="em">${b.time_created||'—'}</span></div>`}).join('')}</div>`}
|
||||
function rExpSecLists(d){
|
||||
return`<div style="font-size:.74rem;color:var(--t4);margin-bottom:.5rem">${d.length} security list(s)</div>${d.map((sl,idx)=>{
|
||||
const rules=sl.rules||[];
|
||||
|
||||
Reference in New Issue
Block a user