Multi workspace config - still not finished
This commit is contained in:
@@ -13,9 +13,18 @@ public class ChartWorkspace {
|
||||
private Integer id;
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(nullable = false, unique = true)
|
||||
@Column(nullable = false)
|
||||
private ChartWorkspaceScope scope;
|
||||
|
||||
@Column(nullable = false, length = 120)
|
||||
private String name;
|
||||
|
||||
@Column(name = "sort_order", nullable = false)
|
||||
private Integer sortOrder = 0;
|
||||
|
||||
@Column(name = "is_default", nullable = false)
|
||||
private Boolean defaultWorkspace = false;
|
||||
|
||||
@Column(name = "layout_mode", nullable = false)
|
||||
private String layoutMode;
|
||||
|
||||
@@ -33,10 +42,12 @@ public class ChartWorkspace {
|
||||
|
||||
public ChartWorkspace(
|
||||
ChartWorkspaceScope scope,
|
||||
String name,
|
||||
String layoutMode,
|
||||
String chartsJson
|
||||
) {
|
||||
this.scope = scope;
|
||||
this.name = name;
|
||||
this.layoutMode = layoutMode;
|
||||
this.chartsJson = chartsJson;
|
||||
}
|
||||
@@ -71,6 +82,30 @@ public class ChartWorkspace {
|
||||
this.scope = scope;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public Integer getSortOrder() {
|
||||
return sortOrder;
|
||||
}
|
||||
|
||||
public void setSortOrder(Integer sortOrder) {
|
||||
this.sortOrder = sortOrder;
|
||||
}
|
||||
|
||||
public Boolean getDefaultWorkspace() {
|
||||
return defaultWorkspace;
|
||||
}
|
||||
|
||||
public void setDefaultWorkspace(Boolean defaultWorkspace) {
|
||||
this.defaultWorkspace = defaultWorkspace;
|
||||
}
|
||||
|
||||
public String getLayoutMode() {
|
||||
return layoutMode;
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@ import com.litoralregas.backend.charts.dto.ChartWorkspaceRequest;
|
||||
import com.litoralregas.backend.charts.dto.ChartWorkspaceResponse;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/chart-workspaces")
|
||||
public class ChartWorkspaceController {
|
||||
@@ -16,6 +18,54 @@ public class ChartWorkspaceController {
|
||||
this.service = service;
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
public List<ChartWorkspaceResponse> listWorkspaces(
|
||||
@RequestParam ChartWorkspaceScope scope
|
||||
) {
|
||||
|
||||
return service.listWorkspaces(scope);
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
public ChartWorkspaceResponse createWorkspace(
|
||||
@RequestParam ChartWorkspaceScope scope,
|
||||
@RequestBody ChartWorkspaceRequest request
|
||||
) {
|
||||
|
||||
return service.createWorkspace(
|
||||
scope,
|
||||
request
|
||||
);
|
||||
}
|
||||
|
||||
@GetMapping("/id/{id}")
|
||||
public ChartWorkspaceResponse getWorkspaceById(
|
||||
@PathVariable Integer id
|
||||
) {
|
||||
|
||||
return service.getWorkspaceById(id);
|
||||
}
|
||||
|
||||
@PutMapping("/id/{id}")
|
||||
public ChartWorkspaceResponse updateWorkspaceById(
|
||||
@PathVariable Integer id,
|
||||
@RequestBody ChartWorkspaceRequest request
|
||||
) {
|
||||
|
||||
return service.updateWorkspace(
|
||||
id,
|
||||
request
|
||||
);
|
||||
}
|
||||
|
||||
@DeleteMapping("/id/{id}")
|
||||
public void deleteWorkspaceById(
|
||||
@PathVariable Integer id
|
||||
) {
|
||||
|
||||
service.deleteWorkspace(id);
|
||||
}
|
||||
|
||||
@GetMapping("/{scope}")
|
||||
public ChartWorkspaceResponse getWorkspace(
|
||||
@PathVariable ChartWorkspaceScope scope
|
||||
|
||||
@@ -2,12 +2,21 @@ package com.litoralregas.backend.charts;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public interface ChartWorkspaceRepository
|
||||
extends JpaRepository<ChartWorkspace, Integer> {
|
||||
|
||||
Optional<ChartWorkspace> findByScope(
|
||||
List<ChartWorkspace> findAllByScopeOrderBySortOrderAscIdAsc(
|
||||
ChartWorkspaceScope scope
|
||||
);
|
||||
|
||||
Optional<ChartWorkspace> findFirstByScopeAndDefaultWorkspaceTrue(
|
||||
ChartWorkspaceScope scope
|
||||
);
|
||||
|
||||
long countByScope(
|
||||
ChartWorkspaceScope scope
|
||||
);
|
||||
}
|
||||
@@ -1,19 +1,31 @@
|
||||
package com.litoralregas.backend.charts;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.litoralregas.backend.charts.dto.ChartWorkspaceRequest;
|
||||
import com.litoralregas.backend.charts.dto.ChartWorkspaceResponse;
|
||||
import jakarta.transaction.Transactional;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
public class ChartWorkspaceService {
|
||||
|
||||
private static final int MAX_WORKSPACES_PER_SCOPE = 10;
|
||||
private static final int MAX_CHARTS_PER_WORKSPACE = 10;
|
||||
|
||||
private final ChartWorkspaceRepository repository;
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
public ChartWorkspaceService(
|
||||
ChartWorkspaceRepository repository
|
||||
ChartWorkspaceRepository repository,
|
||||
ObjectMapper objectMapper
|
||||
) {
|
||||
this.repository = repository;
|
||||
this.objectMapper = objectMapper;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@@ -21,24 +33,11 @@ public class ChartWorkspaceService {
|
||||
ChartWorkspaceScope scope,
|
||||
ChartWorkspaceRequest request
|
||||
) {
|
||||
|
||||
ChartWorkspace workspace =
|
||||
repository.findByScope(scope)
|
||||
.orElseGet(() ->
|
||||
new ChartWorkspace(
|
||||
scope,
|
||||
request.layoutMode(),
|
||||
request.chartsJson()
|
||||
)
|
||||
);
|
||||
repository.findFirstByScopeAndDefaultWorkspaceTrue(scope)
|
||||
.orElseGet(() -> createDefaultWorkspace(scope));
|
||||
|
||||
workspace.setLayoutMode(
|
||||
request.layoutMode()
|
||||
);
|
||||
|
||||
workspace.setChartsJson(
|
||||
request.chartsJson()
|
||||
);
|
||||
applyRequest(workspace, request, true);
|
||||
|
||||
ChartWorkspace saved =
|
||||
repository.save(workspace);
|
||||
@@ -49,19 +48,275 @@ public class ChartWorkspaceService {
|
||||
public ChartWorkspaceResponse getWorkspace(
|
||||
ChartWorkspaceScope scope
|
||||
) {
|
||||
return toResponse(getOrCreateDefaultWorkspace(scope));
|
||||
}
|
||||
|
||||
public List<ChartWorkspaceResponse> listWorkspaces(
|
||||
ChartWorkspaceScope scope
|
||||
) {
|
||||
ensureDefaultWorkspace(scope);
|
||||
|
||||
return repository.findAllByScopeOrderBySortOrderAscIdAsc(scope)
|
||||
.stream()
|
||||
.map(this::toResponse)
|
||||
.toList();
|
||||
}
|
||||
|
||||
public ChartWorkspaceResponse getWorkspaceById(
|
||||
Integer id
|
||||
) {
|
||||
return toResponse(requireWorkspace(id));
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public ChartWorkspaceResponse createWorkspace(
|
||||
ChartWorkspaceScope scope,
|
||||
ChartWorkspaceRequest request
|
||||
) {
|
||||
if (repository.countByScope(scope) >= MAX_WORKSPACES_PER_SCOPE) {
|
||||
throw new ResponseStatusException(
|
||||
HttpStatus.BAD_REQUEST,
|
||||
"Maximum workspaces reached for this scope."
|
||||
);
|
||||
}
|
||||
|
||||
ChartWorkspace workspace =
|
||||
repository.findByScope(scope)
|
||||
.orElseGet(() ->
|
||||
repository.save(
|
||||
new ChartWorkspace(
|
||||
scope,
|
||||
"fourGrid",
|
||||
"[]"
|
||||
)
|
||||
)
|
||||
normalizeName(request.name(), "Novo Workspace"),
|
||||
normalizeLayoutMode(request.layoutMode()),
|
||||
normalizeChartsJson(request.chartsJson())
|
||||
);
|
||||
|
||||
return toResponse(workspace);
|
||||
workspace.setSortOrder(
|
||||
request.sortOrder() == null
|
||||
? nextSortOrder(scope)
|
||||
: request.sortOrder()
|
||||
);
|
||||
|
||||
workspace.setDefaultWorkspace(false);
|
||||
|
||||
if (Boolean.TRUE.equals(request.defaultWorkspace())) {
|
||||
setDefaultWorkspace(workspace);
|
||||
}
|
||||
|
||||
ChartWorkspace saved =
|
||||
repository.save(workspace);
|
||||
|
||||
return toResponse(saved);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public ChartWorkspaceResponse updateWorkspace(
|
||||
Integer id,
|
||||
ChartWorkspaceRequest request
|
||||
) {
|
||||
ChartWorkspace workspace =
|
||||
requireWorkspace(id);
|
||||
|
||||
applyRequest(workspace, request, false);
|
||||
|
||||
ChartWorkspace saved =
|
||||
repository.save(workspace);
|
||||
|
||||
return toResponse(saved);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void deleteWorkspace(
|
||||
Integer id
|
||||
) {
|
||||
ChartWorkspace workspace =
|
||||
requireWorkspace(id);
|
||||
|
||||
repository.delete(workspace);
|
||||
|
||||
if (Boolean.TRUE.equals(workspace.getDefaultWorkspace())) {
|
||||
repository.findAllByScopeOrderBySortOrderAscIdAsc(workspace.getScope())
|
||||
.stream()
|
||||
.findFirst()
|
||||
.ifPresent(this::setDefaultWorkspace);
|
||||
}
|
||||
}
|
||||
|
||||
private ChartWorkspace getOrCreateDefaultWorkspace(
|
||||
ChartWorkspaceScope scope
|
||||
) {
|
||||
return repository.findFirstByScopeAndDefaultWorkspaceTrue(scope)
|
||||
.orElseGet(() ->
|
||||
repository.findAllByScopeOrderBySortOrderAscIdAsc(scope)
|
||||
.stream()
|
||||
.findFirst()
|
||||
.map(workspace -> {
|
||||
setDefaultWorkspace(workspace);
|
||||
return repository.save(workspace);
|
||||
})
|
||||
.orElseGet(() ->
|
||||
repository.save(createDefaultWorkspace(scope))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private void ensureDefaultWorkspace(
|
||||
ChartWorkspaceScope scope
|
||||
) {
|
||||
getOrCreateDefaultWorkspace(scope);
|
||||
}
|
||||
|
||||
private ChartWorkspace createDefaultWorkspace(
|
||||
ChartWorkspaceScope scope
|
||||
) {
|
||||
ChartWorkspace workspace =
|
||||
new ChartWorkspace(
|
||||
scope,
|
||||
"Workspace principal",
|
||||
"fourGrid",
|
||||
"[]"
|
||||
);
|
||||
|
||||
workspace.setDefaultWorkspace(true);
|
||||
workspace.setSortOrder(0);
|
||||
|
||||
return workspace;
|
||||
}
|
||||
|
||||
private ChartWorkspace requireWorkspace(
|
||||
Integer id
|
||||
) {
|
||||
return repository.findById(id)
|
||||
.orElseThrow(() ->
|
||||
new ResponseStatusException(
|
||||
HttpStatus.NOT_FOUND,
|
||||
"Workspace not found."
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private void applyRequest(
|
||||
ChartWorkspace workspace,
|
||||
ChartWorkspaceRequest request,
|
||||
boolean legacyDefaultUpdate
|
||||
) {
|
||||
if (request.name() != null || legacyDefaultUpdate) {
|
||||
workspace.setName(
|
||||
normalizeName(request.name(), workspace.getName())
|
||||
);
|
||||
}
|
||||
|
||||
if (request.sortOrder() != null) {
|
||||
workspace.setSortOrder(request.sortOrder());
|
||||
}
|
||||
|
||||
if (Boolean.TRUE.equals(request.defaultWorkspace())) {
|
||||
setDefaultWorkspace(workspace);
|
||||
} else if (
|
||||
Boolean.FALSE.equals(request.defaultWorkspace()) &&
|
||||
!Boolean.TRUE.equals(workspace.getDefaultWorkspace())
|
||||
) {
|
||||
workspace.setDefaultWorkspace(false);
|
||||
}
|
||||
|
||||
workspace.setLayoutMode(
|
||||
normalizeLayoutMode(request.layoutMode())
|
||||
);
|
||||
|
||||
workspace.setChartsJson(
|
||||
normalizeChartsJson(request.chartsJson())
|
||||
);
|
||||
}
|
||||
|
||||
private void setDefaultWorkspace(
|
||||
ChartWorkspace workspace
|
||||
) {
|
||||
repository.findAllByScopeOrderBySortOrderAscIdAsc(workspace.getScope())
|
||||
.forEach(candidate -> {
|
||||
if (!candidate.getId().equals(workspace.getId())) {
|
||||
candidate.setDefaultWorkspace(false);
|
||||
}
|
||||
});
|
||||
|
||||
workspace.setDefaultWorkspace(true);
|
||||
}
|
||||
|
||||
private int nextSortOrder(
|
||||
ChartWorkspaceScope scope
|
||||
) {
|
||||
return repository.findAllByScopeOrderBySortOrderAscIdAsc(scope)
|
||||
.stream()
|
||||
.map(ChartWorkspace::getSortOrder)
|
||||
.filter(value -> value != null)
|
||||
.max(Integer::compareTo)
|
||||
.orElse(-1) + 1;
|
||||
}
|
||||
|
||||
private String normalizeName(
|
||||
String name,
|
||||
String fallback
|
||||
) {
|
||||
String normalized =
|
||||
name == null ? "" : name.trim();
|
||||
|
||||
if (normalized.isBlank()) {
|
||||
return fallback == null || fallback.isBlank()
|
||||
? "Workspace"
|
||||
: fallback;
|
||||
}
|
||||
|
||||
if (normalized.length() > 120) {
|
||||
throw new ResponseStatusException(
|
||||
HttpStatus.BAD_REQUEST,
|
||||
"Workspace name is too long."
|
||||
);
|
||||
}
|
||||
|
||||
return normalized;
|
||||
}
|
||||
|
||||
private String normalizeLayoutMode(
|
||||
String layoutMode
|
||||
) {
|
||||
if (layoutMode == null || layoutMode.isBlank()) {
|
||||
return "fourGrid";
|
||||
}
|
||||
|
||||
return layoutMode;
|
||||
}
|
||||
|
||||
private String normalizeChartsJson(
|
||||
String chartsJson
|
||||
) {
|
||||
String normalized =
|
||||
chartsJson == null || chartsJson.isBlank()
|
||||
? "[]"
|
||||
: chartsJson;
|
||||
|
||||
try {
|
||||
JsonNode root =
|
||||
objectMapper.readTree(normalized);
|
||||
|
||||
if (!root.isArray()) {
|
||||
throw new ResponseStatusException(
|
||||
HttpStatus.BAD_REQUEST,
|
||||
"chartsJson must be an array."
|
||||
);
|
||||
}
|
||||
|
||||
if (root.size() > MAX_CHARTS_PER_WORKSPACE) {
|
||||
throw new ResponseStatusException(
|
||||
HttpStatus.BAD_REQUEST,
|
||||
"Maximum charts reached for this workspace."
|
||||
);
|
||||
}
|
||||
} catch (ResponseStatusException exception) {
|
||||
throw exception;
|
||||
} catch (Exception exception) {
|
||||
throw new ResponseStatusException(
|
||||
HttpStatus.BAD_REQUEST,
|
||||
"chartsJson is invalid."
|
||||
);
|
||||
}
|
||||
|
||||
return normalized;
|
||||
}
|
||||
|
||||
private ChartWorkspaceResponse toResponse(
|
||||
@@ -71,6 +326,9 @@ public class ChartWorkspaceService {
|
||||
return new ChartWorkspaceResponse(
|
||||
workspace.getId(),
|
||||
workspace.getScope(),
|
||||
workspace.getName(),
|
||||
workspace.getSortOrder(),
|
||||
workspace.getDefaultWorkspace(),
|
||||
workspace.getLayoutMode(),
|
||||
workspace.getChartsJson(),
|
||||
workspace.getCreatedAt(),
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
package com.litoralregas.backend.charts.dto;
|
||||
|
||||
public record ChartWorkspaceRequest(
|
||||
String name,
|
||||
Integer sortOrder,
|
||||
Boolean defaultWorkspace,
|
||||
String layoutMode,
|
||||
String chartsJson
|
||||
) {
|
||||
|
||||
@@ -7,6 +7,9 @@ import java.time.Instant;
|
||||
public record ChartWorkspaceResponse(
|
||||
Integer id,
|
||||
ChartWorkspaceScope scope,
|
||||
String name,
|
||||
Integer sortOrder,
|
||||
Boolean defaultWorkspace,
|
||||
String layoutMode,
|
||||
String chartsJson,
|
||||
Instant createdAt,
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
package com.litoralregas.backend.vnc.rfb;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.PrintWriter;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketTimeoutException;
|
||||
|
||||
public class GreetClient implements AutoCloseable {
|
||||
private Socket clientSocket;
|
||||
private PrintWriter out;
|
||||
private BufferedReader in;
|
||||
|
||||
public void startConnection(String ip, int port) throws IOException {
|
||||
clientSocket = new Socket(ip, port);
|
||||
clientSocket.setSoTimeout(10000);
|
||||
|
||||
out = new PrintWriter(clientSocket.getOutputStream(), true);
|
||||
in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
|
||||
}
|
||||
|
||||
public String sendMessage(String msg) throws IOException {
|
||||
if (out == null || in == null) {
|
||||
throw new IllegalStateException("Connection not started");
|
||||
}
|
||||
|
||||
out.println(msg);
|
||||
String response = in.readLine();
|
||||
|
||||
if (response == null) {
|
||||
throw new SocketTimeoutException("No response from server");
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
public void stopConnection() {
|
||||
try {
|
||||
if (in != null) in.close();
|
||||
} catch (IOException ignored) {
|
||||
}
|
||||
|
||||
if (out != null) {
|
||||
out.close();
|
||||
}
|
||||
|
||||
try {
|
||||
if (clientSocket != null && !clientSocket.isClosed()) {
|
||||
clientSocket.close();
|
||||
}
|
||||
} catch (IOException ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
stopConnection();
|
||||
}
|
||||
}
|
||||
@@ -109,7 +109,7 @@ public class RfbProtoDesktop {
|
||||
this.sock.setKeepAlive(true);
|
||||
this.sock.setReuseAddress(true);
|
||||
this.sock.connect(new InetSocketAddress(host, port), CONNECT_TIMEOUT_MS);
|
||||
this.sock.setSoTimeout(READ_TIMEOUT_MS);
|
||||
this.sock.setSoTimeout(0);
|
||||
|
||||
// Increase receive buffer — large ZRLE/Tight frames arrive in bursts.
|
||||
this.sock.setReceiveBufferSize(65536);
|
||||
|
||||
@@ -43,7 +43,7 @@ litoralregas:
|
||||
|
||||
modules:
|
||||
climate:
|
||||
enabled: true
|
||||
enabled: false
|
||||
exterior-enabled: true
|
||||
enabled-sites:
|
||||
- 1
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
ALTER TABLE chart_workspace
|
||||
ADD COLUMN name VARCHAR(120) NOT NULL DEFAULT 'Workspace principal';
|
||||
|
||||
ALTER TABLE chart_workspace
|
||||
ADD COLUMN sort_order INTEGER NOT NULL DEFAULT 0;
|
||||
|
||||
ALTER TABLE chart_workspace
|
||||
ADD COLUMN is_default BOOLEAN NOT NULL DEFAULT TRUE;
|
||||
|
||||
CREATE TABLE chart_workspace_new (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
|
||||
scope VARCHAR(50) NOT NULL,
|
||||
name VARCHAR(120) NOT NULL,
|
||||
sort_order INTEGER NOT NULL DEFAULT 0,
|
||||
is_default BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
|
||||
layout_mode VARCHAR(50) NOT NULL,
|
||||
|
||||
charts_json TEXT NOT NULL,
|
||||
|
||||
created_at TIMESTAMP NOT NULL,
|
||||
updated_at TIMESTAMP NOT NULL
|
||||
);
|
||||
|
||||
INSERT INTO chart_workspace_new (
|
||||
id,
|
||||
scope,
|
||||
name,
|
||||
sort_order,
|
||||
is_default,
|
||||
layout_mode,
|
||||
charts_json,
|
||||
created_at,
|
||||
updated_at
|
||||
)
|
||||
SELECT
|
||||
id,
|
||||
scope,
|
||||
name,
|
||||
sort_order,
|
||||
is_default,
|
||||
layout_mode,
|
||||
charts_json,
|
||||
created_at,
|
||||
updated_at
|
||||
FROM chart_workspace;
|
||||
|
||||
DROP TABLE chart_workspace;
|
||||
|
||||
ALTER TABLE chart_workspace_new
|
||||
RENAME TO chart_workspace;
|
||||
|
||||
CREATE INDEX idx_chart_workspace_scope_sort
|
||||
ON chart_workspace (scope, sort_order, id);
|
||||
|
||||
CREATE UNIQUE INDEX idx_chart_workspace_scope_default
|
||||
ON chart_workspace (scope)
|
||||
WHERE is_default = TRUE;
|
||||
Reference in New Issue
Block a user