This commit is contained in:
2026-04-07 21:35:33 +08:00
parent 33d7460ea0
commit ea07322257
6 changed files with 509 additions and 280 deletions

119
src/api/ops/security.ts Normal file
View File

@@ -0,0 +1,119 @@
import { request } from '@/api/request'
/** 安全设备服务项 */
export interface SecurityServiceItem {
id: number
created_at: string
updated_at: string
deleted_at: string | null
service_identity: string
oid: string
server_identity: string
name: string
category: string
type: string
description: string
enabled: boolean
interval: number
extra: string
tags: string
status_url: string
agent_config: string
collect_on: boolean
collect_args: string
collect_interval: number
collect_last_result: string
status: string
status_code: number
status_message: string
response_time: number
last_check_time: string
last_online_time: string | null
last_offline_time: string | null
continuous_errors: number
uptime: number
}
/** 安全设备服务列表数据 */
export interface SecurityServiceListData {
total: number
page: number
page_size: number
data: SecurityServiceItem[]
}
/** 安全设备服务列表响应参数 */
export interface SecurityServiceListParams {
page?: number
size?: number
keyword?: string
enabled?: boolean
}
/** 创建/更新安全设备服务请求参数 */
export interface SecurityServiceFormData {
service_identity?: string
oid?: string
server_identity?: string
name?: string
category?: string
type?: string
description?: string
enabled?: boolean
interval?: number
extra?: string
tags?: string
status_url?: string
agent_config?: string
collect_on?: boolean
collect_args?: string
collect_interval?: number
policy_ids?: number[]
}
/** 安全设备指标项 */
export interface SecurityMetric {
metric_name: string
metric_value: number
metric_unit: string
}
/** 安全设备最新指标数据 */
export interface SecurityMetricsLatestData {
service_identity: string
latest_timestamp: string | null
count: number
metrics: SecurityMetric[]
}
/** 获取安全设备服务列表(分页) */
export const fetchSecurityServiceList = (params?: SecurityServiceListParams) => {
return request.get<{ code: number; message: string; details: SecurityServiceListData }>('/DC-Control/v1/security', { params })
}
/** 获取安全设备服务详情 */
export const fetchSecurityServiceDetail = (id: number) => {
return request.get<{ code: number; message: string; details: SecurityServiceItem }>(`/DC-Control/v1/security/${id}`)
}
/** 创建安全设备服务 */
export const createSecurityService = (data: SecurityServiceFormData) => {
return request.post<{ code: number; message: string; details: { message: string; id: number } }>('/DC-Control/v1/security', data)
}
/** 更新安全设备服务 */
export const updateSecurityService = (id: number, data: Partial<SecurityServiceFormData>) => {
return request.put<{ code: number; message: string; details: { message: string } }>(`/DC-Control/v1/security/${id}`, data)
}
/** 删除安全设备服务 */
export const deleteSecurityService = (id: number) => {
return request.delete<{ code: number; message: string; details: { message: string } }>(`/DC-Control/v1/security/${id}`)
}
/** 获取最新时序指标 */
export const fetchSecurityMetricsLatest = (serviceIdentity: string) => {
return request.get<{ code: number; message: string; details: SecurityMetricsLatestData }>('/DC-Control/v1/security/metrics/latest', {
params: { service_identity: serviceIdentity },
})
}

150
src/api/ops/storage.ts Normal file
View File

@@ -0,0 +1,150 @@
import { request } from '@/api/request'
/** 存储设备服务项 */
export interface StorageItem {
id: number
created_at: string
updated_at: string
deleted_at: string | null
service_identity: string
oid: string
server_identity: string
name: string
category: string
type: string
description: string
enabled: boolean
interval: number
extra: string
tags: string
status_url: string
agent_config: string
collect_on: boolean
collect_args: string
collect_interval: number
collect_last_result: string
status: string
status_code: number
status_message: string
response_time: number
last_check_time: string
last_online_time: string | null
last_offline_time: string | null
continuous_errors: number
uptime: number
}
/** 存储设备列表响应 */
export interface StorageListResponse {
total: number
page: number
page_size: number
data: StorageItem[]
}
/** 存储设备列表请求参数 */
export interface StorageListParams {
page?: number
size?: number
keyword?: string
enabled?: boolean
}
/** 创建存储设备请求参数 */
export interface StorageCreateData {
service_identity: string
name: string
category?: string
type: string
oid?: string
server_identity?: string
description?: string
enabled?: boolean
interval?: number
extra?: string
tags?: string
status_url?: string
agent_config?: string
collect_on?: boolean
collect_args?: string
collect_interval?: number
policy_ids?: number[]
}
/** 更新存储设备请求参数 */
export interface StorageUpdateData {
service_identity?: string
oid?: string
server_identity?: string
name?: string
category?: string
type?: string
description?: string
enabled?: boolean
interval?: number
extra?: string
tags?: string
status_url?: string
agent_config?: string
collect_on?: boolean
collect_args?: string
collect_interval?: number
collect_last_result?: string
status?: string
status_code?: number
status_message?: string
response_time?: number
last_check_time?: string
last_online_time?: string | null
last_offline_time?: string | null
continuous_errors?: number
uptime?: number
policy_ids?: number[]
}
/** 指标项 */
export interface StorageMetricItem {
metric_name: string
metric_value: number
metric_unit: string
}
/** 最新指标响应 */
export interface StorageMetricsLatestResponse {
service_identity: string
latest_timestamp: string | null
count: number
metrics: StorageMetricItem[]
}
/** 获取存储设备服务列表(匿名接口) */
export const fetchStorageList = (params?: StorageListParams) => {
return request.get<StorageListResponse>('/DC-Control/v1/storage', { params })
}
/** 获取存储设备服务详情 */
export const fetchStorageDetail = (id: number) => {
return request.get<StorageItem>(`/DC-Control/v1/storage/${id}`)
}
/** 创建存储设备服务 */
export const createStorage = (data: StorageCreateData) => {
return request.post<{ message: string; id: number }>('/DC-Control/v1/storage', data)
}
/** 更新存储设备服务 */
export const updateStorage = (id: number, data: StorageUpdateData) => {
return request.put<{ message: string }>(`/DC-Control/v1/storage/${id}`, data)
}
/** 删除存储设备服务 */
export const deleteStorage = (id: number) => {
return request.delete<{ message: string }>(`/DC-Control/v1/storage/${id}`)
}
/** 查询最新一批时序指标 */
export const fetchStorageMetricsLatest = (serviceIdentity: string) => {
return request.get<StorageMetricsLatestResponse>('/DC-Control/v1/storage/metrics/latest', {
params: { service_identity: serviceIdentity },
})
}

View File

@@ -94,12 +94,7 @@
</div>
<div class="threat-category-info">
<div class="threat-category-name">{{ item.name }}</div>
<a-progress
:percent="item.percent"
:stroke-width="8"
:show-text="false"
:status="item.progressStatus"
/>
<a-progress :percent="item.percent" :stroke-width="8" :show-text="false" :status="item.progressStatus" />
</div>
<span class="threat-category-value" :style="{ color: item.color }">{{ item.value }}</span>
</div>
@@ -114,42 +109,35 @@
<template #extra>
<a-select v-model="filterType" placeholder="全部类型" style="width: 150px">
<a-option value="">全部类型</a-option>
<a-option value="防火墙">防火墙</a-option>
<a-option value="IDS/IPS">IDS/IPS</a-option>
<a-option value="WAF">WAF</a-option>
<a-option value="VPN">VPN</a-option>
<a-option value="firewall">防火墙</a-option>
<a-option value="ids">IDS</a-option>
<a-option value="ips">IPS</a-option>
<a-option value="waf">WAF</a-option>
<a-option value="vpn">VPN</a-option>
</a-select>
</template>
<a-table
:data="filteredDevices"
:columns="columns"
:loading="loading"
:pagination="false"
row-key="name"
>
<a-table :data="filteredDevices" :columns="columns" :loading="loading" :pagination="false" row-key="id">
<!-- 状态列 -->
<template #status="{ record }">
<a-tag :color="getStatusColor(record.statusValue)" bordered>
{{ record.statusText }}
<a-tag :color="getStatusColor(record.status)" bordered>
{{ getStatusText(record.status) }}
</a-tag>
</template>
<!-- 威胁 -->
<template #threats="{ record }">
<span :class="['threats-value', record.threatsLevel]">{{ record.threats }}</span>
<!-- 响应时间 -->
<template #responseTime="{ record }">
<span v-if="record.response_time">{{ record.response_time.toFixed(2) }} ms</span>
<span v-else>-</span>
</template>
<!-- CPU列 -->
<template #cpu="{ record }">
<div class="cpu-cell">
<a-progress
:percent="record.cpu"
:stroke-width="6"
:show-text="false"
:status="getCpuStatus(record.cpu)"
/>
<span class="cpu-text">{{ record.cpu }}%</span>
</div>
<!-- 运行时长列 -->
<template #uptime="{ record }">
<span>{{ formatUptime(record.uptime) }}</span>
</template>
<!-- 最近检查时间列 -->
<template #lastCheckTime="{ record }">
<span>{{ formatTime(record.last_check_time) }}</span>
</template>
</a-table>
</a-card>
@@ -167,20 +155,13 @@ import {
IconLock,
IconCode,
} from '@arco-design/web-vue/es/icon'
import Breadcrumb from '@/components/breadcrumb/index.vue'
import Chart from '@/components/chart/index.vue'
import type { TableColumnData } from '@arco-design/web-vue/es/table/interface'
// 统计数据
const stats = ref({
totalDevices: 24,
threatsBlocked: '2,456',
highRiskThreats: 12,
offlineDevices: 1,
})
import { fetchSecurityServiceList, type SecurityServiceItem } from '@/api/ops/security'
const loading = ref(false)
const filterType = ref('')
const deviceData = ref<SecurityServiceItem[]>([])
// 表格列配置
const columns: TableColumnData[] = [
@@ -195,9 +176,9 @@ const columns: TableColumnData[] = [
width: 120,
},
{
title: 'IP地址',
dataIndex: 'ip',
width: 120,
title: '服务标识',
dataIndex: 'service_identity',
width: 150,
},
{
title: '状态',
@@ -207,107 +188,46 @@ const columns: TableColumnData[] = [
align: 'center',
},
{
title: '吞吐量',
dataIndex: 'throughput',
title: '响应时间',
dataIndex: 'response_time',
slotName: 'responseTime',
width: 120,
align: 'center',
},
{
title: '运行时长',
dataIndex: 'uptime',
slotName: 'uptime',
width: 120,
align: 'center',
},
{
title: '连续错误',
dataIndex: 'continuous_errors',
width: 100,
align: 'center',
},
{
title: '连接数',
dataIndex: 'connections',
width: 100,
align: 'center',
},
{
title: '今日威胁',
dataIndex: 'threats',
slotName: 'threats',
width: 100,
align: 'center',
},
{
title: 'CPU使用',
dataIndex: 'cpu',
slotName: 'cpu',
title: '最近检查',
dataIndex: 'last_check_time',
slotName: 'lastCheckTime',
width: 180,
},
]
// 设备数据
const deviceData = ref([
{
name: '防火墙-主',
type: '防火墙',
ip: '10.0.0.1',
statusValue: 'online',
statusText: '在线',
throughput: '2.4 Gbps',
connections: '12,456',
threats: '156',
threatsLevel: 'danger',
cpu: 45,
},
{
name: '防火墙-备',
type: '防火墙',
ip: '10.0.0.2',
statusValue: 'online',
statusText: '在线',
throughput: '1.8 Gbps',
connections: '8,234',
threats: '89',
threatsLevel: 'danger',
cpu: 38,
},
{
name: 'IDS-01',
type: 'IDS/IPS',
ip: '10.0.0.10',
statusValue: 'online',
statusText: '在线',
throughput: '3.2 Gbps',
connections: '-',
threats: '23',
threatsLevel: 'warning',
cpu: 62,
},
{
name: 'IPS-01',
type: 'IDS/IPS',
ip: '10.0.0.11',
statusValue: 'warning',
statusText: '负载高',
throughput: '2.8 Gbps',
connections: '-',
threats: '45',
threatsLevel: 'danger',
cpu: 85,
},
{
name: 'WAF-01',
type: 'WAF',
ip: '10.0.0.20',
statusValue: 'online',
statusText: '在线',
throughput: '1.5 Gbps',
connections: '5,678',
threats: '78',
threatsLevel: 'warning',
cpu: 55,
},
{
name: 'VPN网关',
type: 'VPN',
ip: '10.0.0.30',
statusValue: 'offline',
statusText: '离线',
throughput: '-',
connections: '-',
threats: '-',
threatsLevel: 'normal',
cpu: 0,
},
])
// 统计数据
const stats = computed(() => {
const total = deviceData.value.length
const online = deviceData.value.filter((d) => d.status === 'online').length
const offline = deviceData.value.filter((d) => d.status === 'offline').length
const error = deviceData.value.filter((d) => d.status === 'error').length
return {
totalDevices: online,
threatsBlocked: '-',
highRiskThreats: error,
offlineDevices: offline,
}
})
// 过滤后的设备列表
const filteredDevices = computed(() => {
@@ -418,21 +338,53 @@ const getStatusColor = (status: string) => {
offline: 'gray',
warning: 'orange',
error: 'red',
unknown: 'gray',
}
return colorMap[status] || 'gray'
}
// 获取CPU状态
const getCpuStatus = (cpu: number) => {
if (cpu >= 90) return 'danger'
if (cpu >= 70) return 'warning'
return 'normal'
/** 获取状态文本 */
const getStatusText = (status: string) => {
const textMap: Record<string, string> = {
online: '在线',
offline: '离线',
warning: '警告',
error: '异常',
unknown: '未知',
}
return textMap[status] || '未知'
}
// 获取数据
/** 格式化运行时长 */
const formatUptime = (uptime: number) => {
if (!uptime || uptime === 0) return '-'
const days = Math.floor(uptime / 86400)
const hours = Math.floor((uptime % 86400) / 3600)
const minutes = Math.floor((uptime % 3600) / 60)
if (days > 0) return `${days}${hours}小时`
if (hours > 0) return `${hours}小时 ${minutes}分钟`
return `${minutes}分钟`
}
/** 格式化时间 */
const formatTime = (time: string) => {
if (!time) return '-'
return new Date(time).toLocaleString('zh-CN')
}
/** 获取数据 */
const fetchData = async () => {
// TODO: 从API获取数据
loading.value = false
loading.value = true
try {
const response = await fetchSecurityServiceList()
if (response && response.code === 0 && response.details) {
deviceData.value = response.details.data || []
}
} catch (error) {
console.error('获取安全设备列表失败:', error)
} finally {
loading.value = false
}
}
// 初始化
@@ -545,11 +497,11 @@ export default {
border-radius: 50%;
&-1 {
background-color: #165DFF;
background-color: #165dff;
}
&-2 {
background-color: #14C9C9;
background-color: #14c9c9;
}
}
@@ -634,4 +586,4 @@ export default {
min-width: 36px;
}
}
</style>
</style>

View File

@@ -1,59 +1,58 @@
<template>
<div class="storage-monitor">
<!-- 统计卡片 -->
<a-row :gutter="16" class="stats-row">
<a-col :span="6">
<a-card class="stats-card">
<a-card class="stats-card" :loading="loading">
<div class="stats-content">
<div class="stats-icon">
<IconStorage />
</div>
<div class="stats-info">
<div class="stats-title">存储设备</div>
<div class="stats-value">16</div>
<div class="stats-desc">在线 14 / 离线 2</div>
<div class="stats-value">{{ statsData.total }}</div>
<div class="stats-desc">在线 {{ statsData.online }} / 离线 {{ statsData.offline }}</div>
</div>
</div>
</a-card>
</a-col>
<a-col :span="6">
<a-card class="stats-card">
<a-card class="stats-card" :loading="loading">
<div class="stats-content">
<div class="stats-icon icon-blue">
<IconDriveFile />
</div>
<div class="stats-info">
<div class="stats-title">总容量</div>
<div class="stats-value">1.2 PB</div>
<div class="stats-desc">已使用 756 TB</div>
<div class="stats-value">-</div>
<div class="stats-desc">-</div>
</div>
</div>
</a-card>
</a-col>
<a-col :span="6">
<a-card class="stats-card">
<a-card class="stats-card" :loading="loading">
<div class="stats-content">
<div class="stats-icon icon-green">
<IconFile />
</div>
<div class="stats-info">
<div class="stats-title">使用率</div>
<div class="stats-value">63%</div>
<div class="stats-desc">较上月 +5%</div>
<div class="stats-value">-</div>
<div class="stats-desc">-</div>
</div>
</div>
</a-card>
</a-col>
<a-col :span="6">
<a-card class="stats-card">
<a-card class="stats-card" :loading="loading">
<div class="stats-content">
<div class="stats-icon icon-orange">
<IconExclamationCircle />
</div>
<div class="stats-info">
<div class="stats-title">容量告警</div>
<div class="stats-value">1</div>
<div class="stats-desc stats-warning">NAS-01 需扩容</div>
<div class="stats-title">告警设备</div>
<div class="stats-value">{{ statsData.warning }}</div>
<div class="stats-desc">{{ statsData.warning > 0 ? '需要关注' : '运行正常' }}</div>
</div>
</div>
</a-card>
@@ -199,12 +198,11 @@
</a-col>
</a-row>
<!-- 设备列表 -->
<a-card class="table-card">
<template #title>
<div class="table-header">
<span class="table-title">存储设备列表</span>
<a-select v-model="filterType" style="width: 120px" placeholder="全部类型">
<a-select v-model="filterType" style="width: 120px" placeholder="全部类型" allow-clear>
<a-option value="">全部类型</a-option>
<a-option value="SAN">SAN存储</a-option>
<a-option value="NAS">NAS存储</a-option>
@@ -213,14 +211,16 @@
</a-select>
</div>
</template>
<a-table :columns="columns" :data="filteredDevices" :pagination="false">
<a-table :columns="columns" :data="filteredDevices" :loading="loading" :pagination="false">
<template #status="{ record }">
<a-tag v-if="record.status === 'online'" color="green">在线</a-tag>
<a-tag v-else-if="record.status === 'warning'" color="orange">容量告警</a-tag>
<a-tag v-else-if="record.status === 'offline'" color="red">维护中</a-tag>
<a-tag v-else-if="record.status === 'error'" color="orange">异常</a-tag>
<a-tag v-else-if="record.status === 'offline'" color="red">离线</a-tag>
<a-tag v-else color="gray">未知</a-tag>
</template>
<template #used="{ record }">
<a-progress :percent="record.usedPercent / 100" :stroke-width="6" :show-text="false" />
<a-progress v-if="record.usedPercent > 0" :percent="record.usedPercent / 100" :stroke-width="6" :show-text="false" />
<span v-else>-</span>
</template>
</a-table>
</a-card>
@@ -228,100 +228,71 @@
</template>
<script setup lang="ts">
import { ref, computed } from 'vue'
import {
IconStorage,
IconDriveFile,
IconFile,
IconExclamationCircle,
IconArrowRise
} from '@arco-design/web-vue/es/icon'
import Breadcrumb from '@/components/breadcrumb/index.vue'
import { ref, computed, onMounted } from 'vue'
import { IconStorage, IconDriveFile, IconFile, IconExclamationCircle, IconArrowRise } from '@arco-design/web-vue/es/icon'
import Chart from '@/components/chart/index.vue'
import { fetchStorageList, type StorageItem } from '@/api/ops/storage'
// 面包屑
const breadcrumbItems = ['运维管理', '监控中心', '存储设备监控']
/** 表格数据项 */
interface TableDevice {
id: number
name: string
type: string
model: string
status: string
capacity: string
usedPercent: number
iops: string
latency: string
}
// 筛选类型
const loading = ref(false)
const filterType = ref('')
const storageList = ref<StorageItem[]>([])
// 存储设备数据
const storageDevices = ref([
{
name: 'SAN-Storage-01',
type: 'SAN存储',
model: 'Dell EMC Unity 500',
status: 'online',
capacity: '120 TB',
usedPercent: 68,
iops: '45,000',
latency: '0.8ms'
},
{
name: 'SAN-Storage-02',
type: 'SAN存储',
model: 'Dell EMC Unity 500',
status: 'online',
capacity: '120 TB',
usedPercent: 72,
iops: '52,000',
latency: '0.9ms'
},
{
name: 'NAS-01',
type: 'NAS存储',
model: 'Synology RS3621xs+',
status: 'warning',
capacity: '96 TB',
usedPercent: 92,
iops: '8,500',
latency: '2.1ms'
},
{
name: 'NAS-02',
type: 'NAS存储',
model: 'Synology RS3621xs+',
status: 'online',
capacity: '96 TB',
usedPercent: 55,
iops: '6,200',
latency: '1.8ms'
},
{
name: 'Backup-01',
type: '备份存储',
model: 'HPE StoreOnce',
status: 'online',
capacity: '200 TB',
usedPercent: 45,
iops: '3,200',
latency: '5.2ms'
},
{
name: 'Object-Store',
type: '对象存储',
model: 'MinIO Cluster',
status: 'offline',
capacity: '500 TB',
usedPercent: 38,
iops: '-',
latency: '-'
const loadStorageList = async () => {
loading.value = true
try {
const res = await fetchStorageList({ size: 100 })
if (res && res.code === 0 && res.details) {
storageList.value = res.details.data || []
}
} finally {
loading.value = false
}
])
}
// 过滤后的设备列表
const filteredDevices = computed(() => {
if (!filterType.value) return storageDevices.value
const typeMap: Record<string, string> = {
SAN: 'SAN存储',
NAS: 'NAS存储',
backup: '备份存储',
object: '对象存储'
}
return storageDevices.value.filter(d => d.type === typeMap[filterType.value])
onMounted(() => {
loadStorageList()
})
const storageDevices = computed<TableDevice[]>(() => {
return storageList.value.map((item) => ({
id: item.id,
name: item.name,
type: item.type,
model: item.description || '-',
status: item.status,
capacity: '-',
usedPercent: 0,
iops: '-',
latency: item.response_time ? `${item.response_time.toFixed(1)}ms` : '-',
}))
})
const statsData = computed(() => {
const total = storageList.value.length
const online = storageList.value.filter((s) => s.status === 'online').length
const offline = total - online
const warning = storageList.value.filter((s) => s.status === 'error').length
return { total, online, offline, warning }
})
const filteredDevices = computed(() => {
if (!filterType.value) return storageDevices.value
return storageDevices.value.filter((d) => d.type === filterType.value)
})
// 表格列定义
const columns = [
{ title: '存储名称', dataIndex: 'name', width: 140 },
{ title: '类型', dataIndex: 'type', width: 100 },
@@ -330,7 +301,7 @@ const columns = [
{ title: '总容量', dataIndex: 'capacity', width: 100 },
{ title: '使用率', slotName: 'used', width: 160 },
{ title: 'IOPS', dataIndex: 'iops', width: 100 },
{ title: '延迟', dataIndex: 'latency', width: 100 }
{ title: '延迟', dataIndex: 'latency', width: 100 },
]
// I/O 吞吐量图表配置
@@ -338,23 +309,23 @@ const ioChartOptions = {
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'cross'
}
type: 'cross',
},
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
containLabel: true,
},
xAxis: {
type: 'category',
boundaryGap: false,
data: ['00:00', '04:00', '08:00', '12:00', '16:00', '20:00', '24:00']
data: ['00:00', '04:00', '08:00', '12:00', '16:00', '20:00', '24:00'],
},
yAxis: {
type: 'value',
name: 'IOPS'
name: 'IOPS',
},
series: [
{
@@ -362,32 +333,32 @@ const ioChartOptions = {
type: 'line',
smooth: true,
areaStyle: {
opacity: 0.3
opacity: 0.3,
},
lineStyle: {
width: 2
width: 2,
},
itemStyle: {
color: '#3b82f6'
color: '#3b82f6',
},
data: [25000, 15000, 52000, 68000, 58000, 35000, 22000]
data: [25000, 15000, 52000, 68000, 58000, 35000, 22000],
},
{
name: '写入',
type: 'line',
smooth: true,
areaStyle: {
opacity: 0.3
opacity: 0.3,
},
lineStyle: {
width: 2
width: 2,
},
itemStyle: {
color: '#22c55e'
color: '#22c55e',
},
data: [18000, 12000, 38000, 45000, 42000, 28000, 16000]
}
]
data: [18000, 12000, 38000, 45000, 42000, 28000, 16000],
},
],
}
</script>
@@ -623,4 +594,4 @@ const ioChartOptions = {
font-size: 16px;
font-weight: 500;
}
</style>
</style>

View File

@@ -152,6 +152,7 @@ import {
type StatusDistributionItem,
} from '@/api/ops/url-device'
/** 统计概览数据 */
const stats = ref<UrlDeviceStatsOverview>({
total: 0,
online: 0,
@@ -159,10 +160,14 @@ const stats = ref<UrlDeviceStatsOverview>({
avg_response_time_ms: 0,
})
/** 表格加载状态 */
const loading = ref(false)
/** 统计数据加载状态 */
const statsLoading = ref(false)
/** 图表加载状态 */
const chartLoading = ref(false)
/** 表格列配置 */
const columns: TableColumnData[] = [
{
title: '名称',
@@ -205,10 +210,13 @@ const columns: TableColumnData[] = [
},
]
/** 设备列表数据 */
const tableData = ref<UrlDeviceItem[]>([])
/** 可用性报告数据 */
const availabilityData = ref<{ name: string; uptime: number; status: string; progressStatus: 'success' | 'warning' | 'danger' }[]>([])
/** 响应时间趋势图表配置 */
const responseTimeChartOptions = ref({
tooltip: {
trigger: 'axis',
@@ -248,6 +256,11 @@ const responseTimeChartOptions = ref({
],
})
/**
* 获取状态对应的标签颜色
* @param status - 设备状态
* @returns Arco Design 标签颜色值
*/
const getStatusColor = (status: string) => {
const colorMap: Record<string, string> = {
online: 'green',
@@ -257,6 +270,11 @@ const getStatusColor = (status: string) => {
return colorMap[status] || 'gray'
}
/**
* 获取状态的中文显示文本
* @param status - 设备状态
* @returns 中文状态文本
*/
const getStatusText = (status: string) => {
const textMap: Record<string, string> = {
online: '正常',
@@ -266,6 +284,11 @@ const getStatusText = (status: string) => {
return textMap[status] || status
}
/**
* 格式化最后检查时间为相对时间
* @param time - ISO 时间字符串
* @returns 格式化后的相对时间文本(如"刚刚"、"5分钟前"
*/
const formatLastCheckTime = (time: string | null) => {
if (!time) return '-'
const date = new Date(time)
@@ -279,12 +302,15 @@ const formatLastCheckTime = (time: string | null) => {
return `${Math.floor(diffHours / 24)}天前`
}
/**
* 获取统计概览数据
*/
const fetchStatsOverview = async () => {
statsLoading.value = true
try {
const response: any = await fetchUrlDeviceStatsOverview()
if (response && response.data) {
stats.value = response.data
if (response && response.details) {
stats.value = response.details
}
} catch (error) {
console.error('获取统计概览失败:', error)
@@ -293,6 +319,9 @@ const fetchStatsOverview = async () => {
}
}
/**
* 获取响应时间趋势数据并更新图表
*/
const fetchResponseTimeTrend = async () => {
chartLoading.value = true
try {
@@ -313,12 +342,15 @@ const fetchResponseTimeTrend = async () => {
}
}
/**
* 获取设备状态分布数据并更新可用性报告
*/
const fetchStatusDistribution = async () => {
try {
const response: any = await fetchUrlDeviceStatusDistribution()
if (response && response.data && response.data.by_status) {
const distribution: StatusDistributionItem[] = response.data.by_status
const total = response.data.total || 0
if (response && response.details && response.details.by_status) {
const distribution: StatusDistributionItem[] = response.details.by_status
const total = response.details.total || 0
availabilityData.value = distribution.map((item) => {
const uptime = total > 0 ? (item.status === 'online' ? 99.9 : item.status === 'error' ? 95 : 98) : 0
const progressStatus = item.status === 'online' ? 'success' : item.status === 'error' ? 'danger' : 'warning'
@@ -335,14 +367,15 @@ const fetchStatusDistribution = async () => {
}
}
/**
* 获取设备列表数据
*/
const fetchDeviceList = async () => {
loading.value = true
try {
const response: any = await fetchUrlDeviceList({ page: 1, size: 100 })
if (response && response.details) {
tableData.value = response.details.list || []
} else if (response && response.data) {
tableData.value = response.data.data || []
tableData.value = response.details.data || []
}
} catch (error) {
console.error('获取设备列表失败:', error)
@@ -351,10 +384,14 @@ const fetchDeviceList = async () => {
}
}
/**
* 并行获取所有页面数据
*/
const fetchData = async () => {
await Promise.all([fetchStatsOverview(), fetchResponseTimeTrend(), fetchStatusDistribution(), fetchDeviceList()])
}
/** 组件挂载时获取数据 */
onMounted(() => {
fetchData()
})

File diff suppressed because one or more lines are too long