File size: 17,439 Bytes
837f06d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
<!DOCTYPE html>
<html class="dark" lang="en"><head>
<meta charset="utf-8"/><meta content="width=device-width, initial-scale=1.0" name="viewport"/>
<title>Aegis Drone Command - Query Frames</title>
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
<link href="https://fonts.googleapis.com/css2?family=Anton&family=Arimo:wght@400;700&family=JetBrains+Mono:wght@400;500;700&display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap" rel="stylesheet"/>
<script>tailwind.config={darkMode:"class",theme:{extend:{colors:{"surface-tint":"#c6c6cb","on-surface-variant":"#c6c6cb","surface-dim":"#101418","surface-container-highest":"#32353a","surface-container-high":"#272a2f","surface-container-lowest":"#0b0e13","surface-container":"#1d2024","on-error-container":"#ffdad6","on-surface":"#e0e2e8",primary:"#c6c6cb","secondary-fixed":"#72ff70","on-tertiary-container":"#ff5d53","on-primary-container":"#939499",surface:"#101418",tertiary:"#ffb4ab",error:"#ffb4ab",outline:"#909095","error-container":"#93000a",background:"#101418","primary-container":"#2b2d31","outline-variant":"#45474a","primary-fixed-dim":"#c6c6cb","tertiary-fixed-dim":"#ffb4ab","on-background":"#e0e2e8","surface-container-low":"#191c20","on-secondary-container":"#007117","surface-bright":"#36393e","secondary-fixed-dim":"#00e639","on-error":"#690005","surface-variant":"#32353a"},borderRadius:{DEFAULT:"0.125rem",lg:"0.25rem",xl:"0.5rem",full:"0.75rem"},spacing:{"gutter-md":"1.5rem","panel-padding":"2rem","component-gap":"1rem"},fontFamily:{"body-md":["Arimo"],"display-code":["JetBrains Mono"],"headline-lg":["Anton"],"label-caps":["JetBrains Mono"],"headline-md":["Anton"]},fontSize:{"body-md":["16px",{lineHeight:"1.5",fontWeight:"400"}],"display-code":["20px",{lineHeight:"1.2",letterSpacing:"-0.02em",fontWeight:"500"}],"headline-lg":["48px",{lineHeight:"1.1",letterSpacing:"0.05em",fontWeight:"400"}],"label-caps":["14px",{lineHeight:"1.2",fontWeight:"700"}],"headline-md":["32px",{lineHeight:"1.2",fontWeight:"400"}]}}}};</script>
<style>::-webkit-scrollbar{width:10px;background:#101418}::-webkit-scrollbar-thumb{background:#32353a;border:1px solid #45474a;border-radius:2px}</style>
</head>
<body class="bg-background text-on-surface font-body-md overflow-hidden h-screen">
<script src="/static/shell.js"></script>

<main class="ml-60 mt-16 h-[calc(100vh-4rem)] p-panel-padding overflow-y-auto bg-background relative z-0">
<header class="mb-6 flex justify-between items-end border-b-2 border-surface-dim pb-4">
<div><h1 class="font-headline-lg text-[28px] text-primary uppercase tracking-wide">Tactical Frame Interrogation</h1>
<p class="font-display-code text-outline mt-1 text-[13px]">SYS.OP.MODE // DEEP_SCAN_ACTIVE</p></div>
<div class="flex items-center gap-2 bg-surface-container-lowest border border-surface-dim p-2 rounded shadow-[inset_0_2px_4px_rgba(0,0,0,0.5)]">
<div class="w-2 h-2 rounded-full bg-secondary-fixed-dim shadow-[0_0_8px_#00e639] animate-pulse"></div>
<span class="font-label-caps text-[11px] text-secondary-fixed-dim">DATA LINK STABLE</span>
</div>
</header>
<div class="grid lg:grid-cols-12 gap-6">
<!-- Left Panel -->
<div class="lg:col-span-4 flex flex-col gap-5">
<!-- Query Input -->
<section class="bg-surface-container border border-surface-dim rounded-lg p-5 relative shadow-[0_8px_16px_rgba(0,0,0,0.6),inset_0_2px_0_rgba(255,255,255,0.05)]">
<div class="absolute w-2 h-2 rounded-full bg-surface-dim shadow-[inset_0_1px_1px_rgba(255,255,255,0.2)] top-3 left-3"></div>
<div class="absolute w-2 h-2 rounded-full bg-surface-dim shadow-[inset_0_1px_1px_rgba(255,255,255,0.2)] top-3 right-3"></div>
<h3 class="font-label-caps text-[12px] text-outline mb-3 flex items-center gap-2 border-b border-surface-dim pb-2"><span class="material-symbols-outlined text-[16px]">search</span> PARAMETER INPUT</h3>
<div class="space-y-4">
<div class="flex flex-col gap-1.5">
<label class="font-label-caps text-[11px] text-primary-fixed-dim">Entity Designation</label>
<div class="relative"><input id="searchInput" class="w-full bg-surface-container-lowest text-secondary-fixed-dim font-display-code p-2.5 border-t-2 border-l-2 border-black border-b border-r border-white/10 rounded outline-none focus:shadow-[inset_0_0_15px_rgba(0,230,57,0.15)] text-[14px]" type="text" placeholder="e.g. truck, person"/><span class="absolute right-3 top-2.5 material-symbols-outlined text-outline text-[18px]">qr_code_scanner</span></div>
</div>
<div class="grid grid-cols-2 gap-3">
<div class="flex flex-col gap-1.5">
<label class="font-label-caps text-[11px] text-primary-fixed-dim">Risk Level</label>
<div class="relative bg-surface-container-lowest border-t-2 border-l-2 border-black border-b border-r border-white/10 rounded">
<select id="riskFilter" class="w-full bg-transparent text-on-surface font-display-code p-2.5 outline-none appearance-none cursor-pointer text-[13px]"><option value="">ALL LEVELS</option><option value="CRITICAL">CRITICAL</option><option value="HIGH">HIGH</option><option value="MEDIUM">MEDIUM</option><option value="LOW">LOW</option></select>
<span class="absolute right-2 top-2.5 material-symbols-outlined text-outline pointer-events-none text-[16px]">arrow_drop_down</span></div>
</div>
<div class="flex flex-col gap-1.5">
<label class="font-label-caps text-[11px] text-primary-fixed-dim">Timeframe</label>
<div class="relative bg-surface-container-lowest border-t-2 border-l-2 border-black border-b border-r border-white/10 rounded">
<select id="timeFilter" class="w-full bg-transparent text-on-surface font-display-code p-2.5 outline-none appearance-none cursor-pointer text-[13px]"><option value="any">ALL TIME</option><option value="after_hours">AFTER HRS</option><option value="business">BUSINESS</option></select>
<span class="absolute right-2 top-2.5 material-symbols-outlined text-outline pointer-events-none text-[16px]">arrow_drop_down</span></div>
</div>
</div>
</div>
</section>
<!-- Threat Filter -->
<section class="bg-surface-container border border-surface-dim rounded-lg p-5 relative shadow-[0_8px_16px_rgba(0,0,0,0.6),inset_0_2px_0_rgba(255,255,255,0.05)]">
<h3 class="font-label-caps text-[12px] text-outline mb-3 flex items-center gap-2 border-b border-surface-dim pb-2"><span class="material-symbols-outlined text-[16px]">policy</span> THREAT FILTER</h3>
<div class="flex flex-col gap-3 bg-surface-dim p-3 rounded border border-surface shadow-[inset_0_4px_8px_rgba(0,0,0,0.8)]">
<button onclick="setFilter('CRITICAL')" data-level="CRITICAL" class="threat-filter-btn relative w-full bg-surface-container-high text-on-surface font-label-caps text-[12px] px-3 py-2.5 border-t border-l border-white/10 border-b-2 border-r-2 border-black shadow-[0_3px_0_#0b0e13] active:translate-y-[3px] active:shadow-none transition-all flex items-center justify-between">
<div class="flex items-center gap-2"><div class="filter-led w-3 h-3 rounded-full bg-on-tertiary-container shadow-[0_0_8px_#ff5d53] border border-black/50" style="opacity:0.5"></div><span class="filter-label text-outline">CRITICAL</span></div>
<span class="filter-status font-display-code text-outline text-[12px]">OFF</span></button>
<button onclick="setFilter('HIGH')" data-level="HIGH" class="threat-filter-btn relative w-full bg-surface-container-high font-label-caps text-[12px] px-3 py-2.5 border-t border-l border-white/10 border-b-2 border-r-2 border-black shadow-[0_3px_0_#0b0e13] active:translate-y-[3px] active:shadow-none transition-all flex items-center justify-between">
<div class="flex items-center gap-2"><div class="filter-led w-3 h-3 rounded-full bg-error shadow-[0_0_6px_#ffb4ab] border border-black/50" style="opacity:0.5"></div><span class="filter-label text-outline">ELEVATED</span></div>
<span class="filter-status font-display-code text-outline text-[12px]">OFF</span></button>
<button onclick="setFilter('LOW')" data-level="LOW" class="threat-filter-btn relative w-full bg-surface-container-high font-label-caps text-[12px] px-3 py-2.5 border-t border-l border-white/10 border-b-2 border-r-2 border-black shadow-[0_3px_0_#0b0e13] active:translate-y-[3px] active:shadow-none transition-all flex items-center justify-between">
<div class="flex items-center gap-2"><div class="filter-led w-3 h-3 rounded-full bg-secondary-fixed-dim shadow-[0_0_6px_#00e639] border border-black/50" style="opacity:0.5"></div><span class="filter-label text-outline">NOMINAL</span></div>
<span class="filter-status font-display-code text-outline text-[12px]">OFF</span></button>
</div>
</section>
<!-- Semantic Search -->
<section class="bg-surface-container border border-surface-dim rounded-lg p-5 relative shadow-[0_8px_16px_rgba(0,0,0,0.6),inset_0_2px_0_rgba(255,255,255,0.05)]">
<div class="bg-surface-dim p-3 rounded border-2 border-surface-container-highest shadow-[inset_0_4px_12px_rgba(0,0,0,0.9)] flex flex-col items-center justify-center min-h-[100px] relative overflow-hidden">
<div class="absolute inset-0 pointer-events-none opacity-20" style="background-image:repeating-linear-gradient(0deg,transparent,transparent 2px,#000 2px,#000 4px)"></div>
<span class="material-symbols-outlined text-secondary-fixed-dim text-[32px] mb-1 drop-shadow-[0_0_10px_rgba(0,230,57,0.5)] animate-pulse">neurology</span>
<p class="font-display-code text-secondary-fixed-dim text-center text-[12px] leading-tight">NEURAL SEMANTIC<br/>MATCHING ENGAGED</p>
</div>
<input id="semanticInput" class="w-full mt-3 bg-surface-container-lowest text-secondary-fixed-dim font-display-code p-2.5 border-t-2 border-l-2 border-black border-b border-r border-white/10 rounded outline-none text-[13px]" type="text" placeholder="Describe what you're looking for..."/>
<button onclick="executeQuery()" class="w-full mt-3 bg-primary-container text-on-surface font-headline-md py-3 rounded border-b-4 border-r-4 border-black shadow-[0_4px_0_#0b0e13] active:translate-y-[4px] active:shadow-none transition-all flex items-center justify-center gap-2 text-[16px]">
<span class="material-symbols-outlined text-[20px]" style="font-variation-settings:'FILL' 1">manage_search</span>EXECUTE QUERY</button>
</section>
</div>
<!-- Right Panel: CRT Viewport -->
<div class="lg:col-span-8 flex flex-col">
<div class="bg-surface-container-lowest border-4 border-surface rounded-xl p-2 relative shadow-[0_10px_30px_rgba(0,0,0,0.8)] flex flex-col" style="min-height:600px">
<div class="flex justify-between items-center bg-surface-dim px-3 py-1.5 border-b border-surface-container z-10">
<span class="font-display-code text-secondary-fixed-dim text-[12px]">VDP-OUT // CH-01</span>
<div class="flex gap-2 items-center"><span class="w-2 h-2 rounded-full bg-secondary-fixed-dim animate-pulse"></span><span class="font-display-code text-outline text-[12px]" id="resultCount">0 MATCHES</span></div>
</div>
<div class="relative flex-1 bg-[#05080a] overflow-hidden">
<div class="absolute inset-0 pointer-events-none z-10 opacity-40" style="background-image:repeating-linear-gradient(0deg,transparent,transparent 1px,#000 1px,#000 2px)"></div>
<div class="absolute inset-0 pointer-events-none z-10 shadow-[inset_0_0_80px_rgba(0,0,0,1)]"></div>
<div id="resultsGrid" class="absolute inset-0 overflow-y-auto p-4 z-0 space-y-3">
<div class="text-center font-display-code text-outline py-12 text-[14px]">Enter search parameters and execute query</div>
</div>
</div>
<div class="bg-surface border-t border-surface-container h-5 w-full rounded-b flex items-center justify-center gap-8">
<div class="w-12 h-1 bg-surface-dim rounded"></div><div class="w-12 h-1 bg-surface-dim rounded"></div>
</div>
</div>
</div>
</div>
</main>
<script>
document.getElementById('searchInput').addEventListener('keydown',e=>{if(e.key==='Enter')executeQuery()});
document.getElementById('semanticInput').addEventListener('keydown',e=>{if(e.key==='Enter')executeQuery()});

let activeFilter='';
const filterBtns = document.querySelectorAll('[onclick^="setFilter"]');

function setFilter(level){
  activeFilter = activeFilter===level ? '' : level;
  document.getElementById('riskFilter').value = activeFilter;
  // Update filter button visuals
  document.querySelectorAll('.threat-filter-btn').forEach(btn => {
    const btnLevel = btn.getAttribute('data-level');
    const led = btn.querySelector('.filter-led');
    const label = btn.querySelector('.filter-label');
    const status = btn.querySelector('.filter-status');
    if(btnLevel === activeFilter) {
      led.style.opacity = '1';
      label.className = 'filter-label text-on-surface';
      status.textContent = 'ON';
      status.className = 'filter-status font-display-code text-secondary-fixed-dim text-[12px]';
    } else {
      led.style.opacity = '0.5';
      label.className = 'filter-label text-outline';
      status.textContent = 'OFF';
      status.className = 'filter-status font-display-code text-outline text-[12px]';
    }
  });
  executeQuery();
}

function executeQuery(){
  const paramQ = document.getElementById('searchInput').value.trim();
  const semanticQ = document.getElementById('semanticInput').value.trim();
  const risk = document.getElementById('riskFilter').value;
  const time = document.getElementById('timeFilter').value;

  document.getElementById('resultsGrid').innerHTML='<div class="text-center font-display-code text-secondary-fixed-dim py-12 text-[14px] animate-pulse">SCANNING DATABASE...</div>';
  document.getElementById('resultCount').textContent='SEARCHING...';

  // Decide which API to use
  if(semanticQ && !paramQ) {
    // Semantic search via ChromaDB
    fetch('/api/semantic?q='+encodeURIComponent(semanticQ))
      .then(r=>r.json())
      .then(d=>{
        let results = d.results||[];
        // Apply client-side risk filter to semantic results
        if(risk) results = results.filter(r=>(r.risk_level||'').toUpperCase()===risk);
        renderResults(results, 'SEMANTIC');
      })
      .catch(()=>{
        // Fallback: try in-memory
        fetchInMemory(semanticQ, risk, time);
      });
  } else {
    // Parameter search via SQLite + in-memory fallback
    fetch(`/api/frames/search?q=${encodeURIComponent(paramQ)}&risk=${encodeURIComponent(risk)}&time_filter=${encodeURIComponent(time)}`)
      .then(r=>r.json())
      .then(d=>{
        let results = d.results||[];
        if(results.length > 0) {
          renderResults(results, 'DB');
        } else {
          // Fallback to in-memory results
          fetchInMemory(paramQ, risk, time);
        }
      })
      .catch(()=>{
        fetchInMemory(paramQ, risk, time);
      });
  }
}

function fetchInMemory(q, risk, time){
  fetch('/api/status').then(r=>r.json()).then(data=>{
    let results=data.results||[];
    if(q) results=results.filter(f=>(f.objects||[]).some(o=>o.toLowerCase().includes(q.toLowerCase()))||(f.description||'').toLowerCase().includes(q.toLowerCase()));
    if(risk) results=results.filter(f=>(f.risk_level||'').toUpperCase()===risk);
    if(time==='after_hours') results=results.filter(f=>{const h=parseInt((f.time||'12').split(':')[0]);return h>=22||h<6});
    else if(time==='business') results=results.filter(f=>{const h=parseInt((f.time||'12').split(':')[0]);return h>=6&&h<22});
    renderResults(results, 'MEMORY');
  });
}

function renderResults(results, source){
  document.getElementById('resultCount').textContent=results.length+' MATCHES';
  if(!results.length){
    document.getElementById('resultsGrid').innerHTML=`<div class="text-center font-display-code text-outline py-12 text-[14px]">
      <span class="material-symbols-outlined text-[32px] block mb-2 text-outline/50">search_off</span>
      No matches found.<br/><span class="text-[12px] text-surface-variant mt-1 block">Run simulation from Live Feed first, then query.</span></div>`;
    return;
  }
  const rc={CRITICAL:'error',HIGH:'tertiary-fixed-dim',MEDIUM:'primary',LOW:'surface-tint'};
  const srcBadge = `<span class="font-label-caps text-[9px] text-secondary-fixed-dim bg-surface-container-lowest px-1 py-0.5 rounded border border-secondary-fixed-dim/20">${source}</span>`;
  document.getElementById('resultsGrid').innerHTML=results.map(f=>{
    const r=(f.risk_level||'LOW').toUpperCase();
    const fid = f.frame_id || f.id || '?';
    const objs = f.objects || [];
    return `<div class="bg-surface-dim border-l-4 border-${rc[r]||'surface-tint'} p-3 hover:bg-surface-container transition-colors cursor-pointer">
      <div class="flex justify-between items-start mb-1"><div class="flex items-center gap-2">
        <span class="font-display-code text-[13px] text-${rc[r]}">FRM-${fid}</span>
        <span class="font-label-caps text-[10px] text-surface-tint bg-surface-container px-1.5 py-0.5 rounded border border-surface-variant">${f.location||f.sector||''}</span>
        <span class="font-label-caps text-[10px] text-on-surface bg-surface-variant px-1.5 py-0.5 rounded">${r}</span>
        ${srcBadge}
      </div><span class="font-display-code text-[11px] text-outline">${f.time||f.timestamp||''}</span></div>
      <p class="font-display-code text-on-surface-variant text-[12px]">${f.description||''}</p>
      <div class="mt-1.5 flex gap-1.5 flex-wrap">${objs.map(o=>`<span class="font-label-caps text-[10px] bg-surface-container-highest text-secondary-fixed-dim px-1.5 py-0.5 rounded border border-secondary-fixed-dim/30">${o}</span>`).join('')}</div></div>`;
  }).join('');
}
</script>
</body></html>