diff --git a/frontend/src/contexts/LogsContext.js b/frontend/src/contexts/LogsContext.js index fcbed53..61ddac6 100644 --- a/frontend/src/contexts/LogsContext.js +++ b/frontend/src/contexts/LogsContext.js @@ -3,6 +3,7 @@ import { getFullLogApi, getFullLogChartApi, getLogIndexApi, + // @ts-ignore getLogRuleApi, getLogTableInfoAPi, } from 'src/api/logs' @@ -15,6 +16,7 @@ export const useLogsContext = () => useContext(LogsContext) export const LogsProvider = ({ children }) => { const [state, dispatch] = useReducer(logsReducer, logsInitialState) const fetchData = async ({ startTime, endTime }) => { + // @ts-ignore dispatch({ type: 'updateLoading', payload: true }) try { @@ -25,6 +27,7 @@ export const LogsProvider = ({ children }) => { pageSize: state.pagination.pageSize, tableName: state.tableInfo?.tableName, dataBase: state.tableInfo?.dataBase, + timeField: state.tableInfo?.timeField, query: state.query, } @@ -33,16 +36,22 @@ export const LogsProvider = ({ children }) => { getFullLogChartApi(params), // getLogRuleApi({ tableName: 'test_logs', dataBase: 'default' }), ]) + // @ts-ignore let defaultFields = (res1?.defaultFields ?? []).sort() + // @ts-ignore let hiddenFields = (res1?.hiddenFields ?? []).sort() + // @ts-ignore dispatch({ type: 'setLogState', payload: { + // @ts-ignore logs: res1?.logs ?? [], defaultFields: defaultFields, hiddenFields: hiddenFields, + // @ts-ignore logsChartData: res2?.histograms ?? [], pagination: { + // @ts-ignore total: res2?.count ?? 0, pageIndex: state.pagination.pageIndex, pageSize: state.pagination.pageSize, @@ -52,6 +61,7 @@ export const LogsProvider = ({ children }) => { }) } catch (error) { console.error('请求出错:', error) + // @ts-ignore dispatch({ type: 'setLogState', payload: { @@ -68,6 +78,7 @@ export const LogsProvider = ({ children }) => { }, }) } finally { + // @ts-ignore dispatch({ type: 'updateLoading', payload: false }) } } @@ -83,9 +94,11 @@ export const LogsProvider = ({ children }) => { query: state.query, }) + // @ts-ignore dispatch({ type: 'updateFieldIndexMap', payload: { + // @ts-ignore [column]: res.indexs, }, }) @@ -99,16 +112,22 @@ export const LogsProvider = ({ children }) => { const getLogTableInfo = () => { getLogTableInfoAPi().then((res) => { - const dataBase = Object.keys(res.logTables)[0] - const tableList = res.logTables[dataBase][0] - dispatch({ - type: 'updateTableInfo', - payload: { - dataBase: dataBase, - tableName: tableList?.tableName, - cluster: tableList?.cluster, - }, - }) + // @ts-ignore + dispatch({ type: 'setLogRules', payload: res.parses ?? [] }) + // @ts-ignore + + dispatch({ type: 'setInstances', payload: res.instances ?? [] }) + if (res?.parses?.length > 0) { + // @ts-ignore + dispatch({ + type: 'updateTableInfo', + payload: { + dataBase: res.parses[0].dataBase, + tableName: res.parses[0].tableName, + parseName: res.parses[0]?.parseName, + }, + }) + } }) } useEffect(() => { @@ -126,16 +145,30 @@ export const LogsProvider = ({ children }) => { loading: state.loading, fieldIndexMap: state.fieldIndexMap, tableInfo: state.tableInfo, + logRules: state.logRules, + instances: state.instances, fetchData, getFieldIndexData, + // @ts-ignore updateLogs: (logs) => dispatch({ type: 'setLogs', payload: logs }), updateLogsPagination: (pagination) => + // @ts-ignore dispatch({ type: 'setPagination', payload: { ...state.pagination, ...pagination } }), + // @ts-ignore updateLogsChartData: (data) => dispatch({ type: 'setLogsChartData', payload: data }), + // @ts-ignore updateDefaultFields: (data) => dispatch({ type: 'updateDefaultFields', payload: data }), + // @ts-ignore updateHiddenFields: (data) => dispatch({ type: 'updateHiddenFields', payload: data }), + // @ts-ignore updateQuery: (data) => dispatch({ type: 'updateQuery', payload: data }), - updateTableName: (data) => dispatch({ type: 'updateTableName', payload: data }), + // @ts-ignore + updateTableInfo: (data) => dispatch({ type: 'updateTableInfo', payload: data }), + // @ts-ignore + setLogRules: (data) => dispatch({ type: 'setLogRules', payload: data }), + // @ts-ignore + setInstances: (data) => dispatch({ type: 'setInstances', payload: data }), + // @ts-ignore clearFieldIndexMap: () => dispatch({ type: 'clearFieldIndexMap' }), }), [ @@ -148,8 +181,11 @@ export const LogsProvider = ({ children }) => { state.loading, state.fieldIndexMap, state.tableInfo, + state.logRules, + state.instances, ], ) + // @ts-ignore return {children} } diff --git a/frontend/src/store/reducers/logsReducer.js b/frontend/src/store/reducers/logsReducer.js index 937d851..43a068d 100644 --- a/frontend/src/store/reducers/logsReducer.js +++ b/frontend/src/store/reducers/logsReducer.js @@ -2,9 +2,14 @@ export const logsInitialState = { tableInfo: { dataBase: '', tableName: '', + parseName: '', + cluster: '', + instanceName: '', + timeField: '', }, - logRule: {}, + logRules: [], + instances: [], logs: [], pagination: { pageIndex: 1, @@ -48,6 +53,10 @@ const logsReducer = (state = logsInitialState, action) => { case 'updateFieldIndexMap': //增量更新 return { ...state, fieldIndexMap: { ...state.fieldIndexMap, ...action.payload } } + case 'setLogRules': + return { ...state, logRules: action.payload } + case 'setInstances': + return { ...state, instances: action.payload } case 'clearFieldIndexMap': return { ...state, fieldIndexMap: {} } default: diff --git a/frontend/src/views/logs/component/FullLogs/component/IndexList/index.jsx b/frontend/src/views/logs/component/FullLogs/component/IndexList/index.jsx index 1fcc951..ae7c5de 100644 --- a/frontend/src/views/logs/component/FullLogs/component/IndexList/index.jsx +++ b/frontend/src/views/logs/component/FullLogs/component/IndexList/index.jsx @@ -28,7 +28,7 @@ const IndexList = () => { <> diff --git a/frontend/src/views/logs/component/FullLogs/component/LogQueryResult/QueryList/LogItem/component/LogItemDetail.jsx b/frontend/src/views/logs/component/FullLogs/component/LogQueryResult/QueryList/LogItem/component/LogItemDetail.jsx index 7add544..cdf85b0 100644 --- a/frontend/src/views/logs/component/FullLogs/component/LogQueryResult/QueryList/LogItem/component/LogItemDetail.jsx +++ b/frontend/src/views/logs/component/FullLogs/component/LogQueryResult/QueryList/LogItem/component/LogItemDetail.jsx @@ -1,9 +1,10 @@ import { Tag } from 'antd' import React, { useEffect, useState } from 'react' import { useLogsContext } from 'src/contexts/LogsContext' -import LogTag from './LogTag' +import LogKeyTag from './LogKeyTag' const LogItemDetail = ({ log }) => { const [contentInfo, setContentInfo] = useState({}) + const { tableInfo } = useLogsContext() useEffect(() => { try { const obj = JSON.parse(log.content) @@ -23,7 +24,10 @@ const LogItemDetail = ({ log }) => { return (
{Object.entries(contentInfo).map(([key, value]) => ( - + + ))} + {Object.entries(log.tags).map(([key, value]) => ( + ))}
) diff --git a/frontend/src/views/logs/component/FullLogs/component/LogQueryResult/QueryList/LogItem/component/LogItemFold.jsx b/frontend/src/views/logs/component/FullLogs/component/LogQueryResult/QueryList/LogItem/component/LogItemFold.jsx index 61397b4..fbce70f 100644 --- a/frontend/src/views/logs/component/FullLogs/component/LogQueryResult/QueryList/LogItem/component/LogItemFold.jsx +++ b/frontend/src/views/logs/component/FullLogs/component/LogQueryResult/QueryList/LogItem/component/LogItemFold.jsx @@ -1,28 +1,25 @@ import { Tag, Tooltip } from 'antd' import React, { useEffect, useState } from 'react' import LogTagDropDown from './LogTagDropdown' -const LogItemFold = ({ log }) => { +import { useLogsContext } from 'src/contexts/LogsContext' +import LogValueTag from './LogValueTag' +const LogItemFold = ({ tags }) => { + const { tableInfo } = useLogsContext() return ( -
- {Object.entries(log.tags).map( - ([key, value]) => - value && - key !== 'timestamp' && ( -
- - - {value} - - - } - > -
- ), - )} +
+ {Object.entries(tags).map(([key, value]) => { + if ( + value !== '' && // 确保 value 存在且非空 + key !== (tableInfo?.timeField || 'timestamp') && // 排除与 timeField 相同的键 + typeof value !== 'object' // 确保 value 不是对象 + ) { + return + } + return null // 不符合条件时返回 null + })}
) } diff --git a/frontend/src/views/logs/component/FullLogs/component/LogQueryResult/QueryList/LogItem/component/LogTag.jsx b/frontend/src/views/logs/component/FullLogs/component/LogQueryResult/QueryList/LogItem/component/LogKeyTag.jsx similarity index 86% rename from frontend/src/views/logs/component/FullLogs/component/LogQueryResult/QueryList/LogItem/component/LogTag.jsx rename to frontend/src/views/logs/component/FullLogs/component/LogQueryResult/QueryList/LogItem/component/LogKeyTag.jsx index e9e9437..f7e8abc 100644 --- a/frontend/src/views/logs/component/FullLogs/component/LogQueryResult/QueryList/LogItem/component/LogTag.jsx +++ b/frontend/src/views/logs/component/FullLogs/component/LogQueryResult/QueryList/LogItem/component/LogKeyTag.jsx @@ -1,8 +1,8 @@ import { Tag } from 'antd' import React from 'react' import LogTagDropDown from './LogTagDropdown' - -const LogTag = (props) => { +//Key 作为log内容 +const LogKeyTag = (props) => { const { title, description } = props return (
@@ -18,4 +18,4 @@ const LogTag = (props) => {
) } -export default LogTag +export default LogKeyTag diff --git a/frontend/src/views/logs/component/FullLogs/component/LogQueryResult/QueryList/LogItem/component/LogTagDropdown.jsx b/frontend/src/views/logs/component/FullLogs/component/LogQueryResult/QueryList/LogItem/component/LogTagDropdown.jsx index 01ffac4..fadecf5 100644 --- a/frontend/src/views/logs/component/FullLogs/component/LogQueryResult/QueryList/LogItem/component/LogTagDropdown.jsx +++ b/frontend/src/views/logs/component/FullLogs/component/LogQueryResult/QueryList/LogItem/component/LogTagDropdown.jsx @@ -41,7 +41,7 @@ const LogTagDropDown = ({ objKey, value, children }) => { return ( <> -
{children}
+ <>{children}
) diff --git a/frontend/src/views/logs/component/FullLogs/component/LogQueryResult/QueryList/LogItem/component/LogValueTag.jsx b/frontend/src/views/logs/component/FullLogs/component/LogQueryResult/QueryList/LogItem/component/LogValueTag.jsx new file mode 100644 index 0000000..27c9d02 --- /dev/null +++ b/frontend/src/views/logs/component/FullLogs/component/LogQueryResult/QueryList/LogItem/component/LogValueTag.jsx @@ -0,0 +1,21 @@ +import { Tag, Tooltip } from 'antd' +import React from 'react' +import LogTagDropDown from './LogTagDropdown' +// value作为tag内容 +const LogValueTag = (props) => { + const { objKey, value } = props + return ( + + + {value} + + + } + /> + ) +} +export default LogValueTag diff --git a/frontend/src/views/logs/component/FullLogs/component/LogQueryResult/QueryList/LogItem/index.jsx b/frontend/src/views/logs/component/FullLogs/component/LogQueryResult/QueryList/LogItem/index.jsx index 3363b06..9d98fb9 100644 --- a/frontend/src/views/logs/component/FullLogs/component/LogQueryResult/QueryList/LogItem/index.jsx +++ b/frontend/src/views/logs/component/FullLogs/component/LogQueryResult/QueryList/LogItem/index.jsx @@ -3,12 +3,13 @@ import { AiFillCaretDown, AiFillCaretRight } from 'react-icons/ai' import LogItemFold from './component/LogItemFold' import LogItemDetail from './component/LogItemDetail' import { Button } from 'antd' +import { useLogsContext } from 'src/contexts/LogsContext' const LogItem = (props) => { const { log, foldingChecked } = props + const { tableInfo } = useLogsContext() // 是否折叠日志,true 为是,false 为否 const [isFold, setIsFold] = useState(true) - const handleFoldClick = () => setIsFold(() => !isFold) useEffect(() => { setIsFold(foldingChecked ?? true) }, [foldingChecked]) @@ -17,21 +18,22 @@ const LogItem = (props) => { {/* icon 和 时间 */}
- {/* */} - {log?.tags.timestamp} + > + {log?.tags?.[tableInfo?.timeField ? tableInfo?.timeField : 'timestamp']}
{/* 具体日志 */}
- - - {/* {isFold ? : } */} + {/* + */} + {/* */} + {isFold ? : }
) diff --git a/frontend/src/views/logs/component/FullLogs/component/Sider/DataSourceTree/index.jsx b/frontend/src/views/logs/component/FullLogs/component/Sider/DataSourceTree/index.jsx new file mode 100644 index 0000000..fca6a8a --- /dev/null +++ b/frontend/src/views/logs/component/FullLogs/component/Sider/DataSourceTree/index.jsx @@ -0,0 +1,104 @@ +import { Card, Tree } from 'antd' +import React, { useEffect, useState } from 'react' +import { useLogsContext } from 'src/contexts/LogsContext' +import { LuDatabase, LuServer } from 'react-icons/lu' +import { ImTable2 } from 'react-icons/im' +const DataSourceTree = () => { + const { instances, tableInfo, updateTableInfo } = useLogsContext() + const [treeData, setTreeData] = useState([]) + const [expandedKeys, setExpandedKeys] = useState([]) + const [selectedKeys, setSelectedKeys] = useState([]) + // level: + // 0: instance:{dataBases:[],instanceName} + // 1: dataBase:{tables:[],dataBase} + // 2: table:{cluster,tableName,timeField} + const treeTitle = (title, icon) => { + return ( +
+
{icon}
+
{title}
+
+ ) + } + useEffect(() => { + const expandedKeys = [] + const newTreeData = instances?.map((instance) => { + const instanceKey = 'instance-' + instance.instanceName + expandedKeys.push(instanceKey) // 收集instance的key + return { + key: instanceKey, + title: treeTitle( + instance.instanceName, + , + ), + children: instance.dataBases?.map((dataBase) => { + const dataBaseKey = 'instance-' + instance.instanceName + '-dataBase-' + dataBase.dataBase + expandedKeys.push(dataBaseKey) // 收集dataBase的key + return { + key: dataBaseKey, + title: treeTitle( + dataBase.dataBase, + , + ), + children: dataBase.tables?.map((table) => { + const tableKey = instance.instanceName + dataBase.dataBase + table.tableName + return { + key: tableKey, + title: treeTitle( + table.tableName, + , + ), + dataBase: dataBase.dataBase, + instanceName: instance.instanceName, + ...table, + } + }), + } + }), + } + }) + + setTreeData(newTreeData) + setExpandedKeys(expandedKeys) // 更新defaultExpandedKeys + }, [instances]) + + const onSelect = (selectedKeys, { selectedNodes }) => { + if (selectedNodes[0].tableName) { + updateTableInfo({ + dataBase: selectedNodes[0].dataBase, + tableName: selectedNodes[0].tableName, + cluster: selectedNodes[0].cluster, + timeField: selectedNodes[0].timeField, + instanceName: selectedNodes[0].instanceName, + }) + } + } + const onExpand = (expandedKeys) => { + setExpandedKeys(expandedKeys) + } + useEffect(() => { + setSelectedKeys([tableInfo.instanceName + tableInfo.dataBase + tableInfo.tableName]) + }, [tableInfo]) + return ( + + + + ) +} + +export default DataSourceTree diff --git a/frontend/src/views/logs/component/FullLogs/component/Sider/LogRuleList/index.jsx b/frontend/src/views/logs/component/FullLogs/component/Sider/LogRuleList/index.jsx new file mode 100644 index 0000000..379120d --- /dev/null +++ b/frontend/src/views/logs/component/FullLogs/component/Sider/LogRuleList/index.jsx @@ -0,0 +1,58 @@ +import { Card, Tree } from 'antd' +import React, { useEffect, useState } from 'react' +import { useLogsContext } from 'src/contexts/LogsContext' + +const LogRuleList = () => { + const { logRules, tableInfo, updateTableInfo } = useLogsContext() + const [treeData, setTreeData] = useState([]) + + const [selectedKeys, setSelectedKeys] = useState([]) + + const menuLabel = (parseName, parseInfo) => { + return ( +
+
{parseName}
+
{parseInfo}
+
+ ) + } + useEffect(() => { + setTreeData( + logRules.map((rule, index) => ({ + key: rule.dataBase + rule.tableName, + title: menuLabel(rule.parseName, rule.parseInfo), + ...rule, + })), + ) + }, [logRules]) + const onSelect = (selectedKeys, { selectedNodes }) => { + updateTableInfo({ + dataBase: selectedNodes[0].dataBase, + tableName: selectedNodes[0].tableName, + cluster: '', + parseName: selectedNodes[0].parseName, + }) + } + useEffect(() => { + setSelectedKeys([tableInfo.dataBase + tableInfo.tableName]) + }, [tableInfo]) + return ( + + + + ) +} + +export default LogRuleList diff --git a/frontend/src/views/logs/component/FullLogs/component/Sider/index.jsx b/frontend/src/views/logs/component/FullLogs/component/Sider/index.jsx new file mode 100644 index 0000000..7804049 --- /dev/null +++ b/frontend/src/views/logs/component/FullLogs/component/Sider/index.jsx @@ -0,0 +1,16 @@ +import React from 'react' +import LogRuleList from './LogRuleList' +import DataSourceTree from './DataSourceTree' +import Sider from 'antd/es/layout/Sider' +import { Card } from 'antd' + +const FullLogSider = () => { + return ( + <> + + + + ) +} + +export default FullLogSider diff --git a/frontend/src/views/logs/component/FullLogs/index.css b/frontend/src/views/logs/component/FullLogs/index.css new file mode 100644 index 0000000..3f87be7 --- /dev/null +++ b/frontend/src/views/logs/component/FullLogs/index.css @@ -0,0 +1,24 @@ +.logSiderButton { + top: 50%; + position: absolute; + transform: translate(0, -50%); + z-index: 100; + background: #ffffff1a; + height: 40px; + border-radius: 0px 100% 100% 0px / 50%; + display: flex; + align-items: center; + cursor: pointer; +} + +.openButton { + left: 190px; + transition: left 0.2s ease; + /* 添加 transition */ +} + +.closeButton { + left: -1px; + transition: left 0.2s ease; + /* 添加 transition */ +} \ No newline at end of file diff --git a/frontend/src/views/logs/component/FullLogs/index.jsx b/frontend/src/views/logs/component/FullLogs/index.jsx index c754361..f6e8ffd 100644 --- a/frontend/src/views/logs/component/FullLogs/index.jsx +++ b/frontend/src/views/logs/component/FullLogs/index.jsx @@ -9,6 +9,12 @@ import IndexList from './component/IndexList' import LogQueryResult from './component/LogQueryResult' import { useLogsContext } from 'src/contexts/LogsContext' import { useDebounce, useUpdateEffect } from 'react-use' +import FullLogSider from './component/Sider' +import { Button, Layout } from 'antd' +import Sider from 'antd/es/layout/Sider' +import { Content } from 'antd/es/layout/layout' +import { AiOutlineCaretLeft, AiOutlineCaretRight } from 'react-icons/ai' +import './index.css' function FullLogs() { const { query, @@ -21,6 +27,7 @@ function FullLogs() { } = useLogsContext() const [searchParams] = useSearchParams() + const [collapsed, setCollapsed] = useState(false) useUpdateEffect(() => { if (searchParams.get('log-from') && searchParams.get('log-to')) { @@ -59,18 +66,43 @@ function FullLogs() { <> {/* 顶部筛选 */} - -
- -
-
-
- + + +
setCollapsed(!collapsed)} + className={`logSiderButton ${collapsed ? ' closeButton ' : 'openButton'}`} + > + {collapsed ? ( + + ) : ( + + )}
-
- -
-
+ + + + +
+
+ +
+
+
+ +
+
+ +
+
+
+
+ )