fix
This commit is contained in:
@@ -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'
|
||||
|
||||
@@ -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())));
|
||||
}
|
||||
}
|
||||
|
||||
+154
@@ -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;
|
||||
}
|
||||
}
|
||||
+410
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user