diff --git a/cameleer3-server-app/src/main/java/com/cameleer3/server/app/controller/ApiExceptionHandler.java b/cameleer3-server-app/src/main/java/com/cameleer3/server/app/controller/ApiExceptionHandler.java index a55d0ee7..5585f279 100644 --- a/cameleer3-server-app/src/main/java/com/cameleer3/server/app/controller/ApiExceptionHandler.java +++ b/cameleer3-server-app/src/main/java/com/cameleer3/server/app/controller/ApiExceptionHandler.java @@ -14,7 +14,8 @@ public class ApiExceptionHandler { @ExceptionHandler(ResponseStatusException.class) public ResponseEntity handleResponseStatus(ResponseStatusException ex) { + String reason = ex.getReason(); return ResponseEntity.status(ex.getStatusCode()) - .body(new ErrorResponse(ex.getReason() != null ? ex.getReason() : "Unknown error")); + .body(new ErrorResponse(reason != null ? reason : "Unknown error")); } } diff --git a/cameleer3-server-app/src/main/java/com/cameleer3/server/app/controller/DatabaseAdminController.java b/cameleer3-server-app/src/main/java/com/cameleer3/server/app/controller/DatabaseAdminController.java index e6ceff20..d40854f1 100644 --- a/cameleer3-server-app/src/main/java/com/cameleer3/server/app/controller/DatabaseAdminController.java +++ b/cameleer3-server-app/src/main/java/com/cameleer3/server/app/controller/DatabaseAdminController.java @@ -59,7 +59,8 @@ public class DatabaseAdminController { String host = extractHost(dataSource); return ResponseEntity.ok(new DatabaseStatusResponse(true, version, host, schema, timescaleDb)); } catch (Exception e) { - return ResponseEntity.ok(new DatabaseStatusResponse(false, null, null, null, false)); + return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE) + .body(new DatabaseStatusResponse(false, null, null, null, false)); } } diff --git a/cameleer3-server-app/src/main/java/com/cameleer3/server/app/controller/OpenSearchAdminController.java b/cameleer3-server-app/src/main/java/com/cameleer3/server/app/controller/OpenSearchAdminController.java index 11b7f135..aadf602c 100644 --- a/cameleer3-server-app/src/main/java/com/cameleer3/server/app/controller/OpenSearchAdminController.java +++ b/cameleer3-server-app/src/main/java/com/cameleer3/server/app/controller/OpenSearchAdminController.java @@ -80,7 +80,8 @@ public class OpenSearchAdminController { health.numberOfNodes(), opensearchUrl)); } catch (Exception e) { - return ResponseEntity.ok(new OpenSearchStatusResponse( + return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE) + .body(new OpenSearchStatusResponse( false, "UNREACHABLE", null, 0, opensearchUrl)); } } @@ -149,7 +150,8 @@ public class OpenSearchAdminController { pageItems, totalIndices, totalDocs, humanSize(totalBytes), page, size, totalPages)); } catch (Exception e) { - return ResponseEntity.ok(new IndicesPageResponse( + return ResponseEntity.status(HttpStatus.BAD_GATEWAY) + .body(new IndicesPageResponse( List.of(), 0, 0, "0 B", page, size, 0)); } } @@ -234,7 +236,8 @@ public class OpenSearchAdminController { searchLatency, indexingLatency, heapUsed, heapMax)); } catch (Exception e) { - return ResponseEntity.ok(new PerformanceResponse(0, 0, 0, 0, 0, 0)); + return ResponseEntity.status(HttpStatus.BAD_GATEWAY) + .body(new PerformanceResponse(0, 0, 0, 0, 0, 0)); } } diff --git a/cameleer3-server-app/src/main/java/com/cameleer3/server/app/diagram/ElkDiagramRenderer.java b/cameleer3-server-app/src/main/java/com/cameleer3/server/app/diagram/ElkDiagramRenderer.java index a1b20137..e357711f 100644 --- a/cameleer3-server-app/src/main/java/com/cameleer3/server/app/diagram/ElkDiagramRenderer.java +++ b/cameleer3-server-app/src/main/java/com/cameleer3/server/app/diagram/ElkDiagramRenderer.java @@ -415,12 +415,13 @@ public class ElkDiagramRenderer implements DiagramRenderer { for (ElkEdge elkEdge : allEdges) { String sourceId = elkEdge.getSources().isEmpty() ? "" : elkEdge.getSources().get(0).getIdentifier(); String targetId = elkEdge.getTargets().isEmpty() ? "" : elkEdge.getTargets().get(0).getIdentifier(); - ElkNode edgeRoot = getElkRoot(elkEdge.getContainingNode()); + ElkNode containingNode = elkEdge.getContainingNode(); + ElkNode edgeRoot = containingNode != null ? getElkRoot(containingNode) : null; List points = new ArrayList<>(); for (ElkEdgeSection section : elkEdge.getSections()) { - double cx = getAbsoluteX(elkEdge.getContainingNode(), edgeRoot); - double cy = getAbsoluteY(elkEdge.getContainingNode(), edgeRoot); + double cx = containingNode != null ? getAbsoluteX(containingNode, edgeRoot) : 0; + double cy = containingNode != null ? getAbsoluteY(containingNode, edgeRoot) : 0; points.add(new double[]{section.getStartX() + cx, section.getStartY() + cy}); for (ElkBendPoint bp : section.getBendPoints()) { points.add(new double[]{bp.getX() + cx, bp.getY() + cy}); diff --git a/cameleer3-server-app/src/main/java/com/cameleer3/server/app/web/SpaForwardController.java b/cameleer3-server-app/src/main/java/com/cameleer3/server/app/web/SpaForwardController.java index 4b7e674a..7fdd5efb 100644 --- a/cameleer3-server-app/src/main/java/com/cameleer3/server/app/web/SpaForwardController.java +++ b/cameleer3-server-app/src/main/java/com/cameleer3/server/app/web/SpaForwardController.java @@ -16,9 +16,9 @@ public class SpaForwardController { @GetMapping(value = { "/login", "/executions", - "/executions/{path:[^\\.]*}", + "/executions/**", "/oidc/callback", - "/admin/{path:[^\\.]*}" + "/admin/**" }) public String forward() { return "forward:/index.html"; diff --git a/cameleer3-server-core/src/main/java/com/cameleer3/server/core/ingestion/WriteBuffer.java b/cameleer3-server-core/src/main/java/com/cameleer3/server/core/ingestion/WriteBuffer.java index bcd1077c..46a7be35 100644 --- a/cameleer3-server-core/src/main/java/com/cameleer3/server/core/ingestion/WriteBuffer.java +++ b/cameleer3-server-core/src/main/java/com/cameleer3/server/core/ingestion/WriteBuffer.java @@ -1,5 +1,8 @@ package com.cameleer3.server.core.ingestion; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import java.util.ArrayList; import java.util.List; import java.util.concurrent.ArrayBlockingQueue; @@ -16,6 +19,8 @@ import java.util.concurrent.BlockingQueue; */ public class WriteBuffer { + private static final Logger log = LoggerFactory.getLogger(WriteBuffer.class); + private final BlockingQueue queue; private final int capacity; @@ -45,7 +50,10 @@ public class WriteBuffer { return false; } for (T item : items) { - queue.offer(item); + if (!queue.offer(item)) { + log.warn("WriteBuffer offer rejected despite capacity check — possible concurrent modification"); + return false; + } } return true; } diff --git a/ui/src/pages/Admin/DatabaseAdminPage.tsx b/ui/src/pages/Admin/DatabaseAdminPage.tsx index 5890335e..7b9c2943 100644 --- a/ui/src/pages/Admin/DatabaseAdminPage.tsx +++ b/ui/src/pages/Admin/DatabaseAdminPage.tsx @@ -3,7 +3,8 @@ import type { Column } from '@cameleer/design-system'; import { useDatabaseStatus, useConnectionPool, useDatabaseTables, useActiveQueries, useKillQuery } from '../../api/queries/admin/database'; export default function DatabaseAdminPage() { - const { data: status } = useDatabaseStatus(); + const { data: status, isError: statusError } = useDatabaseStatus(); + const unreachable = statusError || (status && !status.connected); const { data: pool } = useConnectionPool(); const { data: tables } = useDatabaseTables(); const { data: queries } = useActiveQueries(); @@ -34,7 +35,7 @@ export default function DatabaseAdminPage() {

Database Administration

- +
diff --git a/ui/src/pages/Admin/OpenSearchAdminPage.tsx b/ui/src/pages/Admin/OpenSearchAdminPage.tsx index 3447517f..ad441367 100644 --- a/ui/src/pages/Admin/OpenSearchAdminPage.tsx +++ b/ui/src/pages/Admin/OpenSearchAdminPage.tsx @@ -4,11 +4,12 @@ import { useOpenSearchStatus, usePipelineStats, useOpenSearchIndices, useOpenSea import styles from './OpenSearchAdminPage.module.css'; export default function OpenSearchAdminPage() { - const { data: status } = useOpenSearchStatus(); + const { data: status, isError: statusError } = useOpenSearchStatus(); const { data: pipeline } = usePipelineStats(); const { data: perf } = useOpenSearchPerformance(); const { data: execIndices } = useOpenSearchIndices(0, 50, '', 'executions'); const { data: logIndices } = useOpenSearchIndices(0, 50, '', 'logs'); + const unreachable = statusError || (status && !status.reachable); const deleteIndex = useDeleteIndex(); const indexColumns: Column[] = [ @@ -22,7 +23,7 @@ export default function OpenSearchAdminPage() { return (
- +