фикс фикс фикс фикс фикс фикс
This commit is contained in:
parent
057a798f3c
commit
2cc1c71cfa
3 changed files with 114 additions and 66 deletions
|
|
@ -269,7 +269,7 @@ async def dashboard_devices(
|
||||||
|
|
||||||
# All eligible devices for this object+category
|
# All eligible devices for this object+category
|
||||||
devices_q = (
|
devices_q = (
|
||||||
select(Device.id, Device.ip, Device.hostname)
|
select(Device.id, Device.ip, Device.hostname, Device.status)
|
||||||
.where(
|
.where(
|
||||||
Device.object_id == obj_id,
|
Device.object_id == obj_id,
|
||||||
Device.category == policy.category,
|
Device.category == policy.category,
|
||||||
|
|
@ -324,6 +324,7 @@ async def dashboard_devices(
|
||||||
"device_id": d.id,
|
"device_id": d.id,
|
||||||
"ip": d.ip,
|
"ip": d.ip,
|
||||||
"hostname": d.hostname,
|
"hostname": d.hostname,
|
||||||
|
"status": d.status,
|
||||||
"has_snapshot": has_snapshot,
|
"has_snapshot": has_snapshot,
|
||||||
"last_collected_at": latest_map.get(d.id),
|
"last_collected_at": latest_map.get(d.id),
|
||||||
"snapshot_id": snap_info[0] if snap_info else None,
|
"snapshot_id": snap_info[0] if snap_info else None,
|
||||||
|
|
|
||||||
|
|
@ -135,6 +135,7 @@ export interface DeviceSnapshotStatus {
|
||||||
device_id: number
|
device_id: number
|
||||||
ip: string
|
ip: string
|
||||||
hostname: string | null
|
hostname: string | null
|
||||||
|
status: string | null
|
||||||
has_snapshot: boolean
|
has_snapshot: boolean
|
||||||
last_collected_at: string | null
|
last_collected_at: string | null
|
||||||
snapshot_id: number | null
|
snapshot_id: number | null
|
||||||
|
|
|
||||||
|
|
@ -291,13 +291,12 @@ export function SnapshotDashboardPage() {
|
||||||
<Tag>{objects.length}</Tag>
|
<Tag>{objects.length}</Tag>
|
||||||
</Space>
|
</Space>
|
||||||
),
|
),
|
||||||
children: objects.map((obj) => (
|
children: (
|
||||||
<ObjectPoliciesTable
|
<ObjectsTable
|
||||||
key={obj.object_id}
|
objects={objects}
|
||||||
obj={obj}
|
onCollect={handleCollectObj}
|
||||||
onCollect={(policyId) => handleCollectObj(policyId, obj.object_id)}
|
|
||||||
/>
|
/>
|
||||||
)),
|
),
|
||||||
}))}
|
}))}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|
@ -331,51 +330,58 @@ export function SnapshotDashboardPage() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function ObjectPoliciesTable({
|
function ObjectsTable({
|
||||||
obj,
|
objects,
|
||||||
onCollect,
|
onCollect,
|
||||||
}: {
|
}: {
|
||||||
obj: ObjectSnapshotSummary
|
objects: ObjectSnapshotSummary[]
|
||||||
onCollect: (policyId: number) => void
|
onCollect: (policyId: number, objId: number) => void
|
||||||
}) {
|
}) {
|
||||||
const [expandedKey, setExpandedKey] = useState<number | null>(null)
|
const [expandedKeys, setExpandedKeys] = useState<number[]>([])
|
||||||
|
|
||||||
const columns: ColumnsType<PolicySnapshotStatus> = [
|
const columns: ColumnsType<ObjectSnapshotSummary> = [
|
||||||
{
|
{
|
||||||
title: 'Политика',
|
title: 'Объект',
|
||||||
dataIndex: 'policy_label',
|
key: 'name',
|
||||||
key: 'label',
|
render: (_, obj) => (
|
||||||
},
|
<span>
|
||||||
{
|
<Typography.Text strong>{obj.object_name}</Typography.Text>
|
||||||
title: 'Категория',
|
{obj.client && <Typography.Text type="secondary"> — {obj.client}</Typography.Text>}
|
||||||
dataIndex: 'category',
|
</span>
|
||||||
key: 'category',
|
),
|
||||||
render: (cat: string) => <Tag>{cat}</Tag>,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Последний сбор',
|
title: 'Последний сбор',
|
||||||
dataIndex: 'last_collected_at',
|
|
||||||
key: 'last_collected',
|
key: 'last_collected',
|
||||||
render: (val: string | null) =>
|
width: 160,
|
||||||
val ? (
|
render: (_, obj) => {
|
||||||
<Tooltip title={dayjs(val).format('DD.MM.YYYY HH:mm:ss')}>
|
const latest = obj.policies.reduce<string | null>((acc, p) => {
|
||||||
{dayjs(val).fromNow()}
|
if (!p.last_collected_at) return acc
|
||||||
|
if (!acc) return p.last_collected_at
|
||||||
|
return p.last_collected_at > acc ? p.last_collected_at : acc
|
||||||
|
}, null)
|
||||||
|
return latest ? (
|
||||||
|
<Tooltip title={dayjs(latest).format('DD.MM.YYYY HH:mm:ss')}>
|
||||||
|
{dayjs(latest).fromNow()}
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
) : (
|
) : (
|
||||||
<Typography.Text type="secondary">—</Typography.Text>
|
<Typography.Text type="secondary">—</Typography.Text>
|
||||||
),
|
)
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Покрытие',
|
title: 'Покрытие',
|
||||||
key: 'coverage',
|
key: 'coverage',
|
||||||
render: (_, row) => {
|
width: 120,
|
||||||
const { success_count, total_devices } = row
|
render: (_, obj) => {
|
||||||
if (total_devices === 0) return <Tag>нет устройств</Tag>
|
const total = obj.policies.reduce((s, p) => s + p.total_devices, 0)
|
||||||
const ratio = success_count / total_devices
|
const success = obj.policies.reduce((s, p) => s + p.success_count, 0)
|
||||||
|
if (total === 0) return <Tag>нет устройств</Tag>
|
||||||
|
const ratio = success / total
|
||||||
const color = ratio >= 1 ? 'green' : ratio > 0 ? 'orange' : 'red'
|
const color = ratio >= 1 ? 'green' : ratio > 0 ? 'orange' : 'red'
|
||||||
return (
|
return (
|
||||||
<Tag color={color}>
|
<Tag color={color}>
|
||||||
{success_count} / {total_devices}
|
{success} / {total}
|
||||||
</Tag>
|
</Tag>
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
|
@ -383,16 +389,16 @@ function ObjectPoliciesTable({
|
||||||
{
|
{
|
||||||
title: '',
|
title: '',
|
||||||
key: 'actions',
|
key: 'actions',
|
||||||
width: 60,
|
width: 50,
|
||||||
render: (_, row) => (
|
render: (_, obj) => (
|
||||||
<Tooltip title="Собрать для этого объекта">
|
<Tooltip title="Собрать все политики для объекта">
|
||||||
<Button
|
<Button
|
||||||
type="text"
|
type="text"
|
||||||
size="small"
|
size="small"
|
||||||
icon={<PlayCircleOutlined />}
|
icon={<PlayCircleOutlined />}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
onCollect(row.policy_id)
|
obj.policies.forEach((p) => onCollect(p.policy_id, obj.object_id))
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
@ -401,30 +407,71 @@ function ObjectPoliciesTable({
|
||||||
]
|
]
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ marginBottom: 16 }}>
|
|
||||||
<Typography.Text strong>
|
|
||||||
{obj.object_name}
|
|
||||||
{obj.client && <Typography.Text type="secondary"> — {obj.client}</Typography.Text>}
|
|
||||||
</Typography.Text>
|
|
||||||
<Table
|
<Table
|
||||||
dataSource={obj.policies}
|
dataSource={objects}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
rowKey="policy_id"
|
rowKey="object_id"
|
||||||
size="small"
|
size="small"
|
||||||
pagination={false}
|
pagination={false}
|
||||||
style={{ marginTop: 4 }}
|
|
||||||
expandable={{
|
expandable={{
|
||||||
expandedRowKeys: expandedKey !== null ? [expandedKey] : [],
|
expandedRowKeys: expandedKeys,
|
||||||
onExpand: (expanded, record) => setExpandedKey(expanded ? record.policy_id : null),
|
onExpand: (expanded, record) =>
|
||||||
|
setExpandedKeys(
|
||||||
|
expanded
|
||||||
|
? [...expandedKeys, record.object_id]
|
||||||
|
: expandedKeys.filter((k) => k !== record.object_id)
|
||||||
|
),
|
||||||
expandedRowRender: (record) => (
|
expandedRowRender: (record) => (
|
||||||
<DevicesList
|
<ObjectDevicesExpanded
|
||||||
objId={obj.object_id}
|
obj={record}
|
||||||
objName={obj.object_name}
|
onCollect={onCollect}
|
||||||
policyId={record.policy_id}
|
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function ObjectDevicesExpanded({
|
||||||
|
obj,
|
||||||
|
onCollect,
|
||||||
|
}: {
|
||||||
|
obj: ObjectSnapshotSummary
|
||||||
|
onCollect: (policyId: number, objId: number) => void
|
||||||
|
}) {
|
||||||
|
if (obj.policies.length === 1) {
|
||||||
|
return (
|
||||||
|
<DevicesList
|
||||||
|
objId={obj.object_id}
|
||||||
|
objName={obj.object_name}
|
||||||
|
policyId={obj.policies[0].policy_id}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{obj.policies.map((p) => (
|
||||||
|
<div key={p.policy_id} style={{ marginBottom: 12 }}>
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 4 }}>
|
||||||
|
<Typography.Text strong>{p.policy_label}</Typography.Text>
|
||||||
|
<Tag>{p.category}</Tag>
|
||||||
|
<Tooltip title="Собрать">
|
||||||
|
<Button
|
||||||
|
type="text"
|
||||||
|
size="small"
|
||||||
|
icon={<PlayCircleOutlined />}
|
||||||
|
onClick={() => onCollect(p.policy_id, obj.object_id)}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
<DevicesList
|
||||||
|
objId={obj.object_id}
|
||||||
|
objName={obj.object_name}
|
||||||
|
policyId={p.policy_id}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -483,14 +530,13 @@ function DevicesList({
|
||||||
render: (val: string | null) => val || <Typography.Text type="secondary">—</Typography.Text>,
|
render: (val: string | null) => val || <Typography.Text type="secondary">—</Typography.Text>,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Статус',
|
title: 'Снапшот',
|
||||||
key: 'status',
|
key: 'status',
|
||||||
render: (_, row) =>
|
render: (_, row) => {
|
||||||
row.has_snapshot ? (
|
if (row.has_snapshot) return <Tag color="green">Есть</Tag>
|
||||||
<Tag color="green">Есть</Tag>
|
if (row.status === 'offline') return <Tag color="red">Нет пинга</Tag>
|
||||||
) : (
|
return <Tag color="orange">Нет снапшота</Tag>
|
||||||
<Tag color="red">Нет</Tag>
|
},
|
||||||
),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Собран',
|
title: 'Собран',
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue