2026-06-18 16:15:34 +00:00
|
|
|
import { useState } from 'react'
|
|
|
|
|
import { PieChart, Pie, Cell, ResponsiveContainer, LineChart, Line, XAxis } from 'recharts'
|
2026-06-18 17:56:11 +00:00
|
|
|
import { Plus, Server, Activity, AlertTriangle, DollarSign } from 'lucide-react'
|
2026-06-18 16:15:34 +00:00
|
|
|
|
2026-06-18 16:41:45 +00:00
|
|
|
const subTabs = ['Overview']
|
2026-06-18 16:15:34 +00:00
|
|
|
|
|
|
|
|
const statusCards = [
|
|
|
|
|
{ label: 'Total Resources', value: '128', icon: Server, sub: '+4 this week' },
|
|
|
|
|
{ label: 'Healthy', value: '124', icon: Activity, sub: '96.9%', color: '#2ECC71' },
|
|
|
|
|
{ label: 'Warnings', value: '3', icon: AlertTriangle, sub: 'Needs attention', color: '#E67E22' },
|
|
|
|
|
{ label: 'Critical', value: '1', icon: AlertTriangle, sub: 'Action required', color: '#E74C3C' },
|
|
|
|
|
{ label: 'Monthly Cost', value: '$24,560.75', icon: DollarSign, sub: '↑ 3.2% MTD' },
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
const costData = [
|
|
|
|
|
{ name: 'Compute', value: 38, color: '#C8A434' },
|
|
|
|
|
{ name: 'Storage', value: 22, color: '#E67E22' },
|
|
|
|
|
{ name: 'Database', value: 25, color: '#2ECC71' },
|
|
|
|
|
{ name: 'Network', value: 15, color: '#7A7D85' },
|
|
|
|
|
]
|
|
|
|
|
|
2026-06-18 17:56:11 +00:00
|
|
|
const nodes = [
|
|
|
|
|
{ name: 'web-frontend-lb', status: 'healthy' },
|
|
|
|
|
{ name: 'app-server-01', status: 'healthy' },
|
|
|
|
|
{ name: 'app-server-02', status: 'healthy' },
|
|
|
|
|
{ name: 'app-server-03', status: 'warning' },
|
|
|
|
|
{ name: 'db-primary-01', status: 'healthy' },
|
|
|
|
|
{ name: 'db-replica-02', status: 'healthy' },
|
|
|
|
|
{ name: 'cache-cluster-02', status: 'critical' },
|
|
|
|
|
{ name: 'batch-worker-04', status: 'healthy' },
|
|
|
|
|
{ name: 'storage-node-01', status: 'healthy' },
|
|
|
|
|
{ name: 'storage-node-02', status: 'healthy' },
|
|
|
|
|
{ name: 'monitoring-01', status: 'healthy' },
|
|
|
|
|
{ name: 'vpn-gateway', status: 'warning' },
|
2026-06-18 16:15:34 +00:00
|
|
|
]
|
|
|
|
|
|
2026-06-18 17:56:11 +00:00
|
|
|
const nodeStatusColor: Record<string, string> = {
|
|
|
|
|
healthy: '#2ECC71',
|
|
|
|
|
warning: '#E67E22',
|
|
|
|
|
critical: '#E74C3C',
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-18 16:15:34 +00:00
|
|
|
const trendData = Array.from({ length: 14 }, (_, i) => ({
|
|
|
|
|
day: i,
|
|
|
|
|
compute: 60 + i * 0.6 + Math.sin(i / 2) * 3,
|
|
|
|
|
storage: 35 + i * 0.4 + Math.cos(i / 3) * 2,
|
|
|
|
|
database: 20 + i * 0.2 + Math.sin(i / 4) * 1.5,
|
2026-06-18 17:39:47 +00:00
|
|
|
network: 45 + i * 0.3 + Math.cos(i / 2.5) * 2.5,
|
2026-06-18 16:15:34 +00:00
|
|
|
}))
|
|
|
|
|
|
|
|
|
|
const activities = [
|
|
|
|
|
{ title: 'Auto-scaling triggered', source: 'App Server Group', time: '4m ago' },
|
|
|
|
|
{ title: 'Resource provisioned', source: 'db-replica-02', time: '19m ago' },
|
|
|
|
|
{ title: 'Health check failed', source: 'cache-cluster-02', time: '27m ago' },
|
|
|
|
|
{ title: 'Tag updated', source: '12 resources', time: '1h ago' },
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
const cardBase: React.CSSProperties = {
|
|
|
|
|
backgroundColor: 'rgba(10, 10, 12, 0.92)',
|
|
|
|
|
border: '1px solid rgba(200, 164, 52, 0.08)',
|
|
|
|
|
borderRadius: '12px',
|
|
|
|
|
padding: '20px',
|
|
|
|
|
boxShadow: '0 0 20px rgba(200, 164, 52, 0.03)',
|
|
|
|
|
transition: 'border-color 0.2s ease',
|
|
|
|
|
position: 'relative',
|
|
|
|
|
overflow: 'hidden',
|
|
|
|
|
height: '100%',
|
|
|
|
|
display: 'flex',
|
|
|
|
|
flexDirection: 'column',
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const sectionTitle: React.CSSProperties = {
|
|
|
|
|
fontSize: '11px',
|
|
|
|
|
textTransform: 'uppercase',
|
|
|
|
|
letterSpacing: '1.5px',
|
|
|
|
|
color: '#7A7D85',
|
|
|
|
|
fontWeight: 500,
|
|
|
|
|
marginBottom: '16px',
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-18 17:06:53 +00:00
|
|
|
function framedCard(bgUrl: string): React.CSSProperties {
|
|
|
|
|
return {
|
|
|
|
|
backgroundImage: `url(${bgUrl})`,
|
|
|
|
|
backgroundSize: '100% 100%',
|
|
|
|
|
backgroundPosition: 'center',
|
|
|
|
|
backgroundRepeat: 'no-repeat',
|
|
|
|
|
position: 'relative',
|
|
|
|
|
overflow: 'hidden',
|
|
|
|
|
height: '100%',
|
|
|
|
|
display: 'flex',
|
|
|
|
|
flexDirection: 'column',
|
|
|
|
|
padding: '20px 20px 64px 20px',
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-18 17:47:09 +00:00
|
|
|
const cardVignette: React.CSSProperties = {
|
|
|
|
|
position: 'absolute',
|
|
|
|
|
inset: 0,
|
|
|
|
|
pointerEvents: 'none',
|
|
|
|
|
background: 'radial-gradient(ellipse closest-side at center, transparent 70%, var(--color-page) 100%)',
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const cardDim: React.CSSProperties = {
|
|
|
|
|
position: 'absolute',
|
|
|
|
|
inset: 0,
|
|
|
|
|
pointerEvents: 'none',
|
|
|
|
|
backgroundColor: 'rgba(8, 8, 10, 0.45)',
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-18 17:26:39 +00:00
|
|
|
const distributionData = [
|
|
|
|
|
{ name: 'Compute', value: 42, color: '#C8A434' },
|
|
|
|
|
{ name: 'Storage', value: 28, color: '#E67E22' },
|
|
|
|
|
{ name: 'Database', value: 18, color: '#2ECC71' },
|
|
|
|
|
{ name: 'Network', value: 12, color: '#7A7D85' },
|
|
|
|
|
]
|
|
|
|
|
|
2026-06-18 16:15:34 +00:00
|
|
|
function Donut({ data, centerLabel }: { data: { name: string; value: number; color: string }[]; centerLabel?: string }) {
|
|
|
|
|
return (
|
|
|
|
|
<div className="flex flex-1 items-center gap-4">
|
|
|
|
|
<div className="relative" style={{ width: '120px', height: '120px', flexShrink: 0 }}>
|
|
|
|
|
<ResponsiveContainer width="100%" height="100%">
|
|
|
|
|
<PieChart>
|
|
|
|
|
<Pie data={data} dataKey="value" innerRadius={38} outerRadius={56} paddingAngle={2} isAnimationActive animationDuration={1000}>
|
|
|
|
|
{data.map((entry) => (
|
|
|
|
|
<Cell key={entry.name} fill={entry.color} stroke="none" />
|
|
|
|
|
))}
|
|
|
|
|
</Pie>
|
|
|
|
|
</PieChart>
|
|
|
|
|
</ResponsiveContainer>
|
|
|
|
|
{centerLabel && (
|
|
|
|
|
<div className="absolute inset-0 flex items-center justify-center pointer-events-none">
|
|
|
|
|
<span style={{ fontSize: '18px', fontWeight: 700, color: '#E8E6E0' }}>{centerLabel}</span>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
<div className="flex flex-col gap-2">
|
|
|
|
|
{data.map((entry) => (
|
|
|
|
|
<div key={entry.name} className="flex items-center gap-2">
|
|
|
|
|
<span style={{ width: '8px', height: '8px', borderRadius: '50%', backgroundColor: entry.color, flexShrink: 0 }} />
|
|
|
|
|
<span style={{ fontSize: '12px', color: '#E8E6E0', width: '70px' }}>{entry.name}</span>
|
|
|
|
|
<span style={{ fontSize: '12px', color: '#7A7D85' }}>{entry.value}%</span>
|
|
|
|
|
</div>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export default function Infrastructure() {
|
|
|
|
|
const [activeTab, setActiveTab] = useState('Overview')
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<>
|
2026-06-18 16:50:06 +00:00
|
|
|
{/* Sub-tabs + Add Resource — hero banner is rendered at the layout level behind this */}
|
|
|
|
|
<div className="flex items-center justify-between shrink-0">
|
|
|
|
|
<div className="flex items-center gap-1 overflow-x-auto" style={{ scrollbarWidth: 'none' }}>
|
|
|
|
|
{subTabs.map((tab) => {
|
|
|
|
|
const active = tab === activeTab
|
|
|
|
|
return (
|
|
|
|
|
<button
|
|
|
|
|
key={tab}
|
|
|
|
|
onClick={() => setActiveTab(tab)}
|
|
|
|
|
className="cursor-pointer bg-transparent border-none transition-colors whitespace-nowrap"
|
|
|
|
|
style={{
|
|
|
|
|
fontSize: '12px',
|
|
|
|
|
fontWeight: 500,
|
|
|
|
|
padding: '8px 14px',
|
|
|
|
|
borderRadius: '8px',
|
|
|
|
|
color: active ? '#C8A434' : '#7A7D85',
|
|
|
|
|
backgroundColor: active ? 'rgba(200,164,52,0.1)' : 'transparent',
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
{tab}
|
|
|
|
|
</button>
|
|
|
|
|
)
|
|
|
|
|
})}
|
|
|
|
|
</div>
|
|
|
|
|
<button
|
|
|
|
|
className="flex items-center gap-2 cursor-pointer transition-colors whitespace-nowrap"
|
2026-06-18 16:15:34 +00:00
|
|
|
style={{
|
2026-06-18 16:50:06 +00:00
|
|
|
fontSize: '12px',
|
|
|
|
|
fontWeight: 600,
|
|
|
|
|
color: '#0A0B0D',
|
|
|
|
|
backgroundColor: '#C8A434',
|
|
|
|
|
border: 'none',
|
|
|
|
|
borderRadius: '8px',
|
|
|
|
|
padding: '9px 16px',
|
|
|
|
|
boxShadow: '0 0 14px rgba(200,164,52,0.2)',
|
2026-06-18 16:15:34 +00:00
|
|
|
}}
|
2026-06-18 16:50:06 +00:00
|
|
|
>
|
|
|
|
|
<Plus size={14} />
|
|
|
|
|
Add Resource
|
|
|
|
|
</button>
|
2026-06-18 16:15:34 +00:00
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* Status Cards */}
|
2026-06-18 17:39:47 +00:00
|
|
|
<div className="grid w-full grid-cols-5 gap-5 shrink-0" style={{ marginTop: '8px', height: '110px' }}>
|
2026-06-18 16:15:34 +00:00
|
|
|
{statusCards.map((card) => {
|
|
|
|
|
const Icon = card.icon
|
|
|
|
|
return (
|
2026-06-18 17:39:47 +00:00
|
|
|
<div
|
|
|
|
|
key={card.label}
|
|
|
|
|
style={{ ...cardBase, backgroundColor: 'rgba(10, 10, 12, 0.5)', padding: '18px', justifyContent: 'center', alignItems: 'center', gap: '8px' }}
|
|
|
|
|
className="hover:!border-gold/20"
|
|
|
|
|
>
|
|
|
|
|
<h3 style={{ fontSize: '10px', textTransform: 'uppercase', letterSpacing: '1.5px', color: '#7A7D85', fontWeight: 500, textAlign: 'center' }}>
|
2026-06-18 17:28:52 +00:00
|
|
|
{card.label}
|
|
|
|
|
</h3>
|
|
|
|
|
<div className="flex items-center gap-2">
|
2026-06-18 17:39:47 +00:00
|
|
|
<Icon size={18} style={{ color: card.color ?? '#C8A434' }} />
|
|
|
|
|
<span style={{ fontSize: '26px', fontWeight: 700, color: '#E8E6E0', lineHeight: 1 }}>{card.value}</span>
|
2026-06-18 16:15:34 +00:00
|
|
|
</div>
|
2026-06-18 17:39:47 +00:00
|
|
|
<p style={{ fontSize: '10px', color: card.color ?? '#7A7D85', textAlign: 'center' }}>{card.sub}</p>
|
2026-06-18 16:15:34 +00:00
|
|
|
</div>
|
|
|
|
|
)
|
|
|
|
|
})}
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* Middle Row */}
|
|
|
|
|
<div className="min-h-0 flex-1">
|
2026-06-18 17:26:39 +00:00
|
|
|
<div className="grid h-full w-full grid-cols-[1fr_1.6fr] gap-6">
|
|
|
|
|
{/* Resource Distribution */}
|
2026-06-18 17:32:11 +00:00
|
|
|
<div style={framedCard('/blank-kpi-bg.png')}>
|
2026-06-18 17:47:09 +00:00
|
|
|
<div style={cardDim} />
|
|
|
|
|
<div style={cardVignette} />
|
2026-06-18 17:32:11 +00:00
|
|
|
<div className="relative z-10 flex flex-1 flex-col">
|
|
|
|
|
<h3 style={sectionTitle}>Resource Distribution</h3>
|
|
|
|
|
<div className="flex flex-1 flex-col items-center justify-center">
|
|
|
|
|
<Donut data={distributionData} />
|
|
|
|
|
</div>
|
2026-06-18 17:26:39 +00:00
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
2026-06-18 17:56:11 +00:00
|
|
|
{/* Node Status — expanded */}
|
2026-06-18 17:32:11 +00:00
|
|
|
<div style={framedCard('/blank-kpi-bg.png')}>
|
2026-06-18 17:47:09 +00:00
|
|
|
<div style={cardDim} />
|
|
|
|
|
<div style={cardVignette} />
|
2026-06-18 16:15:34 +00:00
|
|
|
<div className="relative z-10 flex flex-1 flex-col">
|
2026-06-18 17:56:11 +00:00
|
|
|
<h3 style={sectionTitle}>Node Status</h3>
|
|
|
|
|
<div className="grid flex-1 grid-cols-4 gap-3 content-center">
|
|
|
|
|
{nodes.map((node) => (
|
|
|
|
|
<div
|
|
|
|
|
key={node.name}
|
|
|
|
|
title={`${node.name}: ${node.status}`}
|
|
|
|
|
style={{
|
|
|
|
|
backgroundColor: 'rgba(10, 10, 12, 0.55)',
|
|
|
|
|
border: `1px solid ${nodeStatusColor[node.status]}33`,
|
|
|
|
|
borderRadius: '8px',
|
|
|
|
|
padding: '10px 12px',
|
|
|
|
|
display: 'flex',
|
|
|
|
|
flexDirection: 'column',
|
|
|
|
|
gap: '6px',
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
<div className="flex items-center gap-1.5">
|
|
|
|
|
<span style={{ width: '7px', height: '7px', borderRadius: '50%', backgroundColor: nodeStatusColor[node.status], boxShadow: `0 0 6px ${nodeStatusColor[node.status]}` }} />
|
|
|
|
|
<Server size={12} style={{ color: '#7A7D85' }} />
|
|
|
|
|
</div>
|
|
|
|
|
<span style={{ fontSize: '11px', color: '#E8E6E0', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{node.name}</span>
|
2026-06-18 16:15:34 +00:00
|
|
|
</div>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* Bottom Row */}
|
|
|
|
|
<div className="shrink-0">
|
|
|
|
|
<div className="grid w-full grid-cols-[1.4fr_1fr_1fr] gap-6">
|
|
|
|
|
{/* Resource Trend */}
|
2026-06-18 17:39:47 +00:00
|
|
|
<div style={{ ...cardBase, height: 'auto', ...framedCard('/archnest-network-traffic-bg.png'), padding: '20px' }} className="hover:!border-gold/15">
|
2026-06-18 16:15:34 +00:00
|
|
|
<div className="relative z-10">
|
|
|
|
|
<h3 style={sectionTitle}>Resource Trend</h3>
|
|
|
|
|
<div style={{ height: '100px' }}>
|
|
|
|
|
<ResponsiveContainer width="100%" height="100%">
|
|
|
|
|
<LineChart data={trendData} margin={{ top: 0, right: 0, left: 0, bottom: 0 }}>
|
|
|
|
|
<XAxis dataKey="day" hide />
|
2026-06-18 17:39:47 +00:00
|
|
|
<Line type="monotone" dataKey="compute" stroke="#3B82F6" strokeWidth={1.5} dot={false} isAnimationActive animationDuration={1000} />
|
2026-06-18 16:15:34 +00:00
|
|
|
<Line type="monotone" dataKey="storage" stroke="#E67E22" strokeWidth={1.5} dot={false} isAnimationActive animationDuration={1000} />
|
|
|
|
|
<Line type="monotone" dataKey="database" stroke="#2ECC71" strokeWidth={1.5} dot={false} isAnimationActive animationDuration={1000} />
|
2026-06-18 17:39:47 +00:00
|
|
|
<Line type="monotone" dataKey="network" stroke="#8B5E3C" strokeWidth={1.5} dot={false} isAnimationActive animationDuration={1000} />
|
2026-06-18 16:15:34 +00:00
|
|
|
</LineChart>
|
|
|
|
|
</ResponsiveContainer>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* Cost Breakdown */}
|
|
|
|
|
<div style={{ ...cardBase, height: 'auto' }} className="hover:!border-gold/15">
|
|
|
|
|
<div className="relative z-10 flex flex-col">
|
|
|
|
|
<h3 style={sectionTitle}>Cost Breakdown (MTD)</h3>
|
|
|
|
|
<Donut data={costData} centerLabel="$24.5K" />
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* Recent Activity */}
|
|
|
|
|
<div style={{ ...cardBase, height: 'auto' }} className="hover:!border-gold/15">
|
|
|
|
|
<div className="relative z-10">
|
|
|
|
|
<h3 style={sectionTitle}>Recent Activity</h3>
|
|
|
|
|
<div className="flex flex-col gap-3">
|
|
|
|
|
{activities.map((item, i) => (
|
|
|
|
|
<div key={i} className="flex items-start justify-between gap-3">
|
|
|
|
|
<div style={{ flex: 1, minWidth: 0 }}>
|
|
|
|
|
<p style={{ fontSize: '12px', color: '#E8E6E0', fontWeight: 500, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{item.title}</p>
|
|
|
|
|
<p style={{ fontSize: '11px', color: '#7A7D85', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{item.source}</p>
|
|
|
|
|
</div>
|
|
|
|
|
<span style={{ fontSize: '11px', color: '#7A7D85', flexShrink: 0 }}>{item.time}</span>
|
|
|
|
|
</div>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* Footer stats bar */}
|
|
|
|
|
<div
|
|
|
|
|
className="shrink-0 flex items-center justify-center gap-3"
|
|
|
|
|
style={{
|
|
|
|
|
fontSize: '11px',
|
|
|
|
|
color: '#7A7D85',
|
|
|
|
|
padding: '8px 0',
|
|
|
|
|
borderTop: '1px solid rgba(200,164,52,0.08)',
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
<span>AWS</span><span style={{ color: 'rgba(200,164,52,0.3)' }}>|</span>
|
|
|
|
|
<span>6 Regions</span><span style={{ color: 'rgba(200,164,52,0.3)' }}>|</span>
|
|
|
|
|
<span>18 AZs</span><span style={{ color: 'rgba(200,164,52,0.3)' }}>|</span>
|
|
|
|
|
<span>128 Resources</span><span style={{ color: 'rgba(200,164,52,0.3)' }}>|</span>
|
|
|
|
|
<span style={{ color: '#2ECC71' }}>98.7% Health</span><span style={{ color: 'rgba(200,164,52,0.3)' }}>|</span>
|
|
|
|
|
<span>$24,560.75 MTD</span><span style={{ color: 'rgba(200,164,52,0.3)' }}>|</span>
|
|
|
|
|
<span style={{ color: '#E67E22' }}>2 Alerts</span>
|
|
|
|
|
</div>
|
|
|
|
|
</>
|
|
|
|
|
)
|
|
|
|
|
}
|