File size: 6,637 Bytes
945e815 5f5514e 945e815 5f5514e 945e815 5f5514e 383d246 945e815 383d246 5f5514e 945e815 5f5514e 945e815 5f5514e 945e815 5f5514e 945e815 5f5514e 945e815 5f5514e 383d246 5f5514e 383d246 5f5514e 945e815 5f5514e 945e815 5f5514e 945e815 5f5514e 383d246 5f5514e 383d246 945e815 5f5514e 945e815 5f5514e 383d246 5f5514e 945e815 5f5514e 945e815 5f5514e 945e815 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 | 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>
);
}
|