Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit8db658b

Browse files
author
FalkWolsky
committed
Chart and hierarchic Table
1 parentca8fff9 commit8db658b

File tree

3 files changed

+207
-34
lines changed

3 files changed

+207
-34
lines changed

‎client/packages/lowcoder/src/i18n/locales/en.ts‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2360,6 +2360,11 @@ export const en = {
23602360
"premium":"Premium",
23612361
},
23622362

2363+
"enterprise" :{
2364+
"AuditLogTitle":"Audit Log Dasboard",
2365+
"AuditLogOverview":"Overview",
2366+
},
2367+
23632368
"subscription":{
23642369
"details":"Subscription Details",
23652370
"productDetails":"Product Details",

‎client/packages/lowcoder/src/pages/setting/audit/charts/eventTypesTime.tsx‎

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,31 +2,45 @@ import React from "react";
22
importReactEChartsfrom"echarts-for-react";
33

44
interfaceProps{
5-
data:Array<any>;// replace any with the actual type of data item
5+
data:Array<any>;
6+
eventTypeLabels :any;
67
}
78

8-
constEventTypeTimeChart=({ data}:Props)=>{
9-
9+
constEventTypeTimeChart=({ data, eventTypeLabels}:Props)=>{
1010
constgetChartOptions=()=>{
11-
12-
constgroupedData=data.reduce((acc :any,log:any)=>{
13-
constdate=newDate(log.event_time).toISOString().split("T")[0];
14-
if(!acc[date])acc[date]={};
15-
acc[date][log.event_type]=(acc[date][log.event_type]||0)+1;
11+
// Group data by date and eventType
12+
constgroupedData=data.reduce((acc:any,log:any)=>{
13+
// Validate and parse eventTime
14+
consteventTime=log.eventTime ?newDate(log.eventTime) :null;
15+
16+
if(eventTime&&!isNaN(eventTime.getTime())){
17+
constdate=eventTime.toISOString().split("T")[0];// Extract date part
18+
if(!acc[date])acc[date]={};
19+
acc[date][log.eventType]=(acc[date][log.eventType]||0)+1;
20+
}else{
21+
console.warn("Invalid or missing eventTime:",log.eventTime);
22+
}
23+
1624
returnacc;
1725
},{});
1826

27+
// Extract sorted dates and unique event types
1928
constdates=Object.keys(groupedData).sort();
20-
consteventTypes=[...newSet(data.map((log:any)=>log.event_type))];
29+
consteventTypes=[...newSet(data.map((log:any)=>log.eventType))];
30+
31+
console.log("Dates:",dates);
32+
console.log("Event Types:",eventTypes);
2133

2234
// Prepare series data for each event type
2335
constseries=eventTypes.map((eventType)=>({
24-
name:eventType,
36+
name:eventTypeLabels[eventType]||eventType,
2537
type:"bar",
2638
stack:"total",
2739
data:dates.map((date)=>groupedData[date][eventType]||0),// Fill gaps with 0
2840
}));
2941

42+
console.log("Series Data:",series);
43+
3044
return{
3145
title:{text:"Event Types Over Time",left:"center"},
3246
tooltip:{
@@ -48,11 +62,11 @@ const EventTypeTimeChart = ({ data }: Props) => {
4862
};
4963

5064
return(
51-
<ReactECharts
65+
<ReactECharts
5266
option={getChartOptions()}
5367
style={{height:"400px",marginBottom:"20px"}}
5468
/>
5569
);
5670
};
5771

58-
exportdefaultEventTypeTimeChart;
72+
exportdefaultEventTypeTimeChart;

‎client/packages/lowcoder/src/pages/setting/audit/index.tsx‎

Lines changed: 176 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
1-
import{EmptyContent}from"components/EmptyContent";
2-
import{HelpText}from"components/HelpText";
3-
import{Switch,Card,Input,message,Divider}from"antd";
4-
importReact,{useEffect,useState}from"react";
1+
import{Card,Form,Select,Input,Button,message,Divider,Spin,Table}from"antd";
2+
importReact,{useEffect,useState,useCallback}from"react";
53
import{useDispatch,useSelector}from"react-redux";
64
importstyledfrom"styled-components";
75
import{trans}from"i18n";
@@ -16,6 +14,7 @@ import { fetchCommonSettings } from "@lowcoder-ee/redux/reduxActions/commonSetti
1614
importReactEChartsfrom"echarts-for-react";
1715
import{getAuditLogs}from"api/enterpriseApi";
1816
importEventTypeTimeChartfrom"./charts/eventTypesTime";
17+
import{debounce}from"lodash";
1918

2019
constAuditContent=styled.div`
2120
font-size: 14px;
@@ -35,50 +34,205 @@ const StyleThemeSettingsCover = styled.div`
3534
border-radius: 10px 10px 0 0;
3635
`;
3736

37+
consteventTypes=[
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+
];
3872

39-
exportfunctionAuditLog(){
73+
exportfunctionAuditLog(){
4074
constcurrentUser=useSelector(getUser);
4175
constdispatch=useDispatch();
42-
4376
const[logs,setLogs]=useState([]);
44-
const[filteredLogs,setFilteredLogs]=useState([]);
4577
const[loading,setLoading]=useState(false);
46-
78+
const[form]=Form.useForm();
79+
4780
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]);
5084

51-
constfetchLogs=async()=>{
85+
constfetchLogs=async(params={})=>{
86+
constcleanedParams=Object.fromEntries(
87+
Object.entries(params).filter(([_,value])=>value!==undefined&&value!=="")
88+
);
5289
setLoading(true);
5390
try{
54-
constdata=awaitgetAuditLogs({orgId:currentUser.currentOrgId});
55-
setLogs(data);
91+
constdata=awaitgetAuditLogs(cleanedParams);
92+
setLogs(data.data);
5693
}catch(error){
5794
message.error("Failed to fetch audit logs.");
5895
}finally{
5996
setLoading(false);
6097
}
6198
};
62-
99+
100+
consthandleFormSubmit=(values:any)=>{
101+
constqueryParams={
102+
...values,
103+
orgId:currentUser.currentOrgId,
104+
};
105+
fetchLogs(queryParams);
106+
};
107+
108+
interfaceValueType{
109+
[key:string]:string|any[];// replace any[] with the actual data type if known
110+
}
111+
// Debounce handler for input fields
112+
consthandleInputChange=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+
consthandleSelectChange=(changedValue:any,allValues:any)=>{
122+
handleFormSubmit(allValues);
123+
};
124+
125+
// Generate hierarchical data for the table
126+
constgenerateHierarchy=(data:any[])=>{
127+
constorgMap=newMap();
128+
129+
data.forEach((log)=>{
130+
constorg=orgMap.get(log.orgId)||{key:log.orgId,orgId:log.orgId,children:[]};
131+
constuser=org.children.find((u:any)=>u.userId===log.userId)||{key:`${log.orgId}-${log.userId}`,userId:log.userId,children:[]};
132+
constapp=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+
returnArray.from(orgMap.values());
154+
};
155+
156+
consthierarchicalData=generateHierarchy(logs);
157+
158+
constcolumns=[
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+
consteventTypeLabels=Object.fromEntries(eventTypes.map((et)=>[et.value,et.label]));
167+
63168
return(
64169
<DetailContainer>
65170
<Header>
66171
<HeaderBack>
67-
<span>{trans("branding.title")}</span>
172+
<span>{trans("enterprise.AuditLogTitle")}</span>
68173
</HeaderBack>
69174
</Header>
70175

71176
<DetailContent>
72177
<AuditContent>
73178
<StyleThemeSettingsCover>
74-
<h2style={{color:"#ffffff",marginTop:"8px"}}>{trans("branding.logoSection")}</h2>
179+
<h2style={{color:"#ffffff",marginTop:"8px"}}>{trans("enterprise.AuditLogOverview")}</h2>
75180
</StyleThemeSettingsCover>
76-
<Card>
77-
<div>
78-
<h3>{trans("branding.logo")}</h3>
79-
<EventTypeTimeChartdata={logs}/>
80-
</div>
81-
181+
<Cardtitle="Filter Audit Logs"size="small"style={{marginBottom:"20px"}}>
182+
<Form
183+
form={form}
184+
layout="inline"
185+
onValuesChange={(changedValue,allValues)=>{
186+
constkey=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.Itemname="environmentId">
196+
<Inputplaceholder="Environment ID"allowClear/>
197+
</Form.Item>
198+
<Form.Itemname="userId">
199+
<Inputplaceholder="User ID"allowClear/>
200+
</Form.Item>
201+
<Form.Itemname="appId">
202+
<Inputplaceholder="App ID"allowClear/>
203+
</Form.Item>
204+
<Form.Itemname="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+
<Buttontype="primary"htmlType="submit"loading={loading}>
215+
Fetch Logs
216+
</Button>
217+
</Form.Item>
218+
</Form>
219+
</Card>
220+
<Cardtitle="Audit Logs">
221+
{loading ?(
222+
<Spin/>
223+
) :logs.length>0 ?(
224+
<>
225+
<EventTypeTimeChartdata={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+
)}
82236
</Card>
83237
</AuditContent>
84238
</DetailContent>

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp