Security (Desktop Wallet): - Implement BIP39 mnemonic generation with cryptographic RNG - Add Argon2id password-based key derivation (64MB, 3 iterations) - Add ChaCha20-Poly1305 authenticated encryption for seed storage - Add mnemonic auto-clear (60s timeout) and clipboard auto-clear (30s) - Add sanitized error logging to prevent credential leaks - Strengthen CSP with object-src, base-uri, form-action, frame-ancestors - Clear sensitive state on component unmount Explorer (Gas Estimator): - Add Gas Estimation page with from/to/amount/data inputs - Add bech32 address validation (synor1/tsynor1 prefix) - Add BigInt-based amount parsing to avoid floating point errors - Add production guard for mock mode (cannot enable in prod builds) Monitoring (30-day Testnet): - Add Prometheus config with 30-day retention - Add comprehensive alert rules for node health, consensus, network, mempool - Add Alertmanager with severity-based routing and inhibition rules - Add Grafana with auto-provisioned datasource and dashboard - Add Synor testnet dashboard with uptime SLA tracking Docker: - Update docker-compose.testnet.yml with monitoring profile - Fix node-exporter for macOS Docker Desktop compatibility - Change Grafana port to 3001 to avoid conflict
142 lines
4.6 KiB
TypeScript
142 lines
4.6 KiB
TypeScript
import { useEffect } from 'react';
|
|
import { Link } from 'react-router-dom';
|
|
import {
|
|
Send,
|
|
Download,
|
|
RefreshCw,
|
|
TrendingUp,
|
|
Wallet,
|
|
Activity,
|
|
} from 'lucide-react';
|
|
import { useWalletStore } from '../store/wallet';
|
|
|
|
export default function Dashboard() {
|
|
const { balance, addresses, networkStatus, refreshBalance } = useWalletStore();
|
|
|
|
// Refresh balance periodically
|
|
useEffect(() => {
|
|
refreshBalance();
|
|
const interval = setInterval(refreshBalance, 30000);
|
|
return () => clearInterval(interval);
|
|
}, [refreshBalance]);
|
|
|
|
const primaryAddress = addresses[0]?.address;
|
|
|
|
return (
|
|
<div className="space-y-6">
|
|
{/* Header */}
|
|
<div className="flex items-center justify-between">
|
|
<h1 className="text-2xl font-bold text-white">Dashboard</h1>
|
|
<button
|
|
onClick={() => refreshBalance()}
|
|
className="btn btn-ghost"
|
|
title="Refresh balance"
|
|
>
|
|
<RefreshCw size={18} />
|
|
</button>
|
|
</div>
|
|
|
|
{/* Balance card */}
|
|
<div className="card bg-gradient-to-br from-synor-600 to-synor-800 border-0">
|
|
<div className="flex items-start justify-between">
|
|
<div>
|
|
<p className="text-synor-200 text-sm mb-1">Total Balance</p>
|
|
<p className="text-4xl font-bold text-white mb-2">
|
|
{balance?.balanceHuman || '0 SYN'}
|
|
</p>
|
|
{balance?.pending ? (
|
|
<p className="text-synor-200 text-sm">
|
|
+{(balance.pending / 100_000_000).toFixed(8)} SYN pending
|
|
</p>
|
|
) : null}
|
|
</div>
|
|
<div className="w-12 h-12 rounded-xl bg-white/10 flex items-center justify-center">
|
|
<Wallet className="text-white" size={24} />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Quick actions */}
|
|
<div className="grid grid-cols-2 gap-4">
|
|
<Link
|
|
to="/send"
|
|
className="card hover:border-synor-500 hover:bg-gray-800/50 transition-all"
|
|
>
|
|
<div className="flex items-center gap-4">
|
|
<div className="w-12 h-12 rounded-lg bg-synor-600/20 text-synor-400 flex items-center justify-center">
|
|
<Send size={24} />
|
|
</div>
|
|
<div>
|
|
<h3 className="font-semibold text-white">Send</h3>
|
|
<p className="text-sm text-gray-400">Transfer SYN</p>
|
|
</div>
|
|
</div>
|
|
</Link>
|
|
|
|
<Link
|
|
to="/receive"
|
|
className="card hover:border-synor-500 hover:bg-gray-800/50 transition-all"
|
|
>
|
|
<div className="flex items-center gap-4">
|
|
<div className="w-12 h-12 rounded-lg bg-green-600/20 text-green-400 flex items-center justify-center">
|
|
<Download size={24} />
|
|
</div>
|
|
<div>
|
|
<h3 className="font-semibold text-white">Receive</h3>
|
|
<p className="text-sm text-gray-400">Get your address</p>
|
|
</div>
|
|
</div>
|
|
</Link>
|
|
</div>
|
|
|
|
{/* Network status */}
|
|
<div className="card">
|
|
<h3 className="font-semibold text-white mb-4 flex items-center gap-2">
|
|
<Activity size={18} />
|
|
Network Status
|
|
</h3>
|
|
<div className="grid grid-cols-2 gap-4">
|
|
<div>
|
|
<p className="text-sm text-gray-400">Status</p>
|
|
<p className="text-white">
|
|
{networkStatus.connected ? (
|
|
<span className="text-green-400">Connected</span>
|
|
) : (
|
|
<span className="text-red-400">Disconnected</span>
|
|
)}
|
|
</p>
|
|
</div>
|
|
<div>
|
|
<p className="text-sm text-gray-400">Network</p>
|
|
<p className="text-white capitalize">
|
|
{networkStatus.network || '-'}
|
|
</p>
|
|
</div>
|
|
<div>
|
|
<p className="text-sm text-gray-400">Block Height</p>
|
|
<p className="text-white">
|
|
{networkStatus.blockHeight?.toLocaleString() || '-'}
|
|
</p>
|
|
</div>
|
|
<div>
|
|
<p className="text-sm text-gray-400">Peers</p>
|
|
<p className="text-white">{networkStatus.peerCount ?? '-'}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Primary address */}
|
|
{primaryAddress && (
|
|
<div className="card">
|
|
<h3 className="font-semibold text-white mb-4 flex items-center gap-2">
|
|
<TrendingUp size={18} />
|
|
Primary Address
|
|
</h3>
|
|
<p className="font-mono text-sm text-gray-300 break-all bg-gray-950 rounded-lg p-3">
|
|
{primaryAddress}
|
|
</p>
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|