SIMON Master Console Rebuild
True command center for SIMON + WEB360
This rebuild gives you a cleaner production spine: interactive tree, live viewer, guarded editor, dynamic graphics, architecture map, and note storage. It is designed to sit beside your existing
console.php
and become the stronger replacement path.
Dashboard
Tree
Viewer
System Map
File Registry
Notes
Files
15,557
Folders
441
Scanned Size
3.86 GB
PHP Files
996
Editable Text Files
12,804
File viewer
guarded to /htdocs
/si/operations/portal-v16.js
document.addEventListener('DOMContentLoaded',()=>{ const password=document.querySelector('#access_key'),passwordToggle=document.querySelector('#passwordToggle');if(password&&passwordToggle)passwordToggle.addEventListener('click',()=>{const show=password.type==='password';password.type=show?'text':'password';passwordToggle.textContent=show?'◌':'◉';passwordToggle.setAttribute('aria-label',show?'Hide access key':'Show access key')}); const $=(s,r=document)=>r.querySelector(s),$$=(s,r=document)=>[...r.querySelectorAll(s)]; const esc=v=>String(v??'').replace(/[&<>"']/g,c=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c])); const side=$('.side'),nav=$('.icon-rail'),main=$('.main'); if(!side||!nav||!main)return; side.hidden=true; $('.rail')?.setAttribute('hidden',''); const addNav=(href,icon,label,mode='ops')=>{const a=document.createElement('a');a.href=href;a.title=label;a.dataset.workspace=mode;a.innerHTML=`<span class="nav-icon">${icon}</span><span class="nav-label">${label}</span>`;nav.appendChild(a);return a}; const switcher=document.createElement('div');switcher.className='workspace-switcher';switcher.setAttribute('aria-label','SIMON navigation workspace');switcher.innerHTML='<button type="button" class="active" data-mode="ops" title="Operations icons">◈ OPS</button><button type="button" data-mode="comms" title="Communications icons">◉ COMMS</button>';side.prepend(switcher); $$('.icon-rail a').forEach(a=>a.dataset.workspace=['#sms-center','#voicemail-center','#address-book'].includes(a.getAttribute('href'))?'comms':'ops'); addNav('#calendar-center','▣','Calendar','ops');addNav('#video-center','▰','Video','comms');addNav('#analytics-center','⌁','Analytics','ops');addNav('#email-center','✉','Email','comms'); const setMode=mode=>{switcher.querySelectorAll('button').forEach(b=>b.classList.toggle('active',b.dataset.mode===mode));$$('.icon-rail a').forEach(a=>a.hidden=a.dataset.workspace!==mode);localStorage.setItem('siWorkspace',mode)}; switcher.querySelectorAll('button').forEach(b=>b.onclick=()=>setMode(b.dataset.mode));setMode(localStorage.getItem('siWorkspace')||'ops'); const brand=$('.brand h1');if(brand&&!brand.querySelector('.brand-letter'))brand.innerHTML=[...brand.textContent].map((c,i)=>c===' '?' ':`<span class="brand-letter" style="animation-delay:${(i%12)*.08}s">${esc(c)}</span>`).join(''); /* High priority is an active work queue, not a second archive. Resolved or stale cards remain available in their call/text journals but are removed here. */ const priorityBox=$('#priority-inbox');if(priorityBox){const now=Date.now(),maxAge=24*60*60*1000;$$('.priority-card',priorityBox).forEach(card=>{const raw=card.querySelector('small')?.textContent||'',iso=(raw.match(/\d{4}-\d{2}-\d{2}T[^\s·]+/)||[])[0],stamp=iso?Date.parse(iso):NaN,resolved=card.dataset.priorityStatus==='resolved';if(resolved||(!Number.isNaN(stamp)&&now-stamp>maxAge))card.remove()});const active=$$('.priority-card',priorityBox);const count=$('.priority-count',priorityBox);if(count)count.textContent=`${active.length} active`;if(!active.length)priorityBox.innerHTML='<div class="priority-clear"><b>✓ Priority queue clear</b><span>No current calls, texts or delivery errors require action.</span></div>'} const bar=$('.operations-bar');if(bar){const statuses=[['Phone','Operational',''],['Twilio','Connected',''],['Email','IONOS connected',''],['Alerts',String($$('.priority-card[data-priority-status="new"],.priority-card:not([data-priority-status])').length)+' open','warn'],['Video','Staged','warn'],['Security','Protected','']];const reel=document.createElement('div');reel.className='status-reel';const cells=statuses.map(([a,b,c])=>`<span class="reel-item"><i class="reel-dot ${c}"></i><b>${a}</b> ${b}</span>`).join('');reel.innerHTML=`<div class="status-reel-track">${cells}${cells}</div>`;bar.appendChild(reel)} const createSection=(id,title,sub)=>{const s=document.createElement('section');s.id=id;s.className='ops-section glass v16-section portal-view';s.innerHTML=`<div class="v16-head"><div><h3>${title}</h3><p>${sub}</p></div><span class="v16-badge">SIMON spatial workspace</span></div>`;main.appendChild(s);return s}; const scheduled=(window.SI_CALENDAR_EVENTS||[]).map(x=>({title:x.title||'Scheduled event',detail:[x.type,x.contact_number,x.notes].filter(Boolean).join(' · '),date:new Date(x.start_at),error:false,scheduled:true})).filter(x=>!Number.isNaN(x.date.getTime())); const activityRows=$$('.activity-detail,.priority-card,.task-card,.vm-card').map((el,i)=>{const txt=el.innerText.trim(),time=el.querySelector('time')?.getAttribute('datetime')||el.querySelector('small')?.textContent||'';let date=new Date(time);if(Number.isNaN(date.getTime()))date=new Date(Date.now()-i*86400000);return{title:(el.querySelector('b,h4')?.textContent||'SIMON activity').trim(),detail:txt.split('\n').slice(1,3).join(' · '),date,error:/fail|error|undeliver/i.test(txt),scheduled:false}}).slice(0,80),eventRows=[...scheduled,...activityRows]; const cal=createSection('calendar-center','▣ Operations Calendar','Calls, callbacks, messages, voicemail and tasks organized by date.');let cursor=new Date();cursor.setDate(1);let selected=new Date(); cal.insertAdjacentHTML('beforeend',`<form method="post" class="calendar-event-form"><input type="hidden" name="csrf" value="${esc(window.SI_CSRF||'')}"><input name="event_title" placeholder="Event or callback title" maxlength="160" required><input type="datetime-local" name="event_start" required><select name="event_type"><option value="callback">Callback</option><option value="meeting">Meeting</option><option value="task">Task</option><option value="reminder">Reminder</option></select><input name="event_contact" placeholder="Contact phone (optional)"><input name="event_notes" placeholder="Private notes (optional)"><button name="add_calendar_event">+ Add event</button></form><div class="calendar-shell"><div class="calendar-main"><div class="calendar-toolbar"><button type="button" data-cal="prev">‹</button><b id="calTitle"></b><button type="button" data-cal="next">›</button></div><div class="calendar-grid" id="calGrid"></div></div><aside class="calendar-agenda"><h4>Scheduled & activity</h4><div class="agenda-list" id="agendaList"></div></aside></div>`); const sameDay=(a,b)=>a.getFullYear()===b.getFullYear()&&a.getMonth()===b.getMonth()&&a.getDate()===b.getDate();const drawAgenda=day=>{const list=$('#agendaList',cal),rows=eventRows.filter(x=>sameDay(x.date,day));list.innerHTML=(rows.length?rows:eventRows.slice(0,8)).map(x=>`<article class="agenda-item ${x.error?'error':''} ${x.scheduled?'scheduled':''}"><b>${x.scheduled?'▣ ':''}${esc(x.title)}</b><small>${esc(x.date.toLocaleString('en-US',{timeZone:'America/Chicago',month:'short',day:'numeric',hour:'numeric',minute:'2-digit'}))}</small><div>${esc(x.detail)}</div></article>`).join('')||'<p>No activity yet.</p>'}; const drawCal=()=>{const y=cursor.getFullYear(),m=cursor.getMonth(),first=new Date(y,m,1),start=new Date(y,m,1-first.getDay());$('#calTitle',cal).textContent=new Intl.DateTimeFormat('en-US',{month:'long',year:'numeric'}).format(cursor);let html=['Sun','Mon','Tue','Wed','Thu','Fri','Sat'].map(d=>`<div class="calendar-weekday">${d}</div>`).join('');for(let i=0;i<42;i++){const d=new Date(start);d.setDate(start.getDate()+i);const n=eventRows.filter(x=>sameDay(x.date,d)).length;html+=`<button type="button" class="calendar-day ${d.getMonth()!==m?'muted':''} ${sameDay(d,new Date())?'today':''} ${sameDay(d,selected)?'selected':''}" data-date="${d.toISOString()}"><strong>${d.getDate()}</strong>${'<i class="calendar-event-dot"></i>'.repeat(Math.min(n,4))}</button>`}$('#calGrid',cal).innerHTML=html;$$('.calendar-day',cal).forEach(b=>b.onclick=()=>{selected=new Date(b.dataset.date);drawCal();drawAgenda(selected)})}; $('[data-cal="prev"]',cal).onclick=()=>{cursor.setMonth(cursor.getMonth()-1);drawCal()};$('[data-cal="next"]',cal).onclick=()=>{cursor.setMonth(cursor.getMonth()+1);drawCal()};drawCal();drawAgenda(selected); const contacts=$$('.contact-card').map(c=>({name:(c.querySelector('b')?.textContent||'Contact').trim(),numbers:$$('.number-link',c).map(a=>a.textContent.trim()),label:(c.querySelector('small')?.textContent||'Private directory').trim()})); const contactSection=$('#address-book');if(contactSection&&contacts.length){const old=$('.contact-list',contactSection);if(old)old.hidden=true;const stage=document.createElement('div');stage.className='contact-stage';stage.innerHTML='<aside class="contact-groups"><input class="contact-stage-search" type="search" placeholder="Search contacts"><div class="contact-rows"></div></aside><main><div class="contact-strip"></div><h4>Private directory</h4><p style="color:#97a5c9">Select a person to call, text or review every associated number.</p><div class="contact-rows contact-main-rows"></div></main><aside class="contact-profile"><div class="profile-avatar">SI</div><h3 class="profile-name">Select a contact</h3><div class="profile-detail"><b>Protected directory</b>Contact details remain private.</div></aside>';contactSection.appendChild(stage);const renderContacts=q=>{const rows=contacts.filter(c=>(c.name+' '+c.numbers.join(' ')).toLowerCase().includes(q));const markup=rows.map((c,i)=>`<article class="contact-row" data-contact="${contacts.indexOf(c)}"><span class="contact-row-avatar">${esc(c.name.charAt(0).toUpperCase())}</span><span><b>${esc(c.name)}</b><small>${esc(c.numbers.join(' · '))}</small></span><span class="contact-action-set"><a href="tel:${esc(c.numbers[0]||'')}" title="Call">☎</a><a href="sms:${esc(c.numbers[0]||'')}" title="Text">✦</a></span></article>`).join('');$('.contact-rows',stage).innerHTML=markup;$('.contact-main-rows',stage).innerHTML=markup;$('.contact-strip',stage).innerHTML=rows.slice(0,10).map(c=>`<div class="contact-orb"><span>${esc(c.name.charAt(0).toUpperCase())}</span>${esc(c.name.split(' ')[0])}</div>`).join('');$$('[data-contact]',stage).forEach(r=>r.onclick=()=>selectContact(+r.dataset.contact))};const selectContact=i=>{const c=contacts[i];if(!c)return;$('.contact-profile',stage).innerHTML=`<div class="profile-avatar">${esc(c.name.charAt(0).toUpperCase())}</div><h3 class="profile-name">${esc(c.name)}</h3><div class="profile-detail"><b>${esc(c.label)}</b>Private directory match</div>${c.numbers.map(n=>`<div class="profile-detail"><b>${esc(n)}</b>Phone</div>`).join('')}<div class="profile-actions"><a href="tel:${esc(c.numbers[0]||'')}">☎ Call</a><a href="sms:${esc(c.numbers[0]||'')}">✦ Text</a></div>`;$$('[data-contact]',stage).forEach(r=>r.classList.toggle('active',+r.dataset.contact===i))};$('.contact-stage-search',stage).oninput=e=>renderContacts(e.target.value.trim().toLowerCase());renderContacts('');selectContact(0)} const video=createSection('video-center','▰ Spatial Video Stage','Video operations are staged here; a live room requires the Twilio Video SDK and access-token service.');video.insertAdjacentHTML('beforeend','<div class="video-stage"><div class="video-canvas"><div class="si-video-avatar">SI</div><div class="video-overlay"><div><b>SIMON Spatial Room</b><small style="display:block;color:#93a2c6">Preview stage · camera not broadcasting</small></div><div class="video-controls"><button type="button" title="Camera">▰</button><button type="button" title="Microphone">●</button><button type="button" class="end" title="End">×</button></div></div></div><aside class="video-queue"><article class="video-card"><b>Private consultation room</b><small>Token service required</small><span class="stage-label">STAGED</span></article><article class="video-card"><b>Recorded briefing</b><small>Upload and playback workspace</small><span class="stage-label">READY FOR MEDIA</span></article><article class="video-card"><b>Spatial presentation</b><small>Wide-screen portal mode</small><span class="stage-label">PREVIEW</span></article></aside></div>'); const email=createSection('email-center','✉ Bring Your Own Email','Connect IONOS, Gmail with OAuth, or another private SMTP provider. Passwords never appear in browser code.');email.insertAdjacentHTML('beforeend','<div class="analytics-grid"><article class="analytics-metric"><small>IONOS SMTP</small><strong style="color:var(--si-green)">Live</strong><span>Current alert sender</span></article><article class="analytics-metric"><small>Gmail</small><strong style="color:var(--si-amber)">OAuth</strong><span>Connection authorization required</span></article><article class="analytics-metric"><small>Custom SMTP</small><strong>BYOE</strong><span>Private server configuration</span></article><article class="analytics-metric"><small>Last test</small><strong style="color:var(--si-green)">Sent</strong><span>IONOS accepted diagnostic</span></article></div>'); const callRows=$$('.call-table tbody tr,.activity-detail').filter(x=>/call|route|transfer|ring/i.test(x.innerText)),textRows=$$('.message-card,.journal-entry,.priority-card').filter(x=>/sms|text|message/i.test(x.innerText)),videoRows=$$('.video-card'),all=[...callRows,...textRows],errors=all.filter(x=>/fail|error|undeliver|rejected/i.test(x.innerText)).length,total=Math.max(1,all.length),rate=Math.round(errors/total*100); const analytics=createSection('analytics-center','⌁ Communications Analytics','Live portal counts for calls, texts, video readiness and operational errors.');analytics.insertAdjacentHTML('beforeend',`<div class="analytics-grid"><article class="analytics-metric"><small>Call events</small><strong>${callRows.length}</strong><span>Voice activity</span></article><article class="analytics-metric"><small>Text events</small><strong>${textRows.length}</strong><span>Messaging activity</span></article><article class="analytics-metric"><small>Video rooms</small><strong>${videoRows.length}</strong><span>Staged workspaces</span></article><article class="analytics-metric"><small>Errors</small><strong style="color:var(--si-red)">${errors}</strong><span>Failed or undelivered</span></article></div><div class="analytics-chart"><article class="chart-card"><h4>Channel volume</h4><div class="bar-chart"><div class="bar-col"><i style="height:${Math.min(100,20+callRows.length)}%"></i><span>Calls</span></div><div class="bar-col"><i style="height:${Math.min(100,20+textRows.length)}%"></i><span>Texts</span></div><div class="bar-col"><i style="height:${Math.min(100,20+videoRows.length*12)}%"></i><span>Video</span></div><div class="bar-col"><i style="height:${Math.min(100,20+errors*4)}%"></i><span>Errors</span></div></div></article><article class="chart-card"><h4>Error rate</h4><div class="error-ring" style="--rate:${rate}"><span>${rate}%</span></div><div class="legend"><div class="legend-row"><span><i style="background:var(--si-green)"></i>Processed</span><b>${Math.max(0,total-errors)}</b></div><div class="legend-row"><span><i style="background:var(--si-red)"></i>Errors</span><b>${errors}</b></div></div></article></div>`); /* Consolidate legacy call transitions that have no SID. */ const feed=$('.feed');if(feed){const voice=$$('.activity-detail',feed).filter(x=>x.querySelector('.icon')?.textContent.includes('☎')),groups=new Map;voice.forEach(row=>{const t=row.innerText,from=(t.match(/From:\s*(\+?\d+)/i)||[])[1]||'Unknown',to=(t.match(/To:\s*(\+?\d+)/i)||[])[1]||'Unknown',date=(t.match(/Date:\s*([^\n]+?)(?=\s*Time:)/i)||[])[1]?.trim()||'',time=(t.match(/Time:\s*([^\n]+?)(?=\s*Status:)/i)||[])[1]?.trim()||'',key=[from,to,date,time].join('|');if(!groups.has(key))groups.set(key,[]);groups.get(key).push(row)});groups.forEach(rows=>{if(rows.length<2)return;const first=rows[0],t=first.innerText,from=(t.match(/From:\s*(\+?\d+)/i)||[])[1]||'Unknown',to=(t.match(/To:\s*(\+?\d+)/i)||[])[1]||'Unknown',date=(t.match(/Date:\s*([^\n]+?)(?=\s*Time:)/i)||[])[1]?.trim()||'',time=(t.match(/Time:\s*([^\n]+?)(?=\s*Status:)/i)||[])[1]?.trim()||'',events=rows.map(r=>(r.querySelector('b')?.textContent||'Call event').trim()).reverse();const card=document.createElement('article');card.className='activity-detail call-session';card.innerHTML=`<div class="icon">☎</div><div class="call-session-main"><div class="call-session-title"><b>Call from ${esc(from)}</b><span class="status-pill">${events.some(x=>/completed/i.test(x))?'COMPLETED':'PENDING'}</span></div><div class="activity-meta"><span><b>To:</b> ${esc(to)}</span><span><b>Date:</b> ${esc(date)}</span><span><b>Time:</b> ${esc(time)}</span></div><div class="transition-line">${events.map((e,i)=>`<span>${i+1}. ${esc(e)}</span>`).join('<i>›</i>')}</div><details><summary>View call timeline</summary>${events.map((e,i)=>`<div class="timeline-step"><strong>${i+1}</strong><span><b>${esc(e)}</b><small>${esc(date)} · ${esc(time)}</small></span></div>`).join('')}</details></div>`;first.before(card);rows.forEach(r=>r.remove())});const unique=$$('.call-session',feed).length+$$('.activity-detail',feed).filter(x=>x.querySelector('.icon')?.textContent.includes('☎')).length,metric=$('.metrics .metric strong');if(metric&&unique)metric.textContent=String(unique)} /* Spreadsheet-style contact ledger. */ if(contactSection&&contacts.length){$('.contact-stage',contactSection)?.remove();const table=document.createElement('div');table.className='contact-ledger';table.innerHTML=`<div class="ledger-toolbar"><div><b>SIMON Contacts</b><small>${contacts.length} consolidated people and organizations</small></div><input id="contactLedgerSearch" type="search" placeholder="Search contacts by name, phone, label or source"><span id="contactLedgerCount">${contacts.length} contacts</span></div><div class="contact-alpha" aria-label="Filter contacts alphabetically"></div><div class="contact-table-wrap"><table class="contact-table"><thead><tr><th>Contact</th><th>Phone number(s)</th><th>Type / source</th><th>Quick actions</th></tr></thead><tbody>${contacts.map((c,i)=>`<tr data-letter="${esc((c.name.charAt(0)||'#').toUpperCase())}" data-ledger-search="${esc((c.name+' '+c.numbers.join(' ')+' '+c.label).toLowerCase())}"><td><span class="ledger-person"><i>${esc(c.name.charAt(0).toUpperCase())}</i><span><b>${esc(c.name)}</b><small>${c.numbers.length} ${c.numbers.length===1?'number':'numbers'} · Contact ${String(i+1).padStart(3,'0')}</small></span></span></td><td><span class="contact-phone-list">${c.numbers.map((n,j)=>`<a class="${j===0?'primary-number':''}" href="tel:${esc(n)}">${esc(n)}</a>`).join('')}</span></td><td><span class="source-chip">${esc(c.label)}</span></td><td><span class="ledger-actions"><a href="tel:${esc(c.numbers[0]||'')}" title="Call contact">☎ <span>Call</span></a><a href="sms:${esc(c.numbers[0]||'')}" title="Text contact">✦ <span>Text</span></a><a href="#conversations" title="Open contact journal">▤ <span>Journal</span></a></span></td></tr>`).join('')}</tbody></table></div>`;contactSection.appendChild(table);const search=$('#contactLedgerSearch',table),count=$('#contactLedgerCount',table),rows=$$('tbody tr',table),alpha=$('.contact-alpha',table),letters=['ALL',...'ABCDEFGHIJKLMNOPQRSTUVWXYZ'];let activeLetter='ALL';alpha.innerHTML=letters.map(l=>`<button type="button" class="${l==='ALL'?'active':''}" data-letter-filter="${l}">${l}</button>`).join('');const filter=()=>{const q=search.value.trim().toLowerCase();let shown=0;rows.forEach(r=>{const matchText=q===''||r.dataset.ledgerSearch.includes(q),matchLetter=activeLetter==='ALL'||r.dataset.letter===activeLetter;r.hidden=!(matchText&&matchLetter);if(!r.hidden)shown++});count.textContent=`${shown} of ${rows.length} contacts`};search.oninput=filter;$$('[data-letter-filter]',alpha).forEach(button=>button.onclick=()=>{activeLetter=button.dataset.letterFilter;$$('button',alpha).forEach(b=>b.classList.toggle('active',b===button));filter()});filter()} /* Dynamic phone, identity, weather/time/search and status instrument. */ if(bar){bar.classList.add('command-instrument');const title=bar.querySelector('b');if(title)title.innerHTML='<span class="live-sigil">SI</span><span>GAYLORD + SIMON</span><em>LIVE OPERATIONS</em>';const context=$('.top-context',bar);context?.classList.add('instrument-context');const phone=document.createElement('a');phone.className='instrument-phone';phone.href='https://console.twilio.com/';phone.target='_blank';phone.rel='noopener';phone.innerHTML='<span>☎</span><b>+1 214 239 1203</b><small>Twilio Console ↗</small>';bar.prepend(phone);const alert=document.createElement('a');alert.className='instrument-alert';alert.href='#priority-inbox';alert.innerHTML=`<span>⚠</span><b>${$$('.priority-card:not([data-priority-status="resolved"])').length}</b><small>Alerts</small>`;bar.appendChild(alert)} $$('.priority-card').forEach(card=>{const small=card.querySelector('small');if(!small)return;const match=small.textContent.match(/(\d{4}-\d{2}-\d{2}T[^\s·]+)/);if(!match)return;const d=new Date(match[1]);if(Number.isNaN(d.getTime()))return;small.textContent=small.textContent.replace(match[1],d.toLocaleString('en-US',{timeZone:'America/Chicago',month:'short',day:'numeric',year:'numeric',hour:'numeric',minute:'2-digit',timeZoneName:'short'}));if(Date.now()-d.getTime()>86400000)card.classList.add('historical')}); const activateHash=()=>{const target=$(location.hash);if(target&&target.classList.contains('v16-section'))target.scrollIntoView({behavior:'smooth',block:'start'})};window.addEventListener('hashchange',activateHash); });
Save file
Quick jump
open a path
Open