Compare commits

4 Commits

Author SHA1 Message Date
hsiegeln
88b9faa4f8 chore: point generate-api:live at deployed server instead of localhost
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m33s
CI / docker (push) Successful in 1m41s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 38s
The deployed instance at 192.168.50.86:30090 is the canonical source of
truth for the API schema during development — regen against it instead
of requiring a local backend boot with Postgres + ClickHouse.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 10:24:49 +02:00
hsiegeln
59de424ab9 chore: regenerate openapi.json + schema.d.ts from deployed server
Fetched from http://192.168.50.86:30090/api/v1/api-docs (running origin/main
through b7a107d — full P3B/P3C env-scoping migration live there).

SPA TS types now match the env-scoped URL shape used at runtime:
- /environments/{envSlug}/... for data, config, search, logs, routes, agents
- /agents/config (agent-authoritative)
- /admin/environments/{envSlug}/... (env CRUD)

Note: ExecutionDetail.environment isn't in the regenerated schema yet —
commit d02fa73 (local, not yet pushed/deployed) adds that backend field.
The local type extension in ui/src/components/ExecutionDiagram/types.ts
covers the gap until the next redeploy + regen.

UI typecheck (tsc -p tsconfig.app.json --noEmit) passes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 10:24:24 +02:00
hsiegeln
d02fa73080 fix: scope correlation-chain query to the exchange's own env
Correlated exchanges always share the env of the one being viewed —
using the globally-selected env from the picker was wrong if the user
switched envs after opening a detail view (or arrived via permalink).

Thread `environment` through:
- `ExecutionStore.ExecutionRecord` gains `environment` field; the
  ClickHouse `executions` table already stores this, just not read back.
- `ClickHouseExecutionStore.findById` SELECT adds the column; mapper
  populates it.
- `ExecutionDetail` gains `environment`; `DetailService` passes through.
- `IngestionService.toExecutionRecord` passes null — this legacy PG
  ingestion path isn't active when ClickHouse is enabled, and the
  read-side is what drives the correlation UI.
- UI `ExchangeHeader` reads `detail.environment ?? storeEnv` and
  extends the TS type locally (schema.d.ts catches up on next regen).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 10:19:42 +02:00
hsiegeln
f04e77788e fix: thread environment into correlation-chain query in ExchangeHeader
The env-scoping migration (P3A) changed useCorrelationChain to require an
environment arg and gate on `enabled: !!correlationId && !!environment`,
but ExchangeHeader was still calling it with one arg. Result: the query
never fired, so the header always rendered "no correlated exchanges
found" even when 4+ exchanges shared a correlationId.

Fix: read the selected env from the Zustand environment store and pass
it through.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 10:13:32 +02:00
10 changed files with 2684 additions and 964 deletions

View File

@@ -204,7 +204,7 @@ public class ClickHouseExecutionStore implements ExecutionStore {
@Override
public Optional<ExecutionRecord> findById(String executionId) {
List<ExecutionRecord> results = jdbc.query("""
SELECT execution_id, route_id, instance_id, application_id, status,
SELECT execution_id, route_id, instance_id, application_id, environment, status,
correlation_id, exchange_id, start_time, end_time, duration_ms,
error_message, error_stacktrace, diagram_content_hash, engine_level,
input_body, output_body, input_headers, output_headers,
@@ -304,6 +304,7 @@ public class ClickHouseExecutionStore implements ExecutionStore {
emptyToNull(rs.getString("route_id")),
emptyToNull(rs.getString("instance_id")),
emptyToNull(rs.getString("application_id")),
emptyToNull(rs.getString("environment")),
emptyToNull(rs.getString("status")),
emptyToNull(rs.getString("correlation_id")),
emptyToNull(rs.getString("exchange_id")),

View File

@@ -36,7 +36,7 @@ public class DetailService {
}
return new ExecutionDetail(
exec.executionId(), exec.routeId(), exec.instanceId(),
exec.applicationId(),
exec.applicationId(), exec.environment(),
exec.status(), exec.startTime(), exec.endTime(),
exec.durationMs() != null ? exec.durationMs() : 0L,
exec.correlationId(), exec.exchangeId(),

View File

@@ -13,6 +13,7 @@ import java.util.Map;
* @param executionId unique execution identifier
* @param routeId Camel route ID
* @param instanceId agent instance that reported the execution
* @param environment environment slug this exchange was recorded in
* @param status execution status (COMPLETED, FAILED, RUNNING)
* @param startTime execution start time
* @param endTime execution end time (may be null for RUNNING)
@@ -33,6 +34,7 @@ public record ExecutionDetail(
String routeId,
String instanceId,
String applicationId,
String environment,
String status,
Instant startTime,
Instant endTime,

View File

@@ -114,6 +114,7 @@ public class IngestionService {
return new ExecutionRecord(
exec.getExchangeId(), exec.getRouteId(), instanceId, applicationId,
null, // environment: legacy PG path; ClickHouse path uses MergedExecution with env resolved from registry
exec.getStatus() != null ? exec.getStatus().name() : "RUNNING",
exec.getCorrelationId(), exec.getExchangeId(),
exec.getStartTime(), exec.getEndTime(),

View File

@@ -20,6 +20,7 @@ public interface ExecutionStore {
record ExecutionRecord(
String executionId, String routeId, String instanceId, String applicationId,
String environment,
String status, String correlationId, String exchangeId,
Instant startTime, Instant endTime, Long durationMs,
String errorMessage, String errorStacktrace, String diagramContentHash,

View File

@@ -11,7 +11,7 @@
"lint": "eslint .",
"preview": "vite preview",
"generate-api": "openapi-typescript src/api/openapi.json -o src/api/schema.d.ts",
"generate-api:live": "curl -s http://localhost:8081/api/v1/api-docs -o src/api/openapi.json && openapi-typescript src/api/openapi.json -o src/api/schema.d.ts",
"generate-api:live": "curl -s http://192.168.50.86:30090/api/v1/api-docs -o src/api/openapi.json && openapi-typescript src/api/openapi.json -o src/api/schema.d.ts",
"postinstall": "node -e \"const fs=require('fs');fs.mkdirSync('public',{recursive:true});fs.copyFileSync('node_modules/@cameleer/design-system/assets/cameleer-logo.svg','public/favicon.svg')\""
},
"dependencies": {

File diff suppressed because one or more lines are too long

3622
ui/src/api/schema.d.ts vendored

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,9 @@
import type { components } from '../../api/schema';
export type ExecutionDetail = components['schemas']['ExecutionDetail'];
export type ExecutionDetail = components['schemas']['ExecutionDetail'] & {
/** Environment slug this exchange was recorded in. Added to the backend record; schema.d.ts will pick it up on next openapi regen. */
environment?: string;
};
export type ProcessorNode = components['schemas']['ProcessorNode'];
export interface NodeExecutionState {

View File

@@ -6,6 +6,7 @@ import { useCorrelationChain } from '../../api/queries/correlation';
import { useAgents } from '../../api/queries/agents';
import { useCatalog } from '../../api/queries/catalog';
import { useCanControl } from '../../auth/auth-store';
import { useEnvironmentStore } from '../../api/environment-store';
import type { ExecutionDetail } from '../../components/ExecutionDiagram/types';
import { attributeBadgeColor } from '../../utils/attribute-color';
import { formatDuration, statusLabel } from '../../utils/format-utils';
@@ -32,7 +33,12 @@ function statusVariant(s: string): StatusVariant {
export function ExchangeHeader({ detail, onCorrelatedSelect, onClearSelection }: ExchangeHeaderProps) {
const navigate = useNavigate();
const { timeRange } = useGlobalFilters();
const { data: chainResult } = useCorrelationChain(detail.correlationId ?? null);
const storeEnv = useEnvironmentStore((s) => s.environment);
// Prefer the exchange's own env over the selected env — correlated exchanges
// always live in the same env as the one being viewed, and the user may have
// switched env-picker after opening this detail.
const environment = detail.environment ?? storeEnv;
const { data: chainResult } = useCorrelationChain(detail.correlationId ?? null, environment);
const chain = chainResult?.data;
const showChain = chain && chain.length > 1;
const attrs = Object.entries(detail.attributes ?? {});