Skip to content

Commit fa725fa

Browse files
authored
Merge branch 'ee-setup' into environments-only
2 parents a3dac7e + f3a3693 commit fa725fa

13 files changed

+1290
-98
lines changed

client/packages/lowcoder/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
"copy-to-clipboard": "^3.3.3",
4848
"core-js": "^3.25.2",
4949
"echarts": "^5.4.3",
50+
"echarts-extension-gmap": "^1.7.0",
5051
"echarts-for-react": "^3.0.2",
5152
"echarts-wordcloud": "^2.1.0",
5253
"eslint4b-prebuilt-2": "^7.32.0",

client/packages/lowcoder/src/api/enterpriseApi.ts

+9-3
Original file line numberDiff line numberDiff line change
@@ -65,13 +65,19 @@ export const getAuditLogs = async (params = {}) => {
6565
return response.data;
6666
};
6767

68-
export const getAuditLogStatistics = async (groupByParam : string) => {
69-
const response = await axios.get(`/api/plugins/enterprise/audit-logs/statistics?groupByParam=${groupByParam}`);
68+
export const getAuditLogStatistics = async (params = {}) => {
69+
const query = new URLSearchParams(params).toString();
70+
const response = await axios.get(`/api/plugins/enterprise/audit-logs/statistics?groupByParam=eventTypeId${query ? `&${query}` : ''}`);
7071
return response.data;
7172
};
7273

7374
export const getMeta = async (formData = {}) => {
74-
const response = await axios.post(`/api/meta`, formData);
75+
const response = await axios.post(`/api/meta/`, formData);
76+
return response.data;
77+
}
78+
79+
export const getEnvironmentsByIds = async (formData: string[] = []) => {
80+
const response = await axios.post(`/api/plugins/enterprise/environments/byIds`, formData);
7581
return response.data;
7682
}
7783

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import React, { useMemo, useRef } from "react";
2+
import ReactECharts from "echarts-for-react";
3+
import dayjs from "dayjs";
4+
import { debounce } from "lodash";
5+
6+
interface Props {
7+
data: Array<any>;
8+
}
9+
10+
const BrowserEngineBreakdownChart = ({ data}: Props) => {
11+
const chartRef = useRef<any>(null);
12+
13+
const browserEngine = useMemo(() => {
14+
return data.reduce((acc, e) => {
15+
const browser = e.agentName || 'Unknown';
16+
const engine = e.layoutEngineName || 'Unknown';
17+
acc[browser] = acc[browser] || {};
18+
acc[browser][engine] = (acc[browser][engine] || 0) + 1;
19+
return acc;
20+
}, {} as Record<string, number>);
21+
}, []);
22+
23+
// Get unique browser types
24+
const browserTypeSet = [...new Set(data.map((log: any) => log.agentName || 'Unkown'))];
25+
26+
// Get unique engine types
27+
const engineTypeSet = [...new Set(data.map((log: any) => log.layoutEngineName || 'Unkown'))];
28+
29+
// Prepare series data for each event type
30+
const series = engineTypeSet.map((engineType) => ({
31+
name: engineType,
32+
type: "bar",
33+
stack: "total",
34+
data: Object.keys(browserEngine).map((browserType: string) => browserEngine[browserType][engineType]),
35+
}));
36+
37+
return (
38+
<ReactECharts
39+
ref={chartRef}
40+
option={{
41+
title: { text: "Audit Log", left: "center" },
42+
tooltip: { trigger: "axis", axisPointer: { type: "shadow" } },
43+
legend: { left: "left", orient: "vertical", top: "12%" }, // Ensure labels are on the left
44+
grid: { left: "20%", right: "4%", bottom: "3%", containLabel: true },
45+
xAxis: {
46+
type: "value",
47+
},
48+
yAxis: {
49+
type: "category",
50+
data: browserTypeSet,
51+
axisLabel: { rotate: 45 },
52+
},
53+
series,
54+
}}
55+
// onEvents={{ dataZoom: handleChartEvents }}
56+
style={{ height: "400px", marginBottom: "20px" }}
57+
/>
58+
);
59+
};
60+
61+
export default BrowserEngineBreakdownChart;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import React, { useMemo, useRef } from "react";
2+
import ReactECharts from "echarts-for-react";
3+
import dayjs from "dayjs";
4+
import { debounce } from "lodash";
5+
6+
interface Props {
7+
data: Array<any>;
8+
}
9+
10+
const DeviceOSBreakdownChart = ({ data}: Props) => {
11+
const chartRef = useRef<any>(null);
12+
13+
const deviceOs = useMemo(() => {
14+
return data.reduce((acc, e) => {
15+
const device = e.deviceClass || 'Unknown';
16+
const os = e.operatingSystemName || 'Unknown';
17+
acc[device] = acc[device] || {};
18+
acc[device][os] = (acc[device][os] || 0) + 1;
19+
return acc;
20+
}, {} as Record<string, Record<string, number>>);
21+
}, []);
22+
23+
// Get unique device types
24+
const deviceTypeSet = [...new Set(data.map((log: any) => log.deviceClass || 'Unkown'))];
25+
26+
// Get unique os types
27+
const osTypeSet = [...new Set(data.map((log: any) => log.operatingSystemName || 'Unkown'))];
28+
29+
// Prepare series data for each event type
30+
const series = osTypeSet.map((osType) => ({
31+
name: osType,
32+
type: "bar",
33+
stack: "total",
34+
data: Object.keys(deviceOs).map((deviceType: string) => deviceOs[deviceType][osType]),
35+
}));
36+
37+
return (
38+
<ReactECharts
39+
ref={chartRef}
40+
option={{
41+
title: { text: "Audit Log", left: "center" },
42+
tooltip: { trigger: "axis", axisPointer: { type: "shadow" } },
43+
legend: { left: "left", orient: "vertical", top: "12%" }, // Ensure labels are on the left
44+
grid: { left: "20%", right: "4%", bottom: "3%", containLabel: true },
45+
xAxis: {
46+
type: "category",
47+
data: deviceTypeSet,
48+
axisLabel: { rotate: 45 },
49+
},
50+
yAxis: {
51+
type: "value",
52+
},
53+
series,
54+
}}
55+
// onEvents={{ dataZoom: handleChartEvents }}
56+
style={{ height: "400px", marginBottom: "20px" }}
57+
/>
58+
);
59+
};
60+
61+
export default DeviceOSBreakdownChart;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
import React, { useRef } from "react";
2+
import ReactECharts from "echarts-for-react";
3+
import dayjs from "dayjs";
4+
import { debounce } from "lodash";
5+
6+
interface Props {
7+
data: Array<any>;
8+
setDateRange: (range: { fromTimestamp: string; toTimestamp: string }) => void;
9+
}
10+
11+
const UserActivityByTimeChart = ({ data, setDateRange }: Props) => {
12+
const chartRef = useRef<any>(null);
13+
14+
const debouncedSetDateRange = useRef(
15+
debounce((fromTimestamp: string, toTimestamp: string) => {
16+
setDateRange({ fromTimestamp, toTimestamp });
17+
}, 500) // Delays fetching only after zooming stops
18+
).current;
19+
20+
// Extract min/max dates from the data
21+
const allDates = data.map((log) => log.eventTime && dayjs(log.eventTime).format("YYYY-MM-DD"));
22+
const minDate = allDates.length ? dayjs(Math.min(...allDates.map((d) => new Date(d).getTime()))) : dayjs().subtract(7, "days");
23+
const maxDate = allDates.length ? dayjs(Math.max(...allDates.map((d) => new Date(d).getTime()))) : dayjs();
24+
25+
// Generate full date range including missing days
26+
const fullDateRange: string[] = [];
27+
let currentDate = minDate;
28+
while (currentDate.isBefore(maxDate) || currentDate.isSame(maxDate, "day")) {
29+
fullDateRange.push(currentDate.format("YYYY-MM-DD"));
30+
currentDate = currentDate.add(1, "day");
31+
}
32+
33+
// Group data by date and eventType
34+
const timeSeriesData = data.reduce((acc: any, log: any) => {
35+
const eventTime = log.eventTime ? new Date(log.eventTime) : null;
36+
if (eventTime && !isNaN(eventTime.getTime())) {
37+
const date = eventTime.toISOString().split("T")[0]; // Extract date part
38+
if (!acc[date]) acc[date] = 0;
39+
acc[date] = acc[date] + 1;
40+
}
41+
return acc;
42+
}, {});
43+
44+
// Prepare series data for each event type
45+
const series = [{
46+
name: "App Views",
47+
type: "line",
48+
stack: "total",
49+
data: fullDateRange.map((date) => timeSeriesData[date] || 0), // Fill gaps with 0
50+
itemStyle: {
51+
color: "#1890ff",
52+
},
53+
}];
54+
55+
const handleChartEvents = (params: any) => {
56+
if (params.start !== undefined && params.end !== undefined) {
57+
const startIndex = Math.floor((params.start / 100) * (fullDateRange.length - 1));
58+
const endIndex = Math.floor((params.end / 100) * (fullDateRange.length - 1));
59+
60+
const fromDate = new Date(fullDateRange[startIndex] || fullDateRange[0]); // Keep start of day
61+
const toDate = new Date(fullDateRange[endIndex] || fullDateRange[fullDateRange.length - 1]);
62+
63+
toDate.setHours(23, 59, 59, 999);
64+
65+
const fromTimestamp = fromDate.toISOString();
66+
const toTimestamp = toDate.toISOString();
67+
debouncedSetDateRange(fromTimestamp, toTimestamp);
68+
}
69+
};
70+
71+
return (
72+
<ReactECharts
73+
ref={chartRef}
74+
option={{
75+
title: { text: "App Usage Log", left: "center" },
76+
tooltip: { trigger: "axis", axisPointer: { type: "shadow" } },
77+
legend: { left: "left", orient: "vertical", top: "12%" }, // Ensure labels are on the left
78+
grid: { left: "20%", right: "4%", bottom: "3%", containLabel: true },
79+
xAxis: {
80+
type: "category",
81+
data: fullDateRange,
82+
axisLabel: { rotate: 45 },
83+
},
84+
yAxis: {
85+
type: "value",
86+
},
87+
dataZoom: [
88+
{
89+
type: "slider",
90+
xAxisIndex: 0,
91+
filterMode: "weakFilter",
92+
show: true,
93+
start: 0,
94+
end: 100,
95+
realtime: false,
96+
},
97+
{
98+
type: "inside",
99+
xAxisIndex: 0,
100+
realtime: false,
101+
},
102+
],
103+
series,
104+
}}
105+
onEvents={{ dataZoom: handleChartEvents }}
106+
style={{ height: "400px", marginBottom: "20px" }}
107+
/>
108+
);
109+
};
110+
111+
export default UserActivityByTimeChart;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import React, { useMemo, useRef } from "react";
2+
import ReactECharts from "echarts-for-react";
3+
4+
interface Props {
5+
data: Array<any>;
6+
}
7+
8+
const UserAuthStatusChart = ({ data }: Props) => {
9+
const chartRef = useRef<any>(null);
10+
11+
const anonKnown = useMemo(() => {
12+
return data.reduce((acc, e) => {
13+
const type = e.isAnonymous ? 'Anonymous' : 'Known';
14+
acc[type] = (acc[type] || 0) + 1;
15+
return acc;
16+
}, {} as Record<string, number>);
17+
}, [data]);
18+
19+
const pieData = useMemo(() => {
20+
return Object.entries(anonKnown).map(([name, value]) => ({ name, value }));
21+
}, [anonKnown]);
22+
23+
const series = [{
24+
name: 'Anonymouse',
25+
type: 'pie',
26+
radius: '50%',
27+
data: pieData,
28+
emphasis: {
29+
itemStyle: {
30+
shadowBlur: 10,
31+
shadowOffsetX: 0,
32+
shadowColor: 'rgba(0, 0, 0, 0.5)'
33+
}
34+
}
35+
}];
36+
37+
return (
38+
<ReactECharts
39+
ref={chartRef}
40+
option={{
41+
title: { text: "App Usage Log", left: "center" },
42+
tooltip: { trigger: "axis", axisPointer: { type: "shadow" } },
43+
legend: { left: "left", orient: "vertical", top: "12%" }, // Ensure labels are on the left
44+
grid: { left: "20%", right: "4%", bottom: "3%", containLabel: true },
45+
series,
46+
}}
47+
// onEvents={{ dataZoom: handleChartEvents }}
48+
style={{ height: "400px", marginBottom: "20px" }}
49+
/>
50+
);
51+
};
52+
53+
export default UserAuthStatusChart;

0 commit comments

Comments
 (0)