Skip to content

Commit 8db658b

Browse files
author
FalkWolsky
committed
Chart and hierarchic Table
1 parent ca8fff9 commit 8db658b

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
import ReactECharts from "echarts-for-react";
33

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

8-
const EventTypeTimeChart = ({ data }: Props) => {
9-
9+
const EventTypeTimeChart = ({ data, eventTypeLabels }: Props) => {
1010
const getChartOptions = () => {
11-
12-
const groupedData = data.reduce((acc : any, log: any) => {
13-
const date = new Date(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+
const groupedData = data.reduce((acc: any, log: any) => {
13+
// Validate and parse eventTime
14+
const eventTime = log.eventTime ? new Date(log.eventTime) : null;
15+
16+
if (eventTime && !isNaN(eventTime.getTime())) {
17+
const date = 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
return acc;
1725
}, {});
1826

27+
// Extract sorted dates and unique event types
1928
const dates = Object.keys(groupedData).sort();
20-
const eventTypes = [...new Set(data.map((log: any) => log.event_type))];
29+
const eventTypes = [...new Set(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
const series = 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-
export default EventTypeTimeChart;
72+
export default EventTypeTimeChart;

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-
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";
53
import { useDispatch, useSelector } from "react-redux";
64
import styled from "styled-components";
75
import { trans } from "i18n";
@@ -16,6 +14,7 @@ import { fetchCommonSettings } from "@lowcoder-ee/redux/reduxActions/commonSetti
1614
import ReactECharts from "echarts-for-react";
1715
import { getAuditLogs } from "api/enterpriseApi";
1816
import EventTypeTimeChart from "./charts/eventTypesTime";
17+
import { debounce } from "lodash";
1918

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

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+
];
3872

39-
export function AuditLog() {
73+
export function AuditLog() {
4074
const currentUser = useSelector(getUser);
4175
const dispatch = 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-
const fetchLogs = async () => {
85+
const fetchLogs = async (params = {}) => {
86+
const cleanedParams = Object.fromEntries(
87+
Object.entries(params).filter(([_, value]) => value !== undefined && value !== "")
88+
);
5289
setLoading(true);
5390
try {
54-
const data = await getAuditLogs({ orgId: currentUser.currentOrgId });
55-
setLogs(data);
91+
const data = await getAuditLogs(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+
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+
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-
<h2 style={{ color: "#ffffff", marginTop: "8px" }}>{trans("branding.logoSection")}</h2>
179+
<h2 style={{ color: "#ffffff", marginTop: "8px" }}>{trans("enterprise.AuditLogOverview")}</h2>
75180
</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+
)}
82236
</Card>
83237
</AuditContent>
84238
</DetailContent>

0 commit comments

Comments
 (0)