Fix frontend runtime warnings

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
funnywolf
2026-07-04 23:46:44 +08:00
co-authored by Copilot
parent 42fe735913
commit 22f3ff5352
29 changed files with 138 additions and 46 deletions
+4 -4
View File
@@ -75,7 +75,7 @@ export default function AlertBasicView({ record, onOpenResource }: AlertBasicVie
<Section title="Correlation">
<Descriptions.Item label="Rule ID">{mono(stringValue(record, 'rule_id'))}</Descriptions.Item>
<Descriptions.Item label="Rule Name">{stringValue(record, 'rule_name')}</Descriptions.Item>
<Descriptions.Item label="Correlation UID">{mono(stringValue(record, 'correlation_uid'))}</Descriptions.Item>
<Descriptions.Item label="Correlation UID" span="filled">{mono(stringValue(record, 'correlation_uid'))}</Descriptions.Item>
</Section>
<Section title="MITRE ATT&CK and ATLAS">
@@ -86,10 +86,10 @@ export default function AlertBasicView({ record, onOpenResource }: AlertBasicVie
</Section>
<Section title="Status & Disposition & Action & Remediation">
<Descriptions.Item label="Status">{statusTag(String(value(record, 'status') || ''))}</Descriptions.Item>
<Descriptions.Item label="Status Detail" span={4}>{block(stringValue(record, 'status_detail'))}</Descriptions.Item>
<Descriptions.Item label="Disposition">{alertDispositionTag(String(value(record, 'disposition') || ''))}</Descriptions.Item>
<Descriptions.Item label="Action">{alertActionTag(String(value(record, 'action') || ''))}</Descriptions.Item>
<Descriptions.Item label="Status" span="filled">{statusTag(String(value(record, 'status') || ''))}</Descriptions.Item>
<Descriptions.Item label="Status Detail" span={4}>{block(stringValue(record, 'status_detail'))}</Descriptions.Item>
<Descriptions.Item label="Remediation" span={4}>{block(stringValue(record, 'remediation'))}</Descriptions.Item>
</Section>
@@ -106,7 +106,7 @@ export default function AlertBasicView({ record, onOpenResource }: AlertBasicVie
<Section title="Analytic & Policy">
<Descriptions.Item label="Analytic Name">{stringValue(record, 'analytic_name')}</Descriptions.Item>
<Descriptions.Item label="Analytic Type">{alertAnalyticTypeTag(String(value(record, 'analytic_type') || ''))}</Descriptions.Item>
<Descriptions.Item label="Analytic State">{choiceTag(String(value(record, 'analytic_state') || ''), 'green')}</Descriptions.Item>
<Descriptions.Item label="Analytic State" span="filled">{choiceTag(String(value(record, 'analytic_state') || ''), 'green')}</Descriptions.Item>
<Descriptions.Item label="Analytic Description" span={4}>{block(stringValue(record, 'analytic_desc'))}</Descriptions.Item>
<Descriptions.Item label="Policy Name">{stringValue(record, 'policy_name')}</Descriptions.Item>
<Descriptions.Item label="Policy Type">{choiceTag(String(value(record, 'policy_type') || ''), 'volcano')}</Descriptions.Item>
@@ -0,0 +1,14 @@
import {useLayoutEffect} from 'react'
import {App as AntApp} from 'antd'
import {clearAppMessage, setAppMessage} from '../utils/appMessage'
export default function AppMessageProvider() {
const {message} = AntApp.useApp()
useLayoutEffect(() => {
setAppMessage(message)
return () => clearAppMessage(message)
}, [message])
return null
}
+2 -1
View File
@@ -1,5 +1,6 @@
import type {UploadProps} from 'antd'
import {message, Upload} from 'antd'
import {Upload} from 'antd'
import {message} from '../utils/appMessage'
import {CameraOutlined} from '@ant-design/icons'
import ImgCrop from 'antd-img-crop'
import client from '../api/client'
@@ -1,5 +1,6 @@
import {useCallback, useEffect, useMemo, useRef, useState} from 'react'
import {Empty, message, Spin, theme} from 'antd'
import {Empty, Spin, theme} from 'antd'
import {message} from '../utils/appMessage'
import client from '../api/client'
import type {EditableFieldConfig, FieldEditingController, OpenResourceOptions} from '../types/records'
import DetailDraftActionBar from './DetailDraftActionBar'
@@ -1,5 +1,6 @@
import {type CSSProperties, useCallback, useEffect, useMemo, useRef, useState} from 'react'
import {Alert, Button, Empty, Input, message, Modal, Space, Table, Tag, theme, Tooltip, Typography} from 'antd'
import {Alert, Button, Empty, Input, Modal, Space, Table, Tag, theme, Tooltip, Typography} from 'antd'
import {message} from '../utils/appMessage'
import {ReloadOutlined, SearchOutlined, ThunderboltOutlined} from '@ant-design/icons'
import type {ColumnsType} from 'antd/es/table'
import client from '../api/client'
+2 -1
View File
@@ -1,6 +1,7 @@
import type {Key} from 'react'
import {useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState} from 'react'
import {Button, Checkbox, Divider, Input, message, Pagination, Popconfirm, Popover, Select, Space, Table, Tooltip} from 'antd'
import {Button, Checkbox, Divider, Input, Pagination, Popconfirm, Popover, Select, Space, Table, Tooltip} from 'antd'
import {message} from '../utils/appMessage'
import {ClearOutlined, CloseOutlined, DeleteOutlined, FilterOutlined, HolderOutlined, PushpinFilled, PushpinOutlined, ReloadOutlined, SettingOutlined} from '@ant-design/icons'
import {closestCenter, DndContext, type DragEndEvent, PointerSensor, useSensor, useSensors} from '@dnd-kit/core'
import {arrayMove, SortableContext, useSortable, verticalListSortingStrategy} from '@dnd-kit/sortable'
+2 -2
View File
@@ -1,5 +1,6 @@
import {useCallback, useEffect, useMemo, useState} from 'react'
import {Alert, Button, Input, List, message, Popconfirm, Space, Tooltip} from 'antd'
import {Alert, Button, Input, List, Popconfirm, Space, Tooltip} from 'antd'
import {message} from '../utils/appMessage'
import {MessageOutlined, ReloadOutlined, SearchOutlined} from '@ant-design/icons'
import dayjs from 'dayjs'
import relativeTime from 'dayjs/plugin/relativeTime'
@@ -170,7 +171,6 @@ export default function DiscussionThread({ contentType, objectId }: DiscussionTh
useEffect(() => {
if (reconnectToken === 0) return
// eslint-disable-next-line react-hooks/set-state-in-effect
refreshComments()
}, [reconnectToken, refreshComments])
@@ -1,6 +1,7 @@
import {useEffect, useState} from 'react'
import {ExperimentOutlined} from '@ant-design/icons'
import {Form, Input, message, Modal, Select, theme} from 'antd'
import {Form, Input, Modal, Select, theme} from 'antd'
import {message} from '../utils/appMessage'
import client from '../api/client'
import {fetchResourceMetadata} from '../api/metadata'
import type {ChoiceOption} from '../types/records'
+4 -4
View File
@@ -1,5 +1,6 @@
import {useCallback, useEffect, useState} from 'react'
import {Alert, Badge, Button, Drawer, Empty, List, message, Popconfirm, Segmented, Space, Spin, Tag, Tooltip, Typography} from 'antd'
import {Alert, Badge, Button, Drawer, Empty, List, Popconfirm, Segmented, Space, Spin, Tag, Tooltip, Typography} from 'antd'
import {message} from '../utils/appMessage'
import {CheckCircleOutlined, CloseOutlined, InboxOutlined, MailOutlined, MessageOutlined, ReloadOutlined} from '@ant-design/icons'
import dayjs from 'dayjs'
import relativeTime from 'dayjs/plugin/relativeTime'
@@ -222,7 +223,6 @@ export default function InboxDrawer({ onOpenResource }: InboxDrawerProps) {
useEffect(() => {
if (!open || reconnectToken === 0) return
// eslint-disable-next-line react-hooks/set-state-in-effect
refreshMessages()
}, [open, reconnectToken, refreshMessages])
@@ -379,7 +379,7 @@ export default function InboxDrawer({ onOpenResource }: InboxDrawerProps) {
</Badge>
)}
title={(
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', gap: 8 }}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', gap: 8, marginBottom: 4 }}>
<Space size={8} wrap>
<Tag {...comfortableTagProps} color={row.kind === 'system' ? 'purple' : 'blue'} style={{ marginInlineEnd: 0 }}>
{row.kind}
@@ -423,7 +423,7 @@ export default function InboxDrawer({ onOpenResource }: InboxDrawerProps) {
</div>
)}
</List.Item>
)}
)}
/>
</>
) : loadError ? (
@@ -1,6 +1,7 @@
import {useState} from 'react'
import type {SelectProps} from 'antd'
import {DatePicker, Form, Input, message, Modal, Select, Tag, theme, Typography} from 'antd'
import {DatePicker, Form, Input, Modal, Select, Tag, theme, Typography} from 'antd'
import {message} from '../utils/appMessage'
import {BookOutlined} from '@ant-design/icons'
import type {Dayjs} from 'dayjs'
import client from '../api/client'
+1 -1
View File
@@ -1,6 +1,6 @@
import {type ClipboardEvent, useState} from 'react'
import axios from 'axios'
import {message} from 'antd'
import {message} from '../utils/appMessage'
import type {PreviewType} from '@uiw/react-md-editor'
import MDEditor from '@uiw/react-md-editor'
import client from '../api/client'
+2 -1
View File
@@ -1,6 +1,7 @@
import {useMemo, useState} from 'react'
import type {UploadProps} from 'antd'
import {Alert, Button, Mentions, message, Tooltip, Upload} from 'antd'
import {Alert, Button, Mentions, Tooltip, Upload} from 'antd'
import {message} from '../utils/appMessage'
import {PaperClipOutlined, SendOutlined} from '@ant-design/icons'
import {type Attachment, uploadAttachment} from '../api/attachments'
import {fetchMentionUsers, type MentionUser} from '../api/comments'
@@ -1,5 +1,6 @@
import {useEffect, useState} from 'react'
import {Button, Col, DatePicker, Form, Input, message, Modal, Popconfirm, Row, Space, Switch, Table, Tabs, Tag, Typography} from 'antd'
import {Button, Col, DatePicker, Form, Input, Modal, Popconfirm, Row, Space, Switch, Table, Tabs, Tag, Typography} from 'antd'
import {message} from '../utils/appMessage'
import {ApiOutlined, CopyOutlined, DeleteOutlined, KeyOutlined, LockOutlined, ReloadOutlined, SettingOutlined, UserOutlined} from '@ant-design/icons'
import type {ColumnsType} from 'antd/es/table'
import type {Dayjs} from 'dayjs'
@@ -1,5 +1,6 @@
import {useCallback, useEffect, useMemo, useRef, useState} from 'react'
import {Button, Divider, Empty, message, Modal, Spin, theme, Tooltip} from 'antd'
import {Button, Divider, Empty, Modal, Spin, theme, Tooltip} from 'antd'
import {message} from '../utils/appMessage'
import {CloseOutlined, FileTextOutlined, HistoryOutlined, MenuFoldOutlined, MenuUnfoldOutlined, MessageOutlined, ReloadOutlined, ShareAltOutlined} from '@ant-design/icons'
import client from '../api/client'
import type {FieldEditingController, OpenResourceOptions, ResourceConfig} from '../types/records'
@@ -1,5 +1,5 @@
import {type ReactNode, useCallback, useEffect, useMemo, useState} from 'react'
import {message} from 'antd'
import {message} from '../utils/appMessage'
import DataTable from './DataTable'
import {fetchResourceMetadata} from '../api/metadata'
import type {AdvancedFilterFieldConfig, MetadataResponse, OpenResourceOptions, ResourceColumn, ResourceFilterConfig} from '../types/records'
+6 -1
View File
@@ -1,3 +1,4 @@
import {Fragment} from 'react'
import {choiceTag} from '../utils/recordDisplay'
interface TagListProps {
@@ -10,7 +11,11 @@ export default function TagList({ items, color = 'blue' }: TagListProps) {
return (
<span style={{ display: 'inline-flex', flexWrap: 'wrap', gap: 4 }}>
{items.map((item) => choiceTag(String(item), color))}
{items.map((item, index) => (
<Fragment key={`${String(item)}-${index}`}>
{choiceTag(String(item), color)}
</Fragment>
))}
</span>
)
}
+2
View File
@@ -3,6 +3,7 @@ import ReactDOM from 'react-dom/client'
import {BrowserRouter} from 'react-router-dom'
import {App as AntApp, ConfigProvider} from 'antd'
import App from './App'
import AppMessageProvider from './components/AppMessageProvider'
import themeConfig from './theme'
import './index.css'
@@ -11,6 +12,7 @@ ReactDOM.createRoot(document.getElementById('root')!).render(
<BrowserRouter>
<ConfigProvider theme={themeConfig}>
<AntApp>
<AppMessageProvider />
<App />
</AntApp>
</ConfigProvider>
+2 -1
View File
@@ -1,5 +1,6 @@
import {useCallback, useEffect, useMemo, useState} from 'react'
import {Alert, Button, Descriptions, Drawer, Empty, Flex, Input, InputNumber, List, message, Select, Space, Table, Tabs, Tag, Typography} from 'antd'
import {Alert, Button, Descriptions, Drawer, Empty, Flex, Input, InputNumber, List, Select, Space, Table, Tabs, Tag, Typography} from 'antd'
import {message} from '../utils/appMessage'
import {ReloadOutlined} from '@ant-design/icons'
import type {ColumnsType} from 'antd/es/table'
import {Boxes, BrainCircuit, DatabaseZap} from 'lucide-react'
+2 -1
View File
@@ -1,5 +1,6 @@
import {useCallback, useEffect, useState} from 'react'
import {Button, Card, Col, Form, Input, message, Row, Space, Switch} from 'antd'
import {Button, Card, Col, Form, Input, Row, Space, Switch} from 'antd'
import {message} from '../utils/appMessage'
import client from '../api/client'
interface LDAPConfig {
+2 -1
View File
@@ -1,5 +1,6 @@
import {useMemo, useState} from 'react'
import {Button, Form, Input, InputNumber, message, Modal, Popconfirm, Select, Space, Switch} from 'antd'
import {Button, Form, Input, InputNumber, Modal, Popconfirm, Select, Space, Switch} from 'antd'
import {message} from '../utils/appMessage'
import {CheckCircleOutlined, EditOutlined, PlusOutlined, StopOutlined, ThunderboltOutlined} from '@ant-design/icons'
import client from '../api/client'
import DataTable from '../components/DataTable'
+2 -1
View File
@@ -1,6 +1,7 @@
import {useState} from 'react'
import {useNavigate, useSearchParams} from 'react-router-dom'
import {BorderBeam, Button, Card, Form, Input, message, Segmented} from 'antd'
import {BorderBeam, Button, Card, Form, Input, Segmented} from 'antd'
import {message} from '../utils/appMessage'
import {LockOutlined, UserOutlined} from '@ant-design/icons'
import type {AuthType} from '../api/auth'
import {login} from '../api/auth'
+2 -1
View File
@@ -1,5 +1,6 @@
import {useCallback, useEffect, useState} from 'react'
import {Button, Card, Col, Divider, Form, InputNumber, message, Row, Select, Space, Tooltip, Typography} from 'antd'
import {Button, Card, Col, Divider, Form, InputNumber, Row, Select, Space, Tooltip, Typography} from 'antd'
import {message} from '../utils/appMessage'
import {QuestionCircleOutlined} from '@ant-design/icons'
import client from '../api/client'
+2 -1
View File
@@ -1,5 +1,6 @@
import {useCallback, useEffect, useState} from 'react'
import {Button, Card, Col, Form, Input, InputNumber, message, Row, Select, Space, Switch, Tabs} from 'antd'
import {Button, Card, Col, Form, Input, InputNumber, Row, Select, Space, Switch, Tabs} from 'antd'
import {message} from '../utils/appMessage'
import {Layers, Search} from 'lucide-react'
import client from '../api/client'
import IconTabLabel from '../components/IconTabLabel'
@@ -1,5 +1,6 @@
import {useCallback, useEffect, useState} from 'react'
import {Button, Card, Form, Input, InputNumber, message, Space, Switch, Tabs} from 'antd'
import {Button, Card, Form, Input, InputNumber, Space, Switch, Tabs} from 'antd'
import {message} from '../utils/appMessage'
import {ChartNetwork, SatelliteDish} from 'lucide-react'
import client from '../api/client'
+2 -1
View File
@@ -1,4 +1,5 @@
import {Col, Divider, Form, Input, message, Modal, Row, Select, Typography} from 'antd'
import {Col, Divider, Form, Input, Modal, Row, Select, Typography} from 'antd'
import {message} from '../utils/appMessage'
import client from '../api/client'
import type {AuthType, UserMutationResponse} from '../api/auth'
+12 -8
View File
@@ -1,5 +1,6 @@
import {useEffect, useState} from 'react'
import {Button, Col, Divider, Form, Input, message, Modal, Row, Select, Space, theme, Typography} from 'antd'
import {Button, Col, Divider, Form, Input, Modal, Row, Select, Space, theme, Typography} from 'antd'
import {message} from '../utils/appMessage'
import client from '../api/client'
import type {AuthUser} from '../stores/auth'
import AvatarUpload from '../components/AvatarUpload'
@@ -27,13 +28,8 @@ export default function UserDetailModal({ userId, open, onClose, onSaved }: User
const [saving, setSaving] = useState(false)
useEffect(() => {
if (!open || userId === null) {
// eslint-disable-next-line react-hooks/set-state-in-effect
setUser(null)
form.resetFields()
return
}
setLoading(true)
if (!open || userId === null) return
client.get<AuthUser>(`/auth/users/${userId}/`)
.then(({ data }) => {
setUser(data)
@@ -89,6 +85,14 @@ export default function UserDetailModal({ userId, open, onClose, onSaved }: User
footer: { background: token.colorBgContainer },
}}
destroyOnHidden
afterOpenChange={(nextOpen) => {
if (nextOpen) {
if (userId !== null) setLoading(true)
return
}
setUser(null)
form.resetFields()
}}
>
<Form form={form} layout="vertical" disabled={loading} style={{ paddingTop: 8 }}>
<Row gutter={16}>
+2 -1
View File
@@ -1,5 +1,6 @@
import {useEffect, useMemo, useState} from 'react'
import {Button, Input, message, Modal, Popconfirm, Space, Tooltip} from 'antd'
import {Button, Input, Modal, Popconfirm, Space, Tooltip} from 'antd'
import {message} from '../utils/appMessage'
import {CheckCircleOutlined, DeleteOutlined, KeyOutlined, PlusOutlined, StopOutlined} from '@ant-design/icons'
import client from '../api/client'
import DataTable from '../components/DataTable'
+28 -7
View File
@@ -7,6 +7,20 @@ function websocketUrl(token: string) {
return `${protocol}//${window.location.host}/ws/events/?token=${encodeURIComponent(token)}`
}
function closeSocket(socket: WebSocket) {
if (socket.readyState === WebSocket.CONNECTING) {
socket.onopen = () => socket.close()
socket.onmessage = null
socket.onerror = null
socket.onclose = null
return
}
if (socket.readyState === WebSocket.OPEN) {
socket.close()
}
}
export function RealtimeProvider({ children }: { children: React.ReactNode }) {
const token = useAuthStore((state) => state.token)
const [status, setStatus] = useState<RealtimeStatus>('disconnected')
@@ -32,8 +46,9 @@ export function RealtimeProvider({ children }: { children: React.ReactNode }) {
useEffect(() => {
if (!token) {
shouldReconnectRef.current = false
socketRef.current?.close()
const socket = socketRef.current
socketRef.current = null
if (socket) closeSocket(socket)
return
}
@@ -53,6 +68,10 @@ export function RealtimeProvider({ children }: { children: React.ReactNode }) {
socketRef.current = socket
socket.onopen = () => {
if (socketRef.current !== socket) {
socket.close()
return
}
reconnectAttemptRef.current = 0
setStatus('connected')
setReconnectToken((current) => current + 1)
@@ -62,6 +81,7 @@ export function RealtimeProvider({ children }: { children: React.ReactNode }) {
}
socket.onmessage = (event) => {
if (socketRef.current !== socket) return
let data: unknown
try {
data = JSON.parse(event.data)
@@ -75,9 +95,8 @@ export function RealtimeProvider({ children }: { children: React.ReactNode }) {
}
socket.onclose = () => {
if (socketRef.current === socket) {
socketRef.current = null
}
if (socketRef.current !== socket) return
socketRef.current = null
setStatus('disconnected')
if (!shouldReconnectRef.current) return
const attempt = reconnectAttemptRef.current + 1
@@ -87,17 +106,19 @@ export function RealtimeProvider({ children }: { children: React.ReactNode }) {
}
socket.onerror = () => {
socket.close()
if (socketRef.current !== socket) return
if (socket.readyState === WebSocket.OPEN) socket.close()
}
}
connect()
reconnectTimerRef.current = window.setTimeout(connect, 0)
return () => {
shouldReconnectRef.current = false
clearReconnectTimer()
socketRef.current?.close()
const socket = socketRef.current
socketRef.current = null
if (socket) closeSocket(socket)
}
}, [sendCommentSubscription, token])
+28
View File
@@ -0,0 +1,28 @@
import {App as AntApp} from 'antd'
type MessageApi = ReturnType<typeof AntApp.useApp>['message']
let activeMessage: MessageApi | null = null
export function setAppMessage(message: MessageApi) {
activeMessage = message
}
export function clearAppMessage(message: MessageApi) {
if (activeMessage === message) activeMessage = null
}
function getMessage() {
if (!activeMessage) {
throw new Error('Ant Design message API is not ready')
}
return activeMessage
}
export const message = new Proxy({} as MessageApi, {
get(_target, property: keyof MessageApi) {
const api = getMessage()
const value = api[property]
return typeof value === 'function' ? value.bind(api) : value
},
})