From 162566867f4cd1bb90325e1a8c5495d3316e4141 Mon Sep 17 00:00:00 2001 From: Ekhorn Date: Fri, 6 Nov 2026 12:41:30 +0100 Subject: [PATCH] Fix blank page when a startup request fails A network error on the manager app config fetch left OrApp off the page, and an info response that isn't JSON left Manager.init waiting forever. Fall back to the default config, and reject on a response that isn't JSON. --- ui/app/manager/src/index.ts | 4 +++ ui/app/manager/test/startup.test.ts | 51 +++++++++++++++++++++++++++++ ui/component/core/src/index.ts | 6 +++- 3 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 ui/app/manager/test/startup.test.ts diff --git a/ui/app/manager/src/index.ts b/ui/app/manager/src/index.ts index 0d610418ab..d4f22fe2b5 100644 --- a/ui/app/manager/src/index.ts +++ b/ui/app/manager/src/index.ts @@ -171,6 +171,10 @@ fetch(configURL) return { ...appConfig }; }) + .catch((e) => { + console.error("Failed to load the manager app config, using the defaults", e); + return {} as ManagerAppConfig; + }) .then((appConfig: ManagerAppConfig) => { // Set locales and load path if (!appConfig.manager) { diff --git a/ui/app/manager/test/startup.test.ts b/ui/app/manager/test/startup.test.ts new file mode 100644 index 0000000000..6f17b65244 --- /dev/null +++ b/ui/app/manager/test/startup.test.ts @@ -0,0 +1,51 @@ +/* + * Copyright 2026, OpenRemote Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +import { expect } from "@openremote/test"; +import { test } from "./fixtures/manager.js"; + +// What a reverse proxy answers while the manager restarts +const serviceUnavailable = { + status: 503, + contentType: "text/html", + body: "

503 Service Unavailable

", +}; + +/** + * @given The manager app config can't be fetched + * @when The manager is opened + * @then The login page is shown + */ +test("should show the login page when the app config can't be fetched", async ({ page, manager }) => { + await page.route("**/api/master/configuration/manager", (route) => route.abort("connectionreset")); + await manager.goToRealmStartPage("master"); + // The app was never added to the page, which stayed blank + await expect(page.getByRole("textbox", { name: "Username or email" })).toBeVisible(); +}); + +/** + * @given The manager info endpoint answers with an HTML error page + * @when The manager is opened + * @then The login page is shown + */ +test("should show the login page when the manager info is not JSON", async ({ page, manager }) => { + await page.route("**/api/master/info", (route) => route.fulfill(serviceUnavailable)); + await manager.goToRealmStartPage("master"); + // Initialization waited forever for the info, which left the page blank + await expect(page.getByRole("textbox", { name: "Username or email" })).toBeVisible(); +}); diff --git a/ui/component/core/src/index.ts b/ui/component/core/src/index.ts index 435dd09eb6..f48e7d730f 100644 --- a/ui/component/core/src/index.ts +++ b/ui/component/core/src/index.ts @@ -457,7 +457,11 @@ export class Manager implements EventProviderFactory { const json = await new Promise((resolve, reject) => { const oReq = new XMLHttpRequest(); oReq.addEventListener("load", () => { - resolve(JSON.parse(oReq.responseText)); + try { + resolve(JSON.parse(oReq.responseText)); + } catch (e) { + reject(e); + } }); oReq.addEventListener("error", () => { reject(new Error("Failed to contact the manager"));