From 810ecaa4541484ad0648f2ce063b8b0bc1c2c908 Mon Sep 17 00:00:00 2001 From: Alex | Kronox Date: Sat, 27 Jun 2026 11:28:42 +0200 Subject: [PATCH] make api adress configuratable --- .../webEditor/src/dfdApiClient/backendUrl.ts | 23 +++++++++ .../src/dfdApiClient/dfdApiClient.ts | 8 +++- .../webEditor/src/dfdApiClient/di.config.ts | 5 ++ frontend/webEditor/src/settings/Settings.ts | 1 + frontend/webEditor/src/settings/SettingsUi.ts | 47 +++++++++++++++++++ .../webEditor/src/settings/settingsUi.css | 32 +++++++++++++ 6 files changed, 114 insertions(+), 2 deletions(-) create mode 100644 frontend/webEditor/src/dfdApiClient/backendUrl.ts diff --git a/frontend/webEditor/src/dfdApiClient/backendUrl.ts b/frontend/webEditor/src/dfdApiClient/backendUrl.ts new file mode 100644 index 00000000..8728bf33 --- /dev/null +++ b/frontend/webEditor/src/dfdApiClient/backendUrl.ts @@ -0,0 +1,23 @@ +import { injectable } from "inversify"; +import { SettingsValue } from "../settings/SettingsValue"; + +@injectable() +export class BackEndURL extends SettingsValue { + private static readonly DEFAULT_URL = "https://websocket.dataflowanalysis.org/api/"; + + constructor() { + super(BackEndURL.DEFAULT_URL); + } + + set(newValue: string): void { + super.set(newValue.endsWith("/") ? newValue : newValue + "/"); + } + + setDefault(): void { + this.set(BackEndURL.DEFAULT_URL); + } + + isDefault(): boolean { + return this.get() === BackEndURL.DEFAULT_URL; + } +} diff --git a/frontend/webEditor/src/dfdApiClient/dfdApiClient.ts b/frontend/webEditor/src/dfdApiClient/dfdApiClient.ts index 067ded3b..6b67b0a4 100644 --- a/frontend/webEditor/src/dfdApiClient/dfdApiClient.ts +++ b/frontend/webEditor/src/dfdApiClient/dfdApiClient.ts @@ -1,9 +1,13 @@ import { inject, injectable } from "inversify"; import { FileName } from "../fileName/fileName"; +import { BackEndURL } from "./backendUrl"; @injectable() export class DfdApiClient { - constructor(@inject(FileName) private readonly fileName: FileName) {} + constructor( + @inject(BackEndURL) private readonly backEndURL: BackEndURL, + @inject(FileName) private readonly fileName: FileName, + ) {} public async requestDiagram(message: string, action: string) { try { @@ -23,7 +27,7 @@ export class DfdApiClient { } public sendMessage(message: string, action: string, name?: string): Promise { - const apiUrl = `https://websocket.dataflowanalysis.org/api/${action}`; + const apiUrl = `${this.backEndURL.get()}${action}`; const fileName = name ?? this.fileName.getName(); return fetch(apiUrl, { diff --git a/frontend/webEditor/src/dfdApiClient/di.config.ts b/frontend/webEditor/src/dfdApiClient/di.config.ts index 794f8c69..7dee8c76 100644 --- a/frontend/webEditor/src/dfdApiClient/di.config.ts +++ b/frontend/webEditor/src/dfdApiClient/di.config.ts @@ -1,6 +1,11 @@ import { ContainerModule } from "inversify"; import { DfdApiClient } from "./dfdApiClient"; +import { BackEndURL } from "./backendUrl"; +import { SETTINGS } from "../settings/Settings"; export const dfdApiModule = new ContainerModule((bind) => { + bind(BackEndURL).toSelf().inSingletonScope(); + bind(SETTINGS.BackEndURL).toService(BackEndURL); + bind(DfdApiClient).toSelf().inSingletonScope(); }); diff --git a/frontend/webEditor/src/settings/Settings.ts b/frontend/webEditor/src/settings/Settings.ts index 2bba96f6..32d3a1af 100644 --- a/frontend/webEditor/src/settings/Settings.ts +++ b/frontend/webEditor/src/settings/Settings.ts @@ -6,6 +6,7 @@ export const SETTINGS = { HideEdgeNames: Symbol("HideEdgeNames"), SimplifyNodeNames: Symbol("SimplifyNodeNames"), ShownLabels: Symbol("ShownLabels"), + BackEndURL: Symbol("BackEndURL"), }; export type SimplifyNodeNames = BoolSettingsValue; diff --git a/frontend/webEditor/src/settings/SettingsUi.ts b/frontend/webEditor/src/settings/SettingsUi.ts index 70f6194a..74713fa8 100644 --- a/frontend/webEditor/src/settings/SettingsUi.ts +++ b/frontend/webEditor/src/settings/SettingsUi.ts @@ -6,6 +6,7 @@ import { HideEdgeNames, SETTINGS, SimplifyNodeNames } from "./Settings"; import { EditorModeController } from "./editorMode"; import { Theme, ThemeManager } from "./Theme"; import { ShownLabels, ShownLabelsValue } from "./ShownLabels"; +import { BackEndURL } from "../dfdApiClient/backendUrl"; @injectable() export class SettingsUI extends AccordionUiExtension { @@ -17,6 +18,7 @@ export class SettingsUI extends AccordionUiExtension { @inject(SETTINGS.HideEdgeNames) private readonly hideEdgeNames: HideEdgeNames, @inject(SETTINGS.SimplifyNodeNames) private readonly simplifyNodeNames: SimplifyNodeNames, @inject(SETTINGS.Mode) private readonly editorModeController: EditorModeController, + @inject(SETTINGS.BackEndURL) private readonly backEndURL: BackEndURL, ) { super("right", "up"); } @@ -42,6 +44,7 @@ export class SettingsUI extends AccordionUiExtension { this.addBooleanSwitch(grid, "Hide Edge Names", this.hideEdgeNames); this.addBooleanSwitch(grid, "Simplify Node Names", this.simplifyNodeNames); this.addSwitch(grid, "Read Only", this.editorModeController, { true: "view", false: "edit" }); + this.addURLInput(grid); } protected initializeHeaderContent(headerElement: HTMLElement): void { @@ -118,6 +121,50 @@ export class SettingsUI extends AccordionUiExtension { container.appendChild(textLabel); container.appendChild(dropDown); } + + private addURLInput(container: HTMLElement) { + const title = "Backend URL"; + const textLabel = document.createElement("label"); + textLabel.textContent = title; + textLabel.htmlFor = `setting-${title.toLowerCase().replace(/\s+/g, "-")}`; + + const inputHolder = document.createElement("div"); + inputHolder.id = "url-input-holder"; + const textInput = document.createElement("input"); + textInput.id = `setting-${title.toLowerCase().replace(/\s+/g, "-")}`; + textInput.value = this.backEndURL.get(); + const resetButton = document.createElement("button"); + resetButton.id = "url-reset"; + inputHolder.appendChild(textInput); + inputHolder.appendChild(resetButton); + + const visibilityClass = "visible"; + this.backEndURL.registerListener((v) => { + textInput.value = v; + if (!this.backEndURL.isDefault()) { + resetButton.classList.add(visibilityClass); + } else { + resetButton.classList.remove(visibilityClass); + } + }); + textInput.onchange = () => { + // when changing from the default URL we ask the user for confirmation + const confirmation = this.backEndURL.isDefault() + ? confirm("Are you sure you want to change the Backend URL?") + : true; + if (confirmation) { + this.backEndURL.set(textInput.value); + } else { + textInput.value = this.backEndURL.get(); + } + }; + resetButton.onclick = () => { + this.backEndURL.setDefault(); + }; + + container.appendChild(textLabel); + container.appendChild(inputHolder); + } } interface ToString { diff --git a/frontend/webEditor/src/settings/settingsUi.css b/frontend/webEditor/src/settings/settingsUi.css index 90517902..5ddd1094 100644 --- a/frontend/webEditor/src/settings/settingsUi.css +++ b/frontend/webEditor/src/settings/settingsUi.css @@ -108,3 +108,35 @@ input:checked + .slider:before { .slider.round:before { border-radius: 50%; } + +#url-input-holder { + display: flex; + gap: 4px; + align-items: center; + width: 250px; + overflow: hidden; +} + +#url-input-holder input { + flex: 1 1 auto; + background-color: var(--color-background); + color: var(--color-foreground); + border: 1px solid var(--color-foreground); + border-radius: 6px; + font-size: 12px; + padding: 2px 4px; +} + +#url-reset { + background-color: transparent; + background-image: url("@fortawesome/fontawesome-free/svgs/solid/arrow-rotate-left.svg"); + color: var(--color-foreground); + border: none; + height: 12px; + width: 12px; + cursor: pointer; + display: none; +} +#url-reset.visible { + display: inline-block; +}