function resolveServiceUrl(explicitEnv, fallbackPort, suffix = "") { const explicit = String(explicitEnv || "").trim(); if (explicit) { return explicit.replace(/\/$/, "") + suffix; } if (typeof window !== "undefined") { const { protocol, hostname } = window.location; return `${protocol}//${hostname}:${fallbackPort}${suffix}`; } return `http://localhost:${fallbackPort}${suffix}`; } const API_URL = resolveServiceUrl(import.meta.env.VITE_API_URL, import.meta.env.VITE_API_PORT || "18080", "/api"); const COLLECTOR_URL = resolveServiceUrl(import.meta.env.VITE_COLLECTOR_URL, import.meta.env.VITE_COLLECTOR_PORT || "18110"); async function request(baseUrl, path, options = {}) { const headers = { ...(options.token ? { Authorization: `Bearer ${options.token}` } : {}), ...options.headers, }; const isJsonBody = options.body !== undefined && !(options.body instanceof FormData); if (isJsonBody) { headers["Content-Type"] = "application/json"; } const response = await fetch(baseUrl + path, { headers, method: options.method || "GET", cache: options.cache || "no-store", body: options.body === undefined ? undefined : isJsonBody ? JSON.stringify(options.body) : options.body, }); if (response.status === 204) { return null; } const data = await response.json().catch(() => ({})); if (!response.ok) { throw new Error(data.error || "Anfrage fehlgeschlagen."); } return data; } const apiRequest = (path, options) => request(API_URL, path, options); const collectorRequest = (path, options) => request(COLLECTOR_URL, path, options); export const api = { login: (body) => apiRequest("/auth/login", { method: "POST", body }), me: (token) => apiRequest("/auth/me", { token }), changePassword: (token, body) => apiRequest("/auth/change-password", { method: "POST", token, body }), health: () => apiRequest("/health"), getIsps: (token) => apiRequest("/isps", { token }), getAliases: (token, isp) => apiRequest(`/isps/${isp}/aliases`, { token }), saveAliases: (token, isp, body) => apiRequest(`/isps/${isp}/aliases`, { method: "PUT", token, body }), getUnits: (token) => apiRequest("/units", { token }), createUnit: (token, body) => apiRequest("/units", { method: "POST", token, body }), updateUnit: (token, symbol, body) => apiRequest(`/units/${encodeURIComponent(symbol)}`, { method: "PATCH", token, body }), createTrendTable: (token, body) => apiRequest("/trend-tables", { method: "POST", token, body }), normalizeAliasMetadata: (token, body) => apiRequest("/admin/normalize-alias-metadata", { method: "POST", token, body }), importSql: (token, body) => apiRequest("/admin/import-sql", { method: "POST", token, body }), getUsers: (token) => apiRequest("/users", { token }), createUser: (token, body) => apiRequest("/users", { method: "POST", token, body }), updateUser: (token, id, body) => apiRequest(`/users/${id}`, { method: "PATCH", token, body }), getRoles: (token) => apiRequest("/roles", { token }), createRole: (token, body) => apiRequest("/roles", { method: "POST", token, body }), updateRole: (token, name, body) => apiRequest(`/roles/${encodeURIComponent(name)}`, { method: "PATCH", token, body }), getSelections: (token) => apiRequest("/selections", { token }), createSelection: (token, body) => apiRequest("/selections", { method: "POST", token, body }), updateSelection: (token, id, body) => apiRequest(`/selections/${id}`, { method: "PUT", token, body }), deleteSelection: (token, id) => apiRequest(`/selections/${id}`, { method: "DELETE", token }), queryTrends: (token, body) => apiRequest("/trends/query", { method: "POST", token, body }), latestValues: (token, body) => apiRequest("/points/latest", { method: "POST", token, body }), getDashboard: (token) => apiRequest("/dashboard", { token }), saveDashboard: (token, body) => apiRequest("/dashboard", { method: "PUT", token, body }), loadDashboardData: (token, body) => apiRequest("/dashboard/data", { method: "POST", token, body }), getPreferences: (token) => apiRequest("/preferences", { token }), savePreferences: (token, body) => apiRequest("/preferences", { method: "PUT", token, body }), getIntegrations: (token) => apiRequest("/integrations", { token }), saveIntegrations: (token, body) => apiRequest("/integrations", { method: "PUT", token, body }), getRunnerJobs: (token) => apiRequest("/runner/jobs", { token }), createRunnerJob: (token, body) => apiRequest("/runner/jobs", { method: "POST", token, body }), updateRunnerJob: (token, id, body) => apiRequest(`/runner/jobs/${encodeURIComponent(id)}`, { method: "PATCH", token, body }), testRunnerJob: (token, id) => apiRequest(`/runner/jobs/${encodeURIComponent(id)}/test`, { method: "POST", token }), deleteRunnerJob: (token, id) => apiRequest(`/runner/jobs/${encodeURIComponent(id)}`, { method: "DELETE", token }), getCollectorCapabilities: () => collectorRequest("/capabilities"), getCollectorSources: () => collectorRequest("/sources"), getCollectorDatapoints: (sourceId = "") => collectorRequest(sourceId ? `/datapoints?sourceId=${encodeURIComponent(sourceId)}` : "/datapoints"), createCollectorSource: (body) => collectorRequest("/sources", { method: "POST", body }), updateCollectorSource: (id, body) => collectorRequest(`/sources/${id}`, { method: "PUT", body }), deleteCollectorSource: (id) => collectorRequest(`/sources/${id}`, { method: "DELETE" }), createCollectorDatapoint: (body) => collectorRequest("/datapoints/manual", { method: "POST", body }), updateCollectorDatapoint: (id, body) => collectorRequest(`/datapoints/${encodeURIComponent(id)}`, { method: "PATCH", body }), readCollectorDatapoint: (id) => collectorRequest(`/datapoints/${encodeURIComponent(id)}/read`, { method: "POST" }), getCollectorDebug: () => collectorRequest("/debug"), importModbusCsv: (body) => collectorRequest("/imports/modbus-csv", { method: "POST", body }), importKnxXml: (body) => collectorRequest("/imports/knx-xml", { method: "POST", body }), scanOpcua: (body) => collectorRequest("/scan/opcua", { method: "POST", body }), scanBacnet: (body) => collectorRequest("/scan/bacnet", { method: "POST", body }), scanIpsymcon: (body) => collectorRequest("/scan/ipsymcon", { method: "POST", body }), };