feat(PingServices): add endpoint and dashboard component for measuring RTT to key services (Google, Cloudflare, Yandex, Instagram)
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 1m39s
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 1m39s
This commit is contained in:
@@ -15,7 +15,11 @@ import {
|
||||
IconFilter,
|
||||
IconDownload,
|
||||
IconCreditCard,
|
||||
IconSearch
|
||||
IconSearch,
|
||||
IconBrandGoogle,
|
||||
IconBrandCloudflare,
|
||||
IconBrandYandex,
|
||||
IconBrandInstagram
|
||||
} from '@tabler/icons-react';
|
||||
import PageHeader from './components/PageHeader.jsx';
|
||||
import TopNStats from './components/TopNStats.jsx';
|
||||
@@ -74,6 +78,34 @@ function MetricCard({ title, value, icon: Icon, color, description }) {
|
||||
);
|
||||
}
|
||||
|
||||
/** Карточка сервиса с иконкой и пингом (как на референсном скрине) */
|
||||
const PING_SERVICES_CONFIG = [
|
||||
{ id: 'google', name: 'Google', host: '8.8.8.8', Icon: IconBrandGoogle, color: 'blue' },
|
||||
{ id: 'cloudflare', name: 'Cloudflare', host: '1.1.1.1', Icon: IconBrandCloudflare, color: 'orange' },
|
||||
{ id: 'yandex', name: 'Yandex', host: 'ya.ru', Icon: IconBrandYandex, color: 'red' },
|
||||
{ id: 'instagram', name: 'Instagram', host: 'instagram.com', Icon: IconBrandInstagram, color: 'pink' },
|
||||
];
|
||||
|
||||
function PingServiceCard({ config, ms, loading }) {
|
||||
const { name, host, Icon, color } = config;
|
||||
const value = loading ? '...' : (ms != null ? `${ms} мс` : '—');
|
||||
return (
|
||||
<div className="card h-100 position-relative">
|
||||
<div className="card-body d-flex align-items-center">
|
||||
<span className={`avatar avatar-lg me-3 bg-${color}-lt text-${color} border-0 rounded`}>
|
||||
<Icon size={28} stroke={1.5} />
|
||||
</span>
|
||||
<div className="flex-grow-1 min-w-0">
|
||||
<div className="h4 mb-0 fw-bold">{value}</div>
|
||||
<div className="text-muted small text-truncate" title={`${name} (${host})`}>
|
||||
{name} <span className="opacity-75">({host})</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Dashboard() {
|
||||
// Глобальный поиск удалён по требованию UX
|
||||
const [stats, setStats] = useState({
|
||||
@@ -92,6 +124,8 @@ function Dashboard() {
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState(null);
|
||||
const [lastFetchTime, setLastFetchTime] = useState(null);
|
||||
const [pingServices, setPingServices] = useState(null);
|
||||
const [pingLoading, setPingLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
async function fetchStats() {
|
||||
@@ -190,6 +224,22 @@ function Dashboard() {
|
||||
fetchStats();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
setPingLoading(true);
|
||||
api.get('/ping-services')
|
||||
.then(({ data }) => {
|
||||
if (!cancelled) setPingServices(data || null);
|
||||
})
|
||||
.catch(() => {
|
||||
if (!cancelled) setPingServices(null);
|
||||
})
|
||||
.finally(() => {
|
||||
if (!cancelled) setPingLoading(false);
|
||||
});
|
||||
return () => { cancelled = true; };
|
||||
}, []);
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<div className="alert alert-danger" role="alert">
|
||||
@@ -220,6 +270,19 @@ function Dashboard() {
|
||||
)}
|
||||
/>
|
||||
|
||||
{/* Пинг до сервисов: Google, Cloudflare, Yandex, Instagram */}
|
||||
<div className="row g-3 mb-4">
|
||||
{PING_SERVICES_CONFIG.map((config) => (
|
||||
<div key={config.id} className="col-6 col-md-3">
|
||||
<PingServiceCard
|
||||
config={config}
|
||||
ms={pingServices?.[config.id]?.ms ?? null}
|
||||
loading={pingLoading}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Основные метрики */}
|
||||
<div className="row g-3 mb-4">
|
||||
<div className="col-md-3">
|
||||
|
||||
Reference in New Issue
Block a user