mirror of
https://github.com/Stirling-Tools/Stirling-PDF.git
synced 2025-06-23 16:05:09 +00:00
updates
This commit is contained in:
parent
a3ccc677b1
commit
1023edd66b
@ -2,7 +2,6 @@ package stirling.software.proprietary.audit;
|
||||
|
||||
/**
|
||||
* Standardized audit event types for the application.
|
||||
* Using an enum ensures consistency in event type naming and categorization.
|
||||
*/
|
||||
public enum AuditEventType {
|
||||
// Authentication events - BASIC level
|
||||
@ -17,8 +16,7 @@ public enum AuditEventType {
|
||||
SETTINGS_CHANGED("System or admin settings operation"),
|
||||
|
||||
// File operations - STANDARD level
|
||||
FILE_UPLOAD("File uploaded"),
|
||||
FILE_DOWNLOAD("File downloaded"),
|
||||
FILE_OPERATION("File operation"),
|
||||
|
||||
// PDF operations - STANDARD level
|
||||
PDF_PROCESS("PDF processing operation"),
|
||||
|
@ -176,15 +176,12 @@ public class ControllerAuditAspect {
|
||||
return AuditEventType.SETTINGS_CHANGED;
|
||||
}
|
||||
|
||||
// File operations
|
||||
else if (className.contains("file") || path.contains("file")) {
|
||||
if (path.contains("upload") || path.contains("add")) {
|
||||
return AuditEventType.FILE_UPLOAD;
|
||||
} else if (path.contains("download")) {
|
||||
return AuditEventType.FILE_DOWNLOAD;
|
||||
} else {
|
||||
return AuditEventType.FILE_UPLOAD;
|
||||
}
|
||||
// File operations - using path prefixes to avoid false matches
|
||||
else if (className.contains("file") ||
|
||||
path.startsWith("/file") ||
|
||||
path.startsWith("/files/") ||
|
||||
path.matches("(?i).*/(upload|download)/.*")) {
|
||||
return AuditEventType.FILE_OPERATION;
|
||||
}
|
||||
|
||||
// Default to PDF operations for most controllers
|
||||
|
@ -2,11 +2,21 @@ package stirling.software.proprietary.model.security;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import lombok.*;
|
||||
import org.hibernate.annotations.Index;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
@Entity
|
||||
@Table(name = "audit_events")
|
||||
@Table(
|
||||
name = "audit_events",
|
||||
indexes = {
|
||||
@jakarta.persistence.Index(name = "idx_audit_timestamp", columnList = "timestamp"),
|
||||
@jakarta.persistence.Index(name = "idx_audit_principal", columnList = "principal"),
|
||||
@jakarta.persistence.Index(name = "idx_audit_type", columnList = "type"),
|
||||
@jakarta.persistence.Index(name = "idx_audit_principal_type", columnList = "principal,type"),
|
||||
@jakarta.persistence.Index(name = "idx_audit_type_timestamp", columnList = "type,timestamp")
|
||||
}
|
||||
)
|
||||
@Data @Builder @NoArgsConstructor @AllArgsConstructor
|
||||
public class PersistentAuditEvent {
|
||||
|
||||
|
@ -38,7 +38,11 @@ public interface PersistentAuditEventRepository
|
||||
@Query("DELETE FROM PersistentAuditEvent e WHERE e.timestamp < ?1")
|
||||
@org.springframework.data.jpa.repository.Modifying
|
||||
@org.springframework.transaction.annotation.Transactional
|
||||
void deleteByTimestampBefore(Instant cutoffDate);
|
||||
int deleteByTimestampBefore(Instant cutoffDate);
|
||||
|
||||
// Find IDs for batch deletion - using JPQL with setMaxResults instead of native query
|
||||
@Query("SELECT e.id FROM PersistentAuditEvent e WHERE e.timestamp < ?1 ORDER BY e.id")
|
||||
List<Long> findIdsForBatchDeletion(Instant cutoffDate, Pageable pageable);
|
||||
|
||||
// Stats queries
|
||||
@Query("SELECT e.type, COUNT(e) FROM PersistentAuditEvent e GROUP BY e.type")
|
||||
|
@ -2,10 +2,14 @@ package stirling.software.proprietary.service;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@ -23,6 +27,9 @@ public class AuditCleanupService {
|
||||
private final PersistentAuditEventRepository auditRepository;
|
||||
private final AuditConfigurationProperties auditConfig;
|
||||
|
||||
// Default batch size for deletions
|
||||
private static final int BATCH_SIZE = 10000;
|
||||
|
||||
/**
|
||||
* Scheduled task that runs daily to clean up old audit events.
|
||||
* The retention period is configurable in settings.yml.
|
||||
@ -30,13 +37,11 @@ public class AuditCleanupService {
|
||||
@Scheduled(fixedDelay = 1, initialDelay = 1, timeUnit = TimeUnit.DAYS)
|
||||
public void cleanupOldAuditEvents() {
|
||||
if (!auditConfig.isEnabled()) {
|
||||
log.debug("Audit system is disabled, skipping cleanup");
|
||||
return;
|
||||
}
|
||||
|
||||
int retentionDays = auditConfig.getRetentionDays();
|
||||
if (retentionDays <= 0) {
|
||||
log.info("Audit retention is set to {} days, no cleanup needed", retentionDays);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -44,10 +49,64 @@ public class AuditCleanupService {
|
||||
|
||||
try {
|
||||
Instant cutoffDate = Instant.now().minus(retentionDays, ChronoUnit.DAYS);
|
||||
auditRepository.deleteByTimestampBefore(cutoffDate);
|
||||
log.info("Successfully cleaned up audit events older than {}", cutoffDate);
|
||||
int totalDeleted = batchDeleteEvents(cutoffDate);
|
||||
log.info("Successfully cleaned up {} audit events older than {}", totalDeleted, cutoffDate);
|
||||
} catch (Exception e) {
|
||||
log.error("Error cleaning up old audit events", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs batch deletion of events to prevent long-running transactions
|
||||
* and potential database locks.
|
||||
*/
|
||||
private int batchDeleteEvents(Instant cutoffDate) {
|
||||
int totalDeleted = 0;
|
||||
boolean hasMore = true;
|
||||
|
||||
while (hasMore) {
|
||||
// Start a new transaction for each batch
|
||||
List<Long> batchIds = findBatchOfIdsToDelete(cutoffDate);
|
||||
|
||||
if (batchIds.isEmpty()) {
|
||||
hasMore = false;
|
||||
} else {
|
||||
int deleted = deleteBatch(batchIds);
|
||||
totalDeleted += deleted;
|
||||
|
||||
// If we got fewer records than the batch size, we're done
|
||||
if (batchIds.size() < BATCH_SIZE) {
|
||||
hasMore = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return totalDeleted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a batch of IDs to delete.
|
||||
*/
|
||||
@Transactional(readOnly = true)
|
||||
private List<Long> findBatchOfIdsToDelete(Instant cutoffDate) {
|
||||
PageRequest pageRequest = PageRequest.of(0, BATCH_SIZE, Sort.by("id"));
|
||||
return auditRepository.findIdsForBatchDeletion(cutoffDate, pageRequest);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a batch of events by ID.
|
||||
* Each batch is in its own transaction.
|
||||
*/
|
||||
@Transactional
|
||||
private int deleteBatch(List<Long> batchIds) {
|
||||
if (batchIds.isEmpty()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int batchSize = batchIds.size();
|
||||
auditRepository.deleteAllByIdInBatch(batchIds);
|
||||
log.debug("Deleted batch of {} audit events", batchSize);
|
||||
|
||||
return batchSize;
|
||||
}
|
||||
}
|
@ -9,7 +9,7 @@ import java.util.stream.Collectors;
|
||||
public final class SecretMasker {
|
||||
|
||||
private static final Pattern SENSITIVE =
|
||||
Pattern.compile("(?i)(password|token|secret|api[_-]?key|authorization|auth)");
|
||||
Pattern.compile("(?i)(password|token|secret|api[_-]?key|authorization|auth|jwt|cred|cert)");
|
||||
|
||||
private SecretMasker() {}
|
||||
|
||||
|
@ -502,8 +502,7 @@
|
||||
<ul>
|
||||
<li>HTTP_REQUEST - GET requests for viewing</li>
|
||||
<li>PDF_PROCESS - PDF processing operations</li>
|
||||
<li>FILE_UPLOAD - File uploads</li>
|
||||
<li>FILE_DOWNLOAD - File downloads</li>
|
||||
<li>FILE_OPERATION - File-related operations</li>
|
||||
<li>SETTINGS_CHANGED - System or admin settings operations</li>
|
||||
</ul>
|
||||
</li>
|
||||
@ -835,8 +834,8 @@
|
||||
datasets: [{
|
||||
label: 'Events by Type',
|
||||
data: typeValues,
|
||||
backgroundColor: 'rgba(54, 162, 235, 0.6)',
|
||||
borderColor: 'rgba(54, 162, 235, 1)',
|
||||
backgroundColor: getChartColors(typeLabels.length),
|
||||
borderColor: getChartColors(typeLabels.length, 1), // Full opacity for borders
|
||||
borderWidth: 1
|
||||
}]
|
||||
},
|
||||
@ -864,14 +863,7 @@
|
||||
datasets: [{
|
||||
label: 'Events by User',
|
||||
data: userValues,
|
||||
backgroundColor: [
|
||||
'rgba(54, 162, 235, 0.6)',
|
||||
'rgba(255, 99, 132, 0.6)',
|
||||
'rgba(255, 206, 86, 0.6)',
|
||||
'rgba(75, 192, 192, 0.6)',
|
||||
'rgba(153, 102, 255, 0.6)',
|
||||
'rgba(255, 159, 64, 0.6)'
|
||||
],
|
||||
backgroundColor: getChartColors(userLabels.length),
|
||||
borderWidth: 1
|
||||
}]
|
||||
},
|
||||
@ -938,6 +930,56 @@
|
||||
const loading = document.getElementById(id);
|
||||
if (loading) loading.style.display = 'none';
|
||||
}
|
||||
|
||||
// Function to generate a palette of colors for charts
|
||||
function getChartColors(count, opacity = 0.6) {
|
||||
// Base colors - a larger palette than the default
|
||||
const colors = [
|
||||
[54, 162, 235], // blue
|
||||
[255, 99, 132], // red
|
||||
[75, 192, 192], // teal
|
||||
[255, 206, 86], // yellow
|
||||
[153, 102, 255], // purple
|
||||
[255, 159, 64], // orange
|
||||
[46, 204, 113], // green
|
||||
[231, 76, 60], // dark red
|
||||
[52, 152, 219], // light blue
|
||||
[155, 89, 182], // violet
|
||||
[241, 196, 15], // dark yellow
|
||||
[26, 188, 156], // turquoise
|
||||
[230, 126, 34], // dark orange
|
||||
[149, 165, 166], // light gray
|
||||
[243, 156, 18], // amber
|
||||
[39, 174, 96], // emerald
|
||||
[211, 84, 0], // dark orange red
|
||||
[22, 160, 133], // green sea
|
||||
[41, 128, 185], // belize hole
|
||||
[142, 68, 173] // wisteria
|
||||
];
|
||||
|
||||
const result = [];
|
||||
|
||||
// Always use the same format regardless of color source
|
||||
if (count > colors.length) {
|
||||
// Generate colors algorithmically for large sets
|
||||
for (let i = 0; i < count; i++) {
|
||||
// Generate a color based on position in the hue circle (0-360)
|
||||
const hue = (i * 360 / count) % 360;
|
||||
const sat = 70 + Math.random() * 10; // 70-80%
|
||||
const light = 50 + Math.random() * 10; // 50-60%
|
||||
|
||||
result.push(`hsla(${hue}, ${sat}%, ${light}%, ${opacity})`);
|
||||
}
|
||||
} else {
|
||||
// Use colors from our palette but also return in hsla format for consistency
|
||||
for (let i = 0; i < count; i++) {
|
||||
const color = colors[i % colors.length];
|
||||
result.push(`rgba(${color[0]}, ${color[1]}, ${color[2]}, ${opacity})`);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
</script>
|
||||
</div>
|
||||
</div>
|
||||
|
Loading…
x
Reference in New Issue
Block a user