| import React, { useState, useEffect } from 'react'; |
| import { useToast } from '../components/ui/index'; |
|
|
| export default function SecurityPage() { |
| const [s, setS] = useState<any>({}); |
| const [wl, setWl] = useState<any[]>([]); |
| const [anomalies, setAnomalies] = useState<any[]>([]); |
| const [addr, setAddr] = useState(''); const [label, setLabel] = useState(''); |
| const [err, setErr] = useState(''); |
| const { addToast } = useToast(); |
|
|
| useEffect(() => { load(); }, []); |
| const load = async () => { if (!window.solvox) return; try { const [a, b, c] = await Promise.all([window.solvox.security.getSettings(), window.solvox.security.getWhitelist(), window.solvox.security.getAnomalies()]); setS(a); setWl(b); setAnomalies(c); } catch {} }; |
| const upd = async (k: string, v: any) => { const n = { ...s, [k]: v }; setS(n); if (window.solvox) { await window.solvox.security.updateSettings(n); addToast({ type: 'success', title: 'Saved' }); } }; |
| const addWl = async () => { if (!addr.trim() || !label.trim()) return setErr('Both required'); if (window.solvox) { const r = await window.solvox.security.addWhitelist(addr.trim(), label.trim()); if (r.success) { setAddr(''); setLabel(''); setErr(''); load(); } else setErr(r.error || 'Failed'); } }; |
| const rmWl = async (a: string) => { if (window.solvox) { await window.solvox.security.removeWhitelist(a); load(); } }; |
|
|
| const Toggle = ({ k, label, desc }: { k: string; label: string; desc: string }) => ( |
| <div className="flex items-center justify-between py-3 border-b border-hairline-soft last:border-0"> |
| <div><div className="text-title-sm text-ink">{label}</div><div className="text-caption text-muted">{desc}</div></div> |
| <button onClick={() => upd(k, !s[k])} className="toggle"> |
| <div className={`toggle-track ${s[k] ? 'active' : ''}`} /> |
| <div className={`toggle-thumb ${s[k] ? 'active' : ''}`} /> |
| </button> |
| </div> |
| ); |
|
|
| return ( |
| <div className="max-w-3xl mx-auto px-8 py-section"> |
| <h2 className="display-text text-title-lg text-ink mb-8">Security</h2> |
| |
| {/* Limits */} |
| <div className="card mb-6"> |
| <div className="text-caption-strong text-muted uppercase tracking-wider mb-4">Transaction limits</div> |
| <div className="grid grid-cols-2 gap-4"> |
| {[ |
| { k: 'maxSingleTx', l: 'Max per transaction', u: 'tokens', v: s.maxSingleTx || 1000 }, |
| { k: 'maxDailyVolume', l: 'Daily limit', u: '/day', v: s.maxDailyVolume || 5000 }, |
| { k: 'velocityLimit', l: 'Max TX/hour', u: '', v: s.velocityLimit || 10 }, |
| { k: 'cooldownMinutes', l: 'Cooldown', u: 'min', v: s.cooldownMinutes || 1 }, |
| ].map(f => ( |
| <div key={f.k}> |
| <label className="text-caption text-muted block mb-1">{f.l}</label> |
| <div className="flex items-center gap-2"> |
| <input type="number" value={f.v} onChange={e => upd(f.k, Number(e.target.value))} className="input-field text-body-sm" /> |
| {f.u && <span className="text-caption text-muted whitespace-nowrap">{f.u}</span>} |
| </div> |
| </div> |
| ))} |
| </div> |
| </div> |
| |
| {/* Toggles */} |
| <div className="card mb-6"> |
| <div className="text-caption-strong text-muted uppercase tracking-wider mb-4">Features</div> |
| <Toggle k="whitelistEnabled" label="Address whitelisting" desc="Only allow sends to approved addresses" /> |
| <Toggle k="anomalyDetection" label="AI anomaly detection" desc="LLM analyzes spending patterns locally" /> |
| <Toggle k="requireConfirmation" label="Transaction confirmation" desc="Always require explicit approval" /> |
| </div> |
| |
| {/* Whitelist */} |
| <div className="card mb-6"> |
| <div className="text-caption-strong text-muted uppercase tracking-wider mb-3">Address whitelist</div> |
| <div className="flex gap-2 mb-3"> |
| <input value={label} onChange={e => setLabel(e.target.value)} placeholder="Label" className="input-field text-body-sm w-28" /> |
| <input value={addr} onChange={e => setAddr(e.target.value)} placeholder="Solana address" className="input-field text-body-sm flex-1 font-mono" /> |
| <button onClick={addWl} className="btn-primary text-body-sm py-2 px-4">Add</button> |
| </div> |
| {err && <div className="text-caption text-semantic-down mb-2">{err}</div>} |
| {wl.length === 0 ? <div className="text-body-sm text-muted text-center py-4">No addresses whitelisted</div> : ( |
| <div className="space-y-2"> |
| {wl.map((e, i) => ( |
| <div key={i} className="flex items-center justify-between bg-surface-soft rounded-lg p-3"> |
| <div><div className="text-title-sm text-ink">{e.label}</div><div className="text-caption font-mono text-muted">{e.address.slice(0, 10)}…{e.address.slice(-6)}</div></div> |
| <button onClick={() => rmWl(e.address)} className="btn-text text-semantic-down text-body-sm">Remove</button> |
| </div> |
| ))} |
| </div> |
| )} |
| </div> |
| |
| {/* Anomalies */} |
| <div className="card"> |
| <div className="flex items-center justify-between mb-3"> |
| <div className="text-caption-strong text-muted uppercase tracking-wider">Anomaly log</div> |
| <span className="badge-pill-blue badge-pill text-[10px]">AI-POWERED</span> |
| </div> |
| {anomalies.length === 0 ? ( |
| <div className="text-center py-6"> |
| <div className="text-body-sm text-semantic-up">All clear — no anomalies detected ✓</div> |
| <div className="text-caption text-muted mt-1">Analyzed by @qvac/llm-llamacpp + @qvac/embed-llamacpp</div> |
| </div> |
| ) : ( |
| <div className="space-y-2"> |
| {anomalies.map((a, i) => ( |
| <div key={i} className={`rounded-lg p-3 border ${a.severity === 'high' ? 'bg-semantic-down/5 border-semantic-down/15' : a.severity === 'medium' ? 'bg-accent-yellow/5 border-accent-yellow/15' : 'bg-surface-soft border-hairline'}`}> |
| <div className="flex items-center gap-2 mb-1"> |
| <span className={`badge-pill text-[10px] ${a.severity === 'high' ? 'badge-pill-red' : ''}`}>{a.severity.toUpperCase()}</span> |
| <span className="text-caption text-muted">{a.type}</span> |
| </div> |
| <div className="text-body-sm text-ink">{a.description}</div> |
| <div className="text-caption text-muted-soft mt-1">{new Date(a.timestamp).toLocaleString()}</div> |
| </div> |
| ))} |
| </div> |
| )} |
| </div> |
| </div> |
| ); |
| } |
|
|