This commit is contained in:
liosha84
2025-08-22 18:57:24 +03:00
parent bc18200640
commit fac4e2d4df
41 changed files with 3401 additions and 7 deletions
+4
View File
@@ -6,6 +6,10 @@
#RUN ./gradlew build -x test
FROM openjdk:24
ADD https://download.docker.com/linux/static/stable/x86_64/docker-24.0.5.tgz /tmp/
RUN tar xzvf /tmp/docker-24.0.5.tgz -C /usr/local/bin --strip-components=1 docker/docker
WORKDIR /jambotron/
#VOLUME /jambotron_data/uploads
COPY './build/libs/jambotron-0.0.1-SNAPSHOT.jar' '/app/jambotron.jar'
+9 -1
View File
@@ -13,8 +13,16 @@ services:
volumes:
- certs:/certs
- jambotron_data:/jambotron_data
# Mount host Docker socket
- /var/run/docker.sock:/var/run/docker.sock
- /usr/bin/docker:/usr/bin/docker
# env_file: "webapp.env"
stdin_open: true
tty: true
environment:
DOCKER_HOST: unix:///var/run/docker.sock
SSL_ENABLED: "true"
SERVER_PORT: 443
FULLCHAINPEM: /certs/live/jambotron.run.place/fullchain.pem
@@ -29,7 +37,7 @@ services:
SPRING_FLYWAY_USER: admin
SPRING_FLYWAY_PASSWORD: postgrespw
SPRING_FLYWAY_URL: jdbc:postgresql://postgres_jambotron:5432/jambotronDB
# Uncomment the following lines to use Koyeb secrets for database credentials
# SPRING_DATASOURCE_URL: jdbc:postgresql://ep-red-breeze-a2cxhq5f.eu-central-1.pg.koyeb.app:5432/jambotronDB
# SPRING_DATASOURCE_USERNAME: koyeb-adm
# SPRING_DATASOURCE_PASSWORD: npg_HfFEUA7bay1i
@@ -0,0 +1,140 @@
package com.jambotronGroup.jambotron.controllers;
import com.jambotronGroup.jambotron.docker.ContainerRunRequest;
import com.jambotronGroup.jambotron.docker.DockerResult;
import com.jambotronGroup.jambotron.docker.DockerService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
@RestController
@RequestMapping("/api/user/docker")
public class DockerController {
@Autowired
private DockerService dockerService;
/**
* Выполнить произвольную Docker команду
*/
@PostMapping("/execute")
public ResponseEntity<?> executeCommand(@RequestBody Map<String, String> request) {
String command = request.get("command");
if (command == null || command.trim().isEmpty()) {
return ResponseEntity.badRequest().body("Команда не может быть пустой");
}
try {
DockerResult result = dockerService.executeCommand(command);
return ResponseEntity.ok(Map.of(
"success", result.isSuccess(),
"exitCode", result.getExitCode(),
"output", result.getOutput(),
"command", result.getCommand()
));
} catch (Exception e) {
return ResponseEntity.internalServerError()
.body(Map.of("error", e.getMessage()));
}
}
/**
* Запустить контейнер
*/
@PostMapping("/container/run")
public ResponseEntity<?> runContainer(@RequestBody ContainerRunRequest request) {
DockerResult result = dockerService.runContainer(
request.getImageName(),
request.getContainerName(),
request.getPorts(),
request.getEnvironmentVars()
);
return ResponseEntity.ok(Map.of(
"success", result.isSuccess(),
"output", result.getOutputAsString(),
"containerId", result.isSuccess() && !result.getOutput().isEmpty()
? result.getOutput().get(0) : null
));
}
/**
* Остановить контейнер
*/
@PostMapping("/container/{containerName}/stop")
public ResponseEntity<?> stopContainer(@PathVariable String containerName) {
DockerResult result = dockerService.stopContainer(containerName);
return ResponseEntity.ok(Map.of(
"success", result.isSuccess(),
"output", result.getOutputAsString()
));
}
/**
* Получить список контейнеров
*/
@GetMapping("/containers")
public ResponseEntity<?> listContainers() {
DockerResult result = dockerService.listContainers();
return ResponseEntity.ok(Map.of(
"success", result.isSuccess(),
"containers", result.getOutput()
));
}
/**
* Получить логи контейнера
*/
@GetMapping("/container/{containerName}/logs")
public ResponseEntity<?> getContainerLogs(
@PathVariable String containerName,
@RequestParam(defaultValue = "100") int lines) {
DockerResult result = dockerService.getContainerLogs(containerName, lines);
return ResponseEntity.ok(Map.of(
"success", result.isSuccess(),
"logs", result.getOutput()
));
}
/**
* Выполнить команду в контейнере
*/
@PostMapping("/container/{containerName}/exec")
public ResponseEntity<?> execInContainer(
@PathVariable String containerName,
@RequestBody Map<String, String> request) {
String command = request.get("command");
DockerResult result = dockerService.execInContainer(containerName, command);
return ResponseEntity.ok(Map.of(
"success", result.isSuccess(),
"output", result.getOutput()
));
}
/**
* Асинхронное выполнение команды
*/
@PostMapping("/execute/async")
public CompletableFuture<ResponseEntity<Map<String, Object>>> executeCommandAsync(
@RequestBody Map<String, String> request) {
String command = request.get("command");
return dockerService.executeCommandAsync(command)
.thenApply(result -> ResponseEntity.ok(Map.of(
"success", result.isSuccess(),
"output", result.getOutput(),
"exitCode", result.getExitCode()
)))
.exceptionally(ex -> ResponseEntity.internalServerError()
.body(Map.of("error", ex.getMessage())));
}
}
@@ -0,0 +1,154 @@
package com.jambotronGroup.jambotron.controllers;
import com.jambotronGroup.jambotron.fileBrowserTree.CreateFolderRequest;
import com.jambotronGroup.jambotron.fileBrowserTree.FileItemDto;
import com.jambotronGroup.jambotron.fileBrowserTree.FileBrowserTreeService;
import com.jambotronGroup.jambotron.fileUpload.ResponseMessage;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.util.List;
@RestController
@RequestMapping("/api/file-browser-tree/files")
public class FileBrowserTreeController {
@Autowired
private FileBrowserTreeService fileBrowserTreeService;
@GetMapping
public ResponseEntity<List<FileItemDto>> getFiles(@RequestParam String path) {
try {
List<FileItemDto> files = fileBrowserTreeService.listFiles(path);
return ResponseEntity.ok(files);
} catch (Exception e) {
return ResponseEntity.badRequest().build();
}
}
// New endpoint specifically for folders only (for tree view)
@GetMapping("/folders")
public ResponseEntity<List<FileItemDto>> getFolders(@RequestParam String path) {
try {
List<FileItemDto> folders = fileBrowserTreeService.listFolders(path);
return ResponseEntity.ok(folders);
} catch (Exception e) {
return ResponseEntity.badRequest().build();
}
}
// Search endpoint
@GetMapping("/search")
public ResponseEntity<List<FileItemDto>> searchFiles(
@RequestParam String path,
@RequestParam String query) {
try {
List<FileItemDto> files = fileBrowserTreeService.searchFiles(path, query);
return ResponseEntity.ok(files);
} catch (Exception e) {
return ResponseEntity.badRequest().build();
}
}
@GetMapping("/download")
public ResponseEntity<Resource> downloadFile(@RequestParam String path) {
try {
Resource resource = fileBrowserTreeService.loadFileAsResource(path);
String filename = resource.getFilename();
return ResponseEntity.ok()
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.header(HttpHeaders.CONTENT_DISPOSITION,
"attachment; filename=\"" + filename + "\"")
.body(resource);
} catch (Exception e) {
return ResponseEntity.notFound().build();
}
}
@PostMapping("/upload")
public ResponseEntity<ResponseMessage> uploadFile(
@RequestParam("file") MultipartFile file,
@RequestParam("path") String path) {
try {
fileBrowserTreeService.storeFile(file, path);
return ResponseEntity.ok(new ResponseMessage("File uploaded successfully: " + file.getOriginalFilename()));
} catch (IOException e) {
return ResponseEntity.badRequest()
.body(new ResponseMessage("Failed to upload file: " + e.getMessage()));
}
}
@PostMapping("/folder")
public ResponseEntity<ResponseMessage> createFolder(@RequestBody CreateFolderRequest request) {
try {
fileBrowserTreeService.createFolder(request.getPath(), request.getName());
return ResponseEntity.ok(new ResponseMessage("Folder created successfully: " + request.getName()));
} catch (IOException e) {
return ResponseEntity.badRequest()
.body(new ResponseMessage("Failed to create folder: " + e.getMessage()));
}
}
@DeleteMapping
public ResponseEntity<ResponseMessage> deleteFile(@RequestParam String path) {
try {
fileBrowserTreeService.deleteFile(path);
return ResponseEntity.ok(new ResponseMessage("File deleted successfully: " + path));
} catch (IOException e) {
return ResponseEntity.badRequest()
.body(new ResponseMessage("Failed to delete file/folder: " + e.getMessage()));
}
}
// Move/rename endpoint
@PostMapping("/move")
public ResponseEntity<ResponseMessage> moveFile(@RequestBody MoveRequest request) {
try {
fileBrowserTreeService.moveFile(request.getSourcePath(), request.getTargetPath());
return ResponseEntity.ok(new ResponseMessage("File moved successfully from " + request.getSourcePath() + " to " + request.getTargetPath()));
} catch (IOException e) {
return ResponseEntity.badRequest()
.body(new ResponseMessage("Failed to move file: " + e.getMessage()));
}
}
// Copy endpoint
@PostMapping("/copy")
public ResponseEntity<ResponseMessage> copyFile(@RequestBody CopyRequest request) {
try {
fileBrowserTreeService.copyFile(request.getSourcePath(), request.getTargetPath());
return ResponseEntity.ok(new ResponseMessage("File copied successfully from " + request.getSourcePath() + " to " + request.getTargetPath()));
} catch (IOException e) {
return ResponseEntity.badRequest()
.body(new ResponseMessage("Failed to copy file: " + e.getMessage()));
}
}
// Inner classes for request DTOs
public static class MoveRequest {
private String sourcePath;
private String targetPath;
public String getSourcePath() { return sourcePath; }
public void setSourcePath(String sourcePath) { this.sourcePath = sourcePath; }
public String getTargetPath() { return targetPath; }
public void setTargetPath(String targetPath) { this.targetPath = targetPath; }
}
public static class CopyRequest {
private String sourcePath;
private String targetPath;
public String getSourcePath() { return sourcePath; }
public void setSourcePath(String sourcePath) { this.sourcePath = sourcePath; }
public String getTargetPath() { return targetPath; }
public void setTargetPath(String targetPath) { this.targetPath = targetPath; }
}
}
@@ -0,0 +1,21 @@
package com.jambotronGroup.jambotron.docker;
public class ContainerRunRequest {
private String imageName;
private String containerName;
private String ports;
private String environmentVars;
// Getters and setters
public String getImageName() { return imageName; }
public void setImageName(String imageName) { this.imageName = imageName; }
public String getContainerName() { return containerName; }
public void setContainerName(String containerName) { this.containerName = containerName; }
public String getPorts() { return ports; }
public void setPorts(String ports) { this.ports = ports; }
public String getEnvironmentVars() { return environmentVars; }
public void setEnvironmentVars(String environmentVars) { this.environmentVars = environmentVars; }
}
@@ -0,0 +1,29 @@
package com.jambotronGroup.jambotron.docker;
import java.util.List;
public class DockerResult {
private final boolean success;
private final int exitCode;
private final List<String> output;
private final String command;
public DockerResult(boolean success, int exitCode, List<String> output, String command) {
this.success = success;
this.exitCode = exitCode;
this.output = output;
this.command = command;
}
public boolean isSuccess() { return success; }
public int getExitCode() { return exitCode; }
public List<String> getOutput() { return output; }
public String getCommand() { return command; }
public String getOutputAsString() { return String.join("\n", output); }
@Override
public String toString() {
return String.format("DockerResult{success=%s, exitCode=%d, command='%s', output='%s'}",
success, exitCode, command, getOutputAsString());
}
}
@@ -0,0 +1,159 @@
package com.jambotronGroup.jambotron.docker;
import org.springframework.stereotype.Service;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
@Service
public class DockerService {
private static final Logger logger = LoggerFactory.getLogger(DockerService.class);
/**
* Выполнить Docker команду синхронно
*/
public DockerResult executeCommand(String command) {
try {
String os = System.getProperty("os.name").toLowerCase();
ProcessBuilder processBuilder = new ProcessBuilder();
if (os.contains("win")) {
processBuilder.command("cmd.exe", "/c", command);
} else {
processBuilder.command("bash", "-c", command);
}
processBuilder.redirectErrorStream(true);
Process process = processBuilder.start();
List<String> output = new ArrayList<>();
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(process.getInputStream()))) {
String line;
while ((line = reader.readLine()) != null) {
output.add(line);
logger.info("Docker output: {}", line);
}
}
int exitCode = process.waitFor();
return new DockerResult(exitCode == 0, exitCode, output, command);
} catch (IOException | InterruptedException e) {
logger.error("Ошибка выполнения Docker команды: {}", command, e);
return new DockerResult(false, -1, List.of("Error: " + e.getMessage()), command);
}
}
/**
* Выполнить Docker команду асинхронно
*/
public CompletableFuture<DockerResult> executeCommandAsync(String command) {
return CompletableFuture.supplyAsync(() -> executeCommand(command));
}
/**
* Запустить Docker контейнер
*/
public DockerResult runContainer(String imageName, String containerName,
String ports, String environmentVars) {
StringBuilder cmd = new StringBuilder("docker run -d");
if (containerName != null && !containerName.isEmpty()) {
cmd.append(" --name ").append(containerName);
}
if (ports != null && !ports.isEmpty()) {
cmd.append(" -p ").append(ports);
}
if (environmentVars != null && !environmentVars.isEmpty()) {
cmd.append(" ").append(environmentVars);
}
cmd.append(" ").append(imageName);
logger.info("Запуск контейнера: {}", cmd.toString());
return executeCommand(cmd.toString());
}
/**
* Остановить Docker контейнер
*/
public DockerResult stopContainer(String containerName) {
String command = "docker stop " + containerName;
logger.info("Остановка контейнера: {}", command);
return executeCommand(command);
}
/**
* Удалить Docker контейнер
*/
public DockerResult removeContainer(String containerName) {
String command = "docker rm " + containerName;
logger.info("Удаление контейнера: {}", command);
return executeCommand(command);
}
/**
* Получить список запущенных контейнеров
*/
public DockerResult listContainers() {
String command = "docker ps";
return executeCommand(command);
}
/**
* Получить логи контейнера
*/
public DockerResult getContainerLogs(String containerName, int lines) {
String command = String.format("docker logs --tail %d %s", lines, containerName);
return executeCommand(command);
}
/**
* Построить Docker образ
*/
public DockerResult buildImage(String dockerfilePath, String imageName, String tag) {
String fullTag = tag != null ? imageName + ":" + tag : imageName;
String command = String.format("docker build -t %s %s", fullTag, dockerfilePath);
logger.info("Сборка образа: {}", command);
return executeCommand(command);
}
/**
* Выполнить команду внутри контейнера
*/
public DockerResult execInContainer(String containerName, String command) {
String dockerCommand = String.format("docker exec %s %s", containerName, command);
logger.info("Выполнение команды в контейнере: {}", dockerCommand);
return executeCommand(dockerCommand);
}
/**
* Запустить Docker Compose
*/
public DockerResult dockerComposeUp(String composePath) {
String command = String.format("docker-compose -f %s up -d", composePath);
logger.info("Запуск Docker Compose: {}", command);
return executeCommand(command);
}
/**
* Остановить Docker Compose
*/
public DockerResult dockerComposeDown(String composePath) {
String command = String.format("docker-compose -f %s down", composePath);
logger.info("Остановка Docker Compose: {}", command);
return executeCommand(command);
}
}
@@ -0,0 +1,31 @@
package com.jambotronGroup.jambotron.fileBrowserTree;
public class CreateFolderRequest {
private String path;
private String name;
// Constructors
public CreateFolderRequest() {}
public CreateFolderRequest(String path, String name) {
this.path = path;
this.name = name;
}
// Getters and Setters
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
@@ -0,0 +1,410 @@
package com.jambotronGroup.jambotron.fileBrowserTree;
import com.jambotronGroup.jambotron.model.ERole;
import com.jambotronGroup.jambotron.security.AuthenticationFacade;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource;
import org.springframework.core.io.UrlResource;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.nio.file.*;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.stream.Stream;
@Service
public class FileBrowserTreeService {
@Autowired
private AuthenticationFacade authenticationFacade;
private final Path root = Paths.get("/jambotron_data");
private Path getRootPath() {
boolean isAdmin = authenticationFacade.getUser()
.getRoles().stream()
.anyMatch(role -> role.getName().equals(ERole.ROLE_ADMIN));
if(isAdmin){
// Admins can access the root directory
return root.normalize().toAbsolutePath();
}
return root.resolve(authenticationFacade.getUser().getUsername()).normalize().toAbsolutePath();
}
private Path resolvePath(String path) throws IOException {
Path root = getRootPath();
Path resolvedPath = root.resolve(path.startsWith("/") ? path.substring(1) : path)
.normalize().toAbsolutePath();
// Security check: ensure the resolved path is within the root directory
if (!resolvedPath.startsWith(root)) {
throw new SecurityException("Access denied: Path is outside root directory");
}
return resolvedPath;
}
public List<FileItemDto> listFiles(String path) throws IOException {
Path targetPath = resolvePath(path);
if (!Files.exists(targetPath)) {
throw new IOException("Directory does not exist: " + path);
}
if (!Files.isDirectory(targetPath)) {
throw new IOException("Path is not a directory: " + path);
}
List<FileItemDto> files = new ArrayList<>();
try (Stream<Path> stream = Files.list(targetPath)) {
stream.forEach(filePath -> {
try {
FileItemDto dto = createFileItemDto(filePath);
files.add(dto);
} catch (IOException e) {
// Log and skip files that can't be read
System.err.println("Error reading file: " + filePath + " - " + e.getMessage());
}
});
}
return sortFiles(files);
}
// New method to list only folders
public List<FileItemDto> listFolders(String path) throws IOException {
Path targetPath = resolvePath(path);
if (!Files.exists(targetPath)) {
throw new IOException("Directory does not exist: " + path);
}
if (!Files.isDirectory(targetPath)) {
throw new IOException("Path is not a directory: " + path);
}
List<FileItemDto> folders = new ArrayList<>();
try (Stream<Path> stream = Files.list(targetPath)) {
stream.filter(Files::isDirectory)
.forEach(folderPath -> {
try {
FileItemDto dto = createFileItemDto(folderPath);
folders.add(dto);
} catch (IOException e) {
System.err.println("Error reading folder: " + folderPath + " - " + e.getMessage());
}
});
}
// Sort folders alphabetically
folders.sort((a, b) -> a.getName().compareToIgnoreCase(b.getName()));
return folders;
}
// Search functionality
public List<FileItemDto> searchFiles(String path, String query) throws IOException {
Path targetPath = resolvePath(path);
if (!Files.exists(targetPath)) {
throw new IOException("Directory does not exist: " + path);
}
List<FileItemDto> matchingFiles = new ArrayList<>();
String lowerQuery = query.toLowerCase();
try (Stream<Path> stream = Files.walk(targetPath, 10)) { // Max depth of 10
stream.filter(filePath -> !filePath.equals(targetPath))
.filter(filePath -> filePath.getFileName().toString().toLowerCase().contains(lowerQuery))
.forEach(filePath -> {
try {
FileItemDto dto = createFileItemDto(filePath);
matchingFiles.add(dto);
} catch (IOException e) {
System.err.println("Error reading file during search: " + filePath + " - " + e.getMessage());
}
});
}
return sortFiles(matchingFiles);
}
// Helper method to create FileItemDto
private FileItemDto createFileItemDto(Path filePath) throws IOException {
FileItemDto dto = new FileItemDto();
dto.setName(filePath.getFileName().toString());
dto.setType(Files.isDirectory(filePath) ? "directory" : "file");
// Calculate relative path from root
Path relativePath = getRootPath().relativize(filePath);
String pathString = "/" + relativePath.toString().replace("\\", "/");
dto.setPath(pathString);
if (Files.isRegularFile(filePath)) {
dto.setSize(Files.size(filePath));
} else {
dto.setSize(0L);
}
dto.setLastModified(new Date(Files.getLastModifiedTime(filePath).toMillis()));
return dto;
}
// Helper method to sort files (directories first, then alphabetically)
private List<FileItemDto> sortFiles(List<FileItemDto> files) {
files.sort((a, b) -> {
if (a.getType().equals(b.getType())) {
return a.getName().compareToIgnoreCase(b.getName());
}
return "directory".equals(a.getType()) ? -1 : 1;
});
return files;
}
public Resource loadFileAsResource(String path) throws IOException {
Path filePath = resolvePath(path);
if (!Files.exists(filePath) || !Files.isReadable(filePath)) {
throw new IOException("File not found or not readable: " + path);
}
Resource resource = new UrlResource(filePath.toUri());
if (resource.exists() && resource.isReadable()) {
return resource;
} else {
throw new IOException("File not found or not readable: " + path);
}
}
public void storeFile(MultipartFile file, String targetPath) throws IOException {
String fileName = StringUtils.cleanPath(file.getOriginalFilename());
if (fileName.contains("..")) {
throw new IOException("Invalid file path: " + fileName);
}
Path targetDir = resolvePath(targetPath);
// Create directories if they don't exist
if (!Files.exists(targetDir)) {
Files.createDirectories(targetDir);
}
Path targetFilePath = targetDir.resolve(fileName);
Files.copy(file.getInputStream(), targetFilePath, StandardCopyOption.REPLACE_EXISTING);
}
public void createFolder(String path, String folderName) throws IOException {
if (folderName.contains("..") || folderName.contains("/") || folderName.contains("\\")) {
throw new IOException("Invalid folder name: " + folderName);
}
Path targetPath = resolvePath(path);
Path newFolderPath = targetPath.resolve(folderName);
if (Files.exists(newFolderPath)) {
throw new IOException("Folder already exists: " + folderName);
}
Files.createDirectories(newFolderPath);
}
public void deleteFile(String path) throws IOException {
Path targetPath = resolvePath(path);
if (!Files.exists(targetPath)) {
throw new IOException("File or directory does not exist: " + path);
}
if (Files.isDirectory(targetPath)) {
// Delete directory and all its contents
deleteDirectoryRecursively(targetPath);
} else {
Files.delete(targetPath);
}
}
// Move/rename functionality
public void moveFile(String sourcePath, String targetPath) throws IOException {
Path source = resolvePath(sourcePath);
Path target = resolvePath(targetPath);
if (!Files.exists(source)) {
throw new IOException("Source file does not exist: " + sourcePath);
}
// Create target directory if it doesn't exist
Path targetParent = target.getParent();
if (targetParent != null && !Files.exists(targetParent)) {
Files.createDirectories(targetParent);
}
Files.move(source, target, StandardCopyOption.REPLACE_EXISTING);
}
// Copy functionality
public void copyFile(String sourcePath, String targetPath) throws IOException {
Path source = resolvePath(sourcePath);
Path target = resolvePath(targetPath);
if (!Files.exists(source)) {
throw new IOException("Source file does not exist: " + sourcePath);
}
// Create target directory if it doesn't exist
Path targetParent = target.getParent();
if (targetParent != null && !Files.exists(targetParent)) {
Files.createDirectories(targetParent);
}
if (Files.isDirectory(source)) {
copyDirectoryRecursively(source, target);
} else {
Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING);
}
}
// Get directory size (recursive)
public long getDirectorySize(String path) throws IOException {
Path targetPath = resolvePath(path);
if (!Files.exists(targetPath) || !Files.isDirectory(targetPath)) {
return 0L;
}
try (Stream<Path> stream = Files.walk(targetPath)) {
return stream
.filter(Files::isRegularFile)
.mapToLong(filePath -> {
try {
return Files.size(filePath);
} catch (IOException e) {
return 0L;
}
})
.sum();
}
}
// Get file count in directory
public long getFileCount(String path) throws IOException {
Path targetPath = resolvePath(path);
if (!Files.exists(targetPath) || !Files.isDirectory(targetPath)) {
return 0L;
}
try (Stream<Path> stream = Files.list(targetPath)) {
return stream.count();
}
}
// Check if path exists
public boolean pathExists(String path) throws IOException {
try {
Path targetPath = resolvePath(path);
return Files.exists(targetPath);
} catch (SecurityException e) {
return false;
}
}
// Get file/directory info
public FileItemDto getFileInfo(String path) throws IOException {
Path targetPath = resolvePath(path);
if (!Files.exists(targetPath)) {
throw new IOException("File or directory does not exist: " + path);
}
return createFileItemDto(targetPath);
}
private void deleteDirectoryRecursively(Path directory) throws IOException {
try (Stream<Path> stream = Files.walk(directory)) {
stream.sorted((a, b) -> b.getNameCount() - a.getNameCount())
.forEach(path -> {
try {
Files.delete(path);
} catch (IOException e) {
System.err.println("Failed to delete: " + path + " - " + e.getMessage());
}
});
}
}
private void copyDirectoryRecursively(Path source, Path target) throws IOException {
Files.createDirectories(target);
try (Stream<Path> stream = Files.walk(source)) {
stream.forEach(sourcePath -> {
try {
Path targetPath = target.resolve(source.relativize(sourcePath));
if (Files.isDirectory(sourcePath)) {
Files.createDirectories(targetPath);
} else {
Files.copy(sourcePath, targetPath, StandardCopyOption.REPLACE_EXISTING);
}
} catch (IOException e) {
System.err.println("Failed to copy: " + sourcePath + " - " + e.getMessage());
}
});
}
}
// Bulk operations
public void deleteMultipleFiles(List<String> paths) throws IOException {
List<String> failedDeletions = new ArrayList<>();
for (String path : paths) {
try {
deleteFile(path);
} catch (IOException e) {
failedDeletions.add(path);
System.err.println("Failed to delete: " + path + " - " + e.getMessage());
}
}
if (!failedDeletions.isEmpty()) {
throw new IOException("Failed to delete " + failedDeletions.size() + " files");
}
}
// Get disk usage information
public DiskUsageInfo getDiskUsage() throws IOException {
Path rootPath = getRootPath();
FileStore store = Files.getFileStore(rootPath);
long totalSpace = store.getTotalSpace();
long usableSpace = store.getUsableSpace();
long usedSpace = totalSpace - usableSpace;
return new DiskUsageInfo(totalSpace, usedSpace, usableSpace);
}
// Inner class for disk usage info
public static class DiskUsageInfo {
private final long totalSpace;
private final long usedSpace;
private final long availableSpace;
public DiskUsageInfo(long totalSpace, long usedSpace, long availableSpace) {
this.totalSpace = totalSpace;
this.usedSpace = usedSpace;
this.availableSpace = availableSpace;
}
public long getTotalSpace() { return totalSpace; }
public long getUsedSpace() { return usedSpace; }
public long getAvailableSpace() { return availableSpace; }
public double getUsagePercentage() {
return totalSpace > 0 ? (double) usedSpace / totalSpace * 100 : 0;
}
}
}
@@ -0,0 +1,67 @@
package com.jambotronGroup.jambotron.fileBrowserTree;
import com.fasterxml.jackson.annotation.JsonFormat;
import java.util.Date;
public class FileItemDto {
private String name;
private String type; // "file" or "directory"
private long size;
@JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSZ")
private Date lastModified;
private String path;
// Constructors
public FileItemDto() {}
public FileItemDto(String name, String type, long size, Date lastModified, String path) {
this.name = name;
this.type = type;
this.size = size;
this.lastModified = lastModified;
this.path = path;
}
// Getters and Setters
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public long getSize() {
return size;
}
public void setSize(long size) {
this.size = size;
}
public Date getLastModified() {
return lastModified;
}
public void setLastModified(Date lastModified) {
this.lastModified = lastModified;
}
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
}
@@ -45,7 +45,7 @@ public class InitDataService {
private UserRepository _userRepository;
private final Path _initDataPath = Path.of("/jambotron-data/initData/");
private final Path _initDataPath = Path.of("/jambotron_data/initData/");
public InitDataService(){
@@ -122,9 +122,12 @@ public class WebSecurityConfig implements WebMvcConfigurer {// extends WebSecuri
.requestMatchers("/api/moderator/**").hasRole("MODERATOR")
//.requestMatchers("/api/admin/**").hasRole("ADMIN")
.requestMatchers("/api/admin/**").permitAll()
.requestMatchers("/api/admin/json-dump/**").permitAll()
.requestMatchers("/api/admin/json-dump/import/**").permitAll()
.requestMatchers("/api/admin/**").hasRole("ADMIN")
.requestMatchers("/api/admin/json-dump/**").hasRole("ADMIN")
.requestMatchers("/api/admin/json-dump/import/**").hasRole("ADMIN")
.requestMatchers("/api/file-browser/files/**").hasAnyRole("USER", "MODERATOR", "ADMIN")
.requestMatchers("/api/file-browser-tree/files/**").hasAnyRole("USER", "MODERATOR", "ADMIN")
.anyRequest().authenticated()
);
@@ -3,6 +3,7 @@ logging:
root: INFO
com.jambotronGroup.jambotron: DEBUG
org.springframework: WARN
com.example.service.DockerService: DEBUG
pattern:
console: "%d{yyyy-MM-dd HH:mm:ss} %-5level - %msg%n"
file: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"
@@ -11,3 +12,7 @@ logging:
max-size: 10MB
max-history: 30
docker:
socket-path: /var/run/docker.sock
enable-security: true
command-timeout: 30000
@@ -3,6 +3,7 @@ logging:
root: INFO
com.jambotronGroup.jambotron: DEBUG
org.springframework: WARN
com.example.service.DockerService: DEBUG
pattern:
console: "%d{yyyy-MM-dd HH:mm:ss} %-5level - %msg%n"
file: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"
@@ -17,3 +18,8 @@ server:
certificate: ${FULLCHAINPEM:""} # Default to empty string if FULLCHAINPEM is not set
certificate-private-key: ${PRIVKEYPEM:""} # Default to empty string if PRIVKEYPEM is not set
# port: ${SERVER_PORT:443} # Default to 443 if SERVER_PORT is not set
docker:
socket-path: /var/run/docker.sock
enable-security: true
command-timeout: 30000