VayaPin_Admin_Dashboard_Enhancement_Plan

VayaPin Admin Dashboard Enhancement Plan

Overview

Enhance the VayaPin admin dashboard with additional testing and debugging features.

Base System (Already Implemented): - Admin dashboard at /admin/vayapin ✓ - Auto-export scheduler (every 5 minutes) ✓ - KPI cards, config modal, export run history ✓ - Toggle enable/disable, manual export trigger ✓

New Enhancement Requirements: 1. Show leads with extracted emails even if not yet verified by SpiderVerify 2. Button to manually export a single specific lead 3. Store request/response JSON for debugging (JSONL files vs database) 4. List of all created VayaPins with search functionality


Implementation Steps

Enhancement 1: Show Leads with Unverified Emails

Problem: Current pending leads query only shows leads where emails_verified IS NOT NULL. User wants to also see leads that have extracted emails from SpiderSite but haven't gone through SpiderVerify yet.

Data Flow:

SpiderSite extracts emails → stored in jobs.results.emails[]
SpiderVerify verifies → stored in campaign_workflow_jobs.emails_verified[]

Changes:

1.1 Add config option - app/database/migrations/061_vayapin_show_unverified.sql

ALTER TABLE vayapin_export_config
ADD COLUMN IF NOT EXISTS show_unverified_emails BOOLEAN DEFAULT false;

1.2 Update service - app/services/vayapin_export_service.py - Add show_unverified_emails to config response - Modify get_pending_leads() to optionally include unverified leads: - When show_unverified_emails=true: Also query leads where spidersite_job_id IS NOT NULL and join to get extracted emails from jobs.results - Add verification_status field: "verified", "unverified", "none"

1.3 Update hooks - apps/web/src/hooks/useVayapin.ts - Add show_unverified_emails to VayapinConfig type - Add verification_status to VayapinPendingLead type

1.4 Update UI - apps/web/src/routes/admin/vayapin.tsx - Add toggle in config modal: "Show leads with unverified emails" - Add badge on pending leads: "Verified" (green) / "Unverified" (yellow)


Enhancement 2: Single Lead Manual Export

Problem: Current manual export processes a batch. User wants to click on a specific lead and export just that one.

Changes:

2.1 Add API endpoint - app/api/v1/admin_vayapin.py

@router.post("/leads/{source}/{lead_id}/export")
async def export_single_lead(
    source: str,  # "campaign" or "playbook"
    lead_id: int,
    dry_run: bool = Query(False),
    db: AsyncSession = Depends(get_db),
    _: bool = Depends(require_admin)
):
    """Export a single specific lead to VayaPin."""

2.2 Add service method - app/services/vayapin_export_service.py

async def export_single_lead(self, source: str, lead_id: int, dry_run: bool = False):
    # 1. Fetch the specific lead
    # 2. Build payload
    # 3. Submit job (or simulate if dry_run)
    # 4. Return result

2.3 Add hook - apps/web/src/hooks/useVayapin.ts

export function useExportSingleLead() {
  return useMutation({
    mutationFn: async ({ source, leadId, dryRun }: { source: string; leadId: number; dryRun?: boolean }) => {
      return api.post(`/api/v1/admin/vayapin/leads/${source}/${leadId}/export`, { dry_run: dryRun });
    },
  });
}

2.4 Add Pending Leads tab in UI - apps/web/src/routes/admin/vayapin.tsx - Add tabbed interface: "Overview" | "Pending Leads" | "Exported Leads" - Pending Leads table with columns: Business Name, Domain, Source, Emails, Verification Status, Actions - "Export" button on each row (opens confirm dialog with dry-run option)


Enhancement 3: Store Request/Response JSON for Debugging

Decision: JSONL files (user preference to avoid database bloat)

Location: /var/log/spideriq/vayapin-export.jsonl

Benefits: - No database storage overhead - Easy to grep/analyze offline - Can be rotated/archived separately - Standard log location

JSONL Format:

{"timestamp": "2026-02-28T10:30:00Z", "job_id": "abc123", "source": "campaign", "source_id": 456, "request": {...}, "response": {...}, "status": "exported"}

Changes:

3.1 Create logging utility - app/services/vayapin_logger.py

import json
from datetime import datetime
from pathlib import Path

LOG_PATH = Path("/var/log/spideriq/vayapin-export.jsonl")

def log_vayapin_export(job_id: str, source: str, source_id: int, request: dict, response: dict, status: str):
    """Append export record to JSONL file."""
    LOG_PATH.parent.mkdir(parents=True, exist_ok=True)

    record = {
        "timestamp": datetime.utcnow().isoformat() + "Z",
        "job_id": job_id,
        "source": source,
        "source_id": source_id,
        "request": request,
        "response": response,
        "status": status,
    }

    with open(LOG_PATH, "a") as f:
        f.write(json.dumps(record) + "\n")

def read_recent_logs(limit: int = 100, job_id: str = None) -> list:
    """Read recent log entries (for UI display)."""
    if not LOG_PATH.exists():
        return []

    # Read last N lines efficiently
    lines = []
    with open(LOG_PATH, "rb") as f:
        # Seek to end and read backwards
        ...
    return lines

3.2 Add API endpoint - app/api/v1/admin_vayapin.py

@router.get("/logs")
async def get_export_logs(
    limit: int = Query(50, ge=1, le=200),
    job_id: Optional[str] = Query(None),
    _: bool = Depends(require_admin)
):
    """Read recent VayaPin export logs from JSONL file."""
    from app.services.vayapin_logger import read_recent_logs
    return {"logs": read_recent_logs(limit=limit, job_id=job_id)}

3.3 Update export logic - app/api/v1/admin_vayapin.py and app/workflows/functions/scheduled.py

from app.services.vayapin_logger import log_vayapin_export

# After building payload, before dispatch:
# (request is logged)

# After job completes (in callback):
log_vayapin_export(
    job_id=job_id,
    source=payload.get("source_type"),
    source_id=payload.get("source_id"),
    request=payload,
    response=result,
    status="exported" if result.get("exported") else "skipped"
)

3.4 Add Docker volume - docker-compose.yml

api-gateway:
  volumes:
    - vayapin-logs:/var/log/spideriq

volumes:
  vayapin-logs:

3.5 Add log rotation - /etc/logrotate.d/vayapin (via Ansible)

/var/log/spideriq/vayapin-export.jsonl {
    daily
    rotate 7
    compress
    missingok
    notifempty
}

3.6 Add to UI - apps/web/src/routes/admin/vayapin.tsx - Add "Export Logs" tab or section - Table showing recent logs with expandable JSON - Filter by job_id for debugging specific exports


Problem: No UI currently shows the exported leads list with search.

Changes:

4.1 Add search parameter - app/api/v1/admin_vayapin.py

@router.get("/exported")
async def get_exported_leads(
    search: Optional[str] = Query(None, description="Search by business name, domain, or PIN name"),
    ...existing params...
):

4.2 Update service - app/services/vayapin_export_service.py

async def get_exported_leads(self, search: str = None, ...):
    # Add WHERE clause:
    # business_name ILIKE '%search%'
    # OR domain ILIKE '%search%'
    # OR vayapin_pin_name ILIKE '%search%'

4.3 Add hook parameter - apps/web/src/hooks/useVayapin.ts

interface LeadsQuery {
  page?: number;
  page_size?: number;
  source?: string;
  status?: string;
  search?: string;  // ADD
}

4.4 Add Exported Leads tab in UI - apps/web/src/routes/admin/vayapin.tsx - Table columns: Business Name, Domain, PIN Name, Account ID, Status, Skip Reason, Exported At, Actions - Search input above table - Status filter dropdown (exported, skipped, failed, queued) - Source filter (campaign, playbook) - Expandable row showing SEO content preview and request/response JSON - Click on PIN name to copy


Files to Create/Modify

New Files

File Purpose
app/database/migrations/061_vayapin_enhancements.sql Add show_unverified_emails config column
app/services/vayapin_logger.py JSONL logging utility for request/response

Files to Modify

File Changes
app/api/v1/admin_vayapin.py Add /leads/{source}/{lead_id}/export, /logs, add search param
app/services/vayapin_export_service.py Add export_single_lead(), update queries for unverified, add search
app/schemas/vayapin_export.py Add new fields to schemas
app/workflows/functions/scheduled.py Call logger after building payload
app/workflows/functions/standalone.py Call logger with response in callback
apps/web/src/hooks/useVayapin.ts Add useExportSingleLead, useVayapinLogs, search param, new types
apps/web/src/routes/admin/vayapin.tsx Add tabbed UI, pending leads table, exported leads table with search, logs viewer
docker-compose.yml Add volume mount for /var/log/spideriq
CHANGELOG.md Document enhancements

UI Design

Tab Structure

┌─────────────────────────────────────────────────────────────────┐
│  [Overview]  [Pending Leads]  [Exported Leads]  [Export Logs]   │
└─────────────────────────────────────────────────────────────────┘

Overview Tab (Current)

  • KPI Cards
  • Config Summary
  • Export Run History

Pending Leads Tab (New)

┌─────────────────────────────────────────────────────────────────┐
│  Filter: [All ▾] [Campaign ▾] [Playbook ▾]   [🔄 Refresh]      │
├─────────────────────────────────────────────────────────────────┤
│  BUSINESS NAME    DOMAIN           EMAILS  STATUS   ACTIONS    │
│  ─────────────────────────────────────────────────────────────  │
│  Acme Corp        acme.com         3       ✓ Verified  [Export]│
│  Beta Inc         beta.io          2       ⚠ Unverified[Export]│
│  ...                                                            │
└─────────────────────────────────────────────────────────────────┘

Exported Leads Tab (New)

┌─────────────────────────────────────────────────────────────────┐
│  🔍 [Search by name, domain, PIN...]                            │
│  Status: [All ▾]  Source: [All ▾]                              │
├─────────────────────────────────────────────────────────────────┤
│  BUSINESS    PIN NAME    STATUS     EXPORTED AT    ACTIONS     │
│  ─────────────────────────────────────────────────────────────  │
│  Acme Corp   DK:ACME     exported   2h ago         [▼ Details] │
│  Beta Inc    --          skipped    1h ago         [▼ Details] │
│  ...                                                            │
├─────────────────────────────────────────────────────────────────┤
│  ▼ Expanded Details:                                            │
│  ┌─────────────────────────────────────────────────────────────┐│
│  │ PIN: DK:ACME  Account: acc_123  Subscription: sub_456      ││
│  │ SEO Preview: "Professional services company..."            ││
│  └─────────────────────────────────────────────────────────────┘│
└─────────────────────────────────────────────────────────────────┘

Export Logs Tab (New)

┌─────────────────────────────────────────────────────────────────┐
│  🔍 [Filter by Job ID...]                     [🔄 Refresh]     │
├─────────────────────────────────────────────────────────────────┤
│  TIMESTAMP          JOB ID      SOURCE    STATUS    ACTIONS    │
│  ─────────────────────────────────────────────────────────────  │
│  2026-02-28 10:30   abc123      campaign  exported  [▼ JSON]   │
│  2026-02-28 10:25   def456      playbook  skipped   [▼ JSON]   │
│  ...                                                            │
├─────────────────────────────────────────────────────────────────┤
│  ▼ Expanded JSON:                                               │
│  ┌─────────────────────────────────────────────────────────────┐│
│  │ Request:                                                    ││
│  │ {                                                           ││
│  │   "business_name": "Acme Corp",                            ││
│  │   "country_code": "DK",                                    ││
│  │   ...                                                       ││
│  │ }                                                           ││
│  │                                                             ││
│  │ Response:                                                   ││
│  │ {                                                           ││
│  │   "exported": true,                                         ││
│  │   "data": { "pin_name": "DK:ACME", ... }                   ││
│  │ }                                                           ││
│  └─────────────────────────────────────────────────────────────┘│
└─────────────────────────────────────────────────────────────────┘

Verification

1. Run Migration

docker exec spideriq-postgres psql -U spideriq -d spideriq \
  -f /migrations/061_vayapin_enhancements.sql

2. Check New Columns

docker exec spideriq-postgres psql -U spideriq -d spideriq -c "
  SELECT column_name FROM information_schema.columns
  WHERE table_name = 'campaign_workflow_jobs'
  AND column_name LIKE 'vayapin%';"

3. Rebuild API Gateway

docker compose build api-gateway && docker compose up -d api-gateway

4. Test Single Lead Export (Dry Run)

curl -X POST "https://spideriq.ai/api/v1/admin/vayapin/leads/campaign/123/export?dry_run=true" \
  -H "Authorization: Bearer $ADMIN_TOKEN"
curl "https://spideriq.ai/api/v1/admin/vayapin/exported?search=acme" \
  -H "Authorization: Bearer $ADMIN_TOKEN"

6. Rebuild Dashboard

cd apps/web && npm run build
docker compose up -d --build dashboard

7. Verify UI

  • Navigate to /admin/vayapin
  • Click "Pending Leads" tab - should show leads
  • Click "Export" on a lead - should trigger export
  • Click "Exported Leads" tab - should show exports with search
  • Expand a row - should show request/response JSON

8. Verify JSONL Log File

# Check log file exists
ls -la /var/log/spideriq/vayapin-export.jsonl

# View recent entries
tail -5 /var/log/spideriq/vayapin-export.jsonl | jq .

# Search for specific job
grep "abc123" /var/log/spideriq/vayapin-export.jsonl | jq .

9. Test Logs API Endpoint

curl "https://spideriq.ai/api/v1/admin/vayapin/logs?limit=10" \
  -H "Authorization: Bearer $ADMIN_TOKEN" | jq .

Configuration Defaults (Updated)

Setting Default Description
enabled false Auto-export disabled for testing
rate_per_hour 100 Max exports per hour
batch_size 10 Leads per scheduler run
require_deliverable_email true Only export with deliverable email
require_markdown_content true Only export with markdown content
show_unverified_emails false Show leads with unverified emails in pending