| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320 |
- 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 {
- AlertHandleLog, DeviceAlert, Attachment,
- } from '../share/monitorTypes.ts';
- import {
- AlertLevel, AlertStatus,
- HandleType, ProblemType,
- HandleTypeNameMap, ProblemTypeNameMap,
- } from '../share/monitorTypes.ts';
- import { getEnumOptions } from './utils.ts';
- import { AlertAPI, AlertHandleAPI } from './api/index.ts';
- import { Uploader } from "./components_uploader.tsx";
- 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 response = await AlertAPI.getAlert(alertId);
- if (response) {
- setAlert(response);
- }
- } catch (error) {
- console.error('获取告警数据失败:', error);
- message.error('获取告警数据失败');
- } finally {
- setLoading(false);
- }
- };
-
- const handleSubmit = async (values: any) => {
- if (!id) return;
-
- setSubmitting(true);
- try {
- const alertHandleLog: Partial<AlertHandleLog> = {
- alert_id: parseInt(id),
- handle_type: values.handle_type,
- problem_type: values.problem_type,
- handle_result: values.handle_result,
- notify_disabled: values.notify_disabled ? 1 : 0,
- attachments: uploadedFiles
- };
-
- const response = await AlertHandleAPI.createAlertHandle(alertHandleLog);
-
- if (response.data) {
- message.success('告警处理成功');
- navigate('/admin/alert-records');
- }
- } 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) => {
- if (level === undefined) 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.device_name}</Descriptions.Item>
- <Descriptions.Item label="告警等级">{getAlertLevelTag(alert.alert_level)}</Descriptions.Item>
- <Descriptions.Item label="状态">{getAlertStatusTag(alert.status)}</Descriptions.Item>
- <Descriptions.Item label="监控指标">{alert.metric_type}</Descriptions.Item>
- <Descriptions.Item label="触发值">{alert.metric_value}</Descriptions.Item>
- <Descriptions.Item label="告警消息">{alert.alert_message}</Descriptions.Item>
- <Descriptions.Item label="告警时间">{dayjs(alert.created_at).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="handle_type"
- label="处理类型"
- rules={[{ required: true, message: '请选择处理类型' }]}
- >
- <Select options={handleTypeOptions} />
- </Form.Item>
-
- <Form.Item
- name="problem_type"
- label="问题类型"
- rules={[{ required: true, message: '请选择问题类型' }]}
- >
- <Select options={problemTypeOptions} />
- </Form.Item>
-
- <Form.Item
- name="handle_result"
- 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="notify_disabled"
- 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>
- );
- };
|