From 25b3241bc5ceb5f6822fa898f2a92104d3a94095 Mon Sep 17 00:00:00 2001 From: BrianOG <74307391+xBrianOG@users.noreply.github.com> Date: Sun, 31 May 2026 13:54:52 +0200 Subject: [PATCH] feat: Localize tool panels (Crypto, QR, Metadata, MAC, Headers) --- frontend/src/components/Sidebar.tsx | 115 ++++++-------- frontend/src/components/tools/ToolPanels.tsx | 144 +++++++++--------- frontend/src/components/views/IdleView.tsx | 152 ++++++++++--------- frontend/src/messages/de.json | 128 ++++++++++++++-- frontend/src/messages/en.json | 128 ++++++++++++++-- frontend/src/messages/ru.json | 130 ++++++++++++++-- 6 files changed, 556 insertions(+), 241 deletions(-) diff --git a/frontend/src/components/Sidebar.tsx b/frontend/src/components/Sidebar.tsx index 54f0594..ffac3f3 100644 --- a/frontend/src/components/Sidebar.tsx +++ b/frontend/src/components/Sidebar.tsx @@ -14,27 +14,21 @@ const TYPE_COLOR: Record = { interface RecentScan { target: string; type: ScanType; ts: number; } -const TIPS = [ - 'Scan domains, IPs, emails, phones & usernames', - 'Toggle modules to customize each scan', - 'Download HTML reports from scan results', - 'Graph tab shows entity relationships', - 'Map tab shows GeoIP location data', - 'Use Dorks module to find exposed files', - 'OPSEC score rates target exposure risk', -]; - -function useTip() { - const [idx, setIdx] = useState(0); - const [visible, setVisible] = useState(true); - useEffect(() => { - const iv = setInterval(() => { - setVisible(false); - setTimeout(() => { setIdx(i => (i + 1) % TIPS.length); setVisible(true); }, 350); - }, 4000); - return () => clearInterval(iv); - }, []); - return { tip: TIPS[idx], visible }; +const SCAN_TYPES: ScanType[] = ['domain', 'ip', 'email', 'phone', 'username']; + +const MODULE_MAP: Record = { + domain: ['whois', 'dns', 'geoip', 'cert_transparency', 'website', 'wayback', 'shodan', 'virustotal', 'censys', 'onion'], + ip: ['geoip', 'shodan', 'virustotal', 'abuseipdb', 'censys'], + email: ['emailrep', 'smtp', 'leaks'], + phone: ['hlr'], + username: ['blackbird', 'maigret'], +}; + +interface Props { + onScan: (target: string, type: ScanType, modules: string[]) => void; + isRunning: boolean; + isOpen: boolean; + onClose: () => void; } function useRecentScans() { @@ -58,50 +52,27 @@ function useRecentScans() { return { recents, add, clear }; } -const MODULE_MAP: Record = { - domain: [ - { id: 'whois', label: 'WHOIS' }, { id: 'dns', label: 'DNS' }, - { id: 'geoip', label: 'GeoIP' }, { id: 'cert_transparency', label: 'Cert CT' }, - { id: 'website', label: 'Website' }, { id: 'wayback', label: 'Wayback' }, - { id: 'shodan', label: 'Shodan' }, { id: 'virustotal', label: 'VirusTotal' }, - { id: 'censys', label: 'Censys' }, { id: 'onion', label: 'Dark Web' }, - ], - ip: [ - { id: 'geoip', label: 'GeoIP' }, { id: 'shodan', label: 'Shodan' }, - { id: 'virustotal', label: 'VirusTotal' }, { id: 'abuseipdb', label: 'AbuseIPDB' }, - { id: 'censys', label: 'Censys' }, - ], - email: [ - { id: 'emailrep', label: 'Email Rep' }, { id: 'smtp', label: 'SMTP Verify' }, - { id: 'leaks', label: 'Breach Check' }, - ], - phone: [ - { id: 'hlr', label: 'Phone Lookup' }, - ], - username: [ - { id: 'blackbird', label: 'Blackbird' }, { id: 'maigret', label: 'Maigret' }, - ], -}; - -interface Props { - onScan: (target: string, type: ScanType, modules: string[]) => void; - isRunning: boolean; - isOpen: boolean; - onClose: () => void; -} - export function Sidebar({ onScan, isRunning, isOpen, onClose }: Props) { const { t } = useTranslations(); const [target, setTarget] = useState(''); const [scanType, setScanType] = useState('domain'); - const [modules, setModules] = useState(MODULE_MAP.domain.map(m => m.id)); + const [modules, setModules] = useState(MODULE_MAP.domain); const [showModules, setShowModules] = useState(false); - const { tip, visible } = useTip(); + const [tipIdx, setTipIdx] = useState(0); + const [tipVisible, setTipVisible] = useState(true); const { recents, add: addRecent, clear: clearRecents } = useRecentScans(); - const handleTypeChange = (t: ScanType) => { - setScanType(t); - setModules(MODULE_MAP[t].map(m => m.id)); + useEffect(() => { + const iv = setInterval(() => { + setTipVisible(false); + setTimeout(() => { setTipIdx(i => (i + 1) % SCAN_TYPES.length); setTipVisible(true); }, 350); + }, 4000); + return () => clearInterval(iv); + }, []); + + const handleTypeChange = (type: ScanType) => { + setScanType(type); + setModules(MODULE_MAP[type]); }; const toggleModule = (id: string) => { @@ -121,6 +92,8 @@ export function Sidebar({ onScan, isRunning, isOpen, onClose }: Props) { handleTypeChange(r.type); }; + const tip = t(`sidebar.tips.${tipIdx}`) || t('sidebar.tips.0'); + return ( ); -} +} \ No newline at end of file diff --git a/frontend/src/components/tools/ToolPanels.tsx b/frontend/src/components/tools/ToolPanels.tsx index 3ac3570..ffd871c 100644 --- a/frontend/src/components/tools/ToolPanels.tsx +++ b/frontend/src/components/tools/ToolPanels.tsx @@ -1,6 +1,7 @@ 'use client'; import { useState, useCallback } from 'react'; import { Loader2, ArrowLeft, ExternalLink, Upload, XCircle, FileUp } from 'lucide-react'; +import { useTranslations } from '@/lib/i18n'; import * as api from '@/lib/api'; import type { ToolMode, CryptoResult, QrResult, HeaderAnalysisResult, MetaResult, MacResult } from '@/lib/types'; @@ -13,7 +14,7 @@ function Card({ title, children, className }: { title?: string; children: React. ); } -function ErrorCard({ message }: { message: string }) { +function ErrorCard({ label, message }: { label: string; message: string }) { return (
@@ -21,7 +22,7 @@ function ErrorCard({ message }: { message: string }) {
-
Error
+
{label}
{message}
@@ -54,6 +55,7 @@ function RunBtn({ loading, label, onClick, disabled }: { loading: boolean; label } function CryptoPanel() { + const { t } = useTranslations(); const [addr, setAddr] = useState(''); const [result, setResult] = useState(null); const [loading, setLoading] = useState(false); @@ -69,25 +71,25 @@ function CryptoPanel() { }; return (
- -
+ +
{result && ( result.error && !result.balance ? ( - + ) : ( - - - - - - - + + + + + + + {result.explorer_url && ( )} @@ -99,6 +101,7 @@ function CryptoPanel() { } function DropZone({ accept, file, onChange, hint }: { accept: string; file: File | null; onChange: (f: File | null) => void; hint?: string }) { + const { t } = useTranslations(); const [drag, setDrag] = useState(false); const onDrop = useCallback((e: React.DragEvent) => { e.preventDefault(); setDrag(false); @@ -119,11 +122,11 @@ function DropZone({ accept, file, onChange, hint }: { accept: string; file: File { file ? ( <>
{file.name}
-
{(file.size / 1024).toFixed(1)} KB · click to change
+
{(file.size / 1024).toFixed(1)} KB · {t('toolPanels.qr.clickToChange')}
) : ( <> -
Drop file here or browse
+
{t('toolPanels.qr.dropFile')} {t('toolPanels.qr.browse')}
{ hint &&
{hint}
} )} @@ -133,6 +136,7 @@ function DropZone({ accept, file, onChange, hint }: { accept: string; file: File } function QrPanel() { + const { t } = useTranslations(); const [file, setFile] = useState(null); const [result, setResult] = useState(null); const [loading, setLoading] = useState(false); @@ -148,21 +152,21 @@ function QrPanel() { }; return (
- +
- - + +
{result && ( {result.error ? ( - + ) : (
- +
- Content + {t('toolPanels.qr.content')} {result.is_url ? ( {result.decoded} ) : ( @@ -171,7 +175,7 @@ function QrPanel() {
{result.is_url && result.decoded && ( - Open URL + {t('toolPanels.qr.openUrl')} )}
@@ -183,6 +187,7 @@ function QrPanel() { } function MetadataPanel() { + const { t } = useTranslations(); const [file, setFile] = useState(null); const [result, setResult] = useState(null); const [loading, setLoading] = useState(false); @@ -198,54 +203,54 @@ function MetadataPanel() { }; return (
- +
- +
{result && (
{result.error ? ( - + ) : ( <> - - - - - - - + + + + + + + {result.timestamps && Object.keys(result.timestamps).length > 0 && ( - + {Object.entries(result.timestamps).map(([k, v]) => ( ))} )} {result.camera && Object.keys(result.camera).length > 0 && ( - + {Object.entries(result.camera).map(([k, v]) => ( ))} )} {result.gps && ( - - - - {result.gps.altitude && } + + + + {result.gps.altitude && } )} {result.exif && Object.keys(result.exif).length > 0 && ( - + {Object.entries(result.exif).slice(0, 30).map(([k, v]) => ( ))} @@ -260,6 +265,7 @@ function MetadataPanel() { } function MacPanel() { + const { t } = useTranslations(); const [addr, setAddr] = useState(''); const [result, setResult] = useState(null); const [loading, setLoading] = useState(false); @@ -275,16 +281,16 @@ function MacPanel() { }; return (
- -
+ +
{result && ( result.error ? ( - + ) : ( - - + + ) )} @@ -293,6 +299,7 @@ function MacPanel() { } function HeadersPanel() { + const { t } = useTranslations(); const [text, setText] = useState(''); const [result, setResult] = useState(null); const [loading, setLoading] = useState(false); @@ -308,42 +315,42 @@ function HeadersPanel() { }; return (
- +