feat
This commit is contained in:
119
src/api/ops/security.ts
Normal file
119
src/api/ops/security.ts
Normal 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
150
src/api/ops/storage.ts
Normal 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 },
|
||||
})
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user