Remove retarded build time variables
All checks were successful
CI / test (push) Successful in 54s
CI / test-analytics (push) Successful in 2m2s
CI / build-api (push) Successful in 3m1s
CI / build-frontend (push) Successful in 1m58s
CI / build-analytics (push) Successful in 41s

This commit is contained in:
2026-05-11 17:00:17 +02:00
parent 5cbc1d50fd
commit b1de6284f7
12 changed files with 65 additions and 59 deletions

View File

@@ -76,21 +76,28 @@ APP_ENV=prod
LOG_LEVEL=INFO
# -----------------------------------------------------------------------------
# Frontend (otel-bi-frontend) → frontend/
# Baked into the image at build time via Docker build-args.
# In Docker Compose these are passed as build args, not runtime env.
# For local dev copy frontend/.env.example to frontend/.env.local instead.
# -----------------------------------------------------------------------------
VITE_API_BASE_URL=http://localhost:8000
VITE_OTEL_COLLECTOR_ENDPOINT=http://alloy:4318
VITE_OTEL_SERVICE_NAME=otel-bi-frontend
VITE_OTEL_SERVICE_NAMESPACE=final-thesis
# -----------------------------------------------------------------------------
# OpenTelemetry — shared collector endpoint
# Same value goes to Go analytics, Python API, and frontend build arg above
# Used by Go analytics + Python API for their own trace/metric export.
# In-cluster K8s DNS for Alloy.
# -----------------------------------------------------------------------------
OTEL_COLLECTOR_ENDPOINT=http://alloy:4318
OTEL_SERVICE_NAMESPACE=final-thesis
# -----------------------------------------------------------------------------
# Frontend telemetry — served to the SPA via GET /api/config.
# These are the values the browser-side OTel SDK uses; the SPA reads them at
# runtime so a single frontend image works across all environments.
# -----------------------------------------------------------------------------
# Browser-reachable OTLP endpoint. Distinct from OTEL_COLLECTOR_ENDPOINT
# because the browser can't reach in-cluster service DNS.
# Default `/otel` assumes Ingress routes `/otel/v1/traces` → alloy:4318.
# Set to an absolute URL (e.g. https://alloy.example.com) if exposing the
# collector on its own host instead.
FRONTEND_OTEL_COLLECTOR_ENDPOINT=/otel
# OTel resource attributes for the frontend service
FRONTEND_OTEL_SERVICE_NAME=otel-bi-frontend
FRONTEND_OTEL_SERVICE_NAMESPACE=final-thesis
FRONTEND_DEPLOYMENT_ENVIRONMENT=production

View File

@@ -148,9 +148,6 @@ jobs:
push: true
cache-from: type=registry,ref=${{ env.IMAGE_FRONTEND }}:latest
cache-to: type=inline
build-args: |
VITE_API_BASE_URL=${{ vars.API_BASE_URL }}
VITE_OTEL_COLLECTOR_ENDPOINT=${{ vars.OTEL_COLLECTOR_ENDPOINT }}
tags: |
${{ env.IMAGE_FRONTEND }}:${{ github.sha }}
${{ env.IMAGE_FRONTEND }}:latest

View File

@@ -53,6 +53,14 @@ class Settings(BaseSettings):
otel_collector_endpoint: str = "http://localhost:4318"
otel_export_timeout_ms: int = 10000
# Browser-reachable OTLP endpoint — served to the SPA via GET /api/config.
# Distinct from otel_collector_endpoint, which is the backend's own
# in-cluster collector address.
frontend_otel_collector_endpoint: str = "/otel"
frontend_otel_service_name: str = "otel-bi-frontend"
frontend_otel_service_namespace: str = "final-thesis"
frontend_deployment_environment: str = "production"
# Report output — points at the K8s CSI / SMB mountpoint in production
report_output_dir: str = "/tmp/otel-bi-reports"

View File

@@ -36,6 +36,10 @@ def frontend_config() -> dict:
"oidc_authority": settings.frontend_jwt_issuer_url,
"oidc_client_id": settings.frontend_oidc_client_id,
"oidc_scope": settings.frontend_oidc_scope,
"otel_collector_endpoint": settings.frontend_otel_collector_endpoint,
"otel_service_name": settings.frontend_otel_service_name,
"otel_service_namespace": settings.frontend_otel_service_namespace,
"deployment_environment": settings.frontend_deployment_environment,
}

View File

@@ -2,12 +2,6 @@ FROM rockylinux/rockylinux:10 AS build
RUN dnf install -y nodejs npm && dnf clean all
ARG VITE_API_BASE_URL=http://localhost:8000
ARG VITE_OTEL_COLLECTOR_ENDPOINT=http://localhost:4318
ENV VITE_API_BASE_URL=$VITE_API_BASE_URL \
VITE_OTEL_COLLECTOR_ENDPOINT=$VITE_OTEL_COLLECTOR_ENDPOINT
WORKDIR /app
COPY package.json package-lock.json ./

View File

@@ -9,14 +9,13 @@ import type {
AWAnomalyPoint,
} from "./types";
const API_BASE = import.meta.env.VITE_API_BASE_URL ?? "http://localhost:8000";
const tracer = trace.getTracer("aw-frontend-api");
async function get<T>(path: string, spanName: string): Promise<T> {
return tracer.startActiveSpan(spanName, async (span) => {
try {
const token = currentAccessToken();
const resp = await fetch(`${API_BASE}${path}`, {
const resp = await fetch(path, {
headers: {
Accept: "application/json",
...(token ? { Authorization: `Bearer ${token}` } : {}),

View File

@@ -3,14 +3,16 @@ export type AppConfig = {
oidc_authority: string;
oidc_client_id: string;
oidc_scope: string;
otel_collector_endpoint: string;
otel_service_name: string;
otel_service_namespace: string;
deployment_environment: string;
};
const API_BASE = (import.meta.env.VITE_API_BASE_URL as string | undefined) ?? "";
let _config: AppConfig | null = null;
export async function fetchAppConfig(): Promise<AppConfig> {
const resp = await fetch(`${API_BASE}/api/config`);
const resp = await fetch("/api/config");
if (!resp.ok) throw new Error(`Failed to fetch app config: ${resp.status}`);
_config = (await resp.json()) as AppConfig;
return _config;

View File

@@ -2,7 +2,6 @@ import { SpanStatusCode, trace } from "@opentelemetry/api";
import { currentAccessToken } from "../auth/oidc";
import type { JobExecution, AuditEntry, ExportRecord } from "./types";
const API_BASE = import.meta.env.VITE_API_BASE_URL ?? "http://localhost:8000";
const tracer = trace.getTracer("gateway-frontend-api");
function authHeaders(): Record<string, string> {
@@ -13,7 +12,7 @@ function authHeaders(): Record<string, string> {
async function get<T>(path: string, spanName: string): Promise<T> {
return tracer.startActiveSpan(spanName, async (span) => {
try {
const resp = await fetch(`${API_BASE}${path}`, {
const resp = await fetch(path, {
headers: { Accept: "application/json", ...authHeaders() },
});
if (!resp.ok) {
@@ -36,7 +35,7 @@ async function get<T>(path: string, spanName: string): Promise<T> {
async function post<T>(path: string, spanName: string, body: unknown = {}): Promise<T> {
return tracer.startActiveSpan(spanName, async (span) => {
try {
const resp = await fetch(`${API_BASE}${path}`, {
const resp = await fetch(path, {
method: "POST",
headers: {
"Content-Type": "application/json",

View File

@@ -9,7 +9,6 @@ import type {
WWIScenario,
} from "./types";
const API_BASE = import.meta.env.VITE_API_BASE_URL ?? "http://localhost:8000";
const tracer = trace.getTracer("wwi-frontend-api");
function authHeaders(): Record<string, string> {
@@ -20,7 +19,7 @@ function authHeaders(): Record<string, string> {
async function get<T>(path: string, spanName: string): Promise<T> {
return tracer.startActiveSpan(spanName, async (span) => {
try {
const resp = await fetch(`${API_BASE}${path}`, {
const resp = await fetch(path, {
headers: { Accept: "application/json", ...authHeaders() },
});
if (!resp.ok) {
@@ -43,7 +42,7 @@ async function get<T>(path: string, spanName: string): Promise<T> {
async function post<T>(path: string, body: unknown, spanName: string): Promise<T> {
return tracer.startActiveSpan(spanName, async (span) => {
try {
const resp = await fetch(`${API_BASE}${path}`, {
const resp = await fetch(path, {
method: "POST",
headers: {
"Content-Type": "application/json",

View File

@@ -11,8 +11,6 @@ import { fetchAppConfig } from "./api/config";
import { AuthProvider } from "./auth/AuthContext";
import { setupTelemetry } from "./telemetry";
setupTelemetry();
const queryClient = new QueryClient({
defaultOptions: {
queries: {
@@ -22,7 +20,13 @@ const queryClient = new QueryClient({
},
});
fetchAppConfig().then(() => {
fetchAppConfig().then((config) => {
setupTelemetry({
collectorEndpoint: config.otel_collector_endpoint,
serviceName: config.otel_service_name,
serviceNamespace: config.otel_service_namespace,
deploymentEnvironment: config.deployment_environment,
});
createRoot(document.getElementById("root")!).render(
<StrictMode>
<BrowserRouter>

View File

@@ -15,25 +15,19 @@ import { UserInteractionInstrumentation } from "@opentelemetry/instrumentation-u
import { XMLHttpRequestInstrumentation } from "@opentelemetry/instrumentation-xml-http-request";
import { ZoneContextManager } from "@opentelemetry/context-zone-peer-dep";
export type TelemetryConfig = {
collectorEndpoint: string;
serviceName: string;
serviceNamespace: string;
deploymentEnvironment: string;
};
let initialized = false;
function escapeRegExp(value: string): string {
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}
export function setupTelemetry(): void {
export function setupTelemetry(config: TelemetryConfig): void {
if (initialized) return;
initialized = true;
const endpoint =
import.meta.env.VITE_OTEL_COLLECTOR_ENDPOINT ?? "http://localhost:4318";
const serviceName =
import.meta.env.VITE_OTEL_SERVICE_NAME ?? "otel-bi-frontend";
const serviceNamespace =
import.meta.env.VITE_OTEL_SERVICE_NAMESPACE ?? "final-thesis";
const apiBaseUrl =
import.meta.env.VITE_API_BASE_URL ?? "http://localhost:8000";
propagation.setGlobalPropagator(
new CompositePropagator({
propagators: [
@@ -45,14 +39,14 @@ export function setupTelemetry(): void {
const provider = new WebTracerProvider({
resource: resourceFromAttributes({
"service.name": serviceName,
"service.namespace": serviceNamespace,
"deployment.environment": import.meta.env.MODE,
"service.name": config.serviceName,
"service.namespace": config.serviceNamespace,
"deployment.environment": config.deploymentEnvironment,
}),
spanProcessors: [
new BatchSpanProcessor(
new OTLPTraceExporter({
url: `${endpoint}/v1/traces`,
url: `${config.collectorEndpoint}/v1/traces`,
}),
),
],
@@ -65,11 +59,7 @@ export function setupTelemetry(): void {
registerInstrumentations({
instrumentations: [
new DocumentLoadInstrumentation(),
new FetchInstrumentation({
propagateTraceHeaderCorsUrls: [
new RegExp(`^${escapeRegExp(apiBaseUrl)}`),
],
}),
new FetchInstrumentation(),
new XMLHttpRequestInstrumentation(),
new UserInteractionInstrumentation(),
],

View File

@@ -7,5 +7,8 @@ export default defineConfig({
server: {
host: "0.0.0.0",
port: 5173,
proxy: {
"/api": "http://localhost:8000",
},
},
});