Remove retarded build time variables
This commit is contained in:
33
.env.example
33
.env.example
@@ -76,21 +76,28 @@ APP_ENV=prod
|
|||||||
LOG_LEVEL=INFO
|
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
|
# 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_COLLECTOR_ENDPOINT=http://alloy:4318
|
||||||
OTEL_SERVICE_NAMESPACE=final-thesis
|
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
|
||||||
|
|||||||
@@ -148,9 +148,6 @@ jobs:
|
|||||||
push: true
|
push: true
|
||||||
cache-from: type=registry,ref=${{ env.IMAGE_FRONTEND }}:latest
|
cache-from: type=registry,ref=${{ env.IMAGE_FRONTEND }}:latest
|
||||||
cache-to: type=inline
|
cache-to: type=inline
|
||||||
build-args: |
|
|
||||||
VITE_API_BASE_URL=${{ vars.API_BASE_URL }}
|
|
||||||
VITE_OTEL_COLLECTOR_ENDPOINT=${{ vars.OTEL_COLLECTOR_ENDPOINT }}
|
|
||||||
tags: |
|
tags: |
|
||||||
${{ env.IMAGE_FRONTEND }}:${{ github.sha }}
|
${{ env.IMAGE_FRONTEND }}:${{ github.sha }}
|
||||||
${{ env.IMAGE_FRONTEND }}:latest
|
${{ env.IMAGE_FRONTEND }}:latest
|
||||||
|
|||||||
@@ -53,6 +53,14 @@ class Settings(BaseSettings):
|
|||||||
otel_collector_endpoint: str = "http://localhost:4318"
|
otel_collector_endpoint: str = "http://localhost:4318"
|
||||||
otel_export_timeout_ms: int = 10000
|
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 — points at the K8s CSI / SMB mountpoint in production
|
||||||
report_output_dir: str = "/tmp/otel-bi-reports"
|
report_output_dir: str = "/tmp/otel-bi-reports"
|
||||||
|
|
||||||
|
|||||||
@@ -36,6 +36,10 @@ def frontend_config() -> dict:
|
|||||||
"oidc_authority": settings.frontend_jwt_issuer_url,
|
"oidc_authority": settings.frontend_jwt_issuer_url,
|
||||||
"oidc_client_id": settings.frontend_oidc_client_id,
|
"oidc_client_id": settings.frontend_oidc_client_id,
|
||||||
"oidc_scope": settings.frontend_oidc_scope,
|
"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,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -2,12 +2,6 @@ FROM rockylinux/rockylinux:10 AS build
|
|||||||
|
|
||||||
RUN dnf install -y nodejs npm && dnf clean all
|
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
|
WORKDIR /app
|
||||||
|
|
||||||
COPY package.json package-lock.json ./
|
COPY package.json package-lock.json ./
|
||||||
|
|||||||
@@ -9,14 +9,13 @@ import type {
|
|||||||
AWAnomalyPoint,
|
AWAnomalyPoint,
|
||||||
} from "./types";
|
} from "./types";
|
||||||
|
|
||||||
const API_BASE = import.meta.env.VITE_API_BASE_URL ?? "http://localhost:8000";
|
|
||||||
const tracer = trace.getTracer("aw-frontend-api");
|
const tracer = trace.getTracer("aw-frontend-api");
|
||||||
|
|
||||||
async function get<T>(path: string, spanName: string): Promise<T> {
|
async function get<T>(path: string, spanName: string): Promise<T> {
|
||||||
return tracer.startActiveSpan(spanName, async (span) => {
|
return tracer.startActiveSpan(spanName, async (span) => {
|
||||||
try {
|
try {
|
||||||
const token = currentAccessToken();
|
const token = currentAccessToken();
|
||||||
const resp = await fetch(`${API_BASE}${path}`, {
|
const resp = await fetch(path, {
|
||||||
headers: {
|
headers: {
|
||||||
Accept: "application/json",
|
Accept: "application/json",
|
||||||
...(token ? { Authorization: `Bearer ${token}` } : {}),
|
...(token ? { Authorization: `Bearer ${token}` } : {}),
|
||||||
|
|||||||
@@ -3,14 +3,16 @@ export type AppConfig = {
|
|||||||
oidc_authority: string;
|
oidc_authority: string;
|
||||||
oidc_client_id: string;
|
oidc_client_id: string;
|
||||||
oidc_scope: 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;
|
let _config: AppConfig | null = null;
|
||||||
|
|
||||||
export async function fetchAppConfig(): Promise<AppConfig> {
|
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}`);
|
if (!resp.ok) throw new Error(`Failed to fetch app config: ${resp.status}`);
|
||||||
_config = (await resp.json()) as AppConfig;
|
_config = (await resp.json()) as AppConfig;
|
||||||
return _config;
|
return _config;
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import { SpanStatusCode, trace } from "@opentelemetry/api";
|
|||||||
import { currentAccessToken } from "../auth/oidc";
|
import { currentAccessToken } from "../auth/oidc";
|
||||||
import type { JobExecution, AuditEntry, ExportRecord } from "./types";
|
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");
|
const tracer = trace.getTracer("gateway-frontend-api");
|
||||||
|
|
||||||
function authHeaders(): Record<string, string> {
|
function authHeaders(): Record<string, string> {
|
||||||
@@ -13,7 +12,7 @@ function authHeaders(): Record<string, string> {
|
|||||||
async function get<T>(path: string, spanName: string): Promise<T> {
|
async function get<T>(path: string, spanName: string): Promise<T> {
|
||||||
return tracer.startActiveSpan(spanName, async (span) => {
|
return tracer.startActiveSpan(spanName, async (span) => {
|
||||||
try {
|
try {
|
||||||
const resp = await fetch(`${API_BASE}${path}`, {
|
const resp = await fetch(path, {
|
||||||
headers: { Accept: "application/json", ...authHeaders() },
|
headers: { Accept: "application/json", ...authHeaders() },
|
||||||
});
|
});
|
||||||
if (!resp.ok) {
|
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> {
|
async function post<T>(path: string, spanName: string, body: unknown = {}): Promise<T> {
|
||||||
return tracer.startActiveSpan(spanName, async (span) => {
|
return tracer.startActiveSpan(spanName, async (span) => {
|
||||||
try {
|
try {
|
||||||
const resp = await fetch(`${API_BASE}${path}`, {
|
const resp = await fetch(path, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import type {
|
|||||||
WWIScenario,
|
WWIScenario,
|
||||||
} from "./types";
|
} from "./types";
|
||||||
|
|
||||||
const API_BASE = import.meta.env.VITE_API_BASE_URL ?? "http://localhost:8000";
|
|
||||||
const tracer = trace.getTracer("wwi-frontend-api");
|
const tracer = trace.getTracer("wwi-frontend-api");
|
||||||
|
|
||||||
function authHeaders(): Record<string, string> {
|
function authHeaders(): Record<string, string> {
|
||||||
@@ -20,7 +19,7 @@ function authHeaders(): Record<string, string> {
|
|||||||
async function get<T>(path: string, spanName: string): Promise<T> {
|
async function get<T>(path: string, spanName: string): Promise<T> {
|
||||||
return tracer.startActiveSpan(spanName, async (span) => {
|
return tracer.startActiveSpan(spanName, async (span) => {
|
||||||
try {
|
try {
|
||||||
const resp = await fetch(`${API_BASE}${path}`, {
|
const resp = await fetch(path, {
|
||||||
headers: { Accept: "application/json", ...authHeaders() },
|
headers: { Accept: "application/json", ...authHeaders() },
|
||||||
});
|
});
|
||||||
if (!resp.ok) {
|
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> {
|
async function post<T>(path: string, body: unknown, spanName: string): Promise<T> {
|
||||||
return tracer.startActiveSpan(spanName, async (span) => {
|
return tracer.startActiveSpan(spanName, async (span) => {
|
||||||
try {
|
try {
|
||||||
const resp = await fetch(`${API_BASE}${path}`, {
|
const resp = await fetch(path, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
|
|||||||
@@ -11,8 +11,6 @@ import { fetchAppConfig } from "./api/config";
|
|||||||
import { AuthProvider } from "./auth/AuthContext";
|
import { AuthProvider } from "./auth/AuthContext";
|
||||||
import { setupTelemetry } from "./telemetry";
|
import { setupTelemetry } from "./telemetry";
|
||||||
|
|
||||||
setupTelemetry();
|
|
||||||
|
|
||||||
const queryClient = new QueryClient({
|
const queryClient = new QueryClient({
|
||||||
defaultOptions: {
|
defaultOptions: {
|
||||||
queries: {
|
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(
|
createRoot(document.getElementById("root")!).render(
|
||||||
<StrictMode>
|
<StrictMode>
|
||||||
<BrowserRouter>
|
<BrowserRouter>
|
||||||
|
|||||||
@@ -15,25 +15,19 @@ import { UserInteractionInstrumentation } from "@opentelemetry/instrumentation-u
|
|||||||
import { XMLHttpRequestInstrumentation } from "@opentelemetry/instrumentation-xml-http-request";
|
import { XMLHttpRequestInstrumentation } from "@opentelemetry/instrumentation-xml-http-request";
|
||||||
import { ZoneContextManager } from "@opentelemetry/context-zone-peer-dep";
|
import { ZoneContextManager } from "@opentelemetry/context-zone-peer-dep";
|
||||||
|
|
||||||
|
export type TelemetryConfig = {
|
||||||
|
collectorEndpoint: string;
|
||||||
|
serviceName: string;
|
||||||
|
serviceNamespace: string;
|
||||||
|
deploymentEnvironment: string;
|
||||||
|
};
|
||||||
|
|
||||||
let initialized = false;
|
let initialized = false;
|
||||||
|
|
||||||
function escapeRegExp(value: string): string {
|
export function setupTelemetry(config: TelemetryConfig): void {
|
||||||
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
||||||
}
|
|
||||||
|
|
||||||
export function setupTelemetry(): void {
|
|
||||||
if (initialized) return;
|
if (initialized) return;
|
||||||
initialized = true;
|
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(
|
propagation.setGlobalPropagator(
|
||||||
new CompositePropagator({
|
new CompositePropagator({
|
||||||
propagators: [
|
propagators: [
|
||||||
@@ -45,14 +39,14 @@ export function setupTelemetry(): void {
|
|||||||
|
|
||||||
const provider = new WebTracerProvider({
|
const provider = new WebTracerProvider({
|
||||||
resource: resourceFromAttributes({
|
resource: resourceFromAttributes({
|
||||||
"service.name": serviceName,
|
"service.name": config.serviceName,
|
||||||
"service.namespace": serviceNamespace,
|
"service.namespace": config.serviceNamespace,
|
||||||
"deployment.environment": import.meta.env.MODE,
|
"deployment.environment": config.deploymentEnvironment,
|
||||||
}),
|
}),
|
||||||
spanProcessors: [
|
spanProcessors: [
|
||||||
new BatchSpanProcessor(
|
new BatchSpanProcessor(
|
||||||
new OTLPTraceExporter({
|
new OTLPTraceExporter({
|
||||||
url: `${endpoint}/v1/traces`,
|
url: `${config.collectorEndpoint}/v1/traces`,
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -65,11 +59,7 @@ export function setupTelemetry(): void {
|
|||||||
registerInstrumentations({
|
registerInstrumentations({
|
||||||
instrumentations: [
|
instrumentations: [
|
||||||
new DocumentLoadInstrumentation(),
|
new DocumentLoadInstrumentation(),
|
||||||
new FetchInstrumentation({
|
new FetchInstrumentation(),
|
||||||
propagateTraceHeaderCorsUrls: [
|
|
||||||
new RegExp(`^${escapeRegExp(apiBaseUrl)}`),
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
new XMLHttpRequestInstrumentation(),
|
new XMLHttpRequestInstrumentation(),
|
||||||
new UserInteractionInstrumentation(),
|
new UserInteractionInstrumentation(),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -7,5 +7,8 @@ export default defineConfig({
|
|||||||
server: {
|
server: {
|
||||||
host: "0.0.0.0",
|
host: "0.0.0.0",
|
||||||
port: 5173,
|
port: 5173,
|
||||||
|
proxy: {
|
||||||
|
"/api": "http://localhost:8000",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user