fix: say the server is down and keep retrying instead of a database error (#1637)

This commit is contained in:
LukeGus committed 2026-10-15 21:40:07 -05:00
1 parent c3f6db69e9
commit 6084ed4d3c
6 files changed
+32 -5

No files matched your search

+2
View File
@@ -30,6 +30,8 @@
- Synced plugins signed only by a key the server sent are no longer turned on by themselves - Synced plugins signed only by a key the server sent are no longer turned on by themselves
- Two connects to the same host at once could overwrite each other's connection - Two connects to the same host at once could overwrite each other's connection
- Indonesian uses the same technical terms everywhere - Indonesian uses the same technical terms everywhere
- When the server was down or still starting, the login page said it could not connect to the database and stopped trying. It now says the server is not responding and continues on its own once the server is back
- nginx answered 404 instead of 502 while the backend was down
- The Docker server crashed and restarted every few hours in ssh2's native crypto on Node 24 - The Docker server crashed and restarted every few hours in ssh2's native crypto on Node 24
## 26.10.1 ## 26.10.1
+4 -1
View File
@@ -476,9 +476,12 @@ http {
} }
error_page 500 502 503 504 /50x.html; error_page 500 502 503 504 /50x.html;
# Only nginx's own errors land here (the backend is down or too slow).
# There is no 50x page, so answer directly instead of a 404.
location = /50x.html { location = /50x.html {
root /app/html;
internal; internal;
default_type application/json;
return 502 '{"error":"The Termix server is not responding"}';
} }
} }
} }
+4 -1
View File
@@ -445,9 +445,12 @@ http {
} }
error_page 500 502 503 504 /50x.html; error_page 500 502 503 504 /50x.html;
# Only nginx's own errors land here (the backend is down or too slow).
# There is no 50x page, so answer directly instead of a 404.
location = /50x.html { location = /50x.html {
root /app/html;
internal; internal;
default_type application/json;
return 502 '{"error":"The Termix server is not responding"}';
} }
} }
} }
+2 -1
View File
@@ -434,10 +434,11 @@ export function Auth({ onLogin }: AuthProps) {
}) })
.catch(() => { .catch(() => {
if (cancelled) return; if (cancelled) return;
// Past the first few tries, show the error but keep checking, so
// the page moves on by itself once the server is back.
if (attemptNumber >= maxAttempts) { if (attemptNumber >= maxAttempts) {
setDbConnectionFailed(true); setDbConnectionFailed(true);
setDbHealthChecking(false); setDbHealthChecking(false);
return;
} }
const delay = Math.min(500 * 2 ** attemptNumber, 5000); const delay = Math.min(500 * 2 ** attemptNumber, 5000);
retryTimer = setTimeout(() => { retryTimer = setTimeout(() => {
+2 -2
View File
@@ -1099,7 +1099,7 @@
"acceptNewKey": "Accept New Key & Continue" "acceptNewKey": "Accept New Key & Continue"
}, },
"errors": { "errors": {
"databaseConnection": "Could not connect to the database", "databaseConnection": "The Termix server is not responding",
"embeddedServerFailed": "Termix's local server could not start", "embeddedServerFailed": "Termix's local server could not start",
"unknownError": "Unknown error", "unknownError": "Unknown error",
"syncedHostMissing": "The synced host does not exist on the linked server", "syncedHostMissing": "The synced host does not exist on the linked server",
@@ -1126,7 +1126,7 @@
"messages": { "messages": {
"registrationDisabled": "You have no Termix account yet. Ask an admin to turn on Auto-create external accounts.", "registrationDisabled": "You have no Termix account yet. Ask an admin to turn on Auto-create external accounts.",
"userNotAllowed": "Your account is not authorized to register. Please contact an administrator.", "userNotAllowed": "Your account is not authorized to register. Please contact an administrator.",
"databaseConnectionFailed": "Failed to connect to the database server", "databaseConnectionFailed": "It may still be starting, or it stopped. This page keeps trying and continues on its own once the server is back.",
"embeddedServerPortInUse": "Port {{port}} is already in use by another program. This is usually a Termix Docker container or a leftover Termix process from a previous session. Close it, then restart Termix.", "embeddedServerPortInUse": "Port {{port}} is already in use by another program. This is usually a Termix Docker container or a leftover Termix process from a previous session. Close it, then restart Termix.",
"embeddedServerPortInUseUnknownPort": "One of the ports Termix needs is already in use by another program. Close whatever is using it, then restart Termix.", "embeddedServerPortInUseUnknownPort": "One of the ports Termix needs is already in use by another program. Close whatever is using it, then restart Termix.",
"embeddedServerOldDatabase": "Your local data is from Termix 2.8 or older and has to pass through 2.9 first. Install Termix 2.9.1, open it once so it can move your data, then update to this version again.", "embeddedServerOldDatabase": "Your local data is from Termix 2.8 or older and has to pass through 2.9 first. Install Termix 2.9.1, open it once so it can move your data, then update to this version again.",
+18
View File
@@ -143,6 +143,24 @@ describe("Auth local desktop flow", () => {
expect(mainAxios.getSetupRequired).not.toHaveBeenCalled(); expect(mainAxios.getSetupRequired).not.toHaveBeenCalled();
}); });
it("keeps checking after the server is unreachable and recovers on its own", async () => {
vi.useFakeTimers({ shouldAdvanceTime: true });
try {
mainAxios.getSetupRequired.mockRejectedValue(new Error("502"));
renderAuth();
await vi.advanceTimersByTimeAsync(20000);
expect(screen.getByText("errors.databaseConnection")).toBeTruthy();
mainAxios.getSetupRequired.mockResolvedValue({ setup_required: false });
await vi.advanceTimersByTimeAsync(6000);
await waitFor(() =>
expect(screen.queryByText("errors.databaseConnection")).toBeNull(),
);
} finally {
vi.useRealTimers();
}
});
it("keeps register visible for normal web auth when registration is allowed", async () => { it("keeps register visible for normal web auth when registration is allowed", async () => {
mainAxios.isElectron.mockReturnValue(false); mainAxios.isElectron.mockReturnValue(false);