feat(bugReport): add service status check and update UI if sendReport server is down

This commit is contained in:
MasterAcnolo
2026-09-29 21:30:20 +02:00
parent 1e099f6bb6
commit 1c0dd32732
5 changed files with 114 additions and 10 deletions

View File

@@ -19,7 +19,7 @@ const config = require("../config");
const { validateDownloadPath, getDefaultDownloadPath } = require("./pathValidator"); const { validateDownloadPath, getDefaultDownloadPath } = require("./pathValidator");
const { userThemesPath } = require("../server/helpers/path.helpers"); const { userThemesPath } = require("../server/helpers/path.helpers");
const { createSystemTray, destroyTray } = require("./tray"); const { createSystemTray, destroyTray } = require("./tray");
const { sendReport } = require("./sendReport"); const {sendReport, checkReportService, getReportServiceStatus} = require("./sendReport");
const { isUpdateAvailable, downloadUpdate, installUpdate } = require("./autoUpdater"); const { isUpdateAvailable, downloadUpdate, installUpdate } = require("./autoUpdater");
/** /**
@@ -210,6 +210,11 @@ function registerIpcHandlers(getMainWindow) {
return await sendReport(params); return await sendReport(params);
}); });
ipcMain.handle("get-report-service-status", async () => {
await checkReportService();
return getReportServiceStatus();
});
ipcMain.handle("is-update-available", () => { ipcMain.handle("is-update-available", () => {
return isUpdateAvailable(); return isUpdateAvailable();
}); });

View File

@@ -2,6 +2,58 @@ const fs = require("fs");
const path = require("path"); const path = require("path");
const {logger, logDir} = require("../server/logger"); const {logger, logDir} = require("../server/logger");
const REPORT_URL = "https://freedom-loader-report.acnolo.fr";
const REPORT_SERVICE_TIMEOUT_MS = 5000;
let reportServiceStatus = {
status: "checking",
message: "Checking bug report service..."
};
let reportServiceCheck = null;
async function checkReportService() {
if (reportServiceCheck) {
return reportServiceCheck;
}
reportServiceCheck = (async () => {
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), REPORT_SERVICE_TIMEOUT_MS);
try {
const response = await fetch(REPORT_URL, {
method: "GET",
signal: controller.signal
});
if (response.status >= 500) {
throw new Error(`Server responded with status ${response.status}`);
}
reportServiceStatus = {
status: "online",
message: "Bug report service is available."
};
logger.info(`Bug report service is reachable (status ${response.status}).`);
} catch (err) {
reportServiceStatus = {
status: "offline",
message: "Bug report service is unavailable. Reports may fail to send."
};
logger.warn(`Bug report service is unreachable: ${err.message}`);
} finally {
clearTimeout(timeout);
}
return reportServiceStatus;
})();
return reportServiceCheck;
}
function getReportServiceStatus() {
return reportServiceStatus;
}
async function sendReport(params) { async function sendReport(params) {
try { try {
const {title, description, includeLogs} = params; const {title, description, includeLogs} = params;
@@ -35,13 +87,7 @@ async function sendReport(params) {
} }
} }
const reportUrl = process.env.BUG_REPORT_URL; const response = await fetch(REPORT_URL, {
if (!reportUrl) {
throw new Error("Bug report service is not configured. Missing BUG_REPORT_URL on backend.");
}
const response = await fetch(reportUrl, {
method: "POST", method: "POST",
body: formData body: formData
}); });
@@ -59,4 +105,4 @@ async function sendReport(params) {
} }
} }
module.exports = {sendReport}; module.exports = {sendReport, checkReportService, getReportServiceStatus};

View File

@@ -77,6 +77,7 @@ const { createSplashWindow, closeSplashWindow, setSplashProgress } = require("./
const { userThemesPath, initUserThemes, validateBinaries, defaultDownloadFolder } = require("./server/helpers/path.helpers"); const { userThemesPath, initUserThemes, validateBinaries, defaultDownloadFolder } = require("./server/helpers/path.helpers");
const {createSystemTray, destroyTray} = require("./app/tray"); const {createSystemTray, destroyTray} = require("./app/tray");
const { stopServer } = require("./server/server"); const { stopServer } = require("./server/server");
const {checkReportService} = require("./app/sendReport");
/** /**
* Expose .env in process.env * Expose .env in process.env
@@ -137,6 +138,9 @@ app.on("second-instance", () => {
app.whenReady().then(async () => { app.whenReady().then(async () => {
logSessionStart(logDir, defaultDownloadFolder); logSessionStart(logDir, defaultDownloadFolder);
checkReportService().catch((err) => {
logger.warn(`Bug report service startup check failed: ${err.message}`);
});
createSplashWindow(); createSplashWindow();

View File

@@ -214,4 +214,9 @@ contextBridge.exposeInMainWorld("topbarAPI", {
* @returns {Promise<any>} * @returns {Promise<any>}
*/ */
sendReport: (params) => ipcRenderer.invoke("send-report", params), sendReport: (params) => ipcRenderer.invoke("send-report", params),
/**
* Returns the startup health status of the bug report service.
*/
getReportServiceStatus: () => ipcRenderer.invoke("get-report-service-status"),
}); });

View File

@@ -29,9 +29,13 @@ class BugReportModal {
<option value="no" style="background:#2a2a2a !important; color:#ffffff !important;">No</option> <option value="no" style="background:#2a2a2a !important; color:#ffffff !important;">No</option>
</select> </select>
</div> </div>
<div id="bugReportServiceStatus" role="status" aria-live="polite" style="display:none !important; align-items:center !important; gap:8px !important; color:#aaaaaa !important; font-size:0.8rem !important;">
<span id="bugReportServiceStatusDot" style="width:8px !important; height:8px !important; border-radius:50% !important; background:#f59e0b !important; flex-shrink:0 !important;"></span>
<span id="bugReportServiceStatusText">Checking bug report service...</span>
</div>
<div style="display:flex !important; justify-content:flex-end !important; gap:8px !important; margin-top:8px !important;"> <div style="display:flex !important; justify-content:flex-end !important; gap:8px !important; margin-top:8px !important;">
<button type="button" id="bugCancelBtn" style="background:#333333 !important; border:none !important; color:#cccccc !important; padding:8px 14px !important; border-radius:6px !important; cursor:pointer !important;">Cancel</button> <button type="button" id="bugCancelBtn" style="background:#333333 !important; border:none !important; color:#cccccc !important; padding:8px 14px !important; border-radius:6px !important; cursor:pointer !important;">Cancel</button>
<button type="submit" style="background:#4f46e5 !important; border:none !important; color:#ffffff !important; padding:8px 16px !important; border-radius:6px !important; cursor:pointer !important; font-weight:500 !important;">Send</button> <button type="submit" id="bugSendBtn" disabled style="background:#4f46e5 !important; border:none !important; color:#ffffff !important; padding:8px 16px !important; border-radius:6px !important; cursor:not-allowed !important; opacity:0.5 !important; font-weight:500 !important;">Send</button>
</div> </div>
</form> </form>
</div> </div>
@@ -46,6 +50,7 @@ class BugReportModal {
bindEvents() { bindEvents() {
const titleInput = this.modal.querySelector("#bugTitle"); const titleInput = this.modal.querySelector("#bugTitle");
const descInput = this.modal.querySelector("#bugDescription"); const descInput = this.modal.querySelector("#bugDescription");
const sendButton = this.modal.querySelector("#bugSendBtn");
titleInput.addEventListener("input", () => { titleInput.addEventListener("input", () => {
localStorage.setItem("draft_bug_title", titleInput.value); localStorage.setItem("draft_bug_title", titleInput.value);
@@ -67,6 +72,10 @@ class BugReportModal {
this.form.addEventListener("submit", async (e) => { this.form.addEventListener("submit", async (e) => {
e.preventDefault(); e.preventDefault();
if (sendButton.disabled) {
return;
}
const data = { const data = {
title: titleInput.value, title: titleInput.value,
description: descInput.value, description: descInput.value,
@@ -107,11 +116,46 @@ class BugReportModal {
open() { open() {
this.loadState(); this.loadState();
this.modal.style.display = "flex"; this.modal.style.display = "flex";
this.updateServiceStatus();
} }
close() { close() {
this.modal.style.display = "none"; this.modal.style.display = "none";
} }
async updateServiceStatus() {
const statusText = this.modal.querySelector("#bugReportServiceStatusText");
const statusDot = this.modal.querySelector("#bugReportServiceStatusDot");
const sendButton = this.modal.querySelector("#bugSendBtn");
sendButton.disabled = true;
sendButton.style.setProperty("cursor", "not-allowed", "important");
sendButton.style.setProperty("opacity", "0.5", "important");
try {
const serviceStatus = await window.topbarAPI.getReportServiceStatus();
const serviceAvailable = serviceStatus.status === "online";
const statusVisible = !serviceAvailable;
statusText.textContent = serviceStatus.message;
statusDot.style.background = "#ef4444";
statusText.style.color = "#fca5a5";
this.modal.querySelector("#bugReportServiceStatus").style.setProperty(
"display",
statusVisible ? "flex" : "none",
"important"
);
sendButton.disabled = !serviceAvailable;
sendButton.style.setProperty("cursor", serviceAvailable ? "pointer" : "not-allowed", "important");
sendButton.style.setProperty("opacity", serviceAvailable ? "1" : "0.5", "important");
} catch (err) {
statusText.textContent = "Unable to check bug report service.";
statusDot.style.background = "#ef4444";
statusText.style.color = "#fca5a5";
this.modal.querySelector("#bugReportServiceStatus").style.setProperty("display", "flex", "important");
sendButton.disabled = true;
window.electronAPI.logError("Failed to retrieve bug report service status:", err.message);
}
}
} }
window.bugModal = new BugReportModal(); window.bugModal = new BugReportModal();