mirror of
https://github.com/RoastSlav/quickdrop.git
synced 2025-12-30 02:59:56 -06:00
Now logs file lifetime renewals and displays them in the file history
This commit is contained in:
@@ -100,8 +100,8 @@ public class AdminViewController {
|
||||
}
|
||||
|
||||
@PostMapping("/keep-indefinitely/{uuid}")
|
||||
public String updateKeepIndefinitely(@PathVariable String uuid, @RequestParam(required = false, defaultValue = "false") boolean keepIndefinitely) {
|
||||
fileService.updateKeepIndefinitely(uuid, keepIndefinitely);
|
||||
public String updateKeepIndefinitely(@PathVariable String uuid, @RequestParam(required = false, defaultValue = "false") boolean keepIndefinitely, HttpServletRequest request) {
|
||||
fileService.updateKeepIndefinitely(uuid, keepIndefinitely, request);
|
||||
return "redirect:/admin/dashboard";
|
||||
}
|
||||
|
||||
|
||||
@@ -4,8 +4,9 @@ import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpSession;
|
||||
import org.rostislav.quickdrop.entity.DownloadLog;
|
||||
import org.rostislav.quickdrop.entity.FileEntity;
|
||||
import org.rostislav.quickdrop.entity.FileRenewalLog;
|
||||
import org.rostislav.quickdrop.model.FileActionLogDTO;
|
||||
import org.rostislav.quickdrop.model.FileEntityView;
|
||||
import org.rostislav.quickdrop.repository.DownloadLogRepository;
|
||||
import org.rostislav.quickdrop.service.AnalyticsService;
|
||||
import org.rostislav.quickdrop.service.ApplicationSettingsService;
|
||||
import org.rostislav.quickdrop.service.FileService;
|
||||
@@ -16,6 +17,8 @@ import org.springframework.ui.Model;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
@@ -26,14 +29,12 @@ import static org.rostislav.quickdrop.util.FileUtils.populateModelAttributes;
|
||||
public class FileViewController {
|
||||
private final FileService fileService;
|
||||
private final ApplicationSettingsService applicationSettingsService;
|
||||
private final DownloadLogRepository downloadLogRepository;
|
||||
private final AnalyticsService analyticsService;
|
||||
private final SessionService sessionService;
|
||||
|
||||
public FileViewController(FileService fileService, ApplicationSettingsService applicationSettingsService, DownloadLogRepository downloadLogRepository, AnalyticsService analyticsService, SessionService sessionService) {
|
||||
public FileViewController(FileService fileService, ApplicationSettingsService applicationSettingsService, AnalyticsService analyticsService, SessionService sessionService) {
|
||||
this.fileService = fileService;
|
||||
this.applicationSettingsService = applicationSettingsService;
|
||||
this.downloadLogRepository = downloadLogRepository;
|
||||
this.analyticsService = analyticsService;
|
||||
this.sessionService = sessionService;
|
||||
}
|
||||
@@ -63,15 +64,24 @@ public class FileViewController {
|
||||
}
|
||||
|
||||
@GetMapping("/history/{uuid}")
|
||||
public String viewDownloadHistory(@PathVariable String uuid, Model model) {
|
||||
FileEntity file = fileService.getFile(uuid);
|
||||
List<DownloadLog> downloadHistory = downloadLogRepository.findByFileUuid(uuid);
|
||||
public String viewFileHistory(@PathVariable String uuid, Model model) {
|
||||
FileEntity fileEntity = fileService.getFile(uuid);
|
||||
long totalDownloads = analyticsService.getTotalDownloadsByFile(uuid);
|
||||
FileEntityView fileEntityView = new FileEntityView(fileEntity, totalDownloads);
|
||||
|
||||
model.addAttribute("file", new FileEntityView(file, totalDownloads));
|
||||
model.addAttribute("downloadHistory", downloadHistory);
|
||||
List<FileActionLogDTO> actionLogs = new ArrayList<>();
|
||||
|
||||
return "download-history";
|
||||
List<DownloadLog> downloadLogs = analyticsService.getDownloadsByFile(uuid);
|
||||
List<FileRenewalLog> renewalLogs = analyticsService.getRenewalLogsByFile(uuid);
|
||||
downloadLogs.forEach(log -> actionLogs.add(new FileActionLogDTO(log)));
|
||||
renewalLogs.forEach(log -> actionLogs.add(new FileActionLogDTO(log)));
|
||||
|
||||
actionLogs.sort(Comparator.comparing(FileActionLogDTO::getActionDate).reversed());
|
||||
|
||||
model.addAttribute("file", fileEntityView);
|
||||
model.addAttribute("actionLogs", actionLogs);
|
||||
|
||||
return "file-history";
|
||||
}
|
||||
|
||||
|
||||
@@ -101,7 +111,7 @@ public class FileViewController {
|
||||
|
||||
@PostMapping("/extend/{uuid}")
|
||||
public String extendFile(@PathVariable String uuid, Model model, HttpServletRequest request) {
|
||||
fileService.extendFile(uuid);
|
||||
fileService.extendFile(uuid, request);
|
||||
|
||||
FileEntity fileEntity = fileService.getFile(uuid);
|
||||
populateModelAttributes(fileEntity, model, request);
|
||||
@@ -126,8 +136,8 @@ public class FileViewController {
|
||||
}
|
||||
|
||||
@PostMapping("/keep-indefinitely/{uuid}")
|
||||
public String updateKeepIndefinitely(@PathVariable String uuid, @RequestParam(required = false, defaultValue = "false") boolean keepIndefinitely) {
|
||||
FileEntity fileEntity = fileService.updateKeepIndefinitely(uuid, keepIndefinitely);
|
||||
public String updateKeepIndefinitely(@PathVariable String uuid, @RequestParam(required = false, defaultValue = "false") boolean keepIndefinitely, HttpServletRequest request) {
|
||||
FileEntity fileEntity = fileService.updateKeepIndefinitely(uuid, keepIndefinitely, request);
|
||||
if (fileEntity != null) {
|
||||
return "redirect:/file/" + fileEntity.uuid;
|
||||
}
|
||||
|
||||
@@ -19,4 +19,9 @@ public class IndexViewController {
|
||||
model.addAttribute("maxFileLifeTime", applicationSettingsService.getMaxFileLifeTime());
|
||||
return "upload";
|
||||
}
|
||||
|
||||
@GetMapping("/error")
|
||||
public String getErrorPage() {
|
||||
return "error";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,4 +74,4 @@ public class DownloadLog {
|
||||
public void setUserAgent(String userAgent) {
|
||||
this.userAgent = userAgent;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package org.rostislav.quickdrop.entity;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Entity
|
||||
public class FileRenewalLog {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "file_id", nullable = false)
|
||||
private FileEntity file;
|
||||
|
||||
@Column(name = "action_date", nullable = false)
|
||||
private LocalDateTime actionDate;
|
||||
|
||||
@Column(name = "ip_address", nullable = false)
|
||||
private String ipAddress;
|
||||
|
||||
@Column(name = "user_agent", nullable = true)
|
||||
private String userAgent;
|
||||
|
||||
public FileRenewalLog() {
|
||||
this.actionDate = LocalDateTime.now();
|
||||
}
|
||||
|
||||
public FileRenewalLog(FileEntity file, String ipAddress, String userAgent) {
|
||||
this.file = file;
|
||||
this.ipAddress = ipAddress;
|
||||
this.userAgent = userAgent;
|
||||
this.actionDate = LocalDateTime.now();
|
||||
}
|
||||
|
||||
public FileEntity getFile() {
|
||||
return file;
|
||||
}
|
||||
|
||||
public void setFile(FileEntity file) {
|
||||
this.file = file;
|
||||
}
|
||||
|
||||
public LocalDateTime getActionDate() {
|
||||
return actionDate;
|
||||
}
|
||||
|
||||
public void setActionDate(LocalDateTime actionDate) {
|
||||
this.actionDate = actionDate;
|
||||
}
|
||||
|
||||
public String getIpAddress() {
|
||||
return ipAddress;
|
||||
}
|
||||
|
||||
public void setIpAddress(String ipAddress) {
|
||||
this.ipAddress = ipAddress;
|
||||
}
|
||||
|
||||
public String getUserAgent() {
|
||||
return userAgent;
|
||||
}
|
||||
|
||||
public void setUserAgent(String userAgent) {
|
||||
this.userAgent = userAgent;
|
||||
}
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package org.rostislav.quickdrop.model;
|
||||
|
||||
import org.rostislav.quickdrop.entity.DownloadLog;
|
||||
import org.rostislav.quickdrop.entity.FileRenewalLog;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
public class FileActionLogDTO {
|
||||
private String actionType; // "Download" or "Lifetime Renewed"
|
||||
private LocalDateTime actionDate;
|
||||
private String ipAddress;
|
||||
private String userAgent;
|
||||
|
||||
public FileActionLogDTO(String actionType, LocalDateTime actionDate, String ipAddress, String userAgent) {
|
||||
this.actionType = actionType;
|
||||
this.actionDate = actionDate;
|
||||
this.ipAddress = ipAddress;
|
||||
this.userAgent = userAgent;
|
||||
}
|
||||
|
||||
public FileActionLogDTO(DownloadLog downloadLog) {
|
||||
this.actionType = "Download";
|
||||
this.actionDate = downloadLog.getDownloadDate();
|
||||
this.ipAddress = downloadLog.getDownloaderIp();
|
||||
this.userAgent = downloadLog.getUserAgent();
|
||||
}
|
||||
|
||||
public FileActionLogDTO(FileRenewalLog renewalLog) {
|
||||
this.actionType = "Lifetime Renewed";
|
||||
this.actionDate = renewalLog.getActionDate();
|
||||
this.ipAddress = renewalLog.getIpAddress();
|
||||
this.userAgent = renewalLog.getUserAgent();
|
||||
}
|
||||
|
||||
// Getters and setters
|
||||
public String getActionType() {
|
||||
return actionType;
|
||||
}
|
||||
|
||||
public void setActionType(String actionType) {
|
||||
this.actionType = actionType;
|
||||
}
|
||||
|
||||
public LocalDateTime getActionDate() {
|
||||
return actionDate;
|
||||
}
|
||||
|
||||
public void setActionDate(LocalDateTime actionDate) {
|
||||
this.actionDate = actionDate;
|
||||
}
|
||||
|
||||
public String getIpAddress() {
|
||||
return ipAddress;
|
||||
}
|
||||
|
||||
public void setIpAddress(String ipAddress) {
|
||||
this.ipAddress = ipAddress;
|
||||
}
|
||||
|
||||
public String getUserAgent() {
|
||||
return userAgent;
|
||||
}
|
||||
|
||||
public void setUserAgent(String userAgent) {
|
||||
this.userAgent = userAgent;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package org.rostislav.quickdrop.repository;
|
||||
|
||||
import org.rostislav.quickdrop.entity.FileRenewalLog;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface RenewalLogRepository extends JpaRepository<FileRenewalLog, Long> {
|
||||
|
||||
@Query("SELECT f FROM FileRenewalLog f WHERE f.file.uuid = :uuid")
|
||||
List<FileRenewalLog> findByFileUuid(String uuid);
|
||||
}
|
||||
@@ -1,20 +1,27 @@
|
||||
package org.rostislav.quickdrop.service;
|
||||
|
||||
|
||||
import org.rostislav.quickdrop.entity.DownloadLog;
|
||||
import org.rostislav.quickdrop.entity.FileRenewalLog;
|
||||
import org.rostislav.quickdrop.model.AnalyticsDataView;
|
||||
import org.rostislav.quickdrop.repository.DownloadLogRepository;
|
||||
import org.rostislav.quickdrop.repository.RenewalLogRepository;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.rostislav.quickdrop.util.FileUtils.formatFileSize;
|
||||
|
||||
@Service
|
||||
public class AnalyticsService {
|
||||
private final FileService fileService;
|
||||
private final DownloadLogRepository downloadLogRepository;
|
||||
private final RenewalLogRepository renewalLogRepository;
|
||||
|
||||
public AnalyticsService(FileService fileService, DownloadLogRepository downloadLogRepository) {
|
||||
public AnalyticsService(FileService fileService, DownloadLogRepository downloadLogRepository, RenewalLogRepository renewalLogRepository) {
|
||||
this.fileService = fileService;
|
||||
this.downloadLogRepository = downloadLogRepository;
|
||||
this.renewalLogRepository = renewalLogRepository;
|
||||
}
|
||||
|
||||
public AnalyticsDataView getAnalytics() {
|
||||
@@ -34,4 +41,12 @@ public class AnalyticsService {
|
||||
public long getTotalDownloadsByFile(String uuid) {
|
||||
return downloadLogRepository.countDownloadsByFileId(uuid);
|
||||
}
|
||||
|
||||
public List<DownloadLog> getDownloadsByFile(String fileUUID) {
|
||||
return downloadLogRepository.findByFileUuid(fileUUID);
|
||||
}
|
||||
|
||||
public List<FileRenewalLog> getRenewalLogsByFile(String fileUUID) {
|
||||
return renewalLogRepository.findByFileUuid(fileUUID);
|
||||
}
|
||||
}
|
||||
@@ -4,10 +4,12 @@ import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.transaction.Transactional;
|
||||
import org.rostislav.quickdrop.entity.DownloadLog;
|
||||
import org.rostislav.quickdrop.entity.FileEntity;
|
||||
import org.rostislav.quickdrop.entity.FileRenewalLog;
|
||||
import org.rostislav.quickdrop.model.FileEntityView;
|
||||
import org.rostislav.quickdrop.model.FileUploadRequest;
|
||||
import org.rostislav.quickdrop.repository.DownloadLogRepository;
|
||||
import org.rostislav.quickdrop.repository.FileRepository;
|
||||
import org.rostislav.quickdrop.repository.RenewalLogRepository;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
@@ -50,14 +52,16 @@ public class FileService {
|
||||
private final DownloadLogRepository downloadLogRepository;
|
||||
private final File tempDir = Paths.get(System.getProperty("java.io.tmpdir")).toFile();
|
||||
private final SessionService sessionService;
|
||||
private final RenewalLogRepository renewalLogRepository;
|
||||
|
||||
@Lazy
|
||||
public FileService(FileRepository fileRepository, PasswordEncoder passwordEncoder, ApplicationSettingsService applicationSettingsService, DownloadLogRepository downloadLogRepository, SessionService sessionService) {
|
||||
public FileService(FileRepository fileRepository, PasswordEncoder passwordEncoder, ApplicationSettingsService applicationSettingsService, DownloadLogRepository downloadLogRepository, SessionService sessionService, RenewalLogRepository renewalLogRepository) {
|
||||
this.fileRepository = fileRepository;
|
||||
this.passwordEncoder = passwordEncoder;
|
||||
this.applicationSettingsService = applicationSettingsService;
|
||||
this.downloadLogRepository = downloadLogRepository;
|
||||
this.sessionService = sessionService;
|
||||
this.renewalLogRepository = renewalLogRepository;
|
||||
}
|
||||
|
||||
private static StreamingResponseBody getStreamingResponseBody(Path outputFile, FileEntity fileEntity) {
|
||||
@@ -199,16 +203,22 @@ public class FileService {
|
||||
return fileRepository.findByUUID(uuid).orElse(null);
|
||||
}
|
||||
|
||||
public void extendFile(String uuid) {
|
||||
Optional<FileEntity> referenceById = fileRepository.findByUUID(uuid);
|
||||
if (referenceById.isEmpty()) {
|
||||
return;
|
||||
private static RequesterInfo getRequesterInfo(HttpServletRequest request) {
|
||||
String forwardedFor = request.getHeader("X-Forwarded-For");
|
||||
String realIp = request.getHeader("X-Real-IP");
|
||||
String ipAddress;
|
||||
|
||||
if (forwardedFor != null && !forwardedFor.isEmpty()) {
|
||||
// The X-Forwarded-For header can contain multiple IPs, pick the first one
|
||||
ipAddress = forwardedFor.split(",")[0].trim();
|
||||
} else if (realIp != null && !realIp.isEmpty()) {
|
||||
ipAddress = realIp;
|
||||
} else {
|
||||
ipAddress = request.getRemoteAddr();
|
||||
}
|
||||
|
||||
FileEntity fileEntity = referenceById.get();
|
||||
fileEntity.uploadDate = LocalDate.now();
|
||||
logger.info("File extended: {}", fileEntity);
|
||||
fileRepository.save(fileEntity);
|
||||
String userAgent = request.getHeader(HttpHeaders.USER_AGENT);
|
||||
return new RequesterInfo(ipAddress, userAgent);
|
||||
}
|
||||
|
||||
public boolean deleteFileFromFileSystem(String uuid) {
|
||||
@@ -267,10 +277,6 @@ public class FileService {
|
||||
return sessionService.getPasswordForFileSessionToken(sessionToken.toString()).getPassword();
|
||||
}
|
||||
|
||||
public List<FileEntity> searchFiles(String query) {
|
||||
return fileRepository.searchFiles(query);
|
||||
}
|
||||
|
||||
public List<FileEntity> searchNotHiddenFiles(String query) {
|
||||
return fileRepository.searchNotHiddenFiles(query);
|
||||
}
|
||||
@@ -279,22 +285,17 @@ public class FileService {
|
||||
return nullToZero(fileRepository.totalFileSizeForAllFiles());
|
||||
}
|
||||
|
||||
public FileEntity updateKeepIndefinitely(String uuid, boolean keepIndefinitely) {
|
||||
public void extendFile(String uuid, HttpServletRequest request) {
|
||||
Optional<FileEntity> referenceById = fileRepository.findByUUID(uuid);
|
||||
if (referenceById.isEmpty()) {
|
||||
logger.info("File not found for 'update keep indefinitely': {}", uuid);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!keepIndefinitely) {
|
||||
extendFile(uuid);
|
||||
return;
|
||||
}
|
||||
|
||||
FileEntity fileEntity = referenceById.get();
|
||||
fileEntity.keepIndefinitely = keepIndefinitely;
|
||||
logger.info("File keepIndefinitely updated: {}", fileEntity);
|
||||
fileEntity.uploadDate = LocalDate.now();
|
||||
logger.info("File extended: {}", fileEntity);
|
||||
fileRepository.save(fileEntity);
|
||||
return fileEntity;
|
||||
logFileRenewal(fileEntity, request);
|
||||
}
|
||||
|
||||
public FileEntity toggleHidden(String uuid) {
|
||||
@@ -478,25 +479,39 @@ public class FileService {
|
||||
return false;
|
||||
}
|
||||
|
||||
private void logDownload(FileEntity fileEntity, HttpServletRequest request) {
|
||||
String forwardedFor = request.getHeader("X-Forwarded-For");
|
||||
String realIp = request.getHeader("X-Real-IP");
|
||||
String downloaderIp;
|
||||
|
||||
if (forwardedFor != null && !forwardedFor.isEmpty()) {
|
||||
// The X-Forwarded-For header can contain multiple IPs, pick the first one
|
||||
downloaderIp = forwardedFor.split(",")[0].trim();
|
||||
} else if (realIp != null && !realIp.isEmpty()) {
|
||||
downloaderIp = realIp;
|
||||
} else {
|
||||
downloaderIp = request.getRemoteAddr();
|
||||
public FileEntity updateKeepIndefinitely(String uuid, boolean keepIndefinitely, HttpServletRequest request) {
|
||||
Optional<FileEntity> referenceById = fileRepository.findByUUID(uuid);
|
||||
if (referenceById.isEmpty()) {
|
||||
logger.info("File not found for 'update keep indefinitely': {}", uuid);
|
||||
return null;
|
||||
}
|
||||
|
||||
String userAgent = request.getHeader(HttpHeaders.USER_AGENT);
|
||||
DownloadLog downloadLog = new DownloadLog(fileEntity, downloaderIp, userAgent);
|
||||
if (!keepIndefinitely) {
|
||||
extendFile(uuid, request);
|
||||
}
|
||||
|
||||
FileEntity fileEntity = referenceById.get();
|
||||
fileEntity.keepIndefinitely = keepIndefinitely;
|
||||
logger.info("File keepIndefinitely updated: {}", fileEntity);
|
||||
fileRepository.save(fileEntity);
|
||||
return fileEntity;
|
||||
}
|
||||
|
||||
private void logDownload(FileEntity fileEntity, HttpServletRequest request) {
|
||||
RequesterInfo info = getRequesterInfo(request);
|
||||
DownloadLog downloadLog = new DownloadLog(fileEntity, info.ipAddress(), info.userAgent());
|
||||
downloadLogRepository.save(downloadLog);
|
||||
}
|
||||
|
||||
private void logFileRenewal(FileEntity fileEntity, HttpServletRequest request) {
|
||||
RequesterInfo info = getRequesterInfo(request);
|
||||
FileRenewalLog fileRenewalLog = new FileRenewalLog(fileEntity, info.ipAddress(), info.userAgent());
|
||||
renewalLogRepository.save(fileRenewalLog);
|
||||
}
|
||||
|
||||
private record RequesterInfo(String ipAddress, String userAgent) {
|
||||
}
|
||||
|
||||
private String getFileChunkName(String fileName, int chunkNumber) {
|
||||
return fileName + "_chunk_" + chunkNumber;
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@
|
||||
|
||||
<!-- Main Content -->
|
||||
<div class="container mt-5">
|
||||
<h1 class="text-center mb-4">Download History</h1>
|
||||
<h1 class="text-center mb-4">History</h1>
|
||||
|
||||
<!-- File Name and Description -->
|
||||
<div class="text-center">
|
||||
@@ -89,30 +89,25 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Download History Table -->
|
||||
<div class="card">
|
||||
<div class="card-header bg-primary text-white">
|
||||
<h2 class="mb-0">Download History</h2>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<table class="table table-striped text-center">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Downloader IP</th>
|
||||
<th>Date</th>
|
||||
<th>User Agent</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr th:each="log : ${downloadHistory}">
|
||||
<td th:text="${log.downloaderIp}">127.0.0.1</td>
|
||||
<td th:text="${#temporals.format(log.downloadDate, 'dd.MM.yyyy HH:mm:ss')}">01.12.2024 20:12:22</td>
|
||||
<td th:text="${log.userAgent ?: 'N/A'}">Mozilla/5.0 (Windows NT 10.0; Win64; x64)</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<!-- History Table -->
|
||||
<table class="table table-striped text-center">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Date</th>
|
||||
<th>Action</th>
|
||||
<th>IP Address</th>
|
||||
<th>User Agent</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr th:each="log : ${actionLogs}">
|
||||
<td th:text="${#temporals.format(log.actionDate, 'dd.MM.yyyy HH:mm:ss')}">01.12.2024 20:12:22</td>
|
||||
<td th:text="${log.actionType}">Action</td>
|
||||
<td th:text="${log.ipAddress}">127.0.0.1</td>
|
||||
<td th:text="${log.userAgent}">Mozilla/5.0 (Windows NT 10.0; Win64; x64)</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Bootstrap Bundle -->
|
||||
Reference in New Issue
Block a user