Some checks failed
CI/CD Pipeline / Solidity Contracts (push) Failing after 1m41s
CI/CD Pipeline / Security Scanning (push) Successful in 2m32s
CI/CD Pipeline / Lint and Format (push) Failing after 51s
CI/CD Pipeline / Terraform Validation (push) Failing after 35s
CI/CD Pipeline / Kubernetes Validation (push) Successful in 30s
Validation / validate-genesis (push) Successful in 34s
Validation / validate-terraform (push) Failing after 36s
Validation / validate-kubernetes (push) Failing after 11s
Validation / validate-smart-contracts (push) Failing after 11s
Validation / validate-security (push) Failing after 1m23s
Validation / validate-documentation (push) Failing after 21s
Replace Binance-style dashboard chrome with a shared telemetry UI, SVG icons, and mission headers across Central Bank, Office 24, trade, swap, and admin screens. Co-authored-by: Cursor <cursoragent@cursor.com>
177 lines
7.1 KiB
TypeScript
177 lines
7.1 KiB
TypeScript
/**
|
|
* AdminDashboard Component - Analytics and monitoring dashboard
|
|
*/
|
|
|
|
import { useState, useEffect } from 'react'
|
|
import { useAccount, usePublicClient } from 'wagmi'
|
|
import { useAdmin } from '../../contexts/AdminContext'
|
|
import { TransactionRequestStatus } from '../../types/admin'
|
|
import { CONTRACT_ADDRESSES } from '../../config/contracts'
|
|
import { MAINNET_TETHER_ABI } from '../../abis/MainnetTether'
|
|
import { subscribeToContractEvents } from '../../utils/contractEvents'
|
|
import toast from 'react-hot-toast'
|
|
import DashboardStatCard from '../../features/omnl-dashboard/DashboardStatCard'
|
|
import DashboardSection from '../../features/omnl-dashboard/DashboardSection'
|
|
import NasaIcon, { StatusIcon } from '../icons/NasaIcon'
|
|
|
|
export default function AdminDashboard() {
|
|
const { adminActions, auditLogs } = useAdmin()
|
|
const { address } = useAccount()
|
|
const publicClient = usePublicClient()
|
|
const [mainnetTetherPaused, setMainnetTetherPaused] = useState<boolean>(false)
|
|
|
|
const stats = {
|
|
totalActions: adminActions.length,
|
|
pending: adminActions.filter((a) => a.status === TransactionRequestStatus.PENDING).length,
|
|
approved: adminActions.filter((a) => a.status === TransactionRequestStatus.APPROVED).length,
|
|
executed: adminActions.filter((a) => a.status === TransactionRequestStatus.SUCCESS).length,
|
|
failed: adminActions.filter((a) => a.status === TransactionRequestStatus.FAILED).length,
|
|
totalAuditLogs: auditLogs.length,
|
|
successRate:
|
|
adminActions.length > 0
|
|
? (
|
|
(adminActions.filter((a) => a.status === TransactionRequestStatus.SUCCESS).length /
|
|
adminActions.length) *
|
|
100
|
|
).toFixed(1)
|
|
: '0',
|
|
}
|
|
|
|
const recentActions = adminActions.slice(-5).reverse()
|
|
const recentLogs = auditLogs.slice(-5).reverse()
|
|
|
|
useEffect(() => {
|
|
if (publicClient && address) {
|
|
// Fetch contract states
|
|
const fetchStates = async () => {
|
|
try {
|
|
// Fetch paused state for MainnetTether
|
|
const paused = await publicClient.readContract({
|
|
address: CONTRACT_ADDRESSES.mainnet.MAINNET_TETHER,
|
|
abi: MAINNET_TETHER_ABI,
|
|
functionName: 'paused',
|
|
})
|
|
setMainnetTetherPaused(paused as boolean)
|
|
} catch (error) {
|
|
console.error('Error fetching contract states:', error)
|
|
}
|
|
}
|
|
|
|
fetchStates()
|
|
const interval = setInterval(fetchStates, 30000) // Refresh every 30 seconds
|
|
|
|
// Subscribe to contract events for real-time updates
|
|
let unsubscribePaused: (() => void) | null = null
|
|
let unsubscribeUnpaused: (() => void) | null = null
|
|
|
|
subscribeToContractEvents(
|
|
publicClient,
|
|
CONTRACT_ADDRESSES.mainnet.MAINNET_TETHER,
|
|
MAINNET_TETHER_ABI,
|
|
'Paused',
|
|
(_event) => {
|
|
setMainnetTetherPaused(true)
|
|
toast.success('MainnetTether paused event detected')
|
|
}
|
|
).then((unsub) => {
|
|
unsubscribePaused = unsub
|
|
}).catch(() => {})
|
|
|
|
subscribeToContractEvents(
|
|
publicClient,
|
|
CONTRACT_ADDRESSES.mainnet.MAINNET_TETHER,
|
|
MAINNET_TETHER_ABI,
|
|
'Unpaused',
|
|
(_event) => {
|
|
setMainnetTetherPaused(false)
|
|
toast.success('MainnetTether unpaused event detected')
|
|
}
|
|
).then((unsub) => {
|
|
unsubscribeUnpaused = unsub
|
|
}).catch(() => {})
|
|
|
|
return () => {
|
|
clearInterval(interval)
|
|
if (unsubscribePaused) unsubscribePaused()
|
|
if (unsubscribeUnpaused) unsubscribeUnpaused()
|
|
}
|
|
}
|
|
}, [publicClient, address])
|
|
|
|
return (
|
|
<div className="space-y-6 nasa-mission-root">
|
|
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
|
<DashboardStatCard label="Total actions" value={String(stats.totalActions)} accent="blue" icon="admin" />
|
|
<DashboardStatCard label="Pending" value={String(stats.pending)} accent="gold" icon="alert" />
|
|
<DashboardStatCard label="Executed" value={String(stats.executed)} accent="green" icon="check" />
|
|
<DashboardStatCard label="Success rate" value={`${stats.successRate}%`} accent="blue" icon="chart" />
|
|
</div>
|
|
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
<div className="dash-card">
|
|
<div className="flex items-center justify-between mb-2">
|
|
<div className="dash-stat-label flex items-center gap-1">
|
|
<NasaIcon name="bridge" size={14} className="nasa-icon--telemetry" />
|
|
MainnetTether
|
|
</div>
|
|
<span className={`nasa-badge ${mainnetTetherPaused ? 'nasa-badge--caution' : 'nasa-badge--nominal'}`}>
|
|
{mainnetTetherPaused ? 'Paused' : 'Active'}
|
|
</span>
|
|
</div>
|
|
<p className="text-[#7a9bb8] text-xs font-mono">{CONTRACT_ADDRESSES.mainnet.MAINNET_TETHER}</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
<DashboardSection title="Recent actions" icon="rocket">
|
|
{recentActions.length === 0 ? (
|
|
<p className="text-[#7a9bb8] text-sm">No actions yet</p>
|
|
) : (
|
|
<div className="space-y-3">
|
|
{recentActions.map((action) => (
|
|
<div key={action.id} className="nasa-inset-panel">
|
|
<div className="flex justify-between items-start">
|
|
<div>
|
|
<p className="text-[#e8f4ff] font-semibold text-sm font-mono">{action.type}</p>
|
|
<p className="text-[#7a9bb8] text-xs font-mono">{action.contractAddress.slice(0, 20)}...</p>
|
|
</div>
|
|
<span className="flex items-center gap-1 text-xs font-mono">
|
|
<StatusIcon ok={action.status === TransactionRequestStatus.SUCCESS} />
|
|
{action.status}
|
|
</span>
|
|
</div>
|
|
<p className="text-[#7a9bb8] text-xs mt-2">{new Date(action.createdAt).toLocaleString()}</p>
|
|
</div>
|
|
))}
|
|
</div>
|
|
)}
|
|
</DashboardSection>
|
|
|
|
<DashboardSection title="Recent audit logs" icon="ledger">
|
|
{recentLogs.length === 0 ? (
|
|
<p className="text-[#7a9bb8] text-sm">No audit logs yet</p>
|
|
) : (
|
|
<div className="space-y-3">
|
|
{recentLogs.map((log) => (
|
|
<div key={log.id} className="nasa-inset-panel">
|
|
<div className="flex justify-between items-start">
|
|
<div>
|
|
<p className="text-[#e8f4ff] font-semibold text-sm font-mono">{log.action}</p>
|
|
<p className="text-[#7a9bb8] text-xs font-mono">{log.user.slice(0, 10)}...</p>
|
|
</div>
|
|
<span className="flex items-center gap-1 text-xs font-mono">
|
|
<StatusIcon ok={log.status === 'success'} />
|
|
{log.status}
|
|
</span>
|
|
</div>
|
|
<p className="text-[#7a9bb8] text-xs mt-2">{new Date(log.timestamp).toLocaleString()}</p>
|
|
</div>
|
|
))}
|
|
</div>
|
|
)}
|
|
</DashboardSection>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|