update
This commit is contained in:
336
src/client/admin/pages/pages_alert_handle.tsx
Normal file
336
src/client/admin/pages/pages_alert_handle.tsx
Normal file
@@ -0,0 +1,336 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import {
|
||||
useNavigate,
|
||||
useLocation,
|
||||
useParams
|
||||
} from 'react-router';
|
||||
import {
|
||||
Button, Space,
|
||||
Form, Input, Select, message,
|
||||
Card, Spin, Typography,
|
||||
Switch, Divider, Descriptions,
|
||||
Tag, List,
|
||||
} from 'antd';
|
||||
import {
|
||||
FileImageOutlined,
|
||||
FilePdfOutlined,
|
||||
FileOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import dayjs from 'dayjs';
|
||||
import 'dayjs/locale/zh-cn';
|
||||
// 从share/types.ts导入所有类型,包括MapMode
|
||||
import type {
|
||||
Attachment,
|
||||
} from '@/share/monitorTypes';
|
||||
|
||||
import {
|
||||
AlertLevel, AlertStatus,
|
||||
HandleType, ProblemType,
|
||||
HandleTypeNameMap, ProblemTypeNameMap,
|
||||
} from '@/share/monitorTypes';
|
||||
|
||||
import { getEnumOptions } from '../utils';
|
||||
|
||||
import { alertsClient, alertsHandleLogsClient } from '@/client/api';
|
||||
import type { InferRequestType, InferResponseType } from 'hono/client';
|
||||
import { Uploader } from "../components/components_uploader";
|
||||
|
||||
type AlertResponse = InferResponseType<typeof alertsClient.$get, 200>;
|
||||
type AlertHandleLogRequest = InferRequestType<typeof alertsHandleLogsClient.$post>['json'];
|
||||
type DeviceAlert = InferResponseType<typeof alertsClient.$get, 200>['data'][0];
|
||||
type AlertHandleLog = InferResponseType<typeof alertsHandleLogsClient.$post, 200>;
|
||||
|
||||
|
||||
|
||||
const { Text } = Typography;
|
||||
|
||||
// 告警处理页面
|
||||
export const AlertHandlePage = () => {
|
||||
const { id } = useParams<{ id: string }>();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
const [alert, setAlert] = useState<DeviceAlert | null>(null);
|
||||
const [form] = Form.useForm();
|
||||
const navigate = useNavigate();
|
||||
const [uploadedFiles, setUploadedFiles] = useState<Attachment[]>([]);
|
||||
const location = useLocation();
|
||||
const searchParams = new URLSearchParams(location.search);
|
||||
const mode = searchParams.get('mode') || 'view'; // 默认为查看模式
|
||||
|
||||
// 判断是否可编辑
|
||||
const isEditable = mode === 'edit' && alert &&
|
||||
(alert.status === AlertStatus.PENDING || alert.status === AlertStatus.HANDLING);
|
||||
|
||||
useEffect(() => {
|
||||
if (id) {
|
||||
fetchAlertData(parseInt(id));
|
||||
}
|
||||
}, [id]);
|
||||
|
||||
const fetchAlertData = async (alertId: number) => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const res = await alertsClient[':id'].$get({
|
||||
param: { id: alertId }
|
||||
});
|
||||
|
||||
if (res.status === 200) {
|
||||
const data = await res.json();
|
||||
setAlert(data);
|
||||
} else {
|
||||
throw new Error(res.statusText);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取告警数据失败:', error);
|
||||
message.error('获取告警数据失败');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSubmit = async (values: any) => {
|
||||
if (!id) return;
|
||||
|
||||
setSubmitting(true);
|
||||
try {
|
||||
const alertHandleLog: AlertHandleLogRequest = {
|
||||
alertId: parseInt(id),
|
||||
handleType: values.handleType,
|
||||
problemType: values.problemType,
|
||||
handleResult: values.handleResult,
|
||||
notifyDisabled: values.notifyDisabled ? 1 : 0,
|
||||
attachments: uploadedFiles
|
||||
};
|
||||
|
||||
const res = await alertsHandleLogsClient.$post({
|
||||
json: alertHandleLog
|
||||
});
|
||||
|
||||
if (res.status === 200) {
|
||||
message.success('告警处理成功');
|
||||
navigate('/admin/alert-records');
|
||||
} else {
|
||||
const error = await res.json();
|
||||
throw new Error(error.message || '处理失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('告警处理失败:', error);
|
||||
message.error('告警处理失败');
|
||||
} finally {
|
||||
setSubmitting(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 文件上传成功回调
|
||||
const handleFileUploadSuccess = (fileUrl: string, fileInfo: any) => {
|
||||
// 添加上传成功的文件到列表
|
||||
const newFile: Attachment = {
|
||||
id: fileInfo.id || String(Date.now()),
|
||||
name: fileInfo.file_name,
|
||||
url: fileUrl,
|
||||
type: fileInfo.file_type,
|
||||
size: fileInfo.file_size,
|
||||
upload_time: new Date().toISOString()
|
||||
};
|
||||
|
||||
setUploadedFiles(prev => [...prev, newFile]);
|
||||
};
|
||||
|
||||
// 删除已上传文件
|
||||
const handleFileDelete = (fileId: string) => {
|
||||
setUploadedFiles(prev => prev.filter(file => file.id !== fileId));
|
||||
};
|
||||
|
||||
const handleTypeOptions = getEnumOptions(HandleType, HandleTypeNameMap);
|
||||
|
||||
const problemTypeOptions = getEnumOptions(ProblemType, ProblemTypeNameMap);
|
||||
|
||||
const getAlertLevelTag = (level: AlertLevel | null) => {
|
||||
if (level === null) return <Tag>未知</Tag>;
|
||||
|
||||
switch (level) {
|
||||
case AlertLevel.MINOR:
|
||||
return <Tag color="blue">次要</Tag>;
|
||||
case AlertLevel.NORMAL:
|
||||
return <Tag color="green">一般</Tag>;
|
||||
case AlertLevel.IMPORTANT:
|
||||
return <Tag color="orange">重要</Tag>;
|
||||
case AlertLevel.URGENT:
|
||||
return <Tag color="red">紧急</Tag>;
|
||||
default:
|
||||
return <Tag>未知</Tag>;
|
||||
}
|
||||
};
|
||||
|
||||
const getAlertStatusTag = (status?: AlertStatus) => {
|
||||
if (status === undefined) return <Tag>未知</Tag>;
|
||||
|
||||
switch (status) {
|
||||
case AlertStatus.PENDING:
|
||||
return <Tag color="red">待处理</Tag>;
|
||||
case AlertStatus.HANDLING:
|
||||
return <Tag color="orange">处理中</Tag>;
|
||||
case AlertStatus.RESOLVED:
|
||||
return <Tag color="green">已解决</Tag>;
|
||||
case AlertStatus.IGNORED:
|
||||
return <Tag color="default">已忽略</Tag>;
|
||||
default:
|
||||
return <Tag>未知</Tag>;
|
||||
}
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div style={{ textAlign: 'center', padding: '50px' }}>
|
||||
<Spin size="large" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!alert) {
|
||||
return (
|
||||
<div style={{ textAlign: 'center', padding: '50px' }}>
|
||||
<Text>未找到告警数据</Text>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Card title={isEditable ? "告警处理" : "告警查看"} style={{ marginBottom: 16 }}>
|
||||
<Descriptions bordered column={2} style={{ marginBottom: 16 }}>
|
||||
<Descriptions.Item label="告警ID">{alert.id}</Descriptions.Item>
|
||||
<Descriptions.Item label="设备名称">{alert.deviceName}</Descriptions.Item>
|
||||
<Descriptions.Item label="告警等级">{getAlertLevelTag(alert.alertLevel)}</Descriptions.Item>
|
||||
<Descriptions.Item label="状态">{getAlertStatusTag(alert.status)}</Descriptions.Item>
|
||||
<Descriptions.Item label="监控指标">{alert.metricType}</Descriptions.Item>
|
||||
<Descriptions.Item label="触发值">{alert.metricValue}</Descriptions.Item>
|
||||
<Descriptions.Item label="告警消息">{alert.alertMessage}</Descriptions.Item>
|
||||
<Descriptions.Item label="告警时间">{dayjs(alert.createdAt).format('YYYY-MM-DD HH:mm:ss')}</Descriptions.Item>
|
||||
</Descriptions>
|
||||
|
||||
{/* 只有可编辑模式或者已经有处理记录的情况下才显示表单 */}
|
||||
{isEditable && (
|
||||
<>
|
||||
<Divider />
|
||||
|
||||
<Form
|
||||
form={form}
|
||||
layout="vertical"
|
||||
onFinish={handleSubmit}
|
||||
initialValues={{
|
||||
handle_type: HandleType.CONFIRM,
|
||||
notify_disabled: false,
|
||||
}}
|
||||
>
|
||||
<Form.Item
|
||||
name="handleType"
|
||||
label="处理类型"
|
||||
rules={[{ required: true, message: '请选择处理类型' }]}
|
||||
>
|
||||
<Select options={handleTypeOptions} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="problemType"
|
||||
label="问题类型"
|
||||
rules={[{ required: true, message: '请选择问题类型' }]}
|
||||
>
|
||||
<Select options={problemTypeOptions} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="handleResult"
|
||||
label="处理结果"
|
||||
rules={[{ required: true, message: '请输入处理结果' }]}
|
||||
>
|
||||
<Input.TextArea rows={4} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label="附件"
|
||||
>
|
||||
<div className="upload-attachments">
|
||||
{/* 使用MinIOUploader代替原始Upload组件 */}
|
||||
<Uploader
|
||||
onSuccess={handleFileUploadSuccess}
|
||||
onError={(error) => message.error(`上传失败: ${error.message}`)}
|
||||
onProgress={(percent) => console.log(`上传进度: ${percent}%`)}
|
||||
prefix="alerts/"
|
||||
maxSize={20 * 1024 * 1024}
|
||||
allowedTypes={['image/jpeg', 'image/png', 'application/pdf', 'text/plain', 'application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document']}
|
||||
/>
|
||||
|
||||
{/* 已上传文件列表 */}
|
||||
{uploadedFiles.length > 0 && (
|
||||
<div style={{ marginTop: 16 }}>
|
||||
<h4>已上传文件:</h4>
|
||||
<List
|
||||
size="small"
|
||||
bordered
|
||||
dataSource={uploadedFiles}
|
||||
renderItem={file => (
|
||||
<List.Item
|
||||
actions={[
|
||||
<Button
|
||||
key="delete"
|
||||
type="link"
|
||||
danger
|
||||
onClick={() => handleFileDelete(file.id)}
|
||||
>
|
||||
删除
|
||||
</Button>
|
||||
]}
|
||||
>
|
||||
<Space>
|
||||
{file.type.includes('image') ? <FileImageOutlined /> :
|
||||
file.type.includes('pdf') ? <FilePdfOutlined /> :
|
||||
<FileOutlined />}
|
||||
<a href={file.url} target="_blank" rel="noopener noreferrer">
|
||||
{file.name}
|
||||
</a>
|
||||
<Text type="secondary">
|
||||
({file.size < 1024 * 1024
|
||||
? `${(file.size / 1024).toFixed(2)} KB`
|
||||
: `${(file.size / 1024 / 1024).toFixed(2)} MB`})
|
||||
</Text>
|
||||
</Space>
|
||||
</List.Item>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="notifyDisabled"
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch checkedChildren="禁用通知" unCheckedChildren="启用通知" />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item>
|
||||
<Button type="primary" htmlType="submit" loading={submitting}>
|
||||
提交
|
||||
</Button>
|
||||
<Button style={{ marginLeft: 8 }} onClick={() => navigate('/admin/alert-records')}>
|
||||
返回
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* 不可编辑模式时只显示返回按钮 */}
|
||||
{!isEditable && (
|
||||
<Form.Item style={{ marginTop: 16 }}>
|
||||
<Button onClick={() => navigate('/admin/alert-records')}>
|
||||
返回
|
||||
</Button>
|
||||
</Form.Item>
|
||||
)}
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user