From 28e1ac20a8cc7a11ca61182329659aebe7920821 Mon Sep 17 00:00:00 2001 From: Marco0300 Date: Fri, 4 Sep 2026 13:58:31 +0200 Subject: [PATCH] deploy ui rebuild v1 --- apps/api/app/main.py | 3 +- apps/api/app/sources.py | 2 +- apps/api/tests/test_sources_phase5.py | 5 ++- apps/web/app.js | 2 +- apps/web/asset-manifest.json | 10 ++--- apps/web/config.js | 2 +- apps/web/index.html | 40 +++++++++++-------- apps/web/styles.css | 56 +++++++++++++++++++++++++++ 8 files changed, 93 insertions(+), 27 deletions(-) diff --git a/apps/api/app/main.py b/apps/api/app/main.py index b6c53df..7df7f5f 100644 --- a/apps/api/app/main.py +++ b/apps/api/app/main.py @@ -1362,6 +1362,7 @@ class ApiHandler(BaseHTTPRequestHandler): item=row_json(raw); meta=adapter_meta.get(item.get("source_code") or item.get("kind"), {}) try: config=json.loads(raw["config_json"] or "{}") except (TypeError,ValueError): config={} + if not meta and config.get("provider"): meta=adapter_meta.get(str(config.get("provider")).lower(), {}) try: policy=json.loads(raw["policy_json"] or "{}") except (TypeError,ValueError): policy={} credential_exists = bool(db.execute("SELECT 1 FROM source_credentials WHERE source_id=? AND organization_id=? AND provider=? AND key_name=?", (raw["id"], org, "google_places", "api_key")).fetchone()) @@ -1402,7 +1403,7 @@ class ApiHandler(BaseHTTPRequestHandler): name=str(payload.get('name','')).strip(); requested=str(payload.get('source_code') or payload.get('kind','')).strip().lower(); config=payload.get('config',{}) provider_kinds={'openstreetmap','wikidata','common_crawl'} kind='approved_directory' if requested in provider_kinds else requested - source_code=requested + source_code=str(config.get('provider','')).strip().lower() if requested == 'approved_directory' and str(config.get('provider','')).strip().lower() in {'openstreetmap','wikidata','common_crawl'} else requested if not name or requested not in ('csv','manual','google_places','bing_local','approved_directory','openstreetmap','wikidata','common_crawl','public_website','permitted_social','ct_logs','dns','rdap') or not isinstance(config,dict):return self.send_json(400,{"error":"invalid_source"}) if contains_secret(config):return self.send_json(400,{"error":"secret_not_permitted"}) try: diff --git a/apps/api/app/sources.py b/apps/api/app/sources.py index 78e1066..eeb6ab7 100644 --- a/apps/api/app/sources.py +++ b/apps/api/app/sources.py @@ -418,4 +418,4 @@ def available_adapters() -> list[dict[str, object]]: "available": bool(getattr(cls, "available", False)), "optional": bool(getattr(cls, "optional", False)), "requires_credentials": bool(getattr(cls, "requires_credentials", False))} - for cls in ADAPTERS.values()] + for cls in ADAPTERS.values() if cls.source_code != "approved_directory"] diff --git a/apps/api/tests/test_sources_phase5.py b/apps/api/tests/test_sources_phase5.py index 9313be4..84a4bcc 100644 --- a/apps/api/tests/test_sources_phase5.py +++ b/apps/api/tests/test_sources_phase5.py @@ -12,8 +12,9 @@ class SourceAdapterTests(unittest.TestCase): for code in ('bing_local', 'permitted_social'): self.assertFalse(catalog[code]['available'], code) self.assertTrue(catalog[code]['optional'], code) - self.assertTrue(catalog['approved_directory']['available']) - self.assertFalse(catalog['approved_directory']['optional']) + for code in ('openstreetmap','wikidata','common_crawl'): + self.assertTrue(catalog[code]['available'], code) + self.assertFalse(catalog[code]['optional'], code) self.assertTrue(catalog['google_places']['available']) self.assertTrue(catalog['google_places']['optional']) diff --git a/apps/web/app.js b/apps/web/app.js index 3e57561..012d71a 100644 --- a/apps/web/app.js +++ b/apps/web/app.js @@ -330,7 +330,7 @@ $('interactionForm').addEventListener('submit',saveInteraction);$('suppressionForm').addEventListener('submit',addSuppression);$('aiProviderForm').addEventListener('submit',saveAiProviderSettings);$('aiProvider').addEventListener('change',updateAiProviderForm);$('aiProviderRefreshBtn').addEventListener('click',loadAiProviderSettings);$('testAiProviderBtn').addEventListener('click',testAiProviderConnection);$('crmRefreshBtn').addEventListener('click',()=>{loadCrmPipeline();loadInteractions(selectedId);});$('reportsRefreshBtn').addEventListener('click',loadReports);$('suppressionRefreshBtn').addEventListener('click',loadSuppressions);$('pipelineViewToggle').addEventListener('click',()=>{crmListMode=!crmListMode;$('pipelineViewToggle').textContent=crmListMode?'▦ Board view':'☷ List view';$('pipelineViewToggle').setAttribute('aria-pressed',String(crmListMode));renderPipeline();});$('pipelineBoard').addEventListener('click',e=>{const selectButton=e.target.closest?.('[data-crm-select]');if(selectButton){selectedId=Number(selectButton.dataset.crmSelect);selectProspect(selectedId);loadInteractions(selectedId);$('crmActivity').scrollIntoView({behavior:'smooth',block:'start'});}const stageButton=e.target.closest?.('[data-crm-save-stage]');if(stageButton)saveCrmStage(stageButton.dataset.crmSaveStage);if(e.target.id==='retryCrmBtn')loadCrmPipeline();if(e.target.id==='retryInteractionsBtn'&&selectedId)loadInteractions(selectedId);});$('suppressionState').addEventListener('change',e=>{const input=e.target.closest?.('[data-suppression-id]');if(input){if(input.checked)selectedSuppressionIds.add(String(input.dataset.suppressionId));else selectedSuppressionIds.delete(String(input.dataset.suppressionId));updateSuppressionSelection();}});$('suppressionState').addEventListener('click',e=>{const button=e.target.closest?.('[data-remove-suppression]');if(button)removeSuppression(button.dataset.removeSuppression);if(e.target.id==='retrySuppressionsBtn')loadSuppressions();});$('selectAllSuppressions').addEventListener('change',e=>{suppressions.forEach(item=>e.target.checked?selectedSuppressionIds.add(String(item.id)):selectedSuppressionIds.delete(String(item.id)));renderSuppressions();});$('bulkReviewSuppressionsBtn').addEventListener('click',bulkReviewSuppressions);$('discoveryWorkspace').addEventListener('click',e=>{const action=e.target.closest?.('[data-run-action]');if(action){const run=discoveryRuns.find(item=>String(item.id)===String(selectedDiscoveryRunId));discoveryAction(action.dataset.runAction,run);}}); $('aiProviderForm').addEventListener('submit',saveAiProviderSettings);$('aiProvider').addEventListener('change',updateAiProviderForm);$('aiProviderRefreshBtn').addEventListener('click',loadAiProviderSettings);$('testAiProviderBtn').addEventListener('click',testAiProviderConnection);$('crmRefreshBtn').addEventListener('click',()=>{loadCrmPipeline();loadInteractions(selectedId);});$('reportsRefreshBtn').addEventListener('click',loadReports);$('suppressionRefreshBtn').addEventListener('click',loadSuppressions);$('pipelineViewToggle').addEventListener('click',()=>{crmListMode=!crmListMode;$('pipelineViewToggle').textContent=crmListMode?'▦ Board view':'☷ List view';$('pipelineViewToggle').setAttribute('aria-pressed',String(crmListMode));renderPipeline();});$('pipelineBoard').addEventListener('click',e=>{const selectButton=e.target.closest?.('[data-crm-select]');if(selectButton){selectedId=Number(selectButton.dataset.crmSelect);selectProspect(selectedId);loadInteractions(selectedId);$('crmActivity').scrollIntoView({behavior:'smooth',block:'start'});}const stageButton=e.target.closest?.('[data-crm-save-stage]');if(stageButton)saveCrmStage(stageButton.dataset.crmSaveStage);if(e.target.id==='retryCrmBtn')loadCrmPipeline();if(e.target.id==='retryInteractionsBtn'&&selectedId)loadInteractions(selectedId);});$('suppressionState').addEventListener('change',e=>{const input=e.target.closest?.('[data-suppression-id]');if(input){if(input.checked)selectedSuppressionIds.add(String(input.dataset.suppressionId));else selectedSuppressionIds.delete(String(input.dataset.suppressionId));updateSuppressionSelection();}});$('suppressionState').addEventListener('click',e=>{const button=e.target.closest?.('[data-remove-suppression]');if(button)removeSuppression(button.dataset.removeSuppression);if(e.target.id==='retrySuppressionsBtn')loadSuppressions();});$('selectAllSuppressions').addEventListener('change',e=>{suppressions.forEach(item=>e.target.checked?selectedSuppressionIds.add(String(item.id)):selectedSuppressionIds.delete(String(item.id)));renderSuppressions();});$('bulkReviewSuppressionsBtn').addEventListener('click',bulkReviewSuppressions);$('outreachPolicyRefreshBtn').addEventListener('click',loadProviderPolicy); const viewMap = {dashboard:'dashboard', explorer:'prospects', add:'add', discoveryWorkspace:'discovery', jobs:'jobs', sources:'sources', crmPipeline:'crm', crmActivity:'crm', crmReports:'crm', suppressionCenter:'settings', outreachSettings:'settings', aiProviderSettings:'settings', scoreRules:'dashboard'}; - function activateView(view) { document.body.dataset.view = view; document.querySelectorAll('.content > section').forEach(section => section.classList.toggle('view-active', viewMap[section.id] === view)); window.scrollTo({top:0, behavior:'smooth'}); } + function activateView(view) { document.body.dataset.view = view; document.querySelectorAll('.content > section').forEach(section => section.classList.toggle('view-active', viewMap[section.id] === view)); const labels = {dashboard:'Dashboard', prospects:'Prospect explorer', add:'Add prospects', discovery:'Discovery runs', jobs:'Jobs', sources:'Sources', crm:'CRM pipeline', settings:'Settings'}; const page = document.getElementById('topbarPage'); if (page) page.textContent = labels[view] || 'Workspace'; window.scrollTo({top:0, behavior:'smooth'}); } document.body.dataset.view = 'dashboard'; const sourceSetup = document.querySelector('.source-config-panel'); if (sourceSetup) { diff --git a/apps/web/asset-manifest.json b/apps/web/asset-manifest.json index f9d228d..4f6281f 100644 --- a/apps/web/asset-manifest.json +++ b/apps/web/asset-manifest.json @@ -1,6 +1,6 @@ { "schema": 1, - "version": "phase-26", + "version": "phase-27", "entrypoints": [ "config.js", "app.js", @@ -13,10 +13,10 @@ "healthz" ], "integrity": { - "config.js": "sha256-036e9b1a2d16cf89ffc1cb23f22aab71608b1330e8323d2d42a6c6b86051698c", - "app.js": "sha256-44809719afe72cd0ddc30819b3c73f36019319bfbda4e59d62055fd040b5c6c0", - "styles.css": "sha256-a9fd194b001c0de98775bcf5fbba5da676ccfdd607a889eae84bd30a54c15e08", - "index.html": "sha256-f48882419575ac49c389f07ba25791115981330a1dd623ce3c21b026a0dedd3c", + "config.js": "sha256-95293871c068b38098cead4af5de4b17ee505a0b051b9379567a716dc5364490", + "app.js": "sha256-3bd0215c904f7a8b0ebce3b96c799944880a3ce123694e3680db4516b690e490", + "styles.css": "sha256-ba90290ab11e82a6b2639dfd70d1e74502c1cacb2b26cf1db92b45beb67ac03f", + "index.html": "sha256-f5d2ab03e5c676f8af8a9bfd201e6d4fb3bfb19ba10098e93eae5680122fa2cc", "health.html": "sha256-c352a6f37aa24628cfc8d5709a70ff2d94181d192ed5fe00916cca9378a61d81", "error.html": "sha256-f3cc28d2dfc9e8af112d0257c6ee47b9e9146e5da8590a754dd589bf43702aaf", "healthz": "sha256-dc51b8c96c2d745df3bd5590d990230a482fd247123599548e0632fdbf97fc22" diff --git a/apps/web/config.js b/apps/web/config.js index feb5584..264ef09 100644 --- a/apps/web/config.js +++ b/apps/web/config.js @@ -1,5 +1,5 @@ /* Public, non-secret runtime configuration. Replace this file at deploy time if needed. */ window.__PROSPECT_CONFIG__ = Object.freeze({ apiBase: '', - assetVersion: 'phase-26' + assetVersion: 'phase-27' }); diff --git a/apps/web/index.html b/apps/web/index.html index 033b9c9..052e9d6 100644 --- a/apps/web/index.html +++ b/apps/web/index.html @@ -5,7 +5,7 @@ ProspectOS · Pipeline intelligence - +
@@ -27,23 +27,31 @@
-
Workspace / Growth pipeline
● Connecting…
?
+
Growth workspace/Dashboard
● Connecting…
?

EVIDENCE-LED PROSPECTING

Good morning, there

Your pipeline has 0 prospects ready for review.

@@ -130,7 +138,7 @@ - - + + diff --git a/apps/web/styles.css b/apps/web/styles.css index d3f8382..378c35c 100644 --- a/apps/web/styles.css +++ b/apps/web/styles.css @@ -347,3 +347,59 @@ body[data-view="settings"] #crmReports { display: none; } .discovery-controls { display:flex; align-items:center; flex-wrap:wrap; gap:8px; margin-bottom:14px; padding-bottom:12px; border-bottom:1px solid var(--line); }.discovery-progress { padding:15px 0; }.discovery-stat-grid { display:grid; grid-template-columns:repeat(4,minmax(0,1fr)); gap:8px; margin:0 0 14px; }.discovery-stat-grid > div { padding:11px; border:1px solid var(--line); border-radius:9px; background:#f8fbfa; }.discovery-stat-grid small { display:block; color:var(--muted); font-size:10px; text-transform:uppercase; letter-spacing:.06em; }.discovery-stat-grid strong { display:block; margin-top:3px; color:var(--ink-strong); font-size:20px; }.discovery-two-col { display:grid; grid-template-columns:minmax(0,.9fr) minmax(0,1.1fr); gap:14px; margin-top:16px; }.live-log { min-width:0; }.live-log h4, .source-health h4 { margin:0 0 10px; }.live-log ol { max-height:240px; overflow:auto; display:grid; gap:11px; margin:0; padding:12px; border:1px solid var(--line); border-radius:9px; background:#f8fbfa; list-style:none; }.live-log li { display:flex; gap:9px; font-size:12px; }.live-log small { display:block; color:var(--muted); font-size:10px; }.source-health { padding:13px; border:1px solid #c8e2de; border-radius:10px; background:var(--teal-soft); }.subheading { display:flex; justify-content:space-between; gap:10px; align-items:center; }.source-health-grid { display:grid; gap:8px; }.source-health-grid article { display:grid; gap:3px; padding:10px; border:1px solid #c8e2de; border-radius:8px; background:#fff; }.source-health-grid small { color:var(--muted); font-size:10px; }.source-health-status { width:fit-content; padding:3px 7px; border-radius:99px; color:var(--green); background:var(--green-soft); font-size:10px; font-weight:800; }.source-health-actions { display:flex; gap:6px; margin-top:5px; }.advanced-discovery { padding:10px 0; border-top:1px solid var(--line); border-bottom:1px solid var(--line); }.advanced-discovery summary { cursor:pointer; color:var(--teal); font-size:12px; font-weight:750; margin-bottom:9px; }.discovery-partial p { margin: 4px 0 0; }.discovery-empty { padding: 30px 10px; color: var(--muted); text-align: center; } .ai-facts { display: grid; grid-template-columns: repeat(4, 1fr); gap: 7px; margin: 13px 0; }.ai-facts div { padding: 8px; border: 1px solid var(--line); border-radius: 8px; background: #fff; }.ai-facts dt { color: var(--muted); font-size: 9px; font-weight: 800; text-transform: uppercase; }.ai-facts dd { margin: 3px 0 0; overflow-wrap: anywhere; font-size: 11px; } @media(max-width:700px) { .discovery-hero { flex-direction: column; }.discovery-workspace-grid { grid-template-columns: 1fr; }.discovery-limit-grid, .ai-facts, .discovery-stat-grid { grid-template-columns: 1fr 1fr; }.discovery-two-col { grid-template-columns: 1fr; }.discovery-run-row { grid-template-columns: 1fr auto; }.discovery-run-row > small { grid-column: 1 / -1; } } + +/* ProspectOS UI refresh: clearer hierarchy, calmer surfaces, and stronger action states. */ +:root { + --ink: #172033; + --ink-strong: #0b1220; + --muted: #667085; + --muted-soft: #98a2b3; + --line: #e4e7ec; + --line-strong: #cfd5df; + --surface: #ffffff; + --surface-alt: #f8fafc; + --bg: #f4f6fa; + --navy: #111827; + --navy-2: #1f2a44; + --navy-3: #344054; + --accent: #635bff; + --accent-dark: #5048d8; + --accent-soft: #eeedff; + --teal: #0f766e; + --teal-soft: #e8f7f4; + --shadow-sm: 0 1px 3px rgba(16,24,40,.06), 0 1px 2px rgba(16,24,40,.03); + --shadow: 0 12px 30px rgba(16,24,40,.08); + --radius: 16px; +} +body { background: var(--bg); color: var(--ink); } +.sidebar { width: 264px; padding: 24px 14px 18px; background: linear-gradient(180deg,#111827 0%,#172033 100%); box-shadow: 14px 0 34px rgba(16,24,40,.10); } +.main { margin-left: 264px; } +.brand { padding: 0 12px 30px; font-size: 20px; } +.brand-mark { width: 32px; height: 32px; border-radius: 10px; color: #fff; background: var(--accent); box-shadow: 0 7px 18px rgba(99,91,255,.34); } +.nav-group { margin: 0 0 22px; } +.nav-label { margin: 0 12px 7px; color: #7f8aa3; font-size: 10px; font-weight: 800; letter-spacing: .12em; text-transform: uppercase; } +.nav-item { padding: 10px 12px; border-radius: 10px; color: #aeb8cb; } +.nav-item span { color: #8390a8; } +.nav-item:hover, .nav-item.active { color: #fff; background: rgba(255,255,255,.09); border-color: rgba(255,255,255,.08); } +.nav-item.active { box-shadow: inset 3px 0 var(--accent); } +.nav-item.active span { color: #a9a4ff; } +.topbar { height: 76px; padding: 0 clamp(22px,4vw,58px); background: rgba(255,255,255,.88); border-bottom-color: #eaecf0; } +.crumb { color: var(--muted); font-size: 13px; }.crumb strong { color: var(--ink); }.crumb span { margin: 0 9px; } +.content { padding-top: 38px; } +.hero { margin-bottom: 25px; }.hero h1 { font-size: clamp(32px,4vw,48px); letter-spacing: -.055em; }.hero h1 span:last-child { color: var(--accent); }.hero-sub { margin-top: 10px; } +.button { min-height: 40px; border-radius: 10px; border-color: var(--line-strong); background: #fff; }.button.primary { border-color: var(--accent); background: var(--accent); box-shadow: 0 5px 12px rgba(99,91,255,.18); }.button.primary:hover { background: var(--accent-dark); } +.panel, .metric-card { border-color: var(--line); border-radius: var(--radius); box-shadow: var(--shadow-sm); } +.metric-card { padding: 18px; background: #fff; }.metric-card:first-child { border-color: #d9d6ff; background: linear-gradient(135deg,#fff,#f5f4ff); }.metric-icon { border-radius: 11px; background: var(--accent-soft); }.metric-icon.violet, .metric-icon.blue { color: var(--accent); background: var(--accent-soft); } +.score-overview { margin-top: 16px; }.score-distribution-panel, .score-rules-section .panel { padding: 22px; } +.eyebrow { color: var(--accent); letter-spacing: .13em; } +input, select, textarea { border-color: #d0d5dd; border-radius: 10px; background: #fff; }.filters { margin-top: 18px; }.filters select, .search-wrap input { min-height: 42px; } +th { background: #f8fafc; } td { padding-top: 14px; padding-bottom: 14px; } +.review-queue { border-color: #d9d6ff; background: #fafaff; }.queue-row:hover, tbody tr:hover, tbody tr.selected, .job-row:hover, .job-row.selected, .discovery-run-row:hover, .discovery-run-row.selected { background: #f5f4ff; } +.source-safety, .suppression-warning { border-color: #f5d48a; background: #fffbeb; }.source-health, .score-safety, .crm-safety, .outreach-safety, .outreach-settings-safety, .contact-extraction-safety, .website-scan-safety, .domain-safety, .ai-provider-safety { background: #effaf8; border-color: #b7e4dc; } +.discovery-badge, .source-health-status { color: var(--teal); background: var(--teal-soft); } +.source-row, .job-row, .discovery-run-row { border-color: #edf0f4; }.source-type { background: #f8fafc; } +.detail-panel, .jobs-list, .job-detail, .sources-list, .source-records, .source-config-panel, .discovery-panel, .add-panel, .csv-panel, .interaction-panel, .reports-grid > article, .direct-discovery-form-panel, .discovery-runs-panel, .discovery-results-panel { background: #fff; } +.sidebar-foot { border-top-color: rgba(255,255,255,.10); }.live-dot { background: #53d1ae; } +footer { border-color: #e4e7ec; } +@media (max-width: 820px) { .sidebar { width: 232px; } .main { margin-left: 232px; } } +@media (max-width: 700px) { .main { margin-left: 0; } .topbar { height: 68px; } .content { padding-top: 26px; } }