feat: add Validate Selected button, Alpha only and No SLD filters to beauty Browse
- /api/validate/batch endpoint: HTTP-check only (no DeepSeek), accepts up to 500 domains - Validate Selected bulk button: runs validate in 500-domain chunks, shows live/dead summary - Alpha only checkbox: passes alpha_only=true to /api/domains to exclude hyphens/numbers - No SLD checkbox: passes no_sld=true to /api/domains to skip com.es / co.uk style domains - Both flags wired into loadDomains() and resetFilters() Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -188,6 +188,26 @@ async def validator_status():
|
|||||||
|
|
||||||
# ── Pre-screen (shared) ───────────────────────────────────────────────────────
|
# ── Pre-screen (shared) ───────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
@app.post("/api/validate/batch")
|
||||||
|
async def validate_batch(body: dict):
|
||||||
|
"""HTTP-check only — no DeepSeek classification. Fast live/dead check for bulk selection."""
|
||||||
|
domains_list = body.get("domains", [])
|
||||||
|
if not domains_list:
|
||||||
|
return JSONResponse({"error": "no domains provided"}, status_code=400)
|
||||||
|
if len(domains_list) > 500:
|
||||||
|
return JSONResponse({"error": "max 500 per batch"}, status_code=400)
|
||||||
|
from app.prescreener import prescreen_domains
|
||||||
|
results = await prescreen_domains(domains_list)
|
||||||
|
await save_prescreen_results(results)
|
||||||
|
counts: dict = {}
|
||||||
|
for r in results:
|
||||||
|
s = r.get("prescreen_status", "dead")
|
||||||
|
counts[s] = counts.get(s, 0) + 1
|
||||||
|
return {"total": len(domains_list), "live": counts.get("live", 0),
|
||||||
|
"dead": counts.get("dead", 0), "parked": counts.get("parked", 0),
|
||||||
|
"redirect": counts.get("redirect", 0), "error": counts.get("error", 0)}
|
||||||
|
|
||||||
|
|
||||||
@app.post("/api/prescreen/batch")
|
@app.post("/api/prescreen/batch")
|
||||||
async def prescreen_batch(body: dict):
|
async def prescreen_batch(body: dict):
|
||||||
domains_list = body.get("domains", [])
|
domains_list = body.get("domains", [])
|
||||||
|
|||||||
@@ -145,6 +145,12 @@ textarea{width:100%;resize:vertical;font-family:monospace;font-size:12px}
|
|||||||
<option value="landing_page">Landing Page</option>
|
<option value="landing_page">Landing Page</option>
|
||||||
</select>
|
</select>
|
||||||
<input x-model="f.country" placeholder="Country (ES, FR…)" style="width:100px" @keyup.enter="goSearch()">
|
<input x-model="f.country" placeholder="Country (ES, FR…)" style="width:100px" @keyup.enter="goSearch()">
|
||||||
|
<label class="tog" style="font-size:12px;display:flex;align-items:center;gap:4px;cursor:pointer;white-space:nowrap">
|
||||||
|
<input type="checkbox" x-model="f.alpha_only" @change="goSearch()"><span>Alpha only</span>
|
||||||
|
</label>
|
||||||
|
<label class="tog" style="font-size:12px;display:flex;align-items:center;gap:4px;cursor:pointer;white-space:nowrap">
|
||||||
|
<input type="checkbox" x-model="f.no_sld" @change="goSearch()"><span>No SLD</span>
|
||||||
|
</label>
|
||||||
<select x-model="f.limit" @change="goSearch()">
|
<select x-model="f.limit" @change="goSearch()">
|
||||||
<option value="50">50</option>
|
<option value="50">50</option>
|
||||||
<option value="100" selected>100</option>
|
<option value="100" selected>100</option>
|
||||||
@@ -159,6 +165,10 @@ textarea{width:100%;resize:vertical;font-family:monospace;font-size:12px}
|
|||||||
<!-- Bulk bar (visible when items selected) -->
|
<!-- Bulk bar (visible when items selected) -->
|
||||||
<div class="bulk-bar" x-show="selected.length>0">
|
<div class="bulk-bar" x-show="selected.length>0">
|
||||||
<span style="color:var(--accent);font-weight:600;font-size:12px" x-text="selected.length+' selected'"></span>
|
<span style="color:var(--accent);font-weight:600;font-size:12px" x-text="selected.length+' selected'"></span>
|
||||||
|
<button class="btn-secondary btn-sm" @click="validateSelected()" :disabled="validating">
|
||||||
|
<span x-show="!validating">Validate Selected</span>
|
||||||
|
<span x-show="validating">Validating…</span>
|
||||||
|
</button>
|
||||||
<button class="btn-ok btn-sm" @click="prescreenSelected()" :disabled="prescreening">
|
<button class="btn-ok btn-sm" @click="prescreenSelected()" :disabled="prescreening">
|
||||||
<span x-show="!prescreening">Pre-screen Selected</span>
|
<span x-show="!prescreening">Pre-screen Selected</span>
|
||||||
<span x-show="prescreening">Screening…</span>
|
<span x-show="prescreening">Screening…</span>
|
||||||
@@ -430,10 +440,10 @@ function app() {
|
|||||||
valSt: {running:false,processed:0,live:0,dead:0,error:0,parked:0,redirect:0,skipped:0,offset:0,rate:0},
|
valSt: {running:false,processed:0,live:0,dead:0,error:0,parked:0,redirect:0,skipped:0,offset:0,rate:0},
|
||||||
valTld: '', valRescan: false,
|
valTld: '', valRescan: false,
|
||||||
toasts: [],
|
toasts: [],
|
||||||
prescreening: false,
|
prescreening: false, validating: false,
|
||||||
exportQuality: '', exportCountry: '',
|
exportQuality: '', exportCountry: '',
|
||||||
f: {keyword:'', tld:'', prescreen_status:'live', niche:'beauty_cosmetics',
|
f: {keyword:'', tld:'', prescreen_status:'live', niche:'beauty_cosmetics',
|
||||||
site_type:'ecommerce', country:'', limit:'100', page:1},
|
site_type:'ecommerce', country:'', alpha_only:false, no_sld:false, limit:'100', page:1},
|
||||||
pf: {quality:'', country:'', limit:'100', page:1},
|
pf: {quality:'', country:'', limit:'100', page:1},
|
||||||
|
|
||||||
async init() {
|
async init() {
|
||||||
@@ -480,6 +490,8 @@ function app() {
|
|||||||
const p = new URLSearchParams({page: this.f.page, limit: this.f.limit});
|
const p = new URLSearchParams({page: this.f.page, limit: this.f.limit});
|
||||||
if (this.f.keyword) p.set('keyword', this.f.keyword.trim());
|
if (this.f.keyword) p.set('keyword', this.f.keyword.trim());
|
||||||
if (this.f.tld) p.set('tld', this.f.tld.trim());
|
if (this.f.tld) p.set('tld', this.f.tld.trim());
|
||||||
|
if (this.f.alpha_only) p.set('alpha_only', 'true');
|
||||||
|
if (this.f.no_sld) p.set('no_sld', 'true');
|
||||||
const d = await fetch('/api/domains?' + p).then(r=>r.json());
|
const d = await fetch('/api/domains?' + p).then(r=>r.json());
|
||||||
this.domainsTotal = d.total || 0;
|
this.domainsTotal = d.total || 0;
|
||||||
let rows = d.results || [];
|
let rows = d.results || [];
|
||||||
@@ -509,7 +521,7 @@ function app() {
|
|||||||
|
|
||||||
resetFilters() {
|
resetFilters() {
|
||||||
this.f = {keyword:'', tld:'', prescreen_status:'live', niche:'beauty_cosmetics',
|
this.f = {keyword:'', tld:'', prescreen_status:'live', niche:'beauty_cosmetics',
|
||||||
site_type:'ecommerce', country:'', limit:'100', page:1};
|
site_type:'ecommerce', country:'', alpha_only:false, no_sld:false, limit:'100', page:1};
|
||||||
this.selected = [];
|
this.selected = [];
|
||||||
this.loadDomains();
|
this.loadDomains();
|
||||||
},
|
},
|
||||||
@@ -518,6 +530,28 @@ function app() {
|
|||||||
this.selected = e.target.checked ? this.domains.map(r=>r.domain) : [];
|
this.selected = e.target.checked ? this.domains.map(r=>r.domain) : [];
|
||||||
},
|
},
|
||||||
|
|
||||||
|
async validateSelected() {
|
||||||
|
if (!this.selected.length || this.validating) return;
|
||||||
|
this.validating = true;
|
||||||
|
this.notify(`Validating ${this.selected.length} domains…`, 'info');
|
||||||
|
try {
|
||||||
|
const chunks = [];
|
||||||
|
for (let i=0; i<this.selected.length; i+=500) chunks.push(this.selected.slice(i,i+500));
|
||||||
|
let totals = {live:0, dead:0, parked:0, redirect:0, error:0};
|
||||||
|
for (const chunk of chunks) {
|
||||||
|
const d = await fetch('/api/validate/batch', {
|
||||||
|
method:'POST', headers:{'Content-Type':'application/json'},
|
||||||
|
body: JSON.stringify({domains: chunk}),
|
||||||
|
}).then(r=>r.json());
|
||||||
|
for (const k of Object.keys(totals)) totals[k] += d[k]||0;
|
||||||
|
}
|
||||||
|
this.notify(`✅ ${totals.live} live · ☠ ${totals.dead} dead · 🅿 ${totals.parked} parked · ↗ ${totals.redirect} redirect`, 'success');
|
||||||
|
this.selected = [];
|
||||||
|
await this.loadDomains();
|
||||||
|
} catch(e) { this.notify('Validate failed: '+e.message, 'error'); }
|
||||||
|
finally { this.validating = false; }
|
||||||
|
},
|
||||||
|
|
||||||
async prescreenSelected() {
|
async prescreenSelected() {
|
||||||
if (!this.selected.length || this.prescreening) return;
|
if (!this.selected.length || this.prescreening) return;
|
||||||
this.prescreening = true;
|
this.prescreening = true;
|
||||||
|
|||||||
Reference in New Issue
Block a user