кодировка
This commit is contained in:
parent
ecb929d780
commit
b282de5355
1 changed files with 47 additions and 46 deletions
|
|
@ -1,4 +1,4 @@
|
||||||
import { useQueryClient } from '@tanstack/react-query'
|
import { useQueryClient } from '@tanstack/react-query'
|
||||||
import {
|
import {
|
||||||
Alert,
|
Alert,
|
||||||
Button,
|
Button,
|
||||||
|
|
@ -24,10 +24,10 @@ import { useTags } from '../api/tags'
|
||||||
import { useDashboardMonitorSSE } from '../hooks/useDashboardMonitorSSE'
|
import { useDashboardMonitorSSE } from '../hooks/useDashboardMonitorSSE'
|
||||||
|
|
||||||
function formatAge(seconds: number | null | undefined): string {
|
function formatAge(seconds: number | null | undefined): string {
|
||||||
if (seconds == null) return '—'
|
if (seconds == null) return '—'
|
||||||
if (seconds < 60) return `${seconds}ñ`
|
if (seconds < 60) return `${seconds}с`
|
||||||
if (seconds < 3600) return `${Math.floor(seconds / 60)}ì`
|
if (seconds < 3600) return `${Math.floor(seconds / 60)}м`
|
||||||
return `${Math.floor(seconds / 3600)}÷ ${Math.floor((seconds % 3600) / 60)}ì`
|
return `${Math.floor(seconds / 3600)}ч ${Math.floor((seconds % 3600) / 60)}м`
|
||||||
}
|
}
|
||||||
|
|
||||||
function scopeToSearch(scope: DashboardScope): string {
|
function scopeToSearch(scope: DashboardScope): string {
|
||||||
|
|
@ -91,9 +91,9 @@ export function HomePage() {
|
||||||
const handleRunNow = async () => {
|
const handleRunNow = async () => {
|
||||||
try {
|
try {
|
||||||
await runNow.mutateAsync({ ...scope, force: true })
|
await runNow.mutateAsync({ ...scope, force: true })
|
||||||
message.success('Ìîíèòîðèíã çàïóùåí')
|
message.success('Мониторинг запущен')
|
||||||
} catch {
|
} catch {
|
||||||
message.error('Íå óäàëîñü çàïóñòèòü ìîíèòîðèíã')
|
message.error('Не удалось запустить мониторинг')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -203,13 +203,13 @@ export function HomePage() {
|
||||||
<Typography.Title level={4} style={{ margin: 0 }}>
|
<Typography.Title level={4} style={{ margin: 0 }}>
|
||||||
NOC Dashboard
|
NOC Dashboard
|
||||||
</Typography.Title>
|
</Typography.Title>
|
||||||
<Typography.Text type="secondary">SLA, ïîêðûòèå ìîíèòîðèíãîì, èíöèäåíòû è î÷åðåäü äåéñòâèé</Typography.Text>
|
<Typography.Text type="secondary">SLA, покрытие мониторингом, инциденты и очередь действий</Typography.Text>
|
||||||
</Col>
|
</Col>
|
||||||
<Col>
|
<Col>
|
||||||
<Space wrap>
|
<Space wrap>
|
||||||
<Button onClick={() => navigate(`/home/settings${scopeToSearch(scope)}`)}>Íàñòðîéêè ìîíèòîðèíãà</Button>
|
<Button onClick={() => navigate(`/home/settings${scopeToSearch(scope)}`)}>Настройки мониторинга</Button>
|
||||||
<Button type="primary" onClick={handleRunNow} loading={runNow.isPending}>
|
<Button type="primary" onClick={handleRunNow} loading={runNow.isPending}>
|
||||||
Çàïóñòèòü ñåé÷àñ
|
Запустить сейчас
|
||||||
</Button>
|
</Button>
|
||||||
</Space>
|
</Space>
|
||||||
</Col>
|
</Col>
|
||||||
|
|
@ -219,7 +219,7 @@ export function HomePage() {
|
||||||
<Row gutter={[8, 8]}>
|
<Row gutter={[8, 8]}>
|
||||||
<Col xs={24} sm={12} lg={6}>
|
<Col xs={24} sm={12} lg={6}>
|
||||||
<Select
|
<Select
|
||||||
placeholder="Ãîðîä"
|
placeholder="Город"
|
||||||
allowClear
|
allowClear
|
||||||
style={{ width: '100%' }}
|
style={{ width: '100%' }}
|
||||||
options={(meta?.cities ?? []).map((c) => ({ value: c, label: c }))}
|
options={(meta?.cities ?? []).map((c) => ({ value: c, label: c }))}
|
||||||
|
|
@ -229,7 +229,7 @@ export function HomePage() {
|
||||||
</Col>
|
</Col>
|
||||||
<Col xs={24} sm={12} lg={6}>
|
<Col xs={24} sm={12} lg={6}>
|
||||||
<Select
|
<Select
|
||||||
placeholder="Êëèåíò"
|
placeholder="Клиент"
|
||||||
allowClear
|
allowClear
|
||||||
style={{ width: '100%' }}
|
style={{ width: '100%' }}
|
||||||
options={(meta?.clients ?? []).map((c) => ({ value: c, label: c }))}
|
options={(meta?.clients ?? []).map((c) => ({ value: c, label: c }))}
|
||||||
|
|
@ -240,7 +240,7 @@ export function HomePage() {
|
||||||
<Col xs={24} sm={12} lg={6}>
|
<Col xs={24} sm={12} lg={6}>
|
||||||
<Select
|
<Select
|
||||||
mode="multiple"
|
mode="multiple"
|
||||||
placeholder="Îáúåêòû"
|
placeholder="Объекты"
|
||||||
allowClear
|
allowClear
|
||||||
style={{ width: '100%' }}
|
style={{ width: '100%' }}
|
||||||
value={objectIds}
|
value={objectIds}
|
||||||
|
|
@ -251,7 +251,7 @@ export function HomePage() {
|
||||||
<Col xs={24} sm={12} lg={6}>
|
<Col xs={24} sm={12} lg={6}>
|
||||||
<Select
|
<Select
|
||||||
mode="multiple"
|
mode="multiple"
|
||||||
placeholder="Òåãè"
|
placeholder="Теги"
|
||||||
allowClear
|
allowClear
|
||||||
style={{ width: '100%' }}
|
style={{ width: '100%' }}
|
||||||
value={tagIds}
|
value={tagIds}
|
||||||
|
|
@ -263,12 +263,12 @@ export function HomePage() {
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<Row gutter={[12, 12]} style={{ marginBottom: 12 }}>
|
<Row gutter={[12, 12]} style={{ marginBottom: 12 }}>
|
||||||
<Col xs={12} md={8} lg={4}><Card size="small" loading={isLoading}><Statistic title="Îáúåêòîâ â ñêîóïå" value={totals.objects} /></Card></Col>
|
<Col xs={12} md={8} lg={4}><Card size="small" loading={isLoading}><Statistic title="Объектов в скоупе" value={totals.objects} /></Card></Col>
|
||||||
<Col xs={12} md={8} lg={4}><Card size="small" loading={isLoading}><Statistic title="Óñòðîéñòâ" value={totals.devices} /></Card></Col>
|
<Col xs={12} md={8} lg={4}><Card size="small" loading={isLoading}><Statistic title="Устройств" value={totals.devices} /></Card></Col>
|
||||||
<Col xs={12} md={8} lg={4}><Card size="small" loading={isLoading}><Statistic title="Èíöèäåíòíûõ îáúåêòîâ" value={incidentObjects.length} /></Card></Col>
|
<Col xs={12} md={8} lg={4}><Card size="small" loading={isLoading}><Statistic title="Инцидентных объектов" value={incidentObjects.length} /></Card></Col>
|
||||||
<Col xs={12} md={8} lg={4}><Card size="small" loading={isLoading}><Statistic title="Ñèãíàëîâ ïðîáëåì" value={signalCount} /></Card></Col>
|
<Col xs={12} md={8} lg={4}><Card size="small" loading={isLoading}><Statistic title="Сигналов проблем" value={signalCount} /></Card></Col>
|
||||||
<Col xs={12} md={8} lg={4}><Card size="small" loading={isLoading}><Statistic title="Ïðîñðî÷åííûå îáúåêòû" value={stalePingObjects.length} /></Card></Col>
|
<Col xs={12} md={8} lg={4}><Card size="small" loading={isLoading}><Statistic title="Просроченные объекты" value={stalePingObjects.length} /></Card></Col>
|
||||||
<Col xs={12} md={8} lg={4}><Card size="small" loading={isLoading}><Statistic title="Ñðåäíèé health" value={avgHealth ?? '—'} /></Card></Col>
|
<Col xs={12} md={8} lg={4}><Card size="small" loading={isLoading}><Statistic title="Средний health" value={avgHealth ?? '—'} /></Card></Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
{monitorStatus?.last_error ? <Alert type="error" showIcon style={{ marginBottom: 12 }} message={monitorStatus.last_error} /> : null}
|
{monitorStatus?.last_error ? <Alert type="error" showIcon style={{ marginBottom: 12 }} message={monitorStatus.last_error} /> : null}
|
||||||
|
|
@ -276,9 +276,9 @@ export function HomePage() {
|
||||||
<Row gutter={[12, 12]}>
|
<Row gutter={[12, 12]}>
|
||||||
<Col xs={24} xl={16}>
|
<Col xs={24} xl={16}>
|
||||||
<Space direction="vertical" size={12} style={{ width: '100%' }}>
|
<Space direction="vertical" size={12} style={{ width: '100%' }}>
|
||||||
<Card title="Îáúåêòû ñ èíöèäåíòàìè" size="small" style={{ borderRadius: 12 }}>
|
<Card title="Объекты с инцидентами" size="small" style={{ borderRadius: 12 }}>
|
||||||
{incidentObjects.length === 0 ? (
|
{incidentObjects.length === 0 ? (
|
||||||
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description="Êðèòè÷íûõ îáúåêòîâ íåò" />
|
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description="Критичных объектов нет" />
|
||||||
) : (
|
) : (
|
||||||
<List
|
<List
|
||||||
dataSource={incidentObjects.slice(0, 12)}
|
dataSource={incidentObjects.slice(0, 12)}
|
||||||
|
|
@ -293,7 +293,7 @@ export function HomePage() {
|
||||||
<List.Item
|
<List.Item
|
||||||
actions={[
|
actions={[
|
||||||
<Button key="open" type="link" onClick={() => navigate(`/home/objects/${item.object_id}${scopeToSearch(scope)}`)}>
|
<Button key="open" type="link" onClick={() => navigate(`/home/objects/${item.object_id}${scopeToSearch(scope)}`)}>
|
||||||
Îòêðûòü
|
Открыть
|
||||||
</Button>,
|
</Button>,
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
|
|
@ -301,17 +301,17 @@ export function HomePage() {
|
||||||
title={
|
title={
|
||||||
<Space wrap size={8}>
|
<Space wrap size={8}>
|
||||||
<Typography.Text strong>{item.object_name}</Typography.Text>
|
<Typography.Text strong>{item.object_name}</Typography.Text>
|
||||||
<Tag>{item.city ?? '—'}</Tag>
|
<Tag>{item.city ?? '—'}</Tag>
|
||||||
<Tag color="blue">{item.client ?? '—'}</Tag>
|
<Tag color="blue">{item.client ?? '—'}</Tag>
|
||||||
<Tag color={healthColor(item.health_score)}>health {item.health_score ?? '—'}</Tag>
|
<Tag color={healthColor(item.health_score)}>health {item.health_score ?? '—'}</Tag>
|
||||||
</Space>
|
</Space>
|
||||||
}
|
}
|
||||||
description={
|
description={
|
||||||
<Space split={<span>•</span>} wrap>
|
<Space split={<span>•</span>} wrap>
|
||||||
<span>offline: {offline}</span>
|
<span>offline: {offline}</span>
|
||||||
<span>unknown: {unknown}</span>
|
<span>unknown: {unknown}</span>
|
||||||
<span>disk warn/failed: {diskWarn}/{diskFailed}</span>
|
<span>disk warn/failed: {diskWarn}/{diskFailed}</span>
|
||||||
<span>óñòðîéñòâ: {item.device_count}</span>
|
<span>устройств: {item.device_count}</span>
|
||||||
<span>age ping: {formatAge(ping?.max_age_sec)}</span>
|
<span>age ping: {formatAge(ping?.max_age_sec)}</span>
|
||||||
</Space>
|
</Space>
|
||||||
}
|
}
|
||||||
|
|
@ -323,15 +323,15 @@ export function HomePage() {
|
||||||
)}
|
)}
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<Card title="Îáúåêòû ñ íåñâåæèìè äàííûìè ìîíèòîðèíãà" size="small" style={{ borderRadius: 12 }}>
|
<Card title="Объекты с несвежими данными мониторинга" size="small" style={{ borderRadius: 12 }}>
|
||||||
{stalePingObjects.length === 0 ? (
|
{stalePingObjects.length === 0 ? (
|
||||||
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description="Âñå îáúåêòû îáíîâëÿþòñÿ â ïðåäåëàõ SLA" />
|
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description="Все объекты обновляются в пределах SLA" />
|
||||||
) : (
|
) : (
|
||||||
<List
|
<List
|
||||||
size="small"
|
size="small"
|
||||||
dataSource={stalePingObjects}
|
dataSource={stalePingObjects}
|
||||||
renderItem={(item) => (
|
renderItem={(item) => (
|
||||||
<List.Item actions={[<Button key="open" type="link" onClick={() => navigate(`/home/objects/${item.obj.object_id}${scopeToSearch(scope)}`)}>Îòêðûòü</Button>]}>
|
<List.Item actions={[<Button key="open" type="link" onClick={() => navigate(`/home/objects/${item.obj.object_id}${scopeToSearch(scope)}`)}>Открыть</Button>]}>
|
||||||
<Space direction="vertical" size={0} style={{ width: '100%' }}>
|
<Space direction="vertical" size={0} style={{ width: '100%' }}>
|
||||||
<Row justify="space-between">
|
<Row justify="space-between">
|
||||||
<Typography.Text>{item.obj.object_name}</Typography.Text>
|
<Typography.Text>{item.obj.object_name}</Typography.Text>
|
||||||
|
|
@ -351,28 +351,28 @@ export function HomePage() {
|
||||||
|
|
||||||
<Col xs={24} xl={8}>
|
<Col xs={24} xl={8}>
|
||||||
<Space direction="vertical" size={12} style={{ width: '100%' }}>
|
<Space direction="vertical" size={12} style={{ width: '100%' }}>
|
||||||
<Card title="SLA è ïîêðûòèå" size="small" style={{ borderRadius: 12 }}>
|
<Card title="SLA и покрытие" size="small" style={{ borderRadius: 12 }}>
|
||||||
<Space direction="vertical" size={10} style={{ width: '100%' }}>
|
<Space direction="vertical" size={10} style={{ width: '100%' }}>
|
||||||
<div>
|
<div>
|
||||||
<Row justify="space-between">
|
<Row justify="space-between">
|
||||||
<Typography.Text>Äîñòóïíîñòü (online)</Typography.Text>
|
<Typography.Text>Доступность (online)</Typography.Text>
|
||||||
<Typography.Text strong>{availabilityPct != null ? `${availabilityPct}%` : '—'}</Typography.Text>
|
<Typography.Text strong>{availabilityPct != null ? `${availabilityPct}%` : '—'}</Typography.Text>
|
||||||
</Row>
|
</Row>
|
||||||
<Progress percent={availabilityPct ?? 0} size="small" showInfo={false} strokeColor={availabilityPct != null && availabilityPct < 95 ? '#ff4d4f' : '#52c41a'} />
|
<Progress percent={availabilityPct ?? 0} size="small" showInfo={false} strokeColor={availabilityPct != null && availabilityPct < 95 ? '#ff4d4f' : '#52c41a'} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<Row justify="space-between">
|
<Row justify="space-between">
|
||||||
<Typography.Text>Ïîêðûòèå ìîíèòîðèíãîì</Typography.Text>
|
<Typography.Text>Покрытие мониторингом</Typography.Text>
|
||||||
<Typography.Text strong>{coveragePct != null ? `${coveragePct}%` : '—'}</Typography.Text>
|
<Typography.Text strong>{coveragePct != null ? `${coveragePct}%` : '—'}</Typography.Text>
|
||||||
</Row>
|
</Row>
|
||||||
<Progress percent={coveragePct ?? 0} size="small" showInfo={false} strokeColor="#1677ff" />
|
<Progress percent={coveragePct ?? 0} size="small" showInfo={false} strokeColor="#1677ff" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<Row justify="space-between">
|
<Row justify="space-between">
|
||||||
<Typography.Text>Ñâåæåñòü äàííûõ</Typography.Text>
|
<Typography.Text>Свежесть данных</Typography.Text>
|
||||||
<Typography.Text strong>{freshnessPct != null ? `${freshnessPct}%` : '—'}</Typography.Text>
|
<Typography.Text strong>{freshnessPct != null ? `${freshnessPct}%` : '—'}</Typography.Text>
|
||||||
</Row>
|
</Row>
|
||||||
<Progress percent={freshnessPct ?? 0} size="small" showInfo={false} strokeColor={freshnessPct != null && freshnessPct < 90 ? '#faad14' : '#52c41a'} />
|
<Progress percent={freshnessPct ?? 0} size="small" showInfo={false} strokeColor={freshnessPct != null && freshnessPct < 90 ? '#faad14' : '#52c41a'} />
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -381,16 +381,16 @@ export function HomePage() {
|
||||||
<Tag color="green">online: {onlineTotal}</Tag>
|
<Tag color="green">online: {onlineTotal}</Tag>
|
||||||
<Tag color="red">offline: {offlineTotal}</Tag>
|
<Tag color="red">offline: {offlineTotal}</Tag>
|
||||||
<Tag color="orange">unknown: {unknownTotal}</Tag>
|
<Tag color="orange">unknown: {unknownTotal}</Tag>
|
||||||
<Tag>ïî ping: {monitoredTotal}</Tag>
|
<Tag>по ping: {monitoredTotal}</Tag>
|
||||||
</Space>
|
</Space>
|
||||||
</Space>
|
</Space>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<Card title="Ñîñòîÿíèå öèêëà ìîíèòîðèíãà" size="small" style={{ borderRadius: 12 }}>
|
<Card title="Состояние цикла мониторинга" size="small" style={{ borderRadius: 12 }}>
|
||||||
<Space direction="vertical" size={8} style={{ width: '100%' }}>
|
<Space direction="vertical" size={8} style={{ width: '100%' }}>
|
||||||
<Row justify="space-between"><Typography.Text>Ñòàòóñ</Typography.Text><Tag color={runStatusColor(monitorStatus?.status)}>{monitorStatus?.status ?? '-'}</Tag></Row>
|
<Row justify="space-between"><Typography.Text>Статус</Typography.Text><Tag color={runStatusColor(monitorStatus?.status)}>{monitorStatus?.status ?? '-'}</Tag></Row>
|
||||||
<Row justify="space-between"><Typography.Text>Îáúåêòû â ìîíèòîðèíãå</Typography.Text><Typography.Text strong>{summary?.included_count ?? totals.allowed_objects}</Typography.Text></Row>
|
<Row justify="space-between"><Typography.Text>Объекты в мониторинге</Typography.Text><Typography.Text strong>{summary?.included_count ?? totals.allowed_objects}</Typography.Text></Row>
|
||||||
<Row justify="space-between"><Typography.Text>Ïîñëåäíèé óñïåõ</Typography.Text><Typography.Text strong>{monitorStatus?.last_success_at ? dayjs(monitorStatus.last_success_at).format('DD.MM HH:mm:ss') : '—'}</Typography.Text></Row>
|
<Row justify="space-between"><Typography.Text>Последний успех</Typography.Text><Typography.Text strong>{monitorStatus?.last_success_at ? dayjs(monitorStatus.last_success_at).format('DD.MM HH:mm:ss') : '—'}</Typography.Text></Row>
|
||||||
|
|
||||||
{lastCounts ? (
|
{lastCounts ? (
|
||||||
<Space direction="vertical" size={2} style={{ width: '100%' }}>
|
<Space direction="vertical" size={2} style={{ width: '100%' }}>
|
||||||
|
|
@ -405,7 +405,7 @@ export function HomePage() {
|
||||||
|
|
||||||
<Card title="Action queue" size="small" style={{ borderRadius: 12 }}>
|
<Card title="Action queue" size="small" style={{ borderRadius: 12 }}>
|
||||||
{actionQueue.length === 0 ? (
|
{actionQueue.length === 0 ? (
|
||||||
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description="Êðèòè÷íûõ äåéñòâèé ñåé÷àñ íåò" />
|
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description="Критичных действий сейчас нет" />
|
||||||
) : (
|
) : (
|
||||||
<List
|
<List
|
||||||
size="small"
|
size="small"
|
||||||
|
|
@ -414,7 +414,7 @@ export function HomePage() {
|
||||||
<List.Item
|
<List.Item
|
||||||
actions={[
|
actions={[
|
||||||
<Button key="open" type="link" onClick={() => navigate(`/home/objects/${row.obj.object_id}${scopeToSearch(scope)}`)}>
|
<Button key="open" type="link" onClick={() => navigate(`/home/objects/${row.obj.object_id}${scopeToSearch(scope)}`)}>
|
||||||
Îòêðûòü
|
Открыть
|
||||||
</Button>,
|
</Button>,
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
|
|
@ -427,7 +427,7 @@ export function HomePage() {
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
<Typography.Text type="secondary" style={{ fontSize: 12 }}>
|
<Typography.Text type="secondary" style={{ fontSize: 12 }}>
|
||||||
{row.reasons.join(' • ')}
|
{row.reasons.join(' • ')}
|
||||||
</Typography.Text>
|
</Typography.Text>
|
||||||
</List.Item>
|
</List.Item>
|
||||||
)}
|
)}
|
||||||
|
|
@ -440,3 +440,4 @@ export function HomePage() {
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue