Compare commits
36 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1f73019714 | |||
| 0b39c9c602 | |||
| ec77b28cf2 | |||
| 983b865ff7 | |||
| 161f7db147 | |||
| 01a2a5bda6 | |||
| 6437b6bf35 | |||
| 848f2a0018 | |||
| eaa855a09e | |||
| a014bbfd13 | |||
| 87962d188c | |||
| d334e55551 | |||
| b8927e064b | |||
| 11b673550b | |||
| c3f3844cd5 | |||
| a43a8b24e3 | |||
| f29ab9f768 | |||
| 2a6f049938 | |||
| 18378b79a6 | |||
| 3fae0b841b | |||
| b8ef57e417 | |||
| c03667b312 | |||
| 2b7b4e7bd9 | |||
| dc8184d5dd | |||
| f36e5c3d3c | |||
| de241334a9 | |||
| c75338cb70 | |||
| 86e3e1726d | |||
| 943aa27de1 | |||
| b4442fe6c4 | |||
| f9f761b4e9 | |||
| 4090c7e6c5 | |||
| 38b43b1c45 | |||
| 9eafc7871b | |||
| 3eb5b06f59 | |||
| f2fc2e732d |
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "ndm-web-platform",
|
||||
"version": "0.0.0",
|
||||
"version": "0.42.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"engines": {
|
||||
|
||||
@@ -1,8 +1,53 @@
|
||||
[
|
||||
{
|
||||
"version": "0.42.0",
|
||||
"date": "2026-05-20",
|
||||
"changes": {
|
||||
"fixes": [
|
||||
{ "content": "优化安防箱环境数据卡片,避免空标签渲染并改进风扇信息展示" },
|
||||
{ "content": "修复设备硬件卡片进度条异常值显示问题,并完善状态判断逻辑" }
|
||||
],
|
||||
"feats": [
|
||||
{ "content": "新增服务器网卡信息展示" },
|
||||
{ "content": "新增安防箱网卡信息展示,并优化相关卡片标题" },
|
||||
{ "content": "为交换机诊断信息新增温度字段" },
|
||||
{ "content": "新增录像机环境状态卡片和网卡信息展示" }
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"version": "0.41.0",
|
||||
"date": "2026-05-19",
|
||||
"changes": {
|
||||
"fixes": [
|
||||
{ "content": "修复交换机端口卡片在端口数据为空时仍展示的问题" },
|
||||
{ "content": "修复安防箱门禁状态和防雷状态显示错误的问题" },
|
||||
{ "content": "修复解码器和录像机历史诊断中硬件占用卡片的加载状态同步问题" }
|
||||
],
|
||||
"feats": [
|
||||
{ "content": "新增摄像机阈值配置功能" },
|
||||
{ "content": "新增摄像机硬件占用率历史诊断记录卡片" },
|
||||
{ "content": "优化设备诊断页的信息展示结构,支持按设备型号、网卡信息、接入信息等分组展示" },
|
||||
{ "content": "为摄像机诊断信息新增网卡信息、IP信息和设备通用信息展示" },
|
||||
{ "content": "新增多厂商安防箱支持,支持BeiDian和NingTech安防箱的空开控制与设备重启" },
|
||||
{ "content": "安防箱配置新增开关数量和团体字符串(写)" },
|
||||
{ "content": "为各类设备实体新增团体字符串(写)字段" }
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"version": "0.40.0",
|
||||
"date": "2026-04-10",
|
||||
"changes": {
|
||||
"fixes": [{ "content": "修复设备树搜索时节点错乱的问题" }, { "content": "将安防箱面板的“电路”统一更正为“空开”" }, { "content": "修复设备查询缓存键冲突问题" }],
|
||||
"feats": [{ "content": "添加视频服务器双机热备状态" }]
|
||||
}
|
||||
},
|
||||
{
|
||||
"version": "0.39.0",
|
||||
"date": "2026-03-02",
|
||||
"changes": {
|
||||
"fixes": [{ "content": "修复设备树搜索时节点错乱的问题" }],
|
||||
"feats": [{ "content": "新版录像记录诊断卡片" }, { "content": "新增平台更新记录页面" }]
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"version": "0.39.0",
|
||||
"buildTime": "2026-03-02 15:14:53"
|
||||
"version": "0.42.0",
|
||||
"buildTime": "2026-05-20 13:52:07"
|
||||
}
|
||||
|
||||
@@ -1,5 +1,45 @@
|
||||
export interface NdmCameraDiagInfo {
|
||||
[key: string]: any;
|
||||
ethInfo?: {
|
||||
adminStatus?: string; // '1'
|
||||
desc?: string; // 'IPcamera'
|
||||
ifType?: string; // '5'
|
||||
inDiscards?: string; // '0'
|
||||
inErrors?: string; // '0'
|
||||
inNUcastPkts?: string; // '0'
|
||||
inOctets?: string; // '0'
|
||||
inUcastPkts?: string; // '0'
|
||||
inUnknownProtos?: string; // '0'
|
||||
index?: string; // '1'
|
||||
lastChange?: string; // '0:00:00.00'
|
||||
mTU?: string; // '1500'
|
||||
macAddress?: string; // '04:ee:cd:52:3a:a5'
|
||||
operStatus?: string; // '1'
|
||||
outDiscards?: string; // '0'
|
||||
outErrors?: string; // '0'
|
||||
outNUcastPkts?: string; // '0'
|
||||
outOctets?: string; // '0'
|
||||
outQLen?: string; // '0'
|
||||
outUcastPkts?: string; // '0'
|
||||
specific?: string; // '0.0'
|
||||
speed?: string; // '10000000'
|
||||
};
|
||||
ipInfo?: {
|
||||
broadcastAddress?: string; // '0'
|
||||
iPAddress?: string; // '0'
|
||||
index?: string; // '1'
|
||||
mASK?: string; // '255.255.255.0'
|
||||
reasmMaxSize?: string; // '0'
|
||||
};
|
||||
logTime?: string;
|
||||
info?: string;
|
||||
stCommonInfo?: {
|
||||
设备ID?: string;
|
||||
软件版本?: string;
|
||||
设备厂商?: string;
|
||||
设备别名?: string;
|
||||
设备型号?: string;
|
||||
硬件版本?: string;
|
||||
内存使用率?: string;
|
||||
CPU使用率?: string;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -10,6 +10,37 @@ export interface NdmNvrDiagInfo {
|
||||
totalSize?: number;
|
||||
}[];
|
||||
};
|
||||
ethInfo: {
|
||||
adminStatus?: string; // '1'
|
||||
desc?: string; // 'bond1'
|
||||
ifType?: string; // '6'
|
||||
inDiscards?: string; // '17931688'
|
||||
inErrors?: string; // '0'
|
||||
inNUcastPkts?: string; // '40945433'
|
||||
inOctets?: string; // '3453544614'
|
||||
inUcastPkts?: string; // '3375411816'
|
||||
inUnknownProtos?: string; // '0'
|
||||
index?: string; // '8'
|
||||
lastChange?: string; // '0:05:42.15'
|
||||
mTU?: string; // '1500'
|
||||
macAddress?: string; // '04:7b:cb:69:92:58'
|
||||
operStatus?: string; // '1'
|
||||
outDiscards?: string; // '0'
|
||||
outErrors?: string; // '0'
|
||||
outNUcastPkts?: string; // '0'
|
||||
outOctets?: string; // '3443476717'
|
||||
outQLen?: string; // '0'
|
||||
outUcastPkts?: string; // '415381735'
|
||||
specific?: string; // '0.0'
|
||||
speed?: string; // '2000000000'
|
||||
};
|
||||
ipInfo: {
|
||||
broadcastAddress?: string; // '1'
|
||||
iPAddress?: string; // '10.14.1.22'
|
||||
index?: string; // '8'
|
||||
mASK?: string; // '255.255.255.0'
|
||||
reasmMaxSize?: string; // '0'
|
||||
};
|
||||
stCommonInfo?: {
|
||||
设备ID?: string;
|
||||
软件版本?: string;
|
||||
@@ -20,12 +51,16 @@ export interface NdmNvrDiagInfo {
|
||||
内存使用率?: string;
|
||||
CPU使用率?: string;
|
||||
};
|
||||
cdFanInfo?: {
|
||||
索引号?: string;
|
||||
'风扇转速(rpm)'?: string;
|
||||
}[];
|
||||
cdPowerSupplyInfo?: {
|
||||
索引号?: string;
|
||||
电源状态?: string;
|
||||
}[];
|
||||
cdFanInfo?: NdmNvrFanInfo[];
|
||||
cdPowerSupplyInfo?: NdmNvrPowerSupplyInfo[];
|
||||
}
|
||||
|
||||
export interface NdmNvrFanInfo {
|
||||
索引号?: string;
|
||||
'风扇转速(rpm)'?: string;
|
||||
}
|
||||
|
||||
export interface NdmNvrPowerSupplyInfo {
|
||||
索引号?: string;
|
||||
电源状态?: string;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,36 @@
|
||||
export interface NdmSecurityBoxDiagInfo {
|
||||
[key: string]: any;
|
||||
ethInfo?: {
|
||||
adminStatus?: string; // '1'
|
||||
desc?: string; // 'br-lan'
|
||||
ifType?: string; // '6'
|
||||
inDiscards?: string; // '84977'
|
||||
inErrors?: string; // '0'
|
||||
inNUcastPkts?: string; // '233473'
|
||||
inOctets?: string; // '92355850'
|
||||
inUcastPkts?: string; // '899970'
|
||||
inUnknownProtos?: string; // '0'
|
||||
index?: string; // '8'
|
||||
lastChange?: string; // '0:00:00.00'
|
||||
mTU?: string; // '1500'
|
||||
macAddress?: string; // '56:62:bc:d3:9e:37'
|
||||
operStatus?: string; // '1'
|
||||
outDiscards?: string; // '0'
|
||||
outErrors?: string; // '0'
|
||||
outNUcastPkts?: string; // '0'
|
||||
outOctets?: string; // '68175904'
|
||||
outQLen?: string; // '0'
|
||||
outUcastPkts?: string; // '748100'
|
||||
specific?: string; // '0.0'
|
||||
speed?: string; // '0'
|
||||
};
|
||||
ipInfo?: {
|
||||
broadcastAddress?: string; // '1'
|
||||
iPAddress?: string; // '10.24.18.101'
|
||||
index?: string; // '8'
|
||||
mASK?: string; // '255.255.255.0'
|
||||
reasmMaxSize?: string; // '0'
|
||||
};
|
||||
info?: [
|
||||
{
|
||||
addrCode?: number;
|
||||
@@ -12,8 +43,14 @@ export interface NdmSecurityBoxDiagInfo {
|
||||
];
|
||||
stCommonInfo?: {
|
||||
[key: string]: any;
|
||||
内存使用率?: string;
|
||||
CPU使用率?: string;
|
||||
设备ID?: string; // 'NTBoxMetro'
|
||||
软件版本?: string; // 'V0101'
|
||||
设备厂商?: string; // 'NingTech'
|
||||
设备别名?: string; // 'SUN-IBOX'
|
||||
设备型号?: string; // 'SUN-IBOX'
|
||||
硬件版本?: string; // 'V0101'
|
||||
内存使用率?: string; // '18'
|
||||
CPU使用率?: string; // '1'
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -6,4 +6,35 @@ export interface NdmServerDiagInfo {
|
||||
磁盘使用率?: string;
|
||||
系统运行时间?: string;
|
||||
};
|
||||
ethInfo?: {
|
||||
adminStatus?: string; // '1'
|
||||
desc?: string; // 'Intel Corporation I350 Gigabit Network Connection'
|
||||
ifType?: string; // '6'
|
||||
inDiscards?: string; // '6707634'
|
||||
inErrors?: string; // '0'
|
||||
inNUcastPkts?: string; // '8991944'
|
||||
inOctets?: string; // '4220524983'
|
||||
inUcastPkts?: string; // '2342740610'
|
||||
inUnknownProtos?: string; // '0'
|
||||
index?: string; // '2'
|
||||
lastChange?: string; // '0:03:15.26'
|
||||
mTU?: string; // '1500'
|
||||
macAddress?: string; // 'e8:78:ee:f6:8d:98'
|
||||
operStatus?: string; // '1'
|
||||
outDiscards?: string; // '0'
|
||||
outErrors?: string; // '0'
|
||||
outNUcastPkts?: string; // '0'
|
||||
outOctets?: string; // '1415770066'
|
||||
outQLen?: string; // '0'
|
||||
outUcastPkts?: string; // '3335494729'
|
||||
specific?: string; // '0.0'
|
||||
speed?: string; // '1000000000'
|
||||
};
|
||||
ipInfo?: {
|
||||
broadcastAddress?: string; // '1'
|
||||
iPAddress?: string; // '10.14.1.8'
|
||||
index?: string; // '2'
|
||||
mASK?: string; // '255.255.255.0'
|
||||
reasmMaxSize?: string; // '0'
|
||||
};
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ export interface NdmSwitchDiagInfo {
|
||||
[key: string]: any;
|
||||
cpuRatio?: string; // 因环境不同可能不存在
|
||||
memoryRatio?: string; // 因环境不同可能不存在
|
||||
temperature?: number;
|
||||
logTime?: string;
|
||||
info?: {
|
||||
overFlowPorts?: string[];
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
export interface HighAvailable {
|
||||
pyip: string;
|
||||
vip: string;
|
||||
changeDate: string;
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
export * from './diag';
|
||||
export * from './high-available';
|
||||
export * from './link-description';
|
||||
export * from './station';
|
||||
|
||||
@@ -20,7 +20,9 @@ export interface NdmSecurityBox extends BaseModel {
|
||||
description: string;
|
||||
deviceStatus: string;
|
||||
deviceType: string;
|
||||
circuitCount: number;
|
||||
community: string;
|
||||
writeCommunity: string;
|
||||
frontendConfig: string;
|
||||
linkDescription: string;
|
||||
snmpEnabled: boolean;
|
||||
|
||||
@@ -21,6 +21,7 @@ export interface NdmSwitch extends BaseModel {
|
||||
deviceStatus: string;
|
||||
deviceType: string;
|
||||
community: string;
|
||||
writeCommunity: string;
|
||||
frontendConfig: string;
|
||||
linkDescription: string;
|
||||
snmpEnabled: boolean;
|
||||
|
||||
@@ -30,6 +30,7 @@ export interface NdmNvr extends BaseModel {
|
||||
deviceStatus: string;
|
||||
deviceType: string;
|
||||
community: string;
|
||||
writeCommunity: string;
|
||||
frontendConfig: string;
|
||||
linkDescription: string;
|
||||
snmpEnabled: boolean;
|
||||
|
||||
@@ -31,6 +31,7 @@ export interface NdmCamera extends BaseModel {
|
||||
deviceType: string;
|
||||
cameraType: string;
|
||||
community: string;
|
||||
writeCommunity: string;
|
||||
frontendConfig: string;
|
||||
linkDescription: string;
|
||||
snmpEnabled: boolean;
|
||||
|
||||
@@ -28,6 +28,7 @@ export interface NdmDecoder extends BaseModel {
|
||||
deviceStatus: string;
|
||||
deviceType: string;
|
||||
community: string;
|
||||
writeCommunity: string;
|
||||
frontendConfig: string;
|
||||
linkDescription: string;
|
||||
snmpEnabled: boolean;
|
||||
|
||||
@@ -21,6 +21,7 @@ export interface NdmKeyboard extends BaseModel {
|
||||
deviceStatus: string;
|
||||
deviceType: string;
|
||||
community: string;
|
||||
writeCommunity: string;
|
||||
frontendConfig: string;
|
||||
linkDescription: string;
|
||||
snmpEnabled: boolean;
|
||||
|
||||
@@ -25,6 +25,7 @@ export interface NdmMediaServer extends BaseModel {
|
||||
deviceStatus: string;
|
||||
deviceType: string;
|
||||
community: string;
|
||||
writeCommunity: string;
|
||||
frontendConfig: string;
|
||||
linkDescription: string;
|
||||
snmpEnabled: boolean;
|
||||
|
||||
@@ -25,6 +25,7 @@ export interface NdmVideoServer extends BaseModel {
|
||||
deviceStatus: string;
|
||||
deviceType: string;
|
||||
community: string;
|
||||
writeCommunity: string;
|
||||
frontendConfig: string;
|
||||
linkDescription: string;
|
||||
snmpEnabled: boolean;
|
||||
|
||||
@@ -93,22 +93,46 @@ export const probeSecurityBoxApi = async (ids: string[], options?: { stationCode
|
||||
unwrapVoidResponse(resp);
|
||||
};
|
||||
|
||||
export const turnCitcuitStatusApi = async (ipAddress: string, circuitIndex: number, status: number, options?: { stationCode?: Station['code']; signal?: AbortSignal }) => {
|
||||
// beidian安防箱切换空开状态
|
||||
export const turnCircuitStatusBeidianApi = async (community: string, ipAddress: string, circuitIndex: number, status: number, options?: { stationCode?: Station['code']; signal?: AbortSignal }) => {
|
||||
const { stationCode, signal } = options ?? {};
|
||||
const client = stationCode ? ndmClient : userClient;
|
||||
const prefix = stationCode ? `/${stationCode}` : '';
|
||||
const endpoint = `${prefix}/api/ndm/ndmSecurityBox/turnStatus`;
|
||||
const resp = await client.post<boolean>(endpoint, { community: 'public', ipAddress, circuit: `${circuitIndex}`, status }, { signal });
|
||||
const resp = await client.post<boolean>(endpoint, { community, ipAddress, circuit: `${circuitIndex}`, status }, { signal });
|
||||
const data = unwrapResponse(resp);
|
||||
return data;
|
||||
};
|
||||
|
||||
export const rebootSecurityBoxApi = async (ipAddress: string, options?: { stationCode?: Station['code']; signal?: AbortSignal }) => {
|
||||
// beidian安防箱重启
|
||||
export const rebootSecurityBoxBeidianApi = async (community: string, ipAddress: string, options?: { stationCode?: Station['code']; signal?: AbortSignal }) => {
|
||||
const { stationCode, signal } = options ?? {};
|
||||
const client = stationCode ? ndmClient : userClient;
|
||||
const prefix = stationCode ? `/${stationCode}` : '';
|
||||
const endpoint = `${prefix}/api/ndm/ndmSecurityBox/reboot`;
|
||||
const resp = await client.post<boolean>(endpoint, { community: 'public', ipAddress }, { signal });
|
||||
const resp = await client.post<boolean>(endpoint, { community, ipAddress }, { signal });
|
||||
const data = unwrapResponse(resp);
|
||||
return data;
|
||||
};
|
||||
|
||||
// ningtech安防箱切换空开状态
|
||||
export const turnCircuitStatusNingTechApi = async (community: string, ipAddress: string, circuitIndex: number, status: number, options?: { stationCode?: Station['code']; signal?: AbortSignal }) => {
|
||||
const { stationCode, signal } = options ?? {};
|
||||
const client = stationCode ? ndmClient : userClient;
|
||||
const prefix = stationCode ? `/${stationCode}` : '';
|
||||
const endpoint = `${prefix}/api/ndm/ndmSecurityBox/turnStatusNingTech`;
|
||||
const resp = await client.post<boolean>(endpoint, { community, ipAddress, circuit: `${circuitIndex}`, status }, { signal });
|
||||
const data = unwrapResponse(resp);
|
||||
return data;
|
||||
};
|
||||
|
||||
// ningtech安防箱重启
|
||||
export const rebootSecurityBoxNingTechApi = async (community: string, ipAddress: string, options?: { stationCode?: Station['code']; signal?: AbortSignal }) => {
|
||||
const { stationCode, signal } = options ?? {};
|
||||
const client = stationCode ? ndmClient : userClient;
|
||||
const prefix = stationCode ? `/${stationCode}` : '';
|
||||
const endpoint = `${prefix}/api/ndm/ndmSecurityBox/rebootNingTech`;
|
||||
const resp = await client.post<boolean>(endpoint, { community, ipAddress }, { signal });
|
||||
const data = unwrapResponse(resp);
|
||||
return data;
|
||||
};
|
||||
|
||||
@@ -1,5 +1,26 @@
|
||||
import { ndmClient, userClient, type MediaServerStatus, type SendRtpInfo, type Station } from '@/apis';
|
||||
import { unwrapResponse } from '@/utils';
|
||||
import { ndmClient, userClient, type HighAvailable, type MediaServerStatus, type SendRtpInfo, type Station } from '@/apis';
|
||||
import { unwrapNullableResponse, unwrapResponse } from '@/utils';
|
||||
import destr from 'destr';
|
||||
|
||||
// {
|
||||
// "code": 0,
|
||||
// "data": "{pyip:\"10.18.128.14\",vip:\"10.18.128.6\",changeDate:\"2026-03-23 15:55:00\"}",
|
||||
// "msg": "ok",
|
||||
// "path": null,
|
||||
// "extra": null,
|
||||
// "timestamp": "1774421387908",
|
||||
// "errorMsg": "",
|
||||
// "isSuccess": true
|
||||
// }
|
||||
export const getHighAvailableApi = async (options?: { stationCode?: Station['code']; signal?: AbortSignal }) => {
|
||||
const { stationCode, signal } = options ?? {};
|
||||
const client = stationCode ? ndmClient : userClient;
|
||||
const prefix = stationCode ? `/${stationCode}` : '';
|
||||
const endpoint = `${prefix}/api/ndm/ndmServiceAvailable/highAvailable/get`;
|
||||
const resp = await client.get<string | null>(endpoint, { signal });
|
||||
const data = unwrapNullableResponse(resp);
|
||||
return destr<HighAvailable | null>(data);
|
||||
};
|
||||
|
||||
export const getAllPushApi = async (options?: { stationCode?: Station['code']; signal?: AbortSignal }) => {
|
||||
const { stationCode, signal } = options ?? {};
|
||||
|
||||
@@ -1,16 +1,20 @@
|
||||
<script setup lang="ts">
|
||||
import { NCard, NDescriptions, NDescriptionsItem } from 'naive-ui';
|
||||
import { NCard, NDescriptions, NDescriptionsItem, NFlex, NText } from 'naive-ui';
|
||||
import { computed, toRefs } from 'vue';
|
||||
|
||||
const props = defineProps<{
|
||||
commonInfo?: Record<string, string>;
|
||||
commonInfo?: Array<{
|
||||
title: string;
|
||||
items: Array<{
|
||||
label: string;
|
||||
value: string;
|
||||
}>;
|
||||
}>;
|
||||
}>();
|
||||
|
||||
const { commonInfo } = toRefs(props);
|
||||
|
||||
const showCard = computed(() => !!commonInfo.value);
|
||||
|
||||
const commonInfoEntries = computed(() => Object.entries(commonInfo.value ?? {}));
|
||||
const showCard = computed(() => (commonInfo.value?.length ?? 0) > 0);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -18,11 +22,20 @@ const commonInfoEntries = computed(() => Object.entries(commonInfo.value ?? {}))
|
||||
<template #header>
|
||||
<div>设备信息</div>
|
||||
</template>
|
||||
<NDescriptions bordered label-placement="left" :columns="2">
|
||||
<template v-for="item in commonInfoEntries" :key="item[0]">
|
||||
<NDescriptionsItem :label="item[0]">{{ item[1] }}</NDescriptionsItem>
|
||||
</template>
|
||||
</NDescriptions>
|
||||
<template v-if="!!commonInfo && commonInfo.length > 0">
|
||||
<NFlex vertical :size="16">
|
||||
<template v-for="({ title, items }, index) in commonInfo" :key="`${title}-${index}`">
|
||||
<NFlex vertical>
|
||||
<NText strong :depth="3">{{ title }}</NText>
|
||||
<NDescriptions bordered :label-placement="'left'" :columns="2">
|
||||
<template v-for="({ label, value }, index) in items" :key="`${label}-${index}`">
|
||||
<NDescriptionsItem :label="label">{{ value }}</NDescriptionsItem>
|
||||
</template>
|
||||
</NDescriptions>
|
||||
</NFlex>
|
||||
</template>
|
||||
</NFlex>
|
||||
</template>
|
||||
</NCard>
|
||||
</template>
|
||||
|
||||
|
||||
+30
-12
@@ -1,3 +1,18 @@
|
||||
<script lang="ts">
|
||||
const parsePercentMetric = (raw?: string) => {
|
||||
const trimmed = raw?.trim();
|
||||
|
||||
if (!!trimmed) {
|
||||
const value = Number(trimmed);
|
||||
if (Number.isFinite(value)) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
};
|
||||
</script>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ClockCheckIcon, CpuIcon, HardDriveIcon, MemoryStickIcon } from 'lucide-vue-next';
|
||||
import { NCard, NFlex, NIcon, NProgress, type ProgressStatus } from 'naive-ui';
|
||||
@@ -21,24 +36,27 @@ const showCard = computed(() => {
|
||||
});
|
||||
|
||||
const cpuPercent = computed(() => {
|
||||
if (!cpuUsage?.value) return 0;
|
||||
return parseFloat(cpuUsage.value.replace('%', ''));
|
||||
return parsePercentMetric(cpuUsage.value);
|
||||
});
|
||||
|
||||
const memPercent = computed(() => {
|
||||
if (!memUsage?.value) return 0;
|
||||
return parseFloat(memUsage.value.replace('%', ''));
|
||||
return parsePercentMetric(memUsage.value);
|
||||
});
|
||||
|
||||
const diskPercent = computed(() => {
|
||||
if (!diskUsage?.value) return 0;
|
||||
return parseFloat(diskUsage.value.replace('%', ''));
|
||||
return parsePercentMetric(diskUsage.value);
|
||||
});
|
||||
|
||||
const formattedRunningTime = computed(() => {
|
||||
return (runningTime?.value ?? '-').replace('days', '天');
|
||||
});
|
||||
|
||||
const getProgressPercentage = (percent: number) => {
|
||||
if (percent < 0) return 0;
|
||||
if (percent > 100) return 100;
|
||||
return percent;
|
||||
};
|
||||
|
||||
const getProgressStatus = (percent: number): ProgressStatus => {
|
||||
if (percent >= 90) return 'error';
|
||||
if (percent >= 70) return 'warning';
|
||||
@@ -53,20 +71,20 @@ const getProgressStatus = (percent: number): ProgressStatus => {
|
||||
</template>
|
||||
<template #default>
|
||||
<NFlex vertical>
|
||||
<NFlex v-if="cpuUsage" style="width: 100%" align="center" :wrap="false">
|
||||
<NFlex v-if="cpuPercent" style="width: 100%" align="center" :wrap="false">
|
||||
<NIcon :component="CpuIcon" />
|
||||
<span style="word-break: keep-all">{{ cpuUsageLabel || 'CPU' }}</span>
|
||||
<NProgress :percentage="cpuPercent" :status="getProgressStatus(cpuPercent)">{{ cpuPercent }}%</NProgress>
|
||||
<NProgress :percentage="getProgressPercentage(cpuPercent)" :status="getProgressStatus(cpuPercent)">{{ cpuPercent }}%</NProgress>
|
||||
</NFlex>
|
||||
<NFlex v-if="memUsage" style="width: 100%" align="center" :wrap="false">
|
||||
<NFlex v-if="memPercent" style="width: 100%" align="center" :wrap="false">
|
||||
<NIcon :component="MemoryStickIcon" />
|
||||
<span style="word-break: keep-all">{{ memUsageLabel || '内存' }}</span>
|
||||
<NProgress :percentage="memPercent" :status="getProgressStatus(memPercent)">{{ memPercent }}%</NProgress>
|
||||
<NProgress :percentage="getProgressPercentage(memPercent)" :status="getProgressStatus(memPercent)">{{ memPercent }}%</NProgress>
|
||||
</NFlex>
|
||||
<NFlex v-if="diskUsage" style="width: 100%" align="center" :wrap="false">
|
||||
<NFlex v-if="diskPercent" style="width: 100%" align="center" :wrap="false">
|
||||
<NIcon :component="HardDriveIcon" />
|
||||
<span style="word-break: keep-all">{{ diskUsageLabel || '磁盘' }}</span>
|
||||
<NProgress :percentage="diskPercent" :status="getProgressStatus(diskPercent)">{{ diskPercent }}%</NProgress>
|
||||
<NProgress :percentage="getProgressPercentage(diskPercent)" :status="getProgressStatus(diskPercent)">{{ diskPercent }}%</NProgress>
|
||||
</NFlex>
|
||||
<NFlex v-if="runningTime" style="width: 100%" align="center" :wrap="false">
|
||||
<NIcon :component="ClockCheckIcon" />
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import type { ComponentInstance } from 'vue';
|
||||
import DeviceCommonCard from './device-common-card.vue';
|
||||
import DeviceHardwareCard from './device-hardware-card.vue';
|
||||
import DeviceHeaderCard from './device-header-card.vue';
|
||||
import NvrEnvCard from './nvr-env-card.vue';
|
||||
import NvrDiskCard from './nvr-disk-card.vue';
|
||||
import NvrRecordCheckCard from './nvr-record-check-card.vue';
|
||||
import SecurityBoxCircuitCard from './security-box-circuit-card.vue';
|
||||
@@ -9,10 +11,13 @@ import SecurityBoxEnvCard from './security-box-env-card.vue';
|
||||
import SwitchPortCard from './switch-port-card.vue';
|
||||
import SwitchPortLinkModal from './switch-port-link-modal.vue';
|
||||
|
||||
export type DeviceCommonCardProps = ComponentInstance<typeof DeviceCommonCard>['$props'];
|
||||
|
||||
export {
|
||||
DeviceCommonCard,
|
||||
DeviceHardwareCard,
|
||||
DeviceHeaderCard,
|
||||
NvrEnvCard,
|
||||
NvrDiskCard,
|
||||
NvrRecordCheckCard,
|
||||
SecurityBoxCircuitCard,
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
<script setup lang="ts">
|
||||
import type { NdmNvrFanInfo, NdmNvrPowerSupplyInfo } from '@/apis';
|
||||
import { FanIcon, PlugIcon } from 'lucide-vue-next';
|
||||
import { NCard, NFlex, NIcon, NTag } from 'naive-ui';
|
||||
import { computed, toRefs } from 'vue';
|
||||
|
||||
const props = defineProps<{
|
||||
fanInfo?: NdmNvrFanInfo[];
|
||||
powerSupplyInfo?: NdmNvrPowerSupplyInfo[];
|
||||
}>();
|
||||
|
||||
const { fanInfo, powerSupplyInfo } = toRefs(props);
|
||||
|
||||
const showCard = computed(() => {
|
||||
return Object.values(props).some((value) => !!value);
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NCard v-if="showCard" hoverable size="small">
|
||||
<template #header>
|
||||
<span>录像机环境状态</span>
|
||||
</template>
|
||||
<template #default>
|
||||
<NFlex vertical>
|
||||
<NTag v-for="info in fanInfo ?? []" :key="info['索引号']">
|
||||
<template #icon>
|
||||
<NIcon :component="FanIcon" />
|
||||
</template>
|
||||
<template #default>
|
||||
<span>风扇{{ info['索引号'] }}: {{ info['风扇转速(rpm)'] }} RPM</span>
|
||||
</template>
|
||||
</NTag>
|
||||
<NTag v-for="info in powerSupplyInfo ?? []" :key="info['索引号']">
|
||||
<template #icon>
|
||||
<NIcon :component="PlugIcon" />
|
||||
</template>
|
||||
<template #default>
|
||||
<span>电源{{ info['索引号'] }}: {{ info['电源状态'] }}</span>
|
||||
</template>
|
||||
</NTag>
|
||||
</NFlex>
|
||||
</template>
|
||||
</NCard>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
+15
-8
@@ -92,6 +92,8 @@ const abortController = ref<AbortController>(new AbortController());
|
||||
|
||||
const NVR_RECORD_CHECK_KEY = 'nvr-record-check-query';
|
||||
|
||||
const deviceUniqueKey = computed(() => [station.value.code, ndmDevice.value.id]);
|
||||
|
||||
const DAY_OFFSET = 90;
|
||||
|
||||
const {
|
||||
@@ -99,7 +101,7 @@ const {
|
||||
isFetching: loading,
|
||||
refetch: refetchRecordChecks,
|
||||
} = useQuery({
|
||||
queryKey: computed(() => [NVR_RECORD_CHECK_KEY, ndmDevice.value.id, ndmDevice.value.lastDiagTime]),
|
||||
queryKey: computed(() => [NVR_RECORD_CHECK_KEY, deviceUniqueKey.value, ndmDevice.value.lastDiagTime]),
|
||||
enabled: computed(() => activeRequests.value),
|
||||
refetchInterval: 30 * 1000,
|
||||
gcTime: 0,
|
||||
@@ -408,13 +410,13 @@ const ndmRecordChecksPaged = computed(() => {
|
||||
return ndmRecordChecksFiltered.value.slice(startIndex, endIndex);
|
||||
});
|
||||
|
||||
// 当设备ID、最后诊断时间或筛选类型变化时,重置分页为第一页
|
||||
watch([() => ndmDevice.value.id, () => ndmDevice.value.lastDiagTime, filterType, searchInputDebounced], () => {
|
||||
// 当车站号、设备IP、最后诊断时间或筛选类型变化时,重置分页为第一页
|
||||
watch([() => station.value.code, () => ndmDevice.value.ipAddress, () => ndmDevice.value.lastDiagTime, filterType, searchInputDebounced], () => {
|
||||
page.value = 1;
|
||||
});
|
||||
|
||||
// 当设备ID变化时,重置搜索内容,并将筛选类型重置为「全部」
|
||||
watch([() => ndmDevice.value.id], () => {
|
||||
// 当车站号、设备IP变化时,重置搜索内容,并将筛选类型重置为「全部」
|
||||
watch([() => station.value.code, () => ndmDevice.value.ipAddress], () => {
|
||||
searchInput.value = '';
|
||||
filterType.value = 'all';
|
||||
});
|
||||
@@ -626,9 +628,14 @@ const columns: DataTableColumns<DailyLossItem['chunks'][number]> = [
|
||||
<template #default>
|
||||
<template v-if="!!dailyCheckContext.info">
|
||||
<div>日期:{{ dailyCheckContext.info.date }}</div>
|
||||
<div>缺失时长:{{ formatDuration(dailyCheckContext.info.total, { withinDay: true }) }}</div>
|
||||
<div>缺失比例:{{ dailyCheckContext.info.percent.toFixed(2) }}%</div>
|
||||
<div v-if="dailyCheckContext.info.percent > 0" style="font-size: xx-small; opacity: 0.5; cursor: pointer" @click="onClickDailyCheck">点击查看详情</div>
|
||||
<template v-if="dailyCheckContext.info.percent > 0">
|
||||
<div>缺失时长:{{ formatDuration(dailyCheckContext.info.total, { withinDay: true }) }}</div>
|
||||
<div>缺失比例:{{ dailyCheckContext.info.percent.toFixed(2) }}%</div>
|
||||
<div style="font-size: xx-small; opacity: 0.5; cursor: pointer" @click="onClickDailyCheck">点击查看详情</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div>录像完整</div>
|
||||
</template>
|
||||
</template>
|
||||
</template>
|
||||
</NPopover>
|
||||
|
||||
+25
-11
@@ -2,8 +2,6 @@
|
||||
import {
|
||||
detailDeviceApi,
|
||||
probeDeviceApi,
|
||||
rebootSecurityBoxApi,
|
||||
turnCitcuitStatusApi,
|
||||
updateDeviceApi,
|
||||
type LinkDescription,
|
||||
type NdmDeviceResultVO,
|
||||
@@ -16,6 +14,7 @@ import { SecurityBoxCircuitLinkModal } from '@/components';
|
||||
import { usePermission } from '@/composables';
|
||||
import { SELECT_DEVICE_FN_INJECTION_KEY } from '@/constants';
|
||||
import { PERMISSION_TYPE_LITERALS } from '@/enums';
|
||||
import { normalizeSecurityBoxWriteCommunity, dispatchRebootSecurityBoxApi, dispatchTurnCircuitStatusApi, normalizeSecurityBoxCircuitIndex } from '@/helpers';
|
||||
import { useDeviceStore, useSettingStore } from '@/stores';
|
||||
import { parseErrorFeedback } from '@/utils';
|
||||
import { useMutation } from '@tanstack/vue-query';
|
||||
@@ -31,6 +30,7 @@ import { computed, inject, ref, toRefs } from 'vue';
|
||||
const props = defineProps<{
|
||||
ndmDevice: NdmSecurityBoxResultVO;
|
||||
station: Station;
|
||||
vendor?: string;
|
||||
circuits?: NdmSecurityBoxCircuit[];
|
||||
}>();
|
||||
|
||||
@@ -44,7 +44,7 @@ const { useLocalDB } = storeToRefs(settingStore);
|
||||
|
||||
const { hasPermission } = usePermission();
|
||||
|
||||
const { ndmDevice, station, circuits } = toRefs(props);
|
||||
const { ndmDevice, station, vendor, circuits } = toRefs(props);
|
||||
|
||||
const showCard = computed(() => !!circuits.value && circuits.value.length > 0);
|
||||
|
||||
@@ -52,6 +52,10 @@ const localCircuits = ref<NdmSecurityBoxCircuit[]>([]);
|
||||
|
||||
watchImmediate(circuits, (newCircuits) => {
|
||||
localCircuits.value = newCircuits?.map((circuit) => ({ ...circuit })) ?? [];
|
||||
const circuitCount = ndmDevice.value.circuitCount;
|
||||
if (!!circuitCount) {
|
||||
localCircuits.value = localCircuits.value.slice(0, circuitCount);
|
||||
}
|
||||
});
|
||||
|
||||
const getCircuitStatusTagType = (circuit: NdmSecurityBoxCircuit): TagProps['type'] => {
|
||||
@@ -79,13 +83,19 @@ const { mutate: turnStatus, isPending: turning } = useMutation({
|
||||
window.$loadingBar.start();
|
||||
|
||||
const { circuitIndex, newStatus } = params;
|
||||
if (!ndmDevice.value.ipAddress) {
|
||||
const community = normalizeSecurityBoxWriteCommunity(ndmDevice.value, vendor.value);
|
||||
const ipAddress = ndmDevice.value.ipAddress;
|
||||
if (!ipAddress) {
|
||||
throw new Error('设备IP地址不存在');
|
||||
}
|
||||
const status = newStatus ? 1 : 0;
|
||||
|
||||
const stationCode = station.value.code;
|
||||
const signal = abortController.value.signal;
|
||||
await turnCitcuitStatusApi(ndmDevice.value.ipAddress, circuitIndex, status, { stationCode, signal });
|
||||
|
||||
const turnCircuitStatusApi = dispatchTurnCircuitStatusApi(vendor.value);
|
||||
const normalizedCircuitIndex = normalizeSecurityBoxCircuitIndex(circuitIndex, vendor.value);
|
||||
await turnCircuitStatusApi(community, ipAddress, normalizedCircuitIndex, status, { stationCode, signal });
|
||||
await probeDeviceApi(ndmDevice.value, { stationCode, signal });
|
||||
return status;
|
||||
},
|
||||
@@ -110,13 +120,17 @@ const { mutate: reboot, isPending: rebooting } = useMutation({
|
||||
|
||||
window.$loadingBar.start();
|
||||
|
||||
if (!ndmDevice.value.ipAddress) {
|
||||
const community = normalizeSecurityBoxWriteCommunity(ndmDevice.value, vendor.value);
|
||||
const ipAddress = ndmDevice.value.ipAddress;
|
||||
if (!ipAddress) {
|
||||
throw new Error('设备IP地址不存在');
|
||||
}
|
||||
|
||||
const stationCode = station.value.code;
|
||||
const signal = abortController.value.signal;
|
||||
await rebootSecurityBoxApi(ndmDevice.value.ipAddress, { stationCode, signal });
|
||||
|
||||
const rebootSecurityBoxApi = dispatchRebootSecurityBoxApi(vendor.value);
|
||||
await rebootSecurityBoxApi(community, ipAddress, { stationCode, signal });
|
||||
},
|
||||
onSuccess: () => {
|
||||
window.$loadingBar.finish();
|
||||
@@ -202,7 +216,7 @@ const contextmenuOptions = computed<DropdownOption[]>(() => [
|
||||
if (!lowerDevice) return;
|
||||
window.$dialog.warning({
|
||||
title: '确认解除关联吗?',
|
||||
content: `将解除【电路${circuitIndex + 1}】与【${lowerDevice.name}】的关联关系。`,
|
||||
content: `将解除【空开${circuitIndex + 1}】与【${lowerDevice.name}】的关联关系。`,
|
||||
style: { width: '600px' },
|
||||
contentStyle: { height: '60px' },
|
||||
negativeText: '取消',
|
||||
@@ -299,7 +313,7 @@ const { mutate: unlinkDevice } = useMutation({
|
||||
<NCard v-if="showCard" hoverable size="small">
|
||||
<template #header>
|
||||
<NFlex align="center">
|
||||
<span>电路状态</span>
|
||||
<span>空开状态</span>
|
||||
<NPopconfirm :positive-text="'确认'" :negative-text="'取消'" @positive-click="() => reboot()">
|
||||
<template #trigger>
|
||||
<NButton secondary size="small" :loading="rebooting">重合闸</NButton>
|
||||
@@ -324,7 +338,7 @@ const { mutate: unlinkDevice } = useMutation({
|
||||
<span>{{ getCircuitStatusText(circuit) }}</span>
|
||||
</template>
|
||||
</NTag>
|
||||
<span>电路{{ circuitIndex + 1 }}</span>
|
||||
<span>空开{{ circuitIndex + 1 }}</span>
|
||||
</NFlex>
|
||||
<NFlex justify="end" align="center">
|
||||
<NPopconfirm :positive-text="'确认'" :negative-text="'取消'" @positive-click="() => turnStatus({ circuitIndex: circuitIndex, newStatus: circuit.status !== 1 })">
|
||||
@@ -332,7 +346,7 @@ const { mutate: unlinkDevice } = useMutation({
|
||||
<NSwitch size="small" :value="circuit.status === 1" :loading="turning" />
|
||||
</template>
|
||||
<template #default>
|
||||
<span>确定要{{ circuit.status === 1 ? '关闭' : '开启' }}电路{{ circuitIndex + 1 }}吗?</span>
|
||||
<span>确定要{{ circuit.status === 1 ? '关闭' : '开启' }}空开{{ circuitIndex + 1 }}吗?</span>
|
||||
</template>
|
||||
</NPopconfirm>
|
||||
</NFlex>
|
||||
|
||||
+2
-2
@@ -60,7 +60,7 @@ const { mutate: linkPortToDevice, isPending: linking } = useMutation({
|
||||
const upperDeviceDbId = ndmDevice.value.id;
|
||||
if (!upperDeviceDbId) throw new Error('本设备没有ID');
|
||||
|
||||
if (circuitIndex.value === undefined) throw new Error('该电路不存在');
|
||||
if (circuitIndex.value === undefined) throw new Error('该空开不存在');
|
||||
|
||||
if (!lowerDevice.value) throw new Error('请选择要关联的设备');
|
||||
const lowerDeviceType = tryGetDeviceType(lowerDevice.value?.deviceType);
|
||||
@@ -195,7 +195,7 @@ const onCancel = () => {
|
||||
<template #header>
|
||||
<span>{{ ndmDevice.name }}</span>
|
||||
<span> - </span>
|
||||
<span>电路{{ circuitIndex ? circuitIndex + 1 : '-' }}</span>
|
||||
<span>空开{{ circuitIndex ? circuitIndex + 1 : '-' }}</span>
|
||||
<span> - </span>
|
||||
<span>关联设备</span>
|
||||
</template>
|
||||
|
||||
+17
-22
@@ -16,40 +16,35 @@ const showCard = computed(() => {
|
||||
return Object.values(props).some((value) => !!value);
|
||||
});
|
||||
|
||||
// 门禁状态
|
||||
const accessControlStatus = computed(() => {
|
||||
if (!switches?.value || switches.value.length < 1) return null;
|
||||
const status = switches.value.at(0)!;
|
||||
return status === 0 ? '开门' : status === 1 ? '关门' : '-';
|
||||
});
|
||||
|
||||
// 防雷状态
|
||||
const lightningProtectionStatus = computed(() => {
|
||||
if (!switches?.value || switches.value.length < 2) return null;
|
||||
const status = switches.value.at(0)!;
|
||||
return status === 0 ? '失效' : status === 1 ? '生效' : '-';
|
||||
});
|
||||
|
||||
// 门禁状态
|
||||
const accessControlStatus = computed(() => {
|
||||
if (!switches?.value || switches.value.length < 1) return null;
|
||||
const status = switches.value.at(1)!;
|
||||
return status === 0 ? '正常' : status === 1 ? '失效' : '-';
|
||||
return status === 0 ? '开门' : status === 1 ? '关门' : '-';
|
||||
});
|
||||
|
||||
const getStatusTagType = (status: string | null) => {
|
||||
if (['正常'].includes(status ?? '')) return 'success';
|
||||
if (['失效'].includes(status ?? '')) return 'error';
|
||||
if (['生效', '关门'].includes(status ?? '')) return 'success';
|
||||
if (['失效', '开门'].includes(status ?? '')) return 'error';
|
||||
return 'default';
|
||||
};
|
||||
|
||||
const formattedFanSpeeds = computed(() => {
|
||||
if (!fanSpeeds?.value || fanSpeeds.value.length === 0) return null;
|
||||
return fanSpeeds.value.map((speed, index) => `风扇${index + 1}: ${speed} RPM`).join(', ');
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NCard v-if="showCard" hoverable size="small">
|
||||
<template #header>
|
||||
<span>安防箱状态</span>
|
||||
<span>安防箱环境状态</span>
|
||||
</template>
|
||||
<template #default>
|
||||
<NFlex vertical>
|
||||
<NTag>
|
||||
<NTag v-if="!!temperature">
|
||||
<template #icon>
|
||||
<NIcon :component="ThermometerIcon" />
|
||||
</template>
|
||||
@@ -57,7 +52,7 @@ const formattedFanSpeeds = computed(() => {
|
||||
<span>温度: {{ temperature }}℃</span>
|
||||
</template>
|
||||
</NTag>
|
||||
<NTag>
|
||||
<NTag v-if="!!humidity">
|
||||
<template #icon>
|
||||
<NIcon :component="DropletIcon" />
|
||||
</template>
|
||||
@@ -65,15 +60,15 @@ const formattedFanSpeeds = computed(() => {
|
||||
<span>湿度: {{ humidity }}%</span>
|
||||
</template>
|
||||
</NTag>
|
||||
<NTag>
|
||||
<NTag v-for="(speed, index) in fanSpeeds ?? []" :key="index">
|
||||
<template #icon>
|
||||
<NIcon :component="FanIcon" />
|
||||
</template>
|
||||
<template #default>
|
||||
<span>风扇: {{ formattedFanSpeeds }}</span>
|
||||
<span>风扇{{ index + 1 }}: {{ speed }} RPM</span>
|
||||
</template>
|
||||
</NTag>
|
||||
<NTag :type="getStatusTagType(accessControlStatus)">
|
||||
<NTag v-if="!!accessControlStatus" :type="getStatusTagType(accessControlStatus)">
|
||||
<template #icon>
|
||||
<NIcon :component="ShieldIcon" />
|
||||
</template>
|
||||
@@ -81,7 +76,7 @@ const formattedFanSpeeds = computed(() => {
|
||||
<span>门禁: {{ accessControlStatus }}</span>
|
||||
</template>
|
||||
</NTag>
|
||||
<NTag :type="getStatusTagType(lightningProtectionStatus)">
|
||||
<NTag v-if="!!lightningProtectionStatus" :type="getStatusTagType(lightningProtectionStatus)">
|
||||
<template #icon>
|
||||
<NIcon :component="ZapIcon" />
|
||||
</template>
|
||||
|
||||
@@ -33,7 +33,7 @@ const { hasPermission } = usePermission();
|
||||
|
||||
const { ndmDevice, station, ports } = toRefs(props);
|
||||
|
||||
const showCard = computed(() => !!ports.value);
|
||||
const showCard = computed(() => !!ports.value && ports.value.length > 0);
|
||||
|
||||
const switchSlots = computed(() => {
|
||||
// 解析端口名称,将端口按槽位进行分组
|
||||
|
||||
+2
-2
@@ -31,7 +31,7 @@ const { ndmDevice, station } = toRefs(props);
|
||||
const showDetailModal = ref(false);
|
||||
|
||||
const detailTableColumns: DataTableColumns<SecurityBoxCircuitRowData> = [
|
||||
{ title: '电路序号', key: 'number' },
|
||||
{ title: '空开序号', key: 'number' },
|
||||
{
|
||||
title: '状态',
|
||||
key: 'status',
|
||||
@@ -98,7 +98,7 @@ const tableColumns: DataTableColumns<SecurityBoxRuntimeRowData> = [
|
||||
},
|
||||
// { title: '开关状态', key: 'switches' },
|
||||
{
|
||||
title: '电路状态',
|
||||
title: '空开状态',
|
||||
key: 'circuits',
|
||||
render(rowData) {
|
||||
const { info } = rowData.diagInfo;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { NFlex } from 'naive-ui';
|
||||
import { DeviceCommonCard, DeviceHeaderCard } from '@/components';
|
||||
import { DeviceCommonCard, DeviceHeaderCard, type DeviceCommonCardProps } from '@/components';
|
||||
import type { NdmAlarmHostResultVO, Station } from '@/apis';
|
||||
import { computed, toRefs } from 'vue';
|
||||
|
||||
@@ -11,13 +11,18 @@ const props = defineProps<{
|
||||
|
||||
const { ndmDevice, station } = toRefs(props);
|
||||
|
||||
const commonInfo = computed(() => {
|
||||
const commonInfo = computed<DeviceCommonCardProps['commonInfo']>(() => {
|
||||
const { createdTime, updatedTime, manufacturer } = ndmDevice.value;
|
||||
return {
|
||||
创建时间: createdTime ?? '-',
|
||||
更新时间: updatedTime ?? '-',
|
||||
制造商: manufacturer ?? '-',
|
||||
};
|
||||
return [
|
||||
{
|
||||
title: '设备接入信息',
|
||||
items: [
|
||||
{ label: '创建时间', value: createdTime || '-' },
|
||||
{ label: '更新时间', value: updatedTime || '-' },
|
||||
{ label: '制造商', value: manufacturer || '-' },
|
||||
],
|
||||
},
|
||||
];
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
@@ -16,11 +16,12 @@ const isCameraTypeCode = (code: string): code is CameraType => {
|
||||
</script>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { NdmCameraResultVO, Station } from '@/apis';
|
||||
import { DeviceCommonCard, DeviceHeaderCard } from '@/components';
|
||||
import type { NdmCameraDiagInfo, NdmCameraResultVO, Station } from '@/apis';
|
||||
import { DeviceCommonCard, DeviceHardwareCard, DeviceHeaderCard, type DeviceCommonCardProps } from '@/components';
|
||||
import { useSettingStore } from '@/stores';
|
||||
import { useQuery, useQueryClient } from '@tanstack/vue-query';
|
||||
import axios from 'axios';
|
||||
import destr from 'destr';
|
||||
import { NDescriptions, NDescriptionsItem, NFlex } from 'naive-ui';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { computed, toRefs, watch } from 'vue';
|
||||
@@ -113,7 +114,17 @@ watch(activeRequests, (active) => {
|
||||
}
|
||||
});
|
||||
|
||||
const commonInfo = computed(() => {
|
||||
const lastDiagInfo = computed(() => {
|
||||
const result = destr<any>(ndmDevice.value.lastDiagInfo);
|
||||
if (!result) return null;
|
||||
if (typeof result !== 'object') return null;
|
||||
return result as NdmCameraDiagInfo;
|
||||
});
|
||||
|
||||
const commonInfo = computed<DeviceCommonCardProps['commonInfo']>(() => {
|
||||
const { stCommonInfo } = lastDiagInfo.value ?? {};
|
||||
const { 设备ID, 软件版本, 设备厂商, 设备别名, 设备型号, 硬件版本 } = stCommonInfo ?? {};
|
||||
const { ethInfo, ipInfo } = lastDiagInfo.value ?? {};
|
||||
const {
|
||||
createdTime,
|
||||
updatedTime,
|
||||
@@ -126,22 +137,58 @@ const commonInfo = computed(() => {
|
||||
onvifMinorIndex,
|
||||
icmpEnabled,
|
||||
community,
|
||||
ipAddress,
|
||||
//
|
||||
} = ndmDevice.value;
|
||||
return {
|
||||
创建时间: createdTime ?? '-',
|
||||
更新时间: updatedTime ?? '-',
|
||||
制造商: manufacturer ?? '-',
|
||||
GB28181启用: `${!!gb28181Enabled ? '是' : '否'}`,
|
||||
ONVIF端口: `${onvifPort ?? '-'}`,
|
||||
ONVIF用户名: onvifUsername ?? '-',
|
||||
ONVIF密码: onvifPassword ?? '-',
|
||||
ONVIF主流索引: `${onvifMajorIndex ?? '-'}`,
|
||||
ONVIF辅流索引: `${onvifMinorIndex ?? '-'}`,
|
||||
ICMP启用: `${!!icmpEnabled ? '是' : '否'}`,
|
||||
团体字符串: community ?? '-',
|
||||
};
|
||||
|
||||
let operStatus = '-';
|
||||
if (!!ethInfo?.operStatus) {
|
||||
operStatus = ethInfo?.operStatus === '1' ? '正常' : '异常';
|
||||
}
|
||||
|
||||
return [
|
||||
{
|
||||
title: '设备型号信息',
|
||||
items: [
|
||||
{ label: '设备ID', value: 设备ID || '-' },
|
||||
{ label: '软件版本', value: 软件版本 || '-' },
|
||||
{ label: '设备厂商', value: 设备厂商 || manufacturer || '-' },
|
||||
{ label: '设备别名', value: 设备别名 || '-' },
|
||||
{ label: '设备型号', value: 设备型号 || '-' },
|
||||
{ label: '硬件版本', value: 硬件版本 || '-' },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '设备网卡信息',
|
||||
items: [
|
||||
{ label: 'IP地址', value: ipAddress || '-' },
|
||||
{ label: '子网掩码', value: ipInfo?.mASK || '-' },
|
||||
{ label: 'MAC地址', value: ethInfo?.macAddress || '-' },
|
||||
{ label: '连接速率', value: ethInfo?.speed || '-' },
|
||||
{ label: 'MTU', value: ethInfo?.mTU || '-' },
|
||||
{ label: '运行状态', value: operStatus },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '设备接入信息',
|
||||
items: [
|
||||
{ label: '创建时间', value: createdTime || '-' },
|
||||
{ label: '更新时间', value: updatedTime || '-' },
|
||||
{ label: 'GB28181启用', value: `${!!gb28181Enabled ? '是' : '否'}` },
|
||||
{ label: 'ICMP启用', value: `${!!icmpEnabled ? '是' : '否'}` },
|
||||
{ label: 'ONVIF用户名', value: onvifUsername || '-' },
|
||||
{ label: 'ONVIF密码', value: onvifPassword || '-' },
|
||||
{ label: 'ONVIF主流索引', value: `${onvifMajorIndex ?? '-'}` },
|
||||
{ label: 'ONVIF辅流索引', value: `${onvifMinorIndex ?? '-'}` },
|
||||
{ label: 'ONVIF端口', value: `${onvifPort ?? '-'}` },
|
||||
{ label: '团体字符串', value: community || '-' },
|
||||
],
|
||||
},
|
||||
];
|
||||
});
|
||||
|
||||
const cpuUsage = computed(() => lastDiagInfo.value?.stCommonInfo?.CPU使用率);
|
||||
const memUsage = computed(() => lastDiagInfo.value?.stCommonInfo?.内存使用率);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -155,6 +202,7 @@ const commonInfo = computed(() => {
|
||||
</template>
|
||||
</DeviceHeaderCard>
|
||||
<DeviceCommonCard :common-info="commonInfo" />
|
||||
<DeviceHardwareCard :cpu-usage="cpuUsage" :mem-usage="memUsage" />
|
||||
</NFlex>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -1,6 +1,14 @@
|
||||
<script setup lang="ts">
|
||||
import type { NdmCameraResultVO, Station } from '@/apis';
|
||||
import { DeviceAlarmHistoryCard, DeviceIcmpHistoryCard, HistoryDiagFilterCard, type DeviceAlarmHistoryCardProps, type DeviceIcmpHistoryCardProps } from '@/components';
|
||||
import {
|
||||
DeviceAlarmHistoryCard,
|
||||
DeviceIcmpHistoryCard,
|
||||
DeviceUsageHistoryCard,
|
||||
HistoryDiagFilterCard,
|
||||
type DeviceAlarmHistoryCardProps,
|
||||
type DeviceIcmpHistoryCardProps,
|
||||
type DeviceUsageHistoryCardProps,
|
||||
} from '@/components';
|
||||
import dayjs from 'dayjs';
|
||||
import { NFlex, type SelectOption } from 'naive-ui';
|
||||
import { onMounted, ref, toRefs, watch } from 'vue';
|
||||
@@ -15,6 +23,7 @@ const { ndmDevice, station } = toRefs(props);
|
||||
const historyDiagOptions: SelectOption[] = [
|
||||
{ label: '设备状态', value: 'icmp' },
|
||||
{ label: '设备告警', value: 'alarm' },
|
||||
{ label: '硬件占用', value: 'usage' },
|
||||
];
|
||||
const getWeekRange = (): [number, number] => {
|
||||
const now = dayjs();
|
||||
@@ -27,7 +36,8 @@ const selected = ref<string[]>([...historyDiagOptions.map((option) => `${option.
|
||||
const loading = ref<boolean>(false);
|
||||
const icmpLoading = ref<boolean>(false);
|
||||
const alarmLoading = ref<boolean>(false);
|
||||
watch([icmpLoading, alarmLoading], (loadings) => {
|
||||
const usageLoading = ref<boolean>(false);
|
||||
watch([icmpLoading, alarmLoading, usageLoading], (loadings) => {
|
||||
loading.value = loadings.some((loading) => loading);
|
||||
});
|
||||
const icmpHistoryQueryFn = ref<() => void>();
|
||||
@@ -38,9 +48,14 @@ const alarmHistoryQueryFn = ref<() => void>();
|
||||
const onExposeAlarmHistoryQueryFn: DeviceAlarmHistoryCardProps['onExposeQueryFn'] = (queryFn) => {
|
||||
alarmHistoryQueryFn.value = queryFn;
|
||||
};
|
||||
const usageHistoryQueryFn = ref<() => void>();
|
||||
const onExposeUsageHistoryQueryFn: DeviceUsageHistoryCardProps['onExposeQueryFn'] = (queryFn) => {
|
||||
usageHistoryQueryFn.value = queryFn;
|
||||
};
|
||||
const queryData = () => {
|
||||
if (selected.value.includes('icmp')) icmpHistoryQueryFn.value?.();
|
||||
if (selected.value.includes('alarm')) alarmHistoryQueryFn.value?.();
|
||||
if (selected.value.includes('usage')) usageHistoryQueryFn.value?.();
|
||||
};
|
||||
const onQuery = () => {
|
||||
queryData();
|
||||
@@ -70,6 +85,16 @@ onMounted(() => {
|
||||
v-model:loading="alarmLoading"
|
||||
@expose-query-fn="onExposeAlarmHistoryQueryFn"
|
||||
/>
|
||||
<DeviceUsageHistoryCard
|
||||
v-if="selected.includes('usage')"
|
||||
:ndm-device="ndmDevice"
|
||||
:station="station"
|
||||
:cpu-usage-field="'stCommonInfo.CPU使用率'"
|
||||
:mem-usage-field="'stCommonInfo.内存使用率'"
|
||||
v-model:range="range"
|
||||
v-model:loading="usageLoading"
|
||||
@expose-query-fn="onExposeUsageHistoryQueryFn"
|
||||
/>
|
||||
</NFlex>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import type { NdmDecoderDiagInfo, NdmDecoderResultVO, Station } from '@/apis';
|
||||
import { DeviceCommonCard, DeviceHardwareCard, DeviceHeaderCard } from '@/components';
|
||||
import { DeviceCommonCard, DeviceHardwareCard, DeviceHeaderCard, type DeviceCommonCardProps } from '@/components';
|
||||
import destr from 'destr';
|
||||
import { NFlex } from 'naive-ui';
|
||||
import { computed, toRefs } from 'vue';
|
||||
@@ -19,17 +19,23 @@ const lastDiagInfo = computed(() => {
|
||||
return result as NdmDecoderDiagInfo;
|
||||
});
|
||||
|
||||
const commonInfo = computed(() => {
|
||||
const commonInfo = computed<DeviceCommonCardProps['commonInfo']>(() => {
|
||||
const { stCommonInfo } = lastDiagInfo.value ?? {};
|
||||
const { 设备ID, 软件版本, 设备厂商, 设备别名, 设备型号, 硬件版本 } = stCommonInfo ?? {};
|
||||
return {
|
||||
设备ID: 设备ID ?? '-',
|
||||
软件版本: 软件版本 ?? '-',
|
||||
设备厂商: 设备厂商 ?? '-',
|
||||
设备别名: 设备别名 ?? '-',
|
||||
设备型号: 设备型号 ?? '-',
|
||||
硬件版本: 硬件版本 ?? '-',
|
||||
};
|
||||
|
||||
return [
|
||||
{
|
||||
title: '设备型号信息',
|
||||
items: [
|
||||
{ label: '设备ID', value: 设备ID || '-' },
|
||||
{ label: '软件版本', value: 软件版本 || '-' },
|
||||
{ label: '设备厂商', value: 设备厂商 || '-' },
|
||||
{ label: '设备别名', value: 设备别名 || '-' },
|
||||
{ label: '设备型号', value: 设备型号 || '-' },
|
||||
{ label: '硬件版本', value: 硬件版本 || '-' },
|
||||
],
|
||||
},
|
||||
];
|
||||
});
|
||||
|
||||
const cpuUsage = computed(() => lastDiagInfo.value?.stCommonInfo?.CPU使用率);
|
||||
|
||||
@@ -36,7 +36,8 @@ const selected = ref([...historyDiagOptions.map((option) => `${option.value}`)])
|
||||
const loading = ref<boolean>(false);
|
||||
const icmpLoading = ref<boolean>(false);
|
||||
const alarmLoading = ref<boolean>(false);
|
||||
watch([icmpLoading, alarmLoading], (loadings) => {
|
||||
const usageLoading = ref<boolean>(false);
|
||||
watch([icmpLoading, alarmLoading, usageLoading], (loadings) => {
|
||||
loading.value = loadings.some((loading) => loading);
|
||||
});
|
||||
const icmpHistoryQueryFn = ref<() => void>();
|
||||
@@ -91,7 +92,7 @@ onMounted(() => {
|
||||
:cpu-usage-field="'stCommonInfo.CPU使用率'"
|
||||
:mem-usage-field="'stCommonInfo.内存使用率'"
|
||||
v-model:range="range"
|
||||
v-model:loading="alarmLoading"
|
||||
v-model:loading="usageLoading"
|
||||
@expose-query-fn="onExposeUsageHistoryQueryFn"
|
||||
/>
|
||||
</NFlex>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import type { NdmNvrDiagInfo, NdmNvrResultVO, Station } from '@/apis';
|
||||
import { DeviceCommonCard, DeviceHardwareCard, DeviceHeaderCard, NvrDiskCard, NvrRecordCheckCard } from '@/components';
|
||||
import { DeviceCommonCard, DeviceHardwareCard, DeviceHeaderCard, NvrDiskCard, NvrEnvCard, NvrRecordCheckCard, type DeviceCommonCardProps } from '@/components';
|
||||
import { isNvrCluster } from '@/helpers';
|
||||
import destr from 'destr';
|
||||
import { NFlex } from 'naive-ui';
|
||||
@@ -20,18 +20,40 @@ const lastDiagInfo = computed(() => {
|
||||
return result as NdmNvrDiagInfo;
|
||||
});
|
||||
|
||||
const commonInfo = computed(() => {
|
||||
const { stCommonInfo } = lastDiagInfo.value ?? {};
|
||||
if (!stCommonInfo) return undefined;
|
||||
const { 设备ID, 软件版本, 生产厂商, 设备别名, 设备型号, 硬件版本 } = stCommonInfo;
|
||||
return {
|
||||
设备ID: 设备ID ?? '-',
|
||||
软件版本: 软件版本 ?? '-',
|
||||
生产厂商: 生产厂商 ?? '-',
|
||||
设备别名: 设备别名 ?? '-',
|
||||
设备型号: 设备型号 ?? '-',
|
||||
硬件版本: 硬件版本 ?? '-',
|
||||
};
|
||||
const commonInfo = computed<DeviceCommonCardProps['commonInfo']>(() => {
|
||||
const { ipAddress } = ndmDevice.value;
|
||||
const { ethInfo, ipInfo, stCommonInfo } = lastDiagInfo.value ?? {};
|
||||
const { 设备ID, 软件版本, 生产厂商, 设备别名, 设备型号, 硬件版本 } = stCommonInfo ?? {};
|
||||
|
||||
let operStatus = '-';
|
||||
if (!!ethInfo?.operStatus) {
|
||||
operStatus = ethInfo?.operStatus === '1' ? '正常' : '异常';
|
||||
}
|
||||
|
||||
return [
|
||||
{
|
||||
title: '设备型号信息',
|
||||
items: [
|
||||
{ label: '设备ID', value: 设备ID || '-' },
|
||||
{ label: '软件版本', value: 软件版本 || '-' },
|
||||
{ label: '生产厂商', value: 生产厂商 || '-' },
|
||||
{ label: '设备别名', value: 设备别名 || '-' },
|
||||
{ label: '设备型号', value: 设备型号 || '-' },
|
||||
{ label: '硬件版本', value: 硬件版本 || '-' },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '设备网卡信息',
|
||||
items: [
|
||||
{ label: 'IP地址', value: ipAddress || '-' },
|
||||
{ label: '子网掩码', value: ipInfo?.mASK || '-' },
|
||||
{ label: 'MAC地址', value: ethInfo?.macAddress || '-' },
|
||||
{ label: '连接速率', value: ethInfo?.speed || '-' },
|
||||
{ label: 'MTU', value: ethInfo?.mTU || '-' },
|
||||
{ label: '运行状态', value: operStatus },
|
||||
],
|
||||
},
|
||||
];
|
||||
});
|
||||
|
||||
const cpuUsage = computed(() => lastDiagInfo.value?.stCommonInfo?.CPU使用率);
|
||||
@@ -39,6 +61,9 @@ const memUsage = computed(() => lastDiagInfo.value?.stCommonInfo?.内存使用
|
||||
|
||||
const diskHealth = computed(() => lastDiagInfo.value?.info?.diskHealth);
|
||||
const diskArray = computed(() => lastDiagInfo.value?.info?.groupInfoList);
|
||||
|
||||
const fanInfo = computed(() => lastDiagInfo.value?.cdFanInfo);
|
||||
const powerSupplyInfo = computed(() => lastDiagInfo.value?.cdPowerSupplyInfo);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -46,6 +71,7 @@ const diskArray = computed(() => lastDiagInfo.value?.info?.groupInfoList);
|
||||
<DeviceHeaderCard :ndm-device="ndmDevice" :station="station" />
|
||||
<DeviceCommonCard :common-info="commonInfo" />
|
||||
<DeviceHardwareCard :cpu-usage="cpuUsage" :mem-usage="memUsage" />
|
||||
<NvrEnvCard :fan-info="fanInfo" :power-supply-info="powerSupplyInfo" />
|
||||
<NvrDiskCard :disk-health="diskHealth" :disk-array="diskArray" />
|
||||
<template v-if="isNvrCluster(ndmDevice)">
|
||||
<NvrRecordCheckCard :ndm-device="ndmDevice" :station="station" />
|
||||
|
||||
@@ -42,8 +42,9 @@ const selected = ref([...historyDiagOptions.value.map((option) => `${option.valu
|
||||
const loading = ref<boolean>(false);
|
||||
const icmpLoading = ref<boolean>(false);
|
||||
const alarmLoading = ref<boolean>(false);
|
||||
const usageLoading = ref<boolean>(false);
|
||||
const diskLoading = ref<boolean>(false);
|
||||
watch([icmpLoading, alarmLoading, diskLoading], (loadings) => {
|
||||
watch([icmpLoading, alarmLoading, usageLoading, diskLoading], (loadings) => {
|
||||
loading.value = loadings.some((loading) => loading);
|
||||
});
|
||||
const icmpHistoryQueryFn = ref<() => void>();
|
||||
@@ -103,7 +104,7 @@ onMounted(() => {
|
||||
:cpu-usage-field="'stCommonInfo.CPU使用率'"
|
||||
:mem-usage-field="'stCommonInfo.内存使用率'"
|
||||
v-model:range="range"
|
||||
v-model:loading="alarmLoading"
|
||||
v-model:loading="usageLoading"
|
||||
@expose-query-fn="onExposeUsageHistoryQueryFn"
|
||||
/>
|
||||
<NvrDiskHistoryCard v-if="selected.includes('disk')" :ndm-device="ndmDevice" :station="station" v-model:range="range" v-model:loading="diskLoading" @expose-query-fn="onExposeDiskHistoryQueryFn" />
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import type { NdmSecurityBoxDiagInfo, NdmSecurityBoxResultVO, Station } from '@/apis';
|
||||
import { DeviceCommonCard, DeviceHardwareCard, DeviceHeaderCard, SecurityBoxCircuitCard, SecurityBoxEnvCard } from '@/components';
|
||||
import { DeviceCommonCard, DeviceHardwareCard, DeviceHeaderCard, SecurityBoxCircuitCard, SecurityBoxEnvCard, type DeviceCommonCardProps } from '@/components';
|
||||
import { SECURITY_BOX_VENDOR_LITERALS } from '@/helpers';
|
||||
import destr from 'destr';
|
||||
import { NFlex } from 'naive-ui';
|
||||
import { computed, toRefs } from 'vue';
|
||||
@@ -19,17 +20,54 @@ const lastDiagInfo = computed(() => {
|
||||
return result as NdmSecurityBoxDiagInfo;
|
||||
});
|
||||
|
||||
const commonInfo = computed(() => {
|
||||
const { stCommonInfo } = lastDiagInfo.value ?? {};
|
||||
// 解析安防箱厂商
|
||||
const vendor = computed(() => {
|
||||
// 先读取诊断信息中的 stCommonInfo 中的设备厂商
|
||||
if (!!lastDiagInfo.value?.stCommonInfo?.设备厂商) {
|
||||
return lastDiagInfo.value.stCommonInfo.设备厂商.trim().toLocaleLowerCase();
|
||||
}
|
||||
// 如果 stCommonInfo 中没有设备厂商,再读取 ndmDevice 中的 manufacturer
|
||||
if (!!ndmDevice.value.manufacturer) {
|
||||
return ndmDevice.value.manufacturer.trim().toLocaleLowerCase();
|
||||
}
|
||||
// 如果 ndmDevice 中也没有 manufacturer,返回 beidian 作为兜底
|
||||
return SECURITY_BOX_VENDOR_LITERALS.beidian;
|
||||
});
|
||||
|
||||
const commonInfo = computed<DeviceCommonCardProps['commonInfo']>(() => {
|
||||
const { ipAddress } = ndmDevice.value;
|
||||
const { ethInfo, ipInfo, stCommonInfo } = lastDiagInfo.value ?? {};
|
||||
const { 设备ID, 软件版本, 设备厂商, 设备别名, 设备型号, 硬件版本 } = stCommonInfo ?? {};
|
||||
return {
|
||||
设备ID: 设备ID ?? '',
|
||||
软件版本: 软件版本 ?? '',
|
||||
设备厂商: 设备厂商 ?? '',
|
||||
设备别名: 设备别名 ?? '',
|
||||
设备型号: 设备型号 ?? '',
|
||||
硬件版本: 硬件版本 ?? '',
|
||||
};
|
||||
|
||||
let operStatus = '-';
|
||||
if (!!ethInfo?.operStatus) {
|
||||
operStatus = ethInfo?.operStatus === '1' ? '正常' : '异常';
|
||||
}
|
||||
|
||||
return [
|
||||
{
|
||||
title: '设备型号信息',
|
||||
items: [
|
||||
{ label: '设备ID', value: 设备ID || '-' },
|
||||
{ label: '软件版本', value: 软件版本 || '-' },
|
||||
{ label: '设备厂商', value: 设备厂商 || '-' },
|
||||
{ label: '设备别名', value: 设备别名 || '-' },
|
||||
{ label: '设备型号', value: 设备型号 || '-' },
|
||||
{ label: '硬件版本', value: 硬件版本 || '-' },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '设备网卡信息',
|
||||
items: [
|
||||
{ label: 'IP地址', value: ipAddress || '-' },
|
||||
{ label: '子网掩码', value: ipInfo?.mASK || '-' },
|
||||
{ label: 'MAC地址', value: ethInfo?.macAddress || '-' },
|
||||
{ label: '连接速率', value: ethInfo?.speed || '-' },
|
||||
{ label: 'MTU', value: ethInfo?.mTU || '-' },
|
||||
{ label: '运行状态', value: operStatus },
|
||||
],
|
||||
},
|
||||
];
|
||||
});
|
||||
|
||||
const cpuUsage = computed(() => lastDiagInfo.value?.stCommonInfo?.CPU使用率);
|
||||
@@ -49,7 +87,7 @@ const circuits = computed(() => lastDiagInfo.value?.info?.at(0)?.circuits);
|
||||
<DeviceCommonCard :common-info="commonInfo" />
|
||||
<DeviceHardwareCard :cpu-usage="cpuUsage" :mem-usage="memUsage" />
|
||||
<SecurityBoxEnvCard :fan-speeds="fanSpeeds" :temperature="temperature" :humidity="humidity" :switches="switches" />
|
||||
<SecurityBoxCircuitCard :ndm-device="ndmDevice" :station="station" :circuits="circuits" />
|
||||
<SecurityBoxCircuitCard :ndm-device="ndmDevice" :station="station" :vendor="vendor" :circuits="circuits" />
|
||||
</NFlex>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import { useMutation } from '@tanstack/vue-query';
|
||||
import { isCancel } from 'axios';
|
||||
import destr from 'destr';
|
||||
import { isString } from 'es-toolkit';
|
||||
import { NButton, NCard, NFlex, NForm, NFormItem, NFormItemGi, NGrid, NInput, NSwitch, type FormInst, type FormRules } from 'naive-ui';
|
||||
import { NButton, NCard, NFlex, NForm, NFormItem, NFormItemGi, NGrid, NInput, NInputNumber, NSwitch, type FormInst, type FormRules } from 'naive-ui';
|
||||
import { computed, onBeforeUnmount, ref, toRefs, useTemplateRef, watch } from 'vue';
|
||||
|
||||
const props = defineProps<{
|
||||
@@ -126,9 +126,15 @@ onBeforeUnmount(() => {
|
||||
<NFormItem label-placement="left" label="型号">
|
||||
<NInput v-model:value="localDevice.model" />
|
||||
</NFormItem>
|
||||
<NFormItem label-placement="left" label="开关数量">
|
||||
<NInputNumber clearable v-model:value="localDevice.circuitCount" />
|
||||
</NFormItem>
|
||||
<NFormItem label-placement="left" label="团体字符串">
|
||||
<NInput v-model:value="localDevice.community" />
|
||||
</NFormItem>
|
||||
<NFormItem label-placement="left" label="团体字符串(写)">
|
||||
<NInput v-model:value="localDevice.writeCommunity" />
|
||||
</NFormItem>
|
||||
<NFormItem label-placement="left" label="设备描述">
|
||||
<NInput v-model:value="localDevice.description" />
|
||||
</NFormItem>
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import ServerAlive from './server-alive.vue';
|
||||
import ServerCard from './server-card.vue';
|
||||
import ServerCurrentDiag from './server-current-diag.vue';
|
||||
import ServerHighAvailable from './server-high-available.vue';
|
||||
import ServerHistoryDiag from './server-history-diag.vue';
|
||||
import ServerStreamPush from './server-stream-push.vue';
|
||||
import ServerUpdate from './server-update.vue';
|
||||
|
||||
export { ServerAlive, ServerCard, ServerCurrentDiag, ServerHistoryDiag, ServerUpdate, ServerStreamPush };
|
||||
export { ServerAlive, ServerCard, ServerCurrentDiag, ServerHighAvailable, ServerHistoryDiag, ServerUpdate, ServerStreamPush };
|
||||
|
||||
@@ -23,8 +23,11 @@ const deviceType = computed(() => tryGetDeviceType(ndmDevice.value.deviceType));
|
||||
|
||||
const MEDIA_SERVER_ALIVE_QUERY_KEY = 'media-server-alive-query';
|
||||
const VIDEO_SERVER_ALIVE_QUERY_KEY = 'video-server-alive-query';
|
||||
|
||||
const deviceUniqueKey = computed(() => [station.value.code, ndmDevice.value.id]);
|
||||
|
||||
const { data: isMediaServerAlive } = useQuery({
|
||||
queryKey: computed(() => [MEDIA_SERVER_ALIVE_QUERY_KEY, ndmDevice.value.id, ndmDevice.value.lastDiagTime]),
|
||||
queryKey: computed(() => [MEDIA_SERVER_ALIVE_QUERY_KEY, deviceUniqueKey.value, ndmDevice.value.lastDiagTime]),
|
||||
enabled: computed(() => activeRequests.value && deviceType.value === DEVICE_TYPE_LITERALS.ndmMediaServer),
|
||||
refetchInterval: 30 * 1000,
|
||||
gcTime: 0,
|
||||
@@ -34,7 +37,7 @@ const { data: isMediaServerAlive } = useQuery({
|
||||
},
|
||||
});
|
||||
const { data: isSipServerAlive } = useQuery({
|
||||
queryKey: computed(() => [VIDEO_SERVER_ALIVE_QUERY_KEY, ndmDevice.value.id, ndmDevice.value.lastDiagTime]),
|
||||
queryKey: computed(() => [VIDEO_SERVER_ALIVE_QUERY_KEY, deviceUniqueKey.value, ndmDevice.value.lastDiagTime]),
|
||||
enabled: computed(() => activeRequests.value && deviceType.value === DEVICE_TYPE_LITERALS.ndmVideoServer),
|
||||
refetchInterval: 30 * 1000,
|
||||
gcTime: 0,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { type NdmServerDiagInfo, type NdmServerResultVO, type Station } from '@/apis';
|
||||
import { DeviceHardwareCard, DeviceHeaderCard, ServerAlive, ServerStreamPush } from '@/components';
|
||||
import { DeviceCommonCard, DeviceHardwareCard, DeviceHeaderCard, ServerAlive, ServerHighAvailable, ServerStreamPush, type DeviceCommonCardProps } from '@/components';
|
||||
import destr from 'destr';
|
||||
import { NFlex } from 'naive-ui';
|
||||
import { computed, toRefs } from 'vue';
|
||||
@@ -19,6 +19,30 @@ const lastDiagInfo = computed(() => {
|
||||
return result as NdmServerDiagInfo;
|
||||
});
|
||||
|
||||
const commonInfo = computed<DeviceCommonCardProps['commonInfo']>(() => {
|
||||
const { ipAddress } = ndmDevice.value;
|
||||
const { ethInfo, ipInfo } = lastDiagInfo.value ?? {};
|
||||
|
||||
let operStatus = '-';
|
||||
if (!!ethInfo?.operStatus) {
|
||||
operStatus = ethInfo?.operStatus === '1' ? '正常' : '异常';
|
||||
}
|
||||
|
||||
return [
|
||||
{
|
||||
title: '设备网卡信息',
|
||||
items: [
|
||||
{ label: 'IP地址', value: ipAddress || '-' },
|
||||
{ label: '子网掩码', value: ipInfo?.mASK || '-' },
|
||||
{ label: 'MAC地址', value: ethInfo?.macAddress || '-' },
|
||||
{ label: '连接速率', value: ethInfo?.speed || '-' },
|
||||
{ label: 'MTU', value: ethInfo?.mTU || '-' },
|
||||
{ label: '运行状态', value: operStatus },
|
||||
],
|
||||
},
|
||||
];
|
||||
});
|
||||
|
||||
const cpuUsage = computed(() => lastDiagInfo.value?.commInfo?.CPU使用率);
|
||||
const memUsage = computed(() => lastDiagInfo.value?.commInfo?.内存使用率);
|
||||
const diskUsage = computed(() => lastDiagInfo.value?.commInfo?.磁盘使用率);
|
||||
@@ -27,7 +51,9 @@ const runningTime = computed(() => lastDiagInfo.value?.commInfo?.系统运行时
|
||||
|
||||
<template>
|
||||
<NFlex vertical>
|
||||
<ServerHighAvailable :ndm-device="ndmDevice" :station="station" />
|
||||
<DeviceHeaderCard :ndm-device="ndmDevice" :station="station" />
|
||||
<DeviceCommonCard :common-info="commonInfo" />
|
||||
<DeviceHardwareCard running-time-label="服务器运行时间" :cpu-usage="cpuUsage" :mem-usage="memUsage" :disk-usage="diskUsage" :running-time="runningTime" />
|
||||
<ServerAlive :ndm-device="ndmDevice" :station="station" />
|
||||
<ServerStreamPush :ndm-device="ndmDevice" :station="station" />
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
<script setup lang="ts">
|
||||
import { getHighAvailableApi, type NdmServerResultVO, type Station } from '@/apis';
|
||||
import { DEVICE_TYPE_LITERALS, tryGetDeviceType } from '@/enums';
|
||||
import { useSettingStore } from '@/stores';
|
||||
import { useQuery, useQueryClient } from '@tanstack/vue-query';
|
||||
import { NAlert, NFlex } from 'naive-ui';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { computed, toRefs, watch } from 'vue';
|
||||
|
||||
const props = defineProps<{
|
||||
ndmDevice: NdmServerResultVO;
|
||||
station: Station;
|
||||
}>();
|
||||
|
||||
const settingStore = useSettingStore();
|
||||
const { activeRequests } = storeToRefs(settingStore);
|
||||
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const { ndmDevice, station } = toRefs(props);
|
||||
|
||||
const deviceType = computed(() => tryGetDeviceType(ndmDevice.value.deviceType));
|
||||
|
||||
const isVideoServer = computed(() => deviceType.value === DEVICE_TYPE_LITERALS.ndmVideoServer);
|
||||
|
||||
const SERVER_HIGH_AVAILABLE_QUERY_KEY = 'server-high-available-query';
|
||||
|
||||
const deviceUniqueKey = computed(() => [station.value.code, ndmDevice.value.id]);
|
||||
|
||||
const { data: highAvailable } = useQuery({
|
||||
queryKey: computed(() => [SERVER_HIGH_AVAILABLE_QUERY_KEY, deviceUniqueKey.value, ndmDevice.value.lastDiagTime]),
|
||||
enabled: computed(() => activeRequests.value && isVideoServer.value),
|
||||
refetchInterval: 30 * 1000,
|
||||
gcTime: 0,
|
||||
queryFn: async ({ signal }) => {
|
||||
const highAvailable = await getHighAvailableApi({ stationCode: station.value.code, signal });
|
||||
return highAvailable;
|
||||
},
|
||||
});
|
||||
watch(activeRequests, (active) => {
|
||||
if (!active) {
|
||||
queryClient.cancelQueries({ queryKey: [SERVER_HIGH_AVAILABLE_QUERY_KEY] });
|
||||
}
|
||||
});
|
||||
|
||||
const showCard = computed(() => {
|
||||
const { pyip: physicalIp } = highAvailable.value ?? {};
|
||||
const ipAddressMatched = physicalIp === ndmDevice.value.ipAddress;
|
||||
return isVideoServer.value && ipAddressMatched;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NAlert v-if="showCard && !!highAvailable" :bordered="false" type="success">
|
||||
<template #header>
|
||||
<NFlex :align="'center'">
|
||||
<div>正在提供服务</div>
|
||||
<NFlex :align="'center'" style="font-size: smaller">
|
||||
<div>虚拟IP: {{ highAvailable.vip }}</div>
|
||||
<div>启用时间: {{ highAvailable.changeDate }}</div>
|
||||
</NFlex>
|
||||
</NFlex>
|
||||
</template>
|
||||
</NAlert>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
@@ -25,8 +25,10 @@ const showCard = computed(() => deviceType.value === DEVICE_TYPE_LITERALS.ndmMed
|
||||
|
||||
const SERVER_STREAM_PUSH_KEY = 'server-stream-push-query';
|
||||
|
||||
const deviceUniqueKey = computed(() => [station.value.code, ndmDevice.value.id]);
|
||||
|
||||
const { data: streamPushes } = useQuery({
|
||||
queryKey: computed(() => [SERVER_STREAM_PUSH_KEY, ndmDevice.value.id, ndmDevice.value.lastDiagTime]),
|
||||
queryKey: computed(() => [SERVER_STREAM_PUSH_KEY, deviceUniqueKey.value, ndmDevice.value.lastDiagTime]),
|
||||
enabled: computed(() => activeRequests.value && showCard.value),
|
||||
refetchInterval: 30 * 1000,
|
||||
gcTime: 0,
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
<script lang="ts">
|
||||
const createDeviceNodeKey = (stationCode?: Station['code'], device?: NdmDeviceResultVO) => {
|
||||
return `${stationCode ?? ''}-${device?.id ?? ''}`;
|
||||
};
|
||||
</script>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { initStationDevices, type NdmDeviceResultVO, type NdmNvrResultVO, type Station } from '@/apis';
|
||||
import { useDeviceTree, usePermission, type UseDeviceTreeReturn } from '@/composables';
|
||||
@@ -11,15 +17,19 @@ import {
|
||||
NButton,
|
||||
NDropdown,
|
||||
NFlex,
|
||||
NGrid,
|
||||
NGridItem,
|
||||
NInput,
|
||||
NRadio,
|
||||
NRadioGroup,
|
||||
NSelect,
|
||||
NTab,
|
||||
NTabs,
|
||||
NTag,
|
||||
NTree,
|
||||
useThemeVars,
|
||||
type DropdownOption,
|
||||
type SelectOption,
|
||||
type TagProps,
|
||||
type TreeInst,
|
||||
type TreeOption,
|
||||
@@ -106,7 +116,7 @@ watchImmediate(selectedDeviceType, (newDeviceType) => {
|
||||
}
|
||||
});
|
||||
|
||||
const selectedKeys = computed(() => (selectedDevice.value?.id ? [selectedDevice.value.id] : undefined));
|
||||
const selectedKeys = computed(() => (selectedDevice.value?.id ? [createDeviceNodeKey(selectedStationCode.value, selectedDevice.value)] : undefined));
|
||||
watch([selectedKeys, selectedDevice, selectedStationCode], ([, device, code]) => {
|
||||
if (device && code) {
|
||||
onSelectDevice(device, code);
|
||||
@@ -312,26 +322,26 @@ const lineDeviceTreeData = computed<Record<Station['code'], TreeOption[]>>(() =>
|
||||
key: stationCode,
|
||||
prefix: () => renderStationNodePrefix(station),
|
||||
suffix: () => renderIcmpStatistics(onlineDevices?.length ?? 0, offlineDevices?.length ?? 0, devices?.length ?? 0),
|
||||
children: nvrClusters.map<TreeOption>((nvrCluster) => {
|
||||
children: nvrClusters.map<TreeOption>((cluster) => {
|
||||
return {
|
||||
label: `${nvrCluster.name}`,
|
||||
key: nvrCluster.id ?? `${nvrCluster.name}`,
|
||||
prefix: () => renderDeviceNodePrefix(nvrCluster, stationCode),
|
||||
suffix: () => `${nvrCluster.ipAddress}`,
|
||||
children: nvrSingletons.map<TreeOption>((nvr) => {
|
||||
label: `${cluster.name}`,
|
||||
key: createDeviceNodeKey(stationCode, cluster),
|
||||
prefix: () => renderDeviceNodePrefix(cluster, stationCode),
|
||||
suffix: () => `${cluster.ipAddress}`,
|
||||
children: nvrSingletons.map<TreeOption>((device) => {
|
||||
return {
|
||||
label: `${nvr.name}`,
|
||||
key: nvr.id ?? `${nvr.name}`,
|
||||
prefix: () => renderDeviceNodePrefix(nvr, stationCode),
|
||||
suffix: () => `${nvr.ipAddress}`,
|
||||
label: `${device.name}`,
|
||||
key: createDeviceNodeKey(stationCode, device),
|
||||
prefix: () => renderDeviceNodePrefix(device, stationCode),
|
||||
suffix: () => `${device.ipAddress}`,
|
||||
// 当选择设备时,能获取到设备的所有信息,以及设备所属的车站
|
||||
stationCode,
|
||||
device: nvr,
|
||||
device: device,
|
||||
};
|
||||
}),
|
||||
// 当选择设备时,能获取到设备的所有信息,以及设备所属的车站
|
||||
stationCode,
|
||||
device: nvrCluster,
|
||||
device: cluster,
|
||||
};
|
||||
}),
|
||||
stationCode,
|
||||
@@ -348,7 +358,7 @@ const lineDeviceTreeData = computed<Record<Station['code'], TreeOption[]>>(() =>
|
||||
const device = dev as NdmDeviceResultVO;
|
||||
return {
|
||||
label: `${device.name}`,
|
||||
key: `${device.id}`,
|
||||
key: createDeviceNodeKey(stationCode, device),
|
||||
prefix: () => renderDeviceNodePrefix(device, stationCode),
|
||||
suffix: () => `${device.ipAddress}`,
|
||||
// 当选择设备时,能获取到设备的所有信息,以及设备所属的车站
|
||||
@@ -379,16 +389,16 @@ const stationDeviceTreeData = computed<TreeOption[]>(() => {
|
||||
label: `${DEVICE_TYPE_NAMES[deviceType]}`,
|
||||
key: deviceType,
|
||||
suffix: () => renderIcmpStatistics(onlineCount, offlineCount, nvrs.length),
|
||||
children: clusters.map<TreeOption>((device) => {
|
||||
children: clusters.map<TreeOption>((cluster) => {
|
||||
return {
|
||||
label: `${device.name}`,
|
||||
key: `${device.id}`,
|
||||
prefix: () => renderDeviceNodePrefix(device, stationCode),
|
||||
suffix: () => `${device.ipAddress}`,
|
||||
label: `${cluster.name}`,
|
||||
key: createDeviceNodeKey(stationCode, cluster),
|
||||
prefix: () => renderDeviceNodePrefix(cluster, stationCode),
|
||||
suffix: () => `${cluster.ipAddress}`,
|
||||
children: singletons.map<TreeOption>((device) => {
|
||||
return {
|
||||
label: `${device.name}`,
|
||||
key: `${device.id}`,
|
||||
key: createDeviceNodeKey(stationCode, device),
|
||||
prefix: () => renderDeviceNodePrefix(device, stationCode),
|
||||
suffix: () => `${device.ipAddress}`,
|
||||
stationCode,
|
||||
@@ -396,7 +406,7 @@ const stationDeviceTreeData = computed<TreeOption[]>(() => {
|
||||
};
|
||||
}),
|
||||
stationCode,
|
||||
device,
|
||||
device: cluster,
|
||||
};
|
||||
}),
|
||||
stationCode,
|
||||
@@ -410,7 +420,7 @@ const stationDeviceTreeData = computed<TreeOption[]>(() => {
|
||||
children: stationDevices[deviceType].map<TreeOption>((device) => {
|
||||
return {
|
||||
label: `${device.name}`,
|
||||
key: `${device.id}`,
|
||||
key: createDeviceNodeKey(stationCode, device),
|
||||
prefix: () => renderDeviceNodePrefix(device, stationCode),
|
||||
suffix: () => `${device.ipAddress}`,
|
||||
stationCode,
|
||||
@@ -425,20 +435,26 @@ const stationDeviceTreeData = computed<TreeOption[]>(() => {
|
||||
|
||||
// ========== 设备树搜索 ==========
|
||||
const searchInput = ref('');
|
||||
const searchTypeOptions: SelectOption[] = [
|
||||
{ label: '设备名称', value: 'name' },
|
||||
{ label: 'IP地址', value: 'ipAddress' },
|
||||
];
|
||||
type SearchType = 'name' | 'ipAddress';
|
||||
const typeInput = ref<SearchType>('name');
|
||||
const statusInput = ref('');
|
||||
// 设备树将搜索框和单选框的值都交给NTree的pattern属性
|
||||
// 设备树将搜索框、选择器以及单选框的值都交给NTree的pattern属性
|
||||
// 但是如果一个车站下没有匹配的设备,那么这个车站节点也不会显示
|
||||
const searchPattern = computed(() => {
|
||||
const search = searchInput.value;
|
||||
const status = statusInput.value;
|
||||
if (!search && !status) return ''; // 如果pattern非空会导致NTree组件认为筛选完成,UI上发生全量匹配
|
||||
return JSON.stringify({ search: searchInput.value, status: statusInput.value });
|
||||
return JSON.stringify({ search: searchInput.value, type: typeInput.value, status: statusInput.value });
|
||||
});
|
||||
const searchFilter = (pattern: string, node: TreeOption): boolean => {
|
||||
const { search, status } = destr<{ search: string; status: string }>(pattern);
|
||||
const { search, type, status } = destr<{ search: string; type: SearchType; status: string }>(pattern);
|
||||
const device = node['device'] as NdmDeviceResultVO | undefined;
|
||||
const { name, ipAddress, deviceId, deviceStatus } = device ?? {};
|
||||
const searchMatched = (name ?? '').includes(search) || (ipAddress ?? '').includes(search) || (deviceId ?? '').includes(search);
|
||||
const { deviceStatus } = device ?? {};
|
||||
const searchMatched = !!device?.[type]?.includes(search);
|
||||
const statusMatched = status === '' || status === deviceStatus;
|
||||
return searchMatched && statusMatched;
|
||||
};
|
||||
@@ -452,6 +468,7 @@ const onFoldDeviceTree = () => {
|
||||
};
|
||||
const onLocateDeviceTree = async () => {
|
||||
if (!selectedStationCode.value) return;
|
||||
const stationCode = selectedStationCode.value;
|
||||
if (!selectedDevice.value) return;
|
||||
const deviceType = tryGetDeviceType(selectedDevice.value.deviceType);
|
||||
if (!deviceType) return;
|
||||
@@ -463,24 +480,24 @@ const onLocateDeviceTree = async () => {
|
||||
activeTab.value = deviceType;
|
||||
|
||||
// 展开选择的车站
|
||||
expandedKeys.value.push(selectedStationCode.value);
|
||||
expandedKeys.value.push(stationCode);
|
||||
|
||||
// 当选择录像机时,如果不是集群,进一步展开该录像机所在的集群节点
|
||||
if (deviceType === DEVICE_TYPE_LITERALS.ndmNvr) {
|
||||
const stationDevices = lineDevices.value[selectedStationCode.value];
|
||||
const stationDevices = lineDevices.value[stationCode];
|
||||
if (stationDevices) {
|
||||
const selectedNvr = selectedDevice.value as NdmNvrResultVO;
|
||||
if (!isNvrCluster(selectedNvr)) {
|
||||
const nvrs = stationDevices[DEVICE_TYPE_LITERALS.ndmNvr];
|
||||
const clusters = nvrs.filter((nvr) => isNvrCluster(nvr) && nvr.clusterList?.includes(selectedNvr.clusterList ?? ''));
|
||||
expandedKeys.value.push(...clusters.map((nvr) => `${nvr.id}`));
|
||||
expandedKeys.value.push(...clusters.map((nvr) => createDeviceNodeKey(stationCode, nvr)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 等待设备树展开完成,滚动到选择的设备
|
||||
await nextTick();
|
||||
deviceTreeInst.value.scrollTo({ key: `${selectedDevice.value.id}`, behavior: 'smooth' });
|
||||
deviceTreeInst.value.scrollTo({ key: createDeviceNodeKey(stationCode, selectedDevice.value), behavior: 'smooth' });
|
||||
|
||||
animated.value = true;
|
||||
};
|
||||
@@ -523,7 +540,14 @@ onMounted(() => {
|
||||
<div style="height: 100%; display: flex; flex-direction: column">
|
||||
<!-- 搜索和筛选 -->
|
||||
<div style="padding: 12px; flex: 0 0 auto">
|
||||
<NInput v-model:value="searchInput" placeholder="搜索设备名称、设备ID或IP地址" clearable />
|
||||
<NGrid :cols="10" :x-gap="8">
|
||||
<NGridItem :span="7">
|
||||
<NInput v-model:value="searchInput" placeholder="搜索设备名称或IP地址" clearable />
|
||||
</NGridItem>
|
||||
<NGridItem :span="3">
|
||||
<NSelect v-model:value="typeInput" :options="searchTypeOptions" placeholder="搜索类型" />
|
||||
</NGridItem>
|
||||
</NGrid>
|
||||
<NFlex align="center">
|
||||
<NRadioGroup v-model:value="statusInput">
|
||||
<NRadio value="">全部</NRadio>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script lang="ts">
|
||||
// 设备参数配置在系统中的key前缀
|
||||
const DEVICE_PARAM_PREFIXES = {
|
||||
Camera: 'CAMERA_',
|
||||
Switch: 'SWITCH_',
|
||||
Server: 'SERVER_',
|
||||
Decoder: 'DECODER_',
|
||||
@@ -67,6 +68,10 @@ const getItemSuffix = (name: string) => {
|
||||
};
|
||||
|
||||
const tabPanes = [
|
||||
{
|
||||
tab: '摄像机阈值',
|
||||
name: DEVICE_PARAM_PREFIXES.Camera,
|
||||
},
|
||||
{
|
||||
tab: '交换机阈值',
|
||||
name: DEVICE_PARAM_PREFIXES.Switch,
|
||||
@@ -109,7 +114,7 @@ const show = defineModel<boolean>('show', { required: true });
|
||||
|
||||
const { station } = toRefs(props);
|
||||
|
||||
const activeTabName = ref<DeviceParamPrefix>(DEVICE_PARAM_PREFIXES.Switch);
|
||||
const activeTabName = ref<DeviceParamPrefix>(DEVICE_PARAM_PREFIXES.Camera);
|
||||
|
||||
const deviceParams = ref<DeviceParamItem[]>([]);
|
||||
|
||||
@@ -211,7 +216,7 @@ const onAfterModalEnter = () => {
|
||||
|
||||
const onBeforeModalLeave = () => {
|
||||
saveDeviceParams({ tabName: activeTabName.value, items: deviceParams.value });
|
||||
activeTabName.value = DEVICE_PARAM_PREFIXES.Switch;
|
||||
activeTabName.value = DEVICE_PARAM_PREFIXES.Camera;
|
||||
deviceParams.value = [];
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
export * from './nvr';
|
||||
export * from './security-box';
|
||||
export * from './switch';
|
||||
@@ -0,0 +1 @@
|
||||
export * from './util';
|
||||
@@ -0,0 +1,64 @@
|
||||
import { rebootSecurityBoxBeidianApi, rebootSecurityBoxNingTechApi, turnCircuitStatusBeidianApi, turnCircuitStatusNingTechApi, type NdmSecurityBoxResultVO } from '@/apis';
|
||||
import { objectEntries } from '@vueuse/core';
|
||||
|
||||
const UNSUPPORTED_SECURITY_BOX_VENDOR = '不支持的安防箱厂商';
|
||||
|
||||
export const SECURITY_BOX_VENDOR_LITERALS = {
|
||||
beidian: 'beidian',
|
||||
ningtech: 'ningtech',
|
||||
} as const;
|
||||
|
||||
export type SecurityBoxVendor = keyof typeof SECURITY_BOX_VENDOR_LITERALS;
|
||||
|
||||
export const resolveSecurityBoxVendor = (vendor?: string) => {
|
||||
const entry = objectEntries(SECURITY_BOX_VENDOR_LITERALS).find(([, value]) => value === vendor);
|
||||
return entry?.at(0);
|
||||
};
|
||||
|
||||
export const normalizeSecurityBoxWriteCommunity = (ndmDevice: NdmSecurityBoxResultVO, vendor?: string) => {
|
||||
const resolved = resolveSecurityBoxVendor(vendor);
|
||||
if (resolved === SECURITY_BOX_VENDOR_LITERALS.beidian) {
|
||||
const community = ndmDevice.community;
|
||||
if (!community) throw new Error('团体字符串不存在');
|
||||
return community;
|
||||
}
|
||||
if (resolved === SECURITY_BOX_VENDOR_LITERALS.ningtech) {
|
||||
const community = ndmDevice.writeCommunity;
|
||||
if (!community) throw new Error('团体字符串(写)不存在');
|
||||
return community;
|
||||
}
|
||||
throw new Error(UNSUPPORTED_SECURITY_BOX_VENDOR);
|
||||
};
|
||||
|
||||
export const normalizeSecurityBoxCircuitIndex = (index: number, vendor?: string) => {
|
||||
const resolved = resolveSecurityBoxVendor(vendor);
|
||||
if (resolved === SECURITY_BOX_VENDOR_LITERALS.beidian) {
|
||||
return index;
|
||||
}
|
||||
if (resolved === SECURITY_BOX_VENDOR_LITERALS.ningtech) {
|
||||
return index + 1;
|
||||
}
|
||||
throw new Error(UNSUPPORTED_SECURITY_BOX_VENDOR);
|
||||
};
|
||||
|
||||
export const dispatchTurnCircuitStatusApi = (vendor?: string) => {
|
||||
const resolved = resolveSecurityBoxVendor(vendor);
|
||||
if (resolved === SECURITY_BOX_VENDOR_LITERALS.beidian) {
|
||||
return turnCircuitStatusBeidianApi;
|
||||
}
|
||||
if (resolved === SECURITY_BOX_VENDOR_LITERALS.ningtech) {
|
||||
return turnCircuitStatusNingTechApi;
|
||||
}
|
||||
throw new Error(UNSUPPORTED_SECURITY_BOX_VENDOR);
|
||||
};
|
||||
|
||||
export const dispatchRebootSecurityBoxApi = (vendor?: string) => {
|
||||
const resolved = resolveSecurityBoxVendor(vendor);
|
||||
if (resolved === SECURITY_BOX_VENDOR_LITERALS.beidian) {
|
||||
return rebootSecurityBoxBeidianApi;
|
||||
}
|
||||
if (resolved === SECURITY_BOX_VENDOR_LITERALS.ningtech) {
|
||||
return rebootSecurityBoxNingTechApi;
|
||||
}
|
||||
throw new Error(UNSUPPORTED_SECURITY_BOX_VENDOR);
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export * from './adapter';
|
||||
@@ -0,0 +1 @@
|
||||
export * from './util';
|
||||
@@ -1,3 +1,2 @@
|
||||
export * from './device-alarm';
|
||||
export * from './nvr-cluster';
|
||||
export * from './switch-port';
|
||||
export * from './device';
|
||||
export * from './log';
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export * from './render';
|
||||
@@ -0,0 +1 @@
|
||||
export * from './alarm-log';
|
||||
@@ -136,6 +136,17 @@ export const unwrapResponse = <T>(resp: HttpResponse<T>) => {
|
||||
return data;
|
||||
};
|
||||
|
||||
export const unwrapNullableResponse = <T>(resp: HttpResponse<T>) => {
|
||||
const [err, data, result] = resp;
|
||||
if (err) throw err;
|
||||
if (result) {
|
||||
const { isSuccess, path, msg, errorMsg } = result;
|
||||
if (!isSuccess) throw new Error(`${path ? `${path}: ` : ''}${msg || errorMsg || '请求失败'}`);
|
||||
}
|
||||
if (data === undefined) throw new Error('响应数据不存在');
|
||||
return data;
|
||||
};
|
||||
|
||||
// 针对没有数据的响应,直接判断是否存在错误
|
||||
export const unwrapVoidResponse = (resp: HttpResponse<void>) => {
|
||||
const [err, , result] = resp;
|
||||
|
||||
@@ -76,6 +76,44 @@ const line01ApiProxyList: ProxyItem[] = [
|
||||
{ key: '/0128/api', target: 'http://10.14.55.10:18760', rewrite: ['/0128/api', '/api'] },
|
||||
];
|
||||
|
||||
const line02ApiProxyList: ProxyItem[] = [
|
||||
{ key: '/0275/api', target: 'http://10.14.128.10:18760', rewrite: ['/0275/api', '/api'] },
|
||||
{ key: '/0202/api', target: 'http://10.14.129.10:18760', rewrite: ['/0202/api', '/api'] },
|
||||
{ key: '/0203/api', target: 'http://10.14.131.10:18760', rewrite: ['/0203/api', '/api'] },
|
||||
{ key: '/0204/api', target: 'http://10.14.133.10:18760', rewrite: ['/0204/api', '/api'] },
|
||||
{ key: '/0205/api', target: 'http://10.14.135.10:18760', rewrite: ['/0205/api', '/api'] },
|
||||
{ key: '/0206/api', target: 'http://10.14.137.10:18760', rewrite: ['/0206/api', '/api'] },
|
||||
{ key: '/0207/api', target: 'http://10.14.139.10:18760', rewrite: ['/0207/api', '/api'] },
|
||||
{ key: '/0208/api', target: 'http://10.14.141.10:18760', rewrite: ['/0208/api', '/api'] },
|
||||
{ key: '/0209/api', target: 'http://10.14.143.10:18760', rewrite: ['/0209/api', '/api'] },
|
||||
{ key: '/0210/api', target: 'http://10.14.145.10:18760', rewrite: ['/0210/api', '/api'] },
|
||||
{ key: '/0211/api', target: 'http://10.14.147.10:18760', rewrite: ['/0211/api', '/api'] },
|
||||
{ key: '/0212/api', target: 'http://10.14.149.10:18760', rewrite: ['/0212/api', '/api'] },
|
||||
{ key: '/0213/api', target: 'http://10.14.151.10:18760', rewrite: ['/0213/api', '/api'] },
|
||||
{ key: '/0214/api', target: 'http://10.14.153.10:18760', rewrite: ['/0214/api', '/api'] },
|
||||
{ key: '/0215/api', target: 'http://10.14.155.10:18760', rewrite: ['/0215/api', '/api'] },
|
||||
{ key: '/0216/api', target: 'http://10.14.157.10:18760', rewrite: ['/0216/api', '/api'] },
|
||||
{ key: '/0217/api', target: 'http://10.14.159.10:18760', rewrite: ['/0217/api', '/api'] },
|
||||
{ key: '/0224/api', target: 'http://10.14.161.10:18760', rewrite: ['/0224/api', '/api'] },
|
||||
{ key: '/0225/api', target: 'http://10.14.163.10:18760', rewrite: ['/0225/api', '/api'] },
|
||||
{ key: '/0226/api', target: 'http://10.14.165.10:18760', rewrite: ['/0226/api', '/api'] },
|
||||
{ key: '/0227/api', target: 'http://10.14.167.10:18760', rewrite: ['/0227/api', '/api'] },
|
||||
{ key: '/0228/api', target: 'http://10.14.169.10:18760', rewrite: ['/0228/api', '/api'] },
|
||||
{ key: '/0229/api', target: 'http://10.14.171.10:18760', rewrite: ['/0229/api', '/api'] },
|
||||
{ key: '/0230/api', target: 'http://10.14.173.10:18760', rewrite: ['/0230/api', '/api'] },
|
||||
{ key: '/0231/api', target: 'http://10.14.175.10:18760', rewrite: ['/0231/api', '/api'] },
|
||||
{ key: '/0232/api', target: 'http://10.14.177.10:18760', rewrite: ['/0232/api', '/api'] },
|
||||
{ key: '/0233/api', target: 'http://10.14.179.10:18760', rewrite: ['/0233/api', '/api'] },
|
||||
{ key: '/0234/api', target: 'http://10.14.181.10:18760', rewrite: ['/0234/api', '/api'] },
|
||||
{ key: '/0235/api', target: 'http://10.14.183.10:18760', rewrite: ['/0235/api', '/api'] },
|
||||
{ key: '/0236/api', target: 'http://10.14.185.10:18760', rewrite: ['/0236/api', '/api'] },
|
||||
{ key: '/0237/api', target: 'http://10.14.187.10:18760', rewrite: ['/0237/api', '/api'] },
|
||||
{ key: '/0238/api', target: 'http://10.14.191.10:18760', rewrite: ['/0238/api', '/api'] },
|
||||
{ key: '/0280/api', target: 'http://10.14.244.10:18760', rewrite: ['/0280/api', '/api'] },
|
||||
{ key: '/0281/api', target: 'http://10.14.248.10:18760', rewrite: ['/0281/api', '/api'] },
|
||||
{ key: '/0282/api', target: 'http://10.14.252.10:18760', rewrite: ['/0282/api', '/api'] },
|
||||
];
|
||||
|
||||
const line04ApiProxyList: ProxyItem[] = [
|
||||
{ key: '/0475/api', target: 'http://10.15.128.10:18760', rewrite: ['/0475/api', '/api'] },
|
||||
{ key: '/0480/api', target: 'http://10.15.244.10:18760', rewrite: ['/0480/api', '/api'] },
|
||||
@@ -143,6 +181,11 @@ const apiProxyList: ProxyItem[] = [
|
||||
// { key: '/ws', target: 'ws://10.14.0.10:18103', ws: true },
|
||||
...line01ApiProxyList,
|
||||
|
||||
// { key: '/minio', target: 'http://10.14.128.10:9000', rewrite: ['/minio', ''] },
|
||||
// { key: '/api', target: 'http://10.14.128.10:18760' },
|
||||
// { key: '/ws', target: 'ws://10.14.128.10:18103', ws: true },
|
||||
...line02ApiProxyList,
|
||||
|
||||
// { key: '/minio', target: 'http://10.15.128.10:9000', rewrite: ['/minio', ''] },
|
||||
// { key: '/api', target: 'http://10.15.128.10:18760' },
|
||||
// { key: '/ws', target: 'ws://10.15.128.10:18103', ws: true },
|
||||
|
||||
Reference in New Issue
Block a user