From e737de6ff51870074e68cd1a9dbd6089dbe0488b Mon Sep 17 00:00:00 2001 From: Yves Gugger Date: Fri, 12 Dec 2025 22:08:07 +0100 Subject: [PATCH] Radar search: track taken domains, Watchlist: radar style cards, fixed health check --- frontend/src/app/terminal/radar/page.tsx | 45 +- frontend/src/app/terminal/watchlist/page.tsx | 464 +++++++++---------- 2 files changed, 241 insertions(+), 268 deletions(-) diff --git a/frontend/src/app/terminal/radar/page.tsx b/frontend/src/app/terminal/radar/page.tsx index 9a82dd3..4c61689 100644 --- a/frontend/src/app/terminal/radar/page.tsx +++ b/frontend/src/app/terminal/radar/page.tsx @@ -295,17 +295,29 @@ export default function RadarPage() { - {/* Available Actions */} - {searchResult.is_available && ( -
- + {/* Registrar Info */} + {!searchResult.is_available && searchResult.registrar && ( +

+ Registered with {searchResult.registrar} +

+ )} + + {/* Actions - Always show */} +
+ + {searchResult.is_available && ( Register Now -
- )} - - {/* Taken Info */} - {!searchResult.is_available && searchResult.registrar && ( -

- Registered with {searchResult.registrar} -

- )} + )} +
)} diff --git a/frontend/src/app/terminal/watchlist/page.tsx b/frontend/src/app/terminal/watchlist/page.tsx index 7d355b1..4c2cd08 100755 --- a/frontend/src/app/terminal/watchlist/page.tsx +++ b/frontend/src/app/terminal/watchlist/page.tsx @@ -15,19 +15,14 @@ import { Eye, X, Activity, - AlertTriangle, ArrowRight, CheckCircle2, XCircle, Target, - Globe, ExternalLink, - Calendar, - Shield, Crosshair } from 'lucide-react' import clsx from 'clsx' -import Link from 'next/link' // ============================================================================ // HELPERS @@ -46,26 +41,12 @@ function formatExpiryDate(expirationDate: string | null): string { return new Date(expirationDate).toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' }) } -function getTimeAgo(date: string | null): string { - if (!date) return 'Never' - const now = new Date() - const past = new Date(date) - const diffMs = now.getTime() - past.getTime() - const diffMins = Math.floor(diffMs / 60000) - const diffHours = Math.floor(diffMs / 3600000) - const diffDays = Math.floor(diffMs / 86400000) - if (diffMins < 1) return 'Just now' - if (diffMins < 60) return `${diffMins}m ago` - if (diffHours < 24) return `${diffHours}h ago` - return `${diffDays}d ago` -} - -const healthConfig: Record = { - healthy: { label: 'Healthy', color: 'text-accent', bg: 'bg-accent/10 border-accent/20' }, - weakening: { label: 'Weak', color: 'text-amber-400', bg: 'bg-amber-500/10 border-amber-500/20' }, - parked: { label: 'Parked', color: 'text-blue-400', bg: 'bg-blue-500/10 border-blue-500/20' }, - critical: { label: 'Critical', color: 'text-rose-400', bg: 'bg-rose-500/10 border-rose-500/20' }, - unknown: { label: 'Unknown', color: 'text-white/40', bg: 'bg-white/5 border-white/10' }, +const healthConfig: Record = { + healthy: { label: 'Healthy', color: 'text-accent', bgClass: 'bg-accent/10' }, + weakening: { label: 'Weak', color: 'text-amber-400', bgClass: 'bg-amber-500/10' }, + parked: { label: 'Parked', color: 'text-blue-400', bgClass: 'bg-blue-500/10' }, + critical: { label: 'Critical', color: 'text-rose-400', bgClass: 'bg-rose-500/10' }, + unknown: { label: 'Unknown', color: 'text-white/40', bgClass: 'bg-white/5' }, } // ============================================================================ @@ -121,10 +102,10 @@ export default function WatchlistPage() { setAdding(true) try { await addDomain(newDomain.trim()) - showToast(`Target locked: ${newDomain.trim()}`, 'success') + showToast(`Added ${newDomain.trim()} to watchlist`, 'success') setNewDomain('') } catch (err: any) { - showToast(err.message || 'Failed', 'error') + showToast(err.message || 'Failed to add domain', 'error') } finally { setAdding(false) } @@ -134,18 +115,18 @@ export default function WatchlistPage() { setRefreshingId(id) try { await refreshDomain(id) - showToast('Intel updated', 'success') - } catch { showToast('Update failed', 'error') } + showToast('Domain refreshed', 'success') + } catch { showToast('Refresh failed', 'error') } finally { setRefreshingId(null) } }, [refreshDomain, showToast]) const handleDelete = useCallback(async (id: number, name: string) => { - if (!confirm(`Drop target: ${name}?`)) return + if (!confirm(`Remove ${name} from watchlist?`)) return setDeletingId(id) try { await deleteDomain(id) - showToast('Target dropped', 'success') - } catch { showToast('Failed', 'error') } + showToast('Domain removed', 'success') + } catch { showToast('Failed to remove', 'error') } finally { setDeletingId(null) } }, [deleteDomain, showToast]) @@ -154,8 +135,8 @@ export default function WatchlistPage() { try { await api.updateDomainNotify(id, !current) updateDomain(id, { notify_on_available: !current }) - showToast(!current ? 'Alerts armed' : 'Alerts disarmed', 'success') - } catch { showToast('Failed', 'error') } + showToast(!current ? 'Notifications enabled' : 'Notifications disabled', 'success') + } catch { showToast('Failed to update', 'error') } finally { setTogglingNotifyId(null) } }, [updateDomain, showToast]) @@ -165,11 +146,13 @@ export default function WatchlistPage() { try { const report = await api.getDomainHealth(id, { refresh: true }) setHealthReports(prev => ({ ...prev, [id]: report })) - } catch {} + } catch (err) { + console.error('Health check failed:', err) + } finally { setLoadingHealth(prev => ({ ...prev, [id]: false })) } }, [loadingHealth]) - // Load health + // Load health on mount useEffect(() => { const load = async () => { if (!domains?.length) return @@ -190,25 +173,21 @@ export default function WatchlistPage() { {toast && } {/* ═══════════════════════════════════════════════════════════════════════ */} - {/* HEADER - Compact */} + {/* HEADER */} {/* ═══════════════════════════════════════════════════════════════════════ */}
- - {/* Left */}
Domain Surveillance
-

Watchlist {stats.total}

- {/* Right: Stats */}
{stats.available}
@@ -223,28 +202,41 @@ export default function WatchlistPage() {
{/* ═══════════════════════════════════════════════════════════════════════ */} - {/* ADD DOMAIN */} + {/* ADD DOMAIN - Radar Style */} {/* ═══════════════════════════════════════════════════════════════════════ */}
-
-
- setNewDomain(e.target.value)} - placeholder="Add domain to watch..." - className="flex-1 bg-transparent px-4 py-3 text-sm text-white placeholder:text-white/25 outline-none" - /> - +
+
+ +
+
+ + + Add Domain + +
+ + +
+ setNewDomain(e.target.value)} + placeholder="example.com" + className="flex-1 bg-black/30 border border-white/10 px-4 py-3 text-white placeholder:text-white/20 outline-none focus:border-accent/40 transition-colors" + /> + +
+
- +
{/* ═══════════════════════════════════════════════════════════════════════ */} @@ -274,30 +266,19 @@ export default function WatchlistPage() { {/* ═══════════════════════════════════════════════════════════════════════ */} - {/* TABLE */} + {/* DOMAIN LIST - Radar Style Cards */} {/* ═══════════════════════════════════════════════════════════════════════ */}
{!filteredDomains.length ? (
-
+

No domains in your watchlist

Add a domain above to start monitoring

) : ( -
- {/* Table Header */} -
-
Domain
-
Status
-
Health
-
Expires
-
Alert
-
Actions
-
- - {/* Rows */} +
{filteredDomains.map((domain) => { const health = healthReports[domain.id] const healthStatus = health?.status || 'unknown' @@ -307,125 +288,105 @@ export default function WatchlistPage() { return (
- {/* Mobile */} -
-
-
-
- {domain.name} -
- - {domain.is_available ? 'Available' : 'Taken'} - -
- -
- {formatExpiryDate(domain.expiration_date)} -
- - -
-
-
- - {/* Desktop */} -
- {/* Domain */} -
+ {/* Card Header */} +
+
- {domain.name} + {domain.name} - +
- - {/* Status */} -
- - {domain.is_available ? 'Available' : 'Taken'} - -
- - {/* Health */} - - - {/* Expires */} -
- {days !== null && days <= 30 && days > 0 ? ( - {days} days - ) : ( - formatExpiryDate(domain.expiration_date) - )} -
- - {/* Alert */} - - - {/* Actions */} -
- - + + {domain.is_available ? 'Available' : 'Taken'} + +
+ + {/* Card Body */} +
+
+ {/* Left: Info */} +
+ {/* Health */} + + + {/* Expiry */} +
+ {days !== null && days <= 30 && days > 0 ? ( + {days} days left + ) : domain.expiration_date ? ( + Expires {formatExpiryDate(domain.expiration_date)} + ) : ( + No expiry data + )} +
+
+ + {/* Right: Actions */} +
+ {/* Notify Toggle */} + + + {/* Refresh */} + + + {/* Delete */} + +
@@ -436,79 +397,86 @@ export default function WatchlistPage() {
{/* ═══════════════════════════════════════════════════════════════════════ */} - {/* HEALTH MODAL */} + {/* HEALTH MODAL - Radar Style */} {/* ═══════════════════════════════════════════════════════════════════════ */} {selectedDomainData && (
setSelectedDomain(null)}> -
e.stopPropagation()}> - {/* Corner Decorations */} -
-
-
-
+
e.stopPropagation()}> +
-
+
{/* Header */} -
-
- - Health Report -
+
+ + + Health Report +
- {/* Domain */} -
-

{selectedDomainData.name}

-
- - {healthConfig[selectedHealth?.status || 'unknown'].label} - +
+ {/* Domain */} +
+

{selectedDomainData.name}

+
+ + {healthConfig[selectedHealth?.status || 'unknown'].label} + + {selectedHealth?.score !== undefined && ( + Score: {selectedHealth.score}/100 + )} +
-
- - {/* Checks */} - {selectedHealth && ( -
- {[ - { label: 'DNS Resolution', value: selectedHealth.dns?.has_a }, - { label: 'HTTP Reachable', value: selectedHealth.http?.is_reachable }, - { label: 'SSL Certificate', value: selectedHealth.ssl?.has_certificate }, - { label: 'Not Parked', value: !selectedHealth.dns?.is_parked && !selectedHealth.http?.is_parked }, - ].map((check) => ( -
- {check.label} - {check.value ? ( - - ) : ( - - )} -
- ))} -
- )} - - {/* Refresh */} - + + {/* Refresh Button */} + +