1
- import { EmptyContent } from "components/EmptyContent" ;
2
- import { HelpText } from "components/HelpText" ;
3
- import { Switch , Card , Input , message , Divider } from "antd" ;
4
- import React , { useEffect , useState } from "react" ;
1
+ import { Card , Form , Select , Input , Button , message , Divider , Spin , Table } from "antd" ;
2
+ import React , { useEffect , useState , useCallback } from "react" ;
5
3
import { useDispatch , useSelector } from "react-redux" ;
6
4
import styled from "styled-components" ;
7
5
import { trans } from "i18n" ;
@@ -16,6 +14,7 @@ import { fetchCommonSettings } from "@lowcoder-ee/redux/reduxActions/commonSetti
16
14
import ReactECharts from "echarts-for-react" ;
17
15
import { getAuditLogs } from "api/enterpriseApi" ;
18
16
import EventTypeTimeChart from "./charts/eventTypesTime" ;
17
+ import { debounce } from "lodash" ;
19
18
20
19
const AuditContent = styled . div `
21
20
font-size: 14px;
@@ -35,50 +34,205 @@ const StyleThemeSettingsCover = styled.div`
35
34
border-radius: 10px 10px 0 0;
36
35
` ;
37
36
37
+ const eventTypes = [
38
+ { value : "USER_LOGIN" , label : "User Login" } ,
39
+ { value : "USER_LOGOUT" , label : "User Logout" } ,
40
+ { value : "APPLICATION_VIEW" , label : "View Application" } ,
41
+ { value : "APPLICATION_CREATE" , label : "Create Application" } ,
42
+ { value : "APPLICATION_DELETE" , label : "Delete Application" } ,
43
+ { value : "APPLICATION_UPDATE" , label : "Update Application" } ,
44
+ { value : "APPLICATION_MOVE" , label : "Move Application" } ,
45
+ { value : "APPLICATION_RECYCLED" , label : "Recycle Application" } ,
46
+ { value : "APPLICATION_RESTORE" , label : "Restore Application" } ,
47
+ { value : "FOLDER_CREATE" , label : "Create Folder" } ,
48
+ { value : "FOLDER_DELETE" , label : "Delete Folder" } ,
49
+ { value : "FOLDER_UPDATE" , label : "Update Folder" } ,
50
+ { value : "QUERY_EXECUTION" , label : "Execute Query" } ,
51
+ { value : "GROUP_CREATE" , label : "Create Group" } ,
52
+ { value : "GROUP_UPDATE" , label : "Update Group" } ,
53
+ { value : "GROUP_DELETE" , label : "Delete Group" } ,
54
+ { value : "GROUP_MEMBER_ADD" , label : "Add Group Member" } ,
55
+ { value : "GROUP_MEMBER_ROLE_UPDATE" , label : "Update Group Member Role" } ,
56
+ { value : "GROUP_MEMBER_LEAVE" , label : "Leave Group" } ,
57
+ { value : "GROUP_MEMBER_REMOVE" , label : "Remove Group Member" } ,
58
+ { value : "SERVER_START_UP" , label : "Server Startup" } ,
59
+ { value : "SERVER_INFO" , label : "View Server Info" } ,
60
+ { value : "DATA_SOURCE_CREATE" , label : "Create Datasource" } ,
61
+ { value : "DATA_SOURCE_UPDATE" , label : "Update Datasource" } ,
62
+ { value : "DATA_SOURCE_DELETE" , label : "Delete Datasource" } ,
63
+ { value : "DATA_SOURCE_PERMISSION_GRANT" , label : "Grant Datasource Permission" } ,
64
+ { value : "DATA_SOURCE_PERMISSION_UPDATE" , label : "Update Datasource Permission" } ,
65
+ { value : "DATA_SOURCE_PERMISSION_DELETE" , label : "Delete Datasource Permission" } ,
66
+ { value : "LIBRARY_QUERY_CREATE" , label : "Create Library Query" } ,
67
+ { value : "LIBRARY_QUERY_UPDATE" , label : "Update Library Query" } ,
68
+ { value : "LIBRARY_QUERY_DELETE" , label : "Delete Library Query" } ,
69
+ { value : "LIBRARY_QUERY_PUBLISH" , label : "Publish Library Query" } ,
70
+ { value : "API_CALL_EVENT" , label : "API Call Event" } ,
71
+ ] ;
38
72
39
- export function AuditLog ( ) {
73
+ export function AuditLog ( ) {
40
74
const currentUser = useSelector ( getUser ) ;
41
75
const dispatch = useDispatch ( ) ;
42
-
43
76
const [ logs , setLogs ] = useState ( [ ] ) ;
44
- const [ filteredLogs , setFilteredLogs ] = useState ( [ ] ) ;
45
77
const [ loading , setLoading ] = useState ( false ) ;
46
-
78
+ const [ form ] = Form . useForm ( ) ;
79
+
47
80
useEffect ( ( ) => {
48
- dispatch ( fetchCommonSettings ( { orgId : currentUser . currentOrgId } ) ) ;
49
- } , [ currentUser . currentOrgId , dispatch ] ) ;
81
+ // Fetch logs automatically on component mount
82
+ fetchLogs ( { orgId : currentUser . currentOrgId } ) ;
83
+ } , [ currentUser . currentOrgId ] ) ;
50
84
51
- const fetchLogs = async ( ) => {
85
+ const fetchLogs = async ( params = { } ) => {
86
+ const cleanedParams = Object . fromEntries (
87
+ Object . entries ( params ) . filter ( ( [ _ , value ] ) => value !== undefined && value !== "" )
88
+ ) ;
52
89
setLoading ( true ) ;
53
90
try {
54
- const data = await getAuditLogs ( { orgId : currentUser . currentOrgId } ) ;
55
- setLogs ( data ) ;
91
+ const data = await getAuditLogs ( cleanedParams ) ;
92
+ setLogs ( data . data ) ;
56
93
} catch ( error ) {
57
94
message . error ( "Failed to fetch audit logs." ) ;
58
95
} finally {
59
96
setLoading ( false ) ;
60
97
}
61
98
} ;
62
-
99
+
100
+ const handleFormSubmit = ( values : any ) => {
101
+ const queryParams = {
102
+ ...values ,
103
+ orgId : currentUser . currentOrgId ,
104
+ } ;
105
+ fetchLogs ( queryParams ) ;
106
+ } ;
107
+
108
+ interface ValueType {
109
+ [ key : string ] : string | any [ ] ; // replace any[] with the actual data type if known
110
+ }
111
+ // Debounce handler for input fields
112
+ const handleInputChange = useCallback (
113
+ debounce ( ( changedValue : ValueType , allValues ) => {
114
+ if ( Object . values ( changedValue ) [ 0 ] ?. length >= 3 ) {
115
+ handleFormSubmit ( allValues ) ;
116
+ }
117
+ } , 300 ) ,
118
+ [ ] // Ensures debounce doesn't recreate on every render
119
+ ) ;
120
+
121
+ const handleSelectChange = ( changedValue : any , allValues : any ) => {
122
+ handleFormSubmit ( allValues ) ;
123
+ } ;
124
+
125
+ // Generate hierarchical data for the table
126
+ const generateHierarchy = ( data : any [ ] ) => {
127
+ const orgMap = new Map ( ) ;
128
+
129
+ data . forEach ( ( log ) => {
130
+ const org = orgMap . get ( log . orgId ) || { key : log . orgId , orgId : log . orgId , children : [ ] } ;
131
+ const user = org . children . find ( ( u : any ) => u . userId === log . userId ) || { key : `${ log . orgId } -${ log . userId } ` , userId : log . userId , children : [ ] } ;
132
+ const app = user . children . find ( ( a : any ) => a . appId === log . appId ) || { key : `${ log . orgId } -${ log . userId } -${ log . appId } ` , appId : log . appId , children : [ ] } ;
133
+
134
+ app . children . push ( {
135
+ key : `${ log . orgId } -${ log . userId } -${ log . appId } -${ log . eventType } -${ log . eventTime } ` ,
136
+ eventType : log . eventType ,
137
+ eventTime : log . eventTime ,
138
+ } ) ;
139
+
140
+ if ( ! user . children . some ( ( a : any ) => a . appId === log . appId ) ) {
141
+ user . children . push ( app ) ;
142
+ }
143
+
144
+ if ( ! org . children . some ( ( u : any ) => u . userId === log . userId ) ) {
145
+ org . children . push ( user ) ;
146
+ }
147
+
148
+ if ( ! orgMap . has ( log . orgId ) ) {
149
+ orgMap . set ( log . orgId , org ) ;
150
+ }
151
+ } ) ;
152
+
153
+ return Array . from ( orgMap . values ( ) ) ;
154
+ } ;
155
+
156
+ const hierarchicalData = generateHierarchy ( logs ) ;
157
+
158
+ const columns = [
159
+ { title : "Org ID" , dataIndex : "orgId" , key : "orgId" } ,
160
+ { title : "User ID" , dataIndex : "userId" , key : "userId" } ,
161
+ { title : "App ID" , dataIndex : "appId" , key : "appId" } ,
162
+ { title : "Event Type" , dataIndex : "eventType" , key : "eventType" } ,
163
+ { title : "Event Time" , dataIndex : "eventTime" , key : "eventTime" } ,
164
+ ] ;
165
+
166
+ const eventTypeLabels = Object . fromEntries ( eventTypes . map ( ( et ) => [ et . value , et . label ] ) ) ;
167
+
63
168
return (
64
169
< DetailContainer >
65
170
< Header >
66
171
< HeaderBack >
67
- < span > { trans ( "branding.title " ) } </ span >
172
+ < span > { trans ( "enterprise.AuditLogTitle " ) } </ span >
68
173
</ HeaderBack >
69
174
</ Header >
70
175
71
176
< DetailContent >
72
177
< AuditContent >
73
178
< StyleThemeSettingsCover >
74
- < h2 style = { { color : "#ffffff" , marginTop : "8px" } } > { trans ( "branding.logoSection " ) } </ h2 >
179
+ < h2 style = { { color : "#ffffff" , marginTop : "8px" } } > { trans ( "enterprise.AuditLogOverview " ) } </ h2 >
75
180
</ StyleThemeSettingsCover >
76
- < Card >
77
- < div >
78
- < h3 > { trans ( "branding.logo" ) } </ h3 >
79
- < EventTypeTimeChart data = { logs } />
80
- </ div >
81
-
181
+ < Card title = "Filter Audit Logs" size = "small" style = { { marginBottom : "20px" } } >
182
+ < Form
183
+ form = { form }
184
+ layout = "inline"
185
+ onValuesChange = { ( changedValue , allValues ) => {
186
+ const key = Object . keys ( changedValue ) [ 0 ] ;
187
+ if ( key === "eventType" ) {
188
+ handleSelectChange ( changedValue , allValues ) ;
189
+ } else {
190
+ handleInputChange ( changedValue , allValues ) ;
191
+ }
192
+ } }
193
+ onFinish = { handleFormSubmit }
194
+ >
195
+ < Form . Item name = "environmentId" >
196
+ < Input placeholder = "Environment ID" allowClear />
197
+ </ Form . Item >
198
+ < Form . Item name = "userId" >
199
+ < Input placeholder = "User ID" allowClear />
200
+ </ Form . Item >
201
+ < Form . Item name = "appId" >
202
+ < Input placeholder = "App ID" allowClear />
203
+ </ Form . Item >
204
+ < Form . Item name = "eventType" >
205
+ < Select
206
+ allowClear
207
+ showSearch
208
+ placeholder = "Event Type"
209
+ options = { eventTypes }
210
+ style = { { width : 200 } }
211
+ />
212
+ </ Form . Item >
213
+ < Form . Item >
214
+ < Button type = "primary" htmlType = "submit" loading = { loading } >
215
+ Fetch Logs
216
+ </ Button >
217
+ </ Form . Item >
218
+ </ Form >
219
+ </ Card >
220
+ < Card title = "Audit Logs" >
221
+ { loading ? (
222
+ < Spin />
223
+ ) : logs . length > 0 ? (
224
+ < >
225
+ < EventTypeTimeChart data = { logs } eventTypeLabels = { eventTypeLabels } />
226
+ < Divider />
227
+ < Table
228
+ columns = { columns }
229
+ dataSource = { hierarchicalData }
230
+ pagination = { { pageSize : 10 } }
231
+ />
232
+ </ >
233
+ ) : (
234
+ < p > No logs found. Adjust the filters and try again.</ p >
235
+ ) }
82
236
</ Card >
83
237
</ AuditContent >
84
238
</ DetailContent >
0 commit comments