фикс ссылок

This commit is contained in:
dv 2026-04-09 11:23:27 +03:00
parent 140d43f8fe
commit 87bbd2844c
2 changed files with 48 additions and 14 deletions

View file

@ -574,6 +574,9 @@ export function DeviceDetailPage() {
{device.rack_name && <Tag color="blue">{device.rack_name}</Tag>} {device.rack_name && <Tag color="blue">{device.rack_name}</Tag>}
{device.vendor && <Tag color="default">{device.vendor}</Tag>} {device.vendor && <Tag color="default">{device.vendor}</Tag>}
{device.model && <Typography.Text type="secondary" style={{ fontSize: 13 }}>{device.model}</Typography.Text>} {device.model && <Typography.Text type="secondary" style={{ fontSize: 13 }}>{device.model}</Typography.Text>}
{device.capabilities.map((c) => (
<Tag key={c} color="geekblue" style={{ fontSize: 11 }}>{c}</Tag>
))}
</Space> </Space>
</Col> </Col>
<Col> <Col>

View file

@ -60,6 +60,30 @@ import dayjs from 'dayjs'
const SERVER_CATEGORIES = new Set(['main_server', 'vm', 'router']) const SERVER_CATEGORIES = new Set(['main_server', 'vm', 'router'])
/** Возвращает URL веб-интерфейса устройства или null, если ссылка не применима. */
function getDeviceWebUrl(device: DeviceRow): string | null {
const hostname = (device.hostname ?? '').toLowerCase()
// Proxmox — определяем по hostname
if (hostname.includes('proxmox') || hostname.includes('pve')) {
return `http://${device.ip}:8006`
}
// RTSP-прокси (go2rtc/mediamtx) — определяем по capabilities
if (device.capabilities?.includes('rtsp')) {
return `http://${device.ip}:1984`
}
switch (device.category) {
case 'main_server': return `http://${device.ip}:80`
case 'router': return `http://${device.ip}:80` // MikroTik WebFig
case 'embedded': return `http://${device.ip}:5000`
case 'camera': return `http://${device.ip}:80`
// vm без proxmox/pve в hostname — asterisk, db и прочие, ссылка не нужна
default: return null
}
}
const rackLabel = (name: string, type: string | null | undefined): string => const rackLabel = (name: string, type: string | null | undefined): string =>
type ? `${name} ${type}` : name type ? `${name} ${type}` : name
const DEVICE_COL_SPAN = 8 const DEVICE_COL_SPAN = 8
@ -337,15 +361,15 @@ export function InventoryObjectDetailPage() {
) )
} }
const d = row as DeviceRow const d = row as DeviceRow
return ( const webUrl = getDeviceWebUrl(d)
<Space size={4}> return webUrl ? (
<code <Tooltip title={`Открыть ${webUrl}`}>
style={{ cursor: 'pointer', color: '#1677ff' }} <a href={webUrl} target="_blank" rel="noopener noreferrer">
onClick={() => navigate(`/inventory/objects/${objId}/devices/${d.id}`)} <code>{d.ip}</code>
> </a>
{d.ip} </Tooltip>
</code> ) : (
</Space> <code style={{ color: 'inherit' }}>{d.ip}</code>
) )
}, },
}, },
@ -354,8 +378,16 @@ export function InventoryObjectDetailPage() {
dataIndex: 'hostname', dataIndex: 'hostname',
key: 'hostname', key: 'hostname',
onCell: (row: TableRow) => row._type === 'group' ? { colSpan: 0 } : {}, onCell: (row: TableRow) => row._type === 'group' ? { colSpan: 0 } : {},
render: (_: unknown, row: TableRow) => render: (_: unknown, row: TableRow) => {
row._type === 'group' ? null : ((row as DeviceRow).hostname ?? '—'), if (row._type === 'group') return null
const d = row as DeviceRow
if (!d.hostname) return <Typography.Text type="secondary"></Typography.Text>
return (
<a onClick={() => navigate(`/inventory/objects/${objId}/devices/${d.id}`)}>
{d.hostname}
</a>
)
},
}, },
{ {
title: 'Стойка / локация', title: 'Стойка / локация',
@ -365,9 +397,8 @@ export function InventoryObjectDetailPage() {
if (row._type === 'group') return null if (row._type === 'group') return null
const d = row as DeviceRow const d = row as DeviceRow
if (d.rack_name) return <Tag color="blue">{rackLabel(d.rack_name, d.rack_type)}</Tag> if (d.rack_name) return <Tag color="blue">{rackLabel(d.rack_name, d.rack_type)}</Tag>
const loc = (d as any).location if (d.location === 'server_room') return <Tag color="purple">Серверная</Tag>
if (loc === 'server_room') return <Tag color="purple">Серверная</Tag> if (d.location === 'street') return <Tag color="orange">Улица</Tag>
if (loc === 'street') return <Tag color="orange">Улица</Tag>
return <Typography.Text type="secondary"></Typography.Text> return <Typography.Text type="secondary"></Typography.Text>
}, },
}, },