mirror of
https://github.com/kjanat/livedash-node.git
synced 2026-01-16 17:12:10 +01:00
feat: implement cache layer, CSP improvements, and database performance optimizations
- Add Redis cache implementation with LRU eviction - Enhance Content Security Policy with nonce generation - Optimize database queries with connection pooling - Add cache invalidation API endpoints - Improve security monitoring performance
This commit is contained in:
@ -136,8 +136,11 @@ export default function AuditLogsPage() {
|
||||
});
|
||||
|
||||
const [selectedLog, setSelectedLog] = useState<AuditLog | null>(null);
|
||||
const [hasFetched, setHasFetched] = useState(false);
|
||||
|
||||
const fetchAuditLogs = useCallback(async () => {
|
||||
if (hasFetched) return;
|
||||
|
||||
try {
|
||||
setLoading(true);
|
||||
const params = new URLSearchParams({
|
||||
@ -161,6 +164,7 @@ export default function AuditLogsPage() {
|
||||
setAuditLogs(data.data.auditLogs);
|
||||
setPagination(data.data.pagination);
|
||||
setError(null);
|
||||
setHasFetched(true);
|
||||
} else {
|
||||
setError(data.error || "Failed to fetch audit logs");
|
||||
}
|
||||
@ -170,17 +174,23 @@ export default function AuditLogsPage() {
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [pagination.page, pagination.limit, filters]);
|
||||
}, [pagination.page, pagination.limit, filters, hasFetched]);
|
||||
|
||||
useEffect(() => {
|
||||
if (session?.user?.role === "ADMIN") {
|
||||
if (session?.user?.role === "ADMIN" && !hasFetched) {
|
||||
fetchAuditLogs();
|
||||
}
|
||||
}, [session, fetchAuditLogs]);
|
||||
}, [session?.user?.role, hasFetched, fetchAuditLogs]);
|
||||
|
||||
// Function to refresh audit logs (for filter changes)
|
||||
const refreshAuditLogs = useCallback(() => {
|
||||
setHasFetched(false);
|
||||
}, []);
|
||||
|
||||
const handleFilterChange = (key: keyof typeof filters, value: string) => {
|
||||
setFilters((prev) => ({ ...prev, [key]: value }));
|
||||
setPagination((prev) => ({ ...prev, page: 1 })); // Reset to first page
|
||||
refreshAuditLogs(); // Trigger fresh fetch with new filters
|
||||
};
|
||||
|
||||
const clearFilters = () => {
|
||||
@ -192,6 +202,7 @@ export default function AuditLogsPage() {
|
||||
startDate: "",
|
||||
endDate: "",
|
||||
});
|
||||
refreshAuditLogs(); // Trigger fresh fetch with cleared filters
|
||||
};
|
||||
|
||||
if (session?.user?.role !== "ADMIN") {
|
||||
@ -424,9 +435,10 @@ export default function AuditLogsPage() {
|
||||
variant="outline"
|
||||
size="sm"
|
||||
disabled={!pagination.hasPrev}
|
||||
onClick={() =>
|
||||
setPagination((prev) => ({ ...prev, page: prev.page - 1 }))
|
||||
}
|
||||
onClick={() => {
|
||||
setPagination((prev) => ({ ...prev, page: prev.page - 1 }));
|
||||
refreshAuditLogs();
|
||||
}}
|
||||
>
|
||||
Previous
|
||||
</Button>
|
||||
@ -434,9 +446,10 @@ export default function AuditLogsPage() {
|
||||
variant="outline"
|
||||
size="sm"
|
||||
disabled={!pagination.hasNext}
|
||||
onClick={() =>
|
||||
setPagination((prev) => ({ ...prev, page: prev.page + 1 }))
|
||||
}
|
||||
onClick={() => {
|
||||
setPagination((prev) => ({ ...prev, page: prev.page + 1 }));
|
||||
refreshAuditLogs();
|
||||
}}
|
||||
>
|
||||
Next
|
||||
</Button>
|
||||
|
||||
@ -503,14 +503,34 @@ function DashboardContent() {
|
||||
totalSessions: overviewData.totalSessions,
|
||||
avgSessionsPerDay: 0, // Will be computed properly later
|
||||
avgSessionLength: null,
|
||||
days: { data: [], labels: [] },
|
||||
languages: { data: [], labels: [] },
|
||||
categories: { data: [], labels: [] },
|
||||
countries: { data: [], labels: [] },
|
||||
days: {},
|
||||
languages: {},
|
||||
categories: {},
|
||||
countries: {},
|
||||
belowThresholdCount: 0,
|
||||
// Map the available data
|
||||
sentimentDistribution: overviewData.sentimentDistribution,
|
||||
categoryDistribution: overviewData.categoryDistribution,
|
||||
// Map sentiment data to individual counts
|
||||
sentimentPositiveCount:
|
||||
overviewData.sentimentDistribution?.find(
|
||||
(s) => s.sentiment === "positive"
|
||||
)?.count || 0,
|
||||
sentimentNeutralCount:
|
||||
overviewData.sentimentDistribution?.find(
|
||||
(s) => s.sentiment === "neutral"
|
||||
)?.count || 0,
|
||||
sentimentNegativeCount:
|
||||
overviewData.sentimentDistribution?.find(
|
||||
(s) => s.sentiment === "negative"
|
||||
)?.count || 0,
|
||||
// Map category data to CategoryMetrics format
|
||||
...(overviewData.categoryDistribution && {
|
||||
categories: overviewData.categoryDistribution.reduce(
|
||||
(acc, item) => {
|
||||
acc[item.category] = item.count;
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, number>
|
||||
),
|
||||
}),
|
||||
};
|
||||
setMetrics(mappedMetrics as MetricsResult);
|
||||
|
||||
|
||||
@ -49,12 +49,16 @@ interface FilterSectionProps {
|
||||
setSortOrder: (_order: string) => void;
|
||||
filterOptions: FilterOptions;
|
||||
searchHeadingId: string;
|
||||
searchId: string;
|
||||
filtersHeadingId: string;
|
||||
filterContentId: string;
|
||||
categoryFilterId: string;
|
||||
categoryHelpId: string;
|
||||
languageFilterId: string;
|
||||
languageHelpId: string;
|
||||
startDateId: string;
|
||||
endDateId: string;
|
||||
sortById: string;
|
||||
sortOrderId: string;
|
||||
sortOrderHelpId: string;
|
||||
}
|
||||
@ -78,12 +82,16 @@ function FilterSection({
|
||||
setSortOrder,
|
||||
filterOptions,
|
||||
searchHeadingId,
|
||||
searchId,
|
||||
filtersHeadingId,
|
||||
filterContentId,
|
||||
categoryFilterId,
|
||||
categoryHelpId,
|
||||
languageFilterId,
|
||||
languageHelpId,
|
||||
startDateId,
|
||||
endDateId,
|
||||
sortById,
|
||||
sortOrderId,
|
||||
sortOrderHelpId,
|
||||
}: FilterSectionProps) {
|
||||
@ -433,12 +441,16 @@ export default function SessionsPage() {
|
||||
const [searchTerm, setSearchTerm] = useState("");
|
||||
|
||||
const searchHeadingId = useId();
|
||||
const searchId = useId();
|
||||
const filtersHeadingId = useId();
|
||||
const filterContentId = useId();
|
||||
const categoryFilterId = useId();
|
||||
const categoryHelpId = useId();
|
||||
const languageFilterId = useId();
|
||||
const languageHelpId = useId();
|
||||
const startDateId = useId();
|
||||
const endDateId = useId();
|
||||
const sortById = useId();
|
||||
const sortOrderId = useId();
|
||||
const sortOrderHelpId = useId();
|
||||
const resultsHeadingId = useId();
|
||||
@ -556,12 +568,16 @@ export default function SessionsPage() {
|
||||
setSortOrder={setSortOrder}
|
||||
filterOptions={filterOptions}
|
||||
searchHeadingId={searchHeadingId}
|
||||
searchId={searchId}
|
||||
filtersHeadingId={filtersHeadingId}
|
||||
filterContentId={filterContentId}
|
||||
categoryFilterId={categoryFilterId}
|
||||
categoryHelpId={categoryHelpId}
|
||||
languageFilterId={languageFilterId}
|
||||
languageHelpId={languageHelpId}
|
||||
startDateId={startDateId}
|
||||
endDateId={endDateId}
|
||||
sortById={sortById}
|
||||
sortOrderId={sortOrderId}
|
||||
sortOrderHelpId={sortOrderHelpId}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user