feat(TrafficDashboard): add summary statistics and visual representation for traffic data across all systems
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 1m56s
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 1m56s
This commit is contained in:
@@ -182,6 +182,44 @@ export default function TrafficDashboard() {
|
||||
fetchStats();
|
||||
}, [fetchStats]);
|
||||
|
||||
// Суммарная статистика по всем отображаемым системам (без ошибок, с данными)
|
||||
const summary = (() => {
|
||||
const withData = (jumphosts || []).filter((jh) => !jh.error && Array.isArray(jh.interfaces) && jh.interfaces.length > 0);
|
||||
let totalBytes = 0;
|
||||
let totalRx = 0;
|
||||
let totalTx = 0;
|
||||
const byJumphost = [];
|
||||
for (const jh of withData) {
|
||||
let jhTotal = 0;
|
||||
let jhRx = 0;
|
||||
let jhTx = 0;
|
||||
for (const i of jh.interfaces) {
|
||||
jhTotal += i.totalBytes || 0;
|
||||
jhRx += i.rxBytes || 0;
|
||||
jhTx += i.txBytes || 0;
|
||||
}
|
||||
totalBytes += jhTotal;
|
||||
totalRx += jhRx;
|
||||
totalTx += jhTx;
|
||||
if (jhTotal > 0) {
|
||||
byJumphost.push({
|
||||
name: jh.name || jh.host || 'Jumphost',
|
||||
value: jhTotal,
|
||||
fill: CHART_COLORS[byJumphost.length % CHART_COLORS.length],
|
||||
});
|
||||
}
|
||||
}
|
||||
return {
|
||||
totalBytes,
|
||||
totalRx,
|
||||
totalTx,
|
||||
systemsCount: withData.length,
|
||||
byJumphost,
|
||||
};
|
||||
})();
|
||||
|
||||
const hasSummary = summary.systemsCount > 0 && summary.totalBytes > 0;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<PageHeader
|
||||
@@ -217,13 +255,92 @@ export default function TrafficDashboard() {
|
||||
</div>
|
||||
)}
|
||||
{!loading && jumphosts.length > 0 && (
|
||||
<div className="row row-cards g-3">
|
||||
{jumphosts.map((jh) => (
|
||||
<div key={jh.serverId || jh.host || jh.name} className="col-12 col-md-6 col-xl-4">
|
||||
<JumphostCard jumphost={jh} />
|
||||
<>
|
||||
{hasSummary && (
|
||||
<div className="card mb-4">
|
||||
<div className="card-header">
|
||||
<h3 className="card-title mb-0">Суммарно по всем системам</h3>
|
||||
</div>
|
||||
<div className="card-body">
|
||||
<div className="d-flex flex-wrap align-items-stretch gap-4">
|
||||
<div className="position-relative flex-shrink-0" style={{ width: 220, height: 220 }}>
|
||||
<ResponsiveContainer width={220} height={220}>
|
||||
<PieChart margin={{ top: 0, right: 0, bottom: 0, left: 0 }}>
|
||||
<Pie
|
||||
data={summary.byJumphost}
|
||||
dataKey="value"
|
||||
nameKey="name"
|
||||
cx="50%"
|
||||
cy="50%"
|
||||
innerRadius={58}
|
||||
outerRadius={98}
|
||||
paddingAngle={0}
|
||||
stroke="none"
|
||||
>
|
||||
{summary.byJumphost.map((entry, index) => (
|
||||
<Cell key={`cell-${index}`} fill={entry.fill} />
|
||||
))}
|
||||
</Pie>
|
||||
<Tooltip formatter={(value) => formatBytes(value)} />
|
||||
</PieChart>
|
||||
</ResponsiveContainer>
|
||||
<div
|
||||
className="position-absolute top-50 start-50 translate-middle text-center text-body"
|
||||
style={{ pointerEvents: 'none', width: '55%' }}
|
||||
>
|
||||
<div className="fw-bold lh-1" style={{ fontSize: '1.35rem' }}>
|
||||
{formatBytes(summary.totalBytes)}
|
||||
</div>
|
||||
<div className="text-muted small mt-1" style={{ fontSize: '0.7rem', letterSpacing: '0.02em' }}>
|
||||
TRAFFIC
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="d-flex flex-wrap gap-4 align-items-center flex-grow-1">
|
||||
<div className="traffic-summary-legend">
|
||||
{summary.byJumphost.map((item) => (
|
||||
<div key={item.name} className="d-flex align-items-center mb-2 small">
|
||||
<span
|
||||
className="flex-shrink-0 rounded"
|
||||
style={{ width: 10, height: 10, backgroundColor: item.fill }}
|
||||
/>
|
||||
<span className="ms-2">
|
||||
{item.name} - {formatBytes(item.value)}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="d-flex flex-wrap gap-3 ms-md-auto">
|
||||
<div className="px-3 py-2 rounded bg-blue-lt text-blue">
|
||||
<span className="d-block small text-muted">Всего трафика</span>
|
||||
<span className="fw-bold">{formatBytes(summary.totalBytes)}</span>
|
||||
</div>
|
||||
<div className="px-3 py-2 rounded bg-green-lt text-green">
|
||||
<span className="d-block small text-muted">RX</span>
|
||||
<span className="fw-bold">{formatBytes(summary.totalRx)}</span>
|
||||
</div>
|
||||
<div className="px-3 py-2 rounded bg-orange-lt text-orange">
|
||||
<span className="d-block small text-muted">TX</span>
|
||||
<span className="fw-bold">{formatBytes(summary.totalTx)}</span>
|
||||
</div>
|
||||
<div className="px-3 py-2 rounded bg-azure-lt text-azure">
|
||||
<span className="d-block small text-muted">Систем</span>
|
||||
<span className="fw-bold">{summary.systemsCount}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
<div className="row row-cards g-3">
|
||||
{jumphosts.map((jh) => (
|
||||
<div key={jh.serverId || jh.host || jh.name} className="col-12 col-md-6 col-xl-4">
|
||||
<JumphostCard jumphost={jh} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user