diff --git a/frontend/src/app/terminal/radar/page.tsx b/frontend/src/app/terminal/radar/page.tsx index 4c61689..330cca0 100644 --- a/frontend/src/app/terminal/radar/page.tsx +++ b/frontend/src/app/terminal/radar/page.tsx @@ -295,9 +295,9 @@ export default function RadarPage() { - {/* Registrar Info */} + {/* Registrar Info for taken domains */} {!searchResult.is_available && searchResult.registrar && ( -

+

Registered with {searchResult.registrar}

)} @@ -308,10 +308,10 @@ export default function RadarPage() { onClick={handleAddToWatchlist} disabled={addingToWatchlist} className={clsx( - "py-3 text-sm font-medium transition-colors flex items-center justify-center gap-2", + "flex-1 py-3 text-sm font-medium transition-colors flex items-center justify-center gap-2", searchResult.is_available - ? "flex-1 border border-white/20 text-white/80 hover:bg-white/5" - : "flex-1 border border-accent/30 text-accent hover:bg-accent/10" + ? "border border-white/20 text-white/80 hover:bg-white/5" + : "border border-accent/30 text-accent hover:bg-accent/10" )} > {addingToWatchlist ? : } diff --git a/frontend/src/app/terminal/watchlist/page.tsx b/frontend/src/app/terminal/watchlist/page.tsx index 4c2cd08..03bc6ec 100755 --- a/frontend/src/app/terminal/watchlist/page.tsx +++ b/frontend/src/app/terminal/watchlist/page.tsx @@ -15,14 +15,19 @@ 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 @@ -41,12 +46,26 @@ function formatExpiryDate(expirationDate: string | null): string { return new Date(expirationDate).toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' }) } -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' }, +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' }, } // ============================================================================ @@ -102,10 +121,10 @@ export default function WatchlistPage() { setAdding(true) try { await addDomain(newDomain.trim()) - showToast(`Added ${newDomain.trim()} to watchlist`, 'success') + showToast(`Target locked: ${newDomain.trim()}`, 'success') setNewDomain('') } catch (err: any) { - showToast(err.message || 'Failed to add domain', 'error') + showToast(err.message || 'Failed', 'error') } finally { setAdding(false) } @@ -115,18 +134,18 @@ export default function WatchlistPage() { setRefreshingId(id) try { await refreshDomain(id) - showToast('Domain refreshed', 'success') - } catch { showToast('Refresh failed', 'error') } + showToast('Intel updated', 'success') + } catch { showToast('Update failed', 'error') } finally { setRefreshingId(null) } }, [refreshDomain, showToast]) const handleDelete = useCallback(async (id: number, name: string) => { - if (!confirm(`Remove ${name} from watchlist?`)) return + if (!confirm(`Drop target: ${name}?`)) return setDeletingId(id) try { await deleteDomain(id) - showToast('Domain removed', 'success') - } catch { showToast('Failed to remove', 'error') } + showToast('Target dropped', 'success') + } catch { showToast('Failed', 'error') } finally { setDeletingId(null) } }, [deleteDomain, showToast]) @@ -135,8 +154,8 @@ export default function WatchlistPage() { try { await api.updateDomainNotify(id, !current) updateDomain(id, { notify_on_available: !current }) - showToast(!current ? 'Notifications enabled' : 'Notifications disabled', 'success') - } catch { showToast('Failed to update', 'error') } + showToast(!current ? 'Alerts armed' : 'Alerts disarmed', 'success') + } catch { showToast('Failed', 'error') } finally { setTogglingNotifyId(null) } }, [updateDomain, showToast]) @@ -146,13 +165,11 @@ export default function WatchlistPage() { try { const report = await api.getDomainHealth(id, { refresh: true }) setHealthReports(prev => ({ ...prev, [id]: report })) - } catch (err) { - console.error('Health check failed:', err) - } + } catch {} finally { setLoadingHealth(prev => ({ ...prev, [id]: false })) } }, [loadingHealth]) - // Load health on mount + // Load health useEffect(() => { const load = async () => { if (!domains?.length) return @@ -173,21 +190,25 @@ export default function WatchlistPage() { {toast && } {/* ═══════════════════════════════════════════════════════════════════════ */} - {/* HEADER */} + {/* HEADER - Compact */} {/* ═══════════════════════════════════════════════════════════════════════ */}
+ + {/* Left */}
Domain Surveillance
+

Watchlist {stats.total}

+ {/* Right: Stats */}
{stats.available}
@@ -202,41 +223,28 @@ export default function WatchlistPage() {
{/* ═══════════════════════════════════════════════════════════════════════ */} - {/* ADD DOMAIN - Radar Style */} + {/* ADD DOMAIN */} {/* ═══════════════════════════════════════════════════════════════════════ */}
-
-
- -
-
- - - 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" - /> - -
-
+
+
+ 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" + /> +
-
+
{/* ═══════════════════════════════════════════════════════════════════════ */} @@ -266,19 +274,30 @@ export default function WatchlistPage() { {/* ═══════════════════════════════════════════════════════════════════════ */} - {/* DOMAIN LIST - Radar Style Cards */} + {/* TABLE */} {/* ═══════════════════════════════════════════════════════════════════════ */}
{!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' @@ -288,107 +307,127 @@ export default function WatchlistPage() { return (
- {/* Card Header */} -
-
-
- {domain.name} - - - -
- - {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 - )} -
+ {/* Mobile */} +
+
+
+
+ {domain.name}
- - {/* Right: Actions */} -
- {/* Notify Toggle */} - - - {/* Refresh */} -
+ +
+ {formatExpiryDate(domain.expiration_date)} +
+ - - {/* Delete */} -
+ + {/* Desktop */} +
+ {/* Domain */} +
+
+ {domain.name} + + + +
+ + {/* Status */} +
+ + {domain.is_available ? 'Available' : 'Taken'} + +
+ + {/* Health */} + + + {/* Expires */} +
+ {days !== null && days <= 30 && days > 0 ? ( + {days} days + ) : ( + formatExpiryDate(domain.expiration_date) + )} +
+ + {/* Alert */} + + + {/* Actions */} +
+ + +
+
) })} @@ -397,86 +436,89 @@ export default function WatchlistPage() {
{/* ═══════════════════════════════════════════════════════════════════════ */} - {/* HEALTH MODAL - Radar Style */} + {/* HEALTH MODAL */} {/* ═══════════════════════════════════════════════════════════════════════ */} {selectedDomainData && ( -
setSelectedDomain(null)}> -
e.stopPropagation()}> -
+
setSelectedDomain(null)} + > +
e.stopPropagation()} + > + {/* Header */} +
+
+ + Health Report +
+ +
-
- {/* Header */} -
- - - Health Report - - + {/* Content */} +
+ {/* Domain */} +
+

{selectedDomainData.name}

+
+ + {healthConfig[selectedHealth?.status || 'unknown'].label} + + {selectedHealth?.score !== undefined && ( + Score: {selectedHealth.score}/100 + )} +
-
- {/* 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 ?? selectedHealth.dns?.has_ns }, + { 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 ? ( + + ) : check.value === false ? ( + + ) : ( + Unknown + )} +
+ ))}
- - {/* Checks */} - {selectedHealth ? ( -
- {[ - { label: 'DNS Resolution', value: selectedHealth.dns?.has_a ?? selectedHealth.dns?.has_ns }, - { 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 ? ( - - ) : check.value === false ? ( - - ) : ( - Unknown - )} -
- ))} -
+ ) : ( +
+ Click the button below to run a health check +
+ )} + + {/* Refresh Button */} + -
+