(function(){ const table=document.querySelector('#dataset-table'); if(!table)return; const panel=table.closest('.panel'); const search=document.createElement('div'); search.innerHTML='

Find lookout

'; panel.insertBefore(search,table); const modal=document.createElement('dialog'); modal.style.cssText='max-width:620px;width:calc(100% - 32px);background:#151f28;color:#edf5f5;border:1px solid #2b3d47;border-radius:10px;padding:24px'; document.body.appendChild(modal); const close=()=>modal.close(); const attr=value=>esc(value).replace(/"/g,'"'); function message(text,title='Admin message'){ modal.innerHTML='

'+esc(title)+'

'+esc(text)+'

'; modal.querySelector('#modal-ok').onclick=close;modal.showModal(); } function confirmAction(text){ return new Promise(resolve=>{modal.innerHTML='

Confirm action

'+esc(text)+'

';modal.querySelector('#modal-yes').onclick=()=>{close();resolve(true)};modal.querySelector('#modal-no').onclick=()=>{close();resolve(false)};modal.addEventListener('cancel',()=>resolve(false),{once:true});modal.showModal()}); } window.alert=text=>message(text); async function details(id){ const response=await fetch('/api/admin/lookouts/'+encodeURIComponent(id),{headers:headers()}); const body=await response.json().catch(()=>({})); if(!response.ok)throw Error(body.detail||'Could not load lookout'); return body; } function showMetadata(item){ modal.innerHTML='

Lookout metadata

Lookout ID:
'+esc(item.id)+'

Added by Telegram ID: '+esc(item.telegram_id||'unknown')+'
Submitted: '+esc(item.created_at||'')+'
Status: '+esc(item.status||'')+'

Name: '+esc(item.name||'')+'

Reason:
'+esc(item.reason||'')+'

'; modal.querySelector('#edit-lookout').onclick=()=>showEdit(item); modal.querySelector('#delete-lookout').onclick=()=>remove(item.id); modal.querySelector('#close-lookout').onclick=close; modal.showModal(); } function showEdit(item){ modal.innerHTML='

Edit lookout

'; modal.querySelector('#save-lookout').onclick=async()=>{const error=modal.querySelector('#edit-error');error.textContent='Saving...';try{const response=await fetch('/api/admin/lookouts/'+encodeURIComponent(item.id),{method:'PUT',headers:{...headers(),'Content-Type':'application/json'},body:JSON.stringify({name:modal.querySelector('#edit-name').value,reason:modal.querySelector('#edit-reason').value})});const body=await response.json().catch(()=>({}));if(!response.ok)throw Error(body.detail||'Could not save lookout');showMetadata(body);await renderDatasetView()}catch(e){error.textContent=e.message}}; modal.querySelector('#cancel-edit').onclick=()=>showMetadata(item); } async function remove(id){ if(!await confirmAction('Remove this lookout and its stored image?'))return; try{const response=await fetch('/api/admin/lookouts/'+encodeURIComponent(id),{method:'DELETE',headers:headers()});const body=await response.json().catch(()=>({}));if(!response.ok)throw Error(body.detail||'Could not remove lookout');close();await renderDatasetView();searchResult.textContent='Lookout removed.'}catch(e){message(e.message,'Could not remove lookout')} } const searchResult=search.querySelector('#lookout-search-result'); search.querySelector('#lookout-id-search-button').onclick=async()=>{const id=search.querySelector('#lookout-id-search').value.trim();if(!id)return;searchResult.textContent='Searching...';try{showMetadata(await details(id));searchResult.textContent=''}catch(e){searchResult.textContent=e.message}}; search.querySelector('#lookout-id-search').onkeydown=e=>{if(e.key==='Enter')search.querySelector('#lookout-id-search-button').click()}; function enhance(){ table.querySelectorAll('.stat').forEach(card=>{ if(card.dataset.lookoutActions||!card.textContent.includes('ID:'))return; card.dataset.lookoutActions='1'; const idText=[...card.querySelectorAll('div')].find(x=>x.textContent.trim().startsWith('ID:')); const id=idText?idText.textContent.trim().slice(3).trim():''; const actions=document.createElement('div');actions.style.cssText='display:flex;gap:6px;margin-top:10px';actions.innerHTML=''; actions.children[0].onclick=async()=>{try{showMetadata(await details(id))}catch(e){message(e.message,'Could not load lookout')}}; actions.children[1].onclick=()=>remove(id);card.appendChild(actions); }); } const sentLink=document.createElement('a');sentLink.href='#sent-reports';sentLink.dataset.view='sent-reports';sentLink.textContent='Sent Reports';document.querySelector('.side').insertBefore(sentLink,document.querySelector('#logout')); const sentPanel=document.createElement('div');sentPanel.id='sent-reports';sentPanel.dataset.panel='';sentPanel.className='hidden';sentPanel.innerHTML='

Sent Reports

Every candidate report released to a user. Newest reports appear first.

'; document.querySelector('.content').appendChild(sentPanel); const sentTable=sentPanel.querySelector('#sent-table'); function sentParams(page){const q=new URLSearchParams({page,per_page:25});[['from_date','sent-from'],['to_date','sent-to'],['search_id','sent-search-id'],['report_id','sent-report-id'],['lookup_id','sent-lookout-id'],['telegram_id','sent-user-id'],['organisation','sent-org'],['status','sent-status']].forEach(([key,id])=>{const value=sentPanel.querySelector('#'+id).value.trim();if(value)q.set(key,value)});[['similarity_min','sent-min'],['similarity_max','sent-max']].forEach(([key,id])=>{const value=sentPanel.querySelector('#'+id).value;if(value)q.set(key,value)});return q} async function loadSent(page=1){sentTable.innerHTML='

Loading sent reports...

';try{const d=await api('/admin/sent-reports?'+sentParams(page));if(!d.items.length){sentTable.innerHTML='

No sent reports found.

';return}let html='';for(const r of d.items)html+='';html+='
TimestampReport IDSearch IDTelegramLookoutSimilarityStatusChannelActions
'+esc(r.sent_at||r.generated_at)+''+esc(r.report_id)+''+esc(r.search_id||'')+''+esc(r.telegram_id||'')+''+esc(r.lookout_id||'')+''+esc(r.similarity==null?'':Math.round(r.similarity*100)+'%')+''+esc(r.status)+''+esc(r.channel)+'
Page '+d.page+' of '+(d.pages||1)+' ยท '+d.total+' report(s)
';sentTable.innerHTML=html;sentTable.querySelector('#sent-prev').disabled=page<=1;sentTable.querySelector('#sent-next').disabled=page>=d.pages;sentTable.querySelector('#sent-prev').onclick=()=>loadSent(page-1);sentTable.querySelector('#sent-next').onclick=()=>loadSent(page+1);sentTable.querySelectorAll('[data-sent-detail]').forEach(b=>b.onclick=()=>showSentDetail(b.dataset.sentDetail))}catch(e){sentTable.innerHTML='

Could not load sent reports: '+esc(e.message)+'

'}} async function showSentDetail(id){try{const r=await api('/admin/sent-reports/'+encodeURIComponent(id));modal.innerHTML='

Sent report details

Report ID: '+esc(r.id)+'
Search ID: '+esc(r.search_id||'')+'
Lookout ID: '+esc(r.report_id||'')+'
Lookup ID: '+esc(r.lookup_id||'')+'
Telegram user: '+esc(r.telegram_id||'')+'
Candidate similarity: '+esc(r.similarity==null?'':Math.round(r.similarity*100)+'%')+'
Threshold used: '+esc(r.threshold==null?'':Math.round(r.threshold*100)+'%')+'
Generated: '+esc(r.generated_at||'')+'
Sent: '+esc(r.sent_at||'')+'
Status: '+esc(r.status||'')+'
Channel: '+esc(r.channel||'')+'
Report version: '+esc(r.report_version||'')+'

Lookout name: '+esc(r.name||'')+'
Delivery attempts: '+esc((r.delivery_attempts||[]).length)+'

';modal.querySelector('#close-sent-detail').onclick=close;modal.showModal()}catch(e){message(e.message,'Could not load sent report')}} const dashboard=document.querySelector('#dashboard .panel');if(dashboard){const thresholdBox=document.createElement('div');thresholdBox.innerHTML='

Candidate similarity threshold

Enter a score from 0 to 100. This is not identity certainty.

';dashboard.appendChild(thresholdBox);thresholdBox.querySelector('#admin-threshold-save').onclick=async()=>{const value=Number(thresholdBox.querySelector('#admin-threshold').value);try{const response=await fetch('/api/admin/config/threshold',{method:'PUT',headers:{...headers(),'Content-Type':'application/json'},body:JSON.stringify({threshold:value,reason:thresholdBox.querySelector('#admin-threshold-reason').value})});const body=await response.json();if(!response.ok)throw Error(body.detail||'Could not update threshold');message('Threshold saved at '+body.threshold+' candidate similarity score.','Configuration updated')}catch(e){message(e.message,'Could not update threshold')}}} sentPanel.querySelector('#sent-filter').onclick=()=>loadSent(1);sentPanel.querySelector('#sent-clear').onclick=()=>{sentPanel.querySelectorAll('input').forEach(x=>x.value='');loadSent(1)};sentLink.onclick=e=>{e.preventDefault();document.querySelectorAll('[data-panel]').forEach(x=>x.classList.add('hidden'));sentPanel.classList.remove('hidden');document.querySelectorAll('[data-view]').forEach(x=>x.classList.remove('active'));sentLink.classList.add('active');document.querySelector('#title').textContent='Sent Reports';loadSent(1)}; new MutationObserver(enhance).observe(table,{childList:true,subtree:true}); enhance(); })();