deploy ui rebuild v3
CI / compose (push) Successful in 13m39s

This commit is contained in:
Marco0300
2026-09-04 14:36:45 +02:00
parent 9258bfd811
commit 08ffa29cb7
2 changed files with 6 additions and 6 deletions
+5 -5
View File
@@ -284,7 +284,7 @@
function renderSourceRecords(items) { const list = $('sourceRecordsList'); if (!items.length) { list.innerHTML = '<div class="source-empty">No source records returned by the workspace.</div>'; return; } list.innerHTML = `<div class="source-record-table"><table><thead><tr><th>Record</th><th>Source</th><th>Status</th><th>Observed</th></tr></thead><tbody>${items.slice(0,25).map(record => `<tr><td>${esc(record.name || record.title || record.external_id || record.id || 'Unnamed record')}</td><td>${esc(record.source_name || record.source || 'Unknown source')}</td><td><span class="status">${esc(record.status || 'Pending')}</span></td><td>${esc(record.observed_at || record.created_at || 'Time unavailable')}</td></tr>`).join('')}</tbody></table></div>`; }
async function loadSources() { sourceMessage('Loading sources…'); $('sourcesList').innerHTML = '<div class="detail-loading" aria-live="polite">Loading source registry…</div>'; $('sourceRecordsList').innerHTML = '<div class="detail-loading" aria-live="polite">Loading source records…</div>'; try { const [sourcePayload, adapterPayload, recordPayload] = await Promise.all([jsonRequest('/api/v1/sources'), jsonRequest('/api/v1/sources/adapters'), jsonRequest('/api/v1/source-records?page_size=25')]); const configured=sourceItems(sourcePayload), registeredCodes=new Set(configured.map(sourceType)); const optional=sourceItems(adapterPayload).filter(adapter=>!registeredCodes.has(adapter.source_code)).map(adapter=>({...adapter,source_code:adapter.source_code,display_name:adapter.display_name,kind:adapter.source_code,optional:Boolean(adapter.optional),configured:false,available:Boolean(adapter.available),enabled:false})); sources=[...configured,...optional]; renderSources(); renderSourceRecords(sourceItems(recordPayload)); $('sourcesUpdatedAt').textContent = `Updated ${new Date().toLocaleTimeString([], {hour:'2-digit', minute:'2-digit'})}`; sourceMessage(sources.some(sourceState) ? '' : 'No live source is enabled. Configure and enable a ready source to begin discovery.'); } catch (error) { sources = []; renderSources(); renderSourceRecords([]); if (error.message !== 'unauthorized') sourceMessage(error.message || 'Unable to load sources.', true); } }
async function saveSource(event) { event.preventDefault(); const form = event.currentTarget, fields = Object.fromEntries(new FormData(form).entries()); if (fields.source_type === 'csv' && !fields.csv_content.trim()) { message('sourceFormMessage', 'CSV content is required for a CSV source.', true); return; } const config = {url:fields.url, terms_url:fields.terms_url, owner:fields.owner, rate_limit:fields.rate_limit}; if (fields.source_type === 'csv') config.csv = fields.csv_content; else config.rows = []; try { await jsonRequest('/api/v1/sources', {method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify({name:fields.name, kind:fields.source_type, config, enabled:false})}); message('sourceFormMessage', 'Source saved. It remains disabled until explicitly enabled.'); form.reset(); $('sourceCsvField').hidden = true; await loadSources(); } catch (error) { if (error.message !== 'unauthorized') message('sourceFormMessage', error.message || 'Unable to save source.', true); } }
async function sourceAction(id, action) { const source = sources.find(item => String(item.id) === String(id)); if (!source || source.optional) return; if (action === 'review') { selectedSourceId=id; document.querySelector(`[data-source-id="${CSS.escape(String(id))}"]`)?.scrollIntoView({behavior:'smooth',block:'center'}); sourceMessage('Source details are shown below. Review terms, owner, limits, health, and circuit state before enabling.'); return; } try { if (action === 'test') { await jsonRequest(`/api/v1/sources/${encodeURIComponent(id)}/test`, {method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify({})}); sourceMessage('Source test completed.'); } else { const enabled = sourceState(source); await jsonRequest(`/api/v1/sources/${encodeURIComponent(id)}`, {method:'PATCH', headers:{'Content-Type':'application/json'}, body:JSON.stringify({enabled:!enabled})}); sourceMessage(`Source ${enabled ? 'disabled' : 'enabled'} by the workspace.`); } await loadSources(); } catch (error) { if (error.message !== 'unauthorized') sourceMessage(error.message || `Unable to ${action} source.`, true); } }
async function sourceAction(id, action) { const source = sources.find(item => String(item.id) === String(id)); if (!source || source.optional) return; if (action === 'review') { selectedSourceId=id; document.querySelector(`[data-source-id="${CSS.escape(String(id))}"]`)?.scrollIntoView({behavior:'smooth',block:'center'}); sourceMessage('Source details are shown below. Review terms, owner, limits, health, and circuit state before enabling.'); return; } try { let notice=''; if (action === 'test') { await jsonRequest(`/api/v1/sources/${encodeURIComponent(id)}/test`, {method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify({})}); notice='Source test completed.'; } else { const enabled = sourceState(source); await jsonRequest(`/api/v1/sources/${encodeURIComponent(id)}`, {method:'PATCH', headers:{'Content-Type':'application/json'}, body:JSON.stringify({enabled:!enabled})}); notice=`Source ${enabled ? 'disabled' : 'enabled'} by the workspace.`; } await loadSources(); sourceMessage(notice); } catch (error) { if (error.message !== 'unauthorized') sourceMessage(error.message || `Unable to ${action} source.`, true); } }
async function runDiscovery(dryRun) { const form = $('discoveryForm'), data = Object.fromEntries(new FormData(form).entries()); data.dry_run = Boolean(dryRun); if (!data.source_id || !data.query.trim()) { message('discoveryMessage', 'Select a source and enter a query.', true); return; } const source = sources.find(item => String(item.id) === String(data.source_id)); if (!dryRun && !sourceState(source)) { message('discoveryMessage', 'This source is disabled. Enable it only after review.', true); return; } const button = dryRun ? $('discoveryDryRunBtn') : $('discoveryRunBtn'); button.disabled = true; message('discoveryMessage', dryRun ? 'Validating query…' : 'Starting discovery…'); try { const query = await jsonRequest('/api/v1/discovery-queries', {method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify({source_id:Number(data.source_id), name:data.query.trim().slice(0,80), query:{text:data.query.trim()}, dry_run:data.dry_run})}); if (!dryRun) await jsonRequest(`/api/v1/discovery-queries/${encodeURIComponent(query.id)}/run`, {method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify({})}); message('discoveryMessage', dryRun ? 'Dry run completed; no discovery job was started.' : 'Discovery request accepted. Check Jobs for progress.'); } catch (error) { if (error.message !== 'unauthorized') message('discoveryMessage', error.message || 'Discovery request failed.', true); } finally { button.disabled = false; } }
let discoveryRuns = [], selectedDiscoveryRunId = null, discoveryPollTimer = null;
const discoveryStatus = run => String(run?.status || run?.state || 'queued').toLowerCase().replaceAll('_','-');
@@ -345,13 +345,13 @@ $('aiProviderForm').addEventListener('submit',saveAiProviderSettings);$('aiProvi
}
async function configureSourceFromUi(source, button) {
const type = sourceType(source);
const label = type === 'public_website' ? 'Enter a public website URL' : type === 'dns' ? 'Enter a domain for DNS lookup' : type === 'rdap' ? 'Enter a domain for RDAP lookup' : type === 'ct_logs' ? 'Enter a domain for certificate-transparency lookup' : 'Enter source configuration';
const label = type === 'public_website' ? 'Enter a public website URL' : type === 'dns' ? 'Enter a domain for DNS lookup' : type === 'rdap' ? 'Enter a domain for RDAP lookup' : type === 'ct_logs' ? 'Enter a domain for certificate-transparency lookup' : type === 'openstreetmap' || type === 'wikidata' || type === 'common_crawl' ? 'Enter the business/category search query' : 'Enter source configuration';
const value = window.prompt(label + ':');
if (!value || !value.trim()) return;
const text = value.trim();
const config = type === 'public_website' ? {urls:[text]} : type === 'dns' ? {domains:[text]} : {domain:text};
const config = type === 'public_website' ? {urls:[text]} : type === 'dns' ? {domains:[text]} : type === 'openstreetmap' || type === 'wikidata' || type === 'common_crawl' ? {provider:type,query:text,location:(window.prompt('Enter the location (optional):','South Africa') || 'South Africa').trim(),approved:true,public_access:true,terms_accepted:true,rate_limit:1} : {domain:text};
button.disabled = true;
try { await jsonRequest(`/api/v1/sources/${encodeURIComponent(source.id)}`, {method:'PATCH', headers:{'Content-Type':'application/json'}, body:JSON.stringify({config})}); sourceMessage('Source configuration saved. Test it before enabling.'); await loadSources(); }
try { await jsonRequest(`/api/v1/sources/${encodeURIComponent(source.id)}`, {method:'PATCH', headers:{'Content-Type':'application/json'}, body:JSON.stringify({config})}); await loadSources(); sourceMessage('Source configuration saved. Test it before enabling.'); }
catch (error) { if (error.message !== 'unauthorized') sourceMessage(error.message || 'Unable to configure source.', true); }
finally { button.disabled = false; }
}
@@ -359,7 +359,7 @@ $('aiProviderForm').addEventListener('submit',saveAiProviderSettings);$('aiProvi
const actionButton = event.target.closest?.('[data-source-action="toggle"]');
if (!actionButton) return;
const source = sources.find(item => String(item.id) === String(actionButton.dataset.sourceId));
const needsPromptConfig = ['public_website','dns','rdap','ct_logs'].includes(sourceType(source));
const needsPromptConfig = ['public_website','dns','rdap','ct_logs','openstreetmap','wikidata','common_crawl'].includes(sourceType(source));
if (source && !source.configured && source.available && needsPromptConfig) { event.preventDefault(); event.stopImmediatePropagation(); configureSourceFromUi(source, actionButton); }
}, true);
document.querySelectorAll('.sidebar nav a, [data-scroll]').forEach(link => link.addEventListener('click', event => { const target = (link.getAttribute('href') || link.dataset.scroll || '').replace(/^#/, ''); const view = viewMap[target]; if (view) { event.preventDefault(); activateView(view); document.querySelector('.sidebar')?.classList.remove('open'); } }));
+1 -1
View File
@@ -14,7 +14,7 @@
],
"integrity": {
"config.js": "sha256-95293871c068b38098cead4af5de4b17ee505a0b051b9379567a716dc5364490",
"app.js": "sha256-b7455ddbfdc755bdd08b1713362ed097ab678fb2c47b425bf11595bdb97ba8b6",
"app.js": "sha256-0c81385217c843cbbd657a818e89c77008007e17c49f0a9a203bcb3af2fae4f3",
"styles.css": "sha256-ba90290ab11e82a6b2639dfd70d1e74502c1cacb2b26cf1db92b45beb67ac03f",
"index.html": "sha256-f5d2ab03e5c676f8af8a9bfd201e6d4fb3bfb19ba10098e93eae5680122fa2cc",
"health.html": "sha256-c352a6f37aa24628cfc8d5709a70ff2d94181d192ed5fe00916cca9378a61d81",