synor/apps/desktop-wallet/src/pages/Dashboard.tsx
Gulshan Yadav 6b5a232a5e feat: Desktop wallet, gas estimator UI, and 30-day monitoring stack
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
2026-01-10 04:38:09 +05:30

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>
);
}