diff --git a/docs/dashboard-server.md b/docs/dashboard-server.md index df40781..4a421c1 100644 --- a/docs/dashboard-server.md +++ b/docs/dashboard-server.md @@ -181,10 +181,10 @@ robotdashboard --server --noautoupdate When active: - API calls (`/add-outputs`, `/add-output-file`, `/remove-outputs`, `/add-log`, `/add-log-file`, `/remove-log`) return immediately after processing the data, without regenerating the dashboard. -- Two **Refresh** buttons appear in the admin page navbar: - - **Refresh Dashboard** — triggers dashboard HTML regeneration on demand by calling `POST /refresh-dashboard`. - - **Refresh Admin Page Tables** — reloads the runs and logs tables from the database without a full page reload. -- The `/refresh-dashboard` endpoint is also callable programmatically: +- A **Refresh Dashboard** button appears in the navbar of both the **dashboard page** and the **admin page** (hidden when `--noautoupdate` is not active): + - On the **dashboard page** — sends a `POST /refresh-dashboard` request, shows a spinner while the server processes the request, and on success displays a notification and automatically reloads the page after 3 seconds to reflect the regenerated dashboard. + - On the **admin page** — same `POST /refresh-dashboard` request and spinner behaviour, but the page is not reloaded; a success or error notification is shown instead. +- A **Refresh Admin Page Tables** button also appears in the **admin page** navbar (hidden when `--noautoupdate` is not active) — reloads the runs and logs tables by querying `/get-outputs` and `/get-logs` from the server, without a full page reload. A spinner is shown briefly and a success notification confirms the refresh. ::: tip When to use this Use `--noautoupdate` when: diff --git a/robotframework_dashboard/dashboard.py b/robotframework_dashboard/dashboard.py index 36036a1..dc42be4 100644 --- a/robotframework_dashboard/dashboard.py +++ b/robotframework_dashboard/dashboard.py @@ -27,6 +27,7 @@ def generate_dashboard( use_logs: bool, offline: bool, force_json_config: bool, + no_autoupdate: bool, ): """ Function that generates the dashboard by replacing all relevant placeholders. @@ -66,6 +67,18 @@ def generate_dashboard( dashboard_data = dashboard_data.replace( '"placeholder_amount"', str(quantity) ) + dashboard_data = dashboard_data.replace( + '"placeholder_server"', str(server).lower() + ) + dashboard_data = dashboard_data.replace( + '"placeholder_no_autoupdate"', str(no_autoupdate).lower() + ) + dashboard_data = dashboard_data.replace( + '"placeholder_force_json_config"', str(force_json_config).lower() + ) + dashboard_data = dashboard_data.replace( + '"placeholder_use_logs"', str(use_logs).lower() + ) if dashboard_title != "": dashboard_data = dashboard_data.replace( '"placeholder_dashboard_title"', dashboard_title @@ -75,10 +88,6 @@ def generate_dashboard( '"placeholder_dashboard_title"', f"Robot Framework Dashboard - {str(generation_datetime)[:-7]}", ) - if server: - dashboard_data = dashboard_data.replace('"placeholder_server"', "true") - else: - dashboard_data = dashboard_data.replace('"placeholder_server"', "false") if message_config: dashboard_data = dashboard_data.replace( '"placeholder_message_config"', @@ -89,15 +98,6 @@ def generate_dashboard( '"placeholder_json_config"', json_config, ) - dashboard_data = dashboard_data.replace('"placeholder_force_json_config"', str(force_json_config).lower()) - if use_logs: - dashboard_data = dashboard_data.replace( - '"placeholder_use_logs"', "true" - ) - else: - dashboard_data = dashboard_data.replace( - '"placeholder_use_logs"', "false" - ) # handle possible subdirectories path = Path(name_dashboard) diff --git a/robotframework_dashboard/js/eventlisteners.js b/robotframework_dashboard/js/eventlisteners.js index 3026c6d..2ed1e44 100644 --- a/robotframework_dashboard/js/eventlisteners.js +++ b/robotframework_dashboard/js/eventlisteners.js @@ -1,4 +1,4 @@ -import { runs, keywords, filteredAmount, filteredAmountDefault, server } from './variables/data.js'; +import { runs, keywords, filteredAmount, filteredAmountDefault, server, no_auto_update } from './variables/data.js'; import { settings } from "./variables/settings.js"; import { showingRunTags, @@ -150,6 +150,34 @@ function setup_filter_modal() { document.getElementById("amount").value = filteredAmount if (server) { document.getElementById("openDashboard").hidden = false + if (no_auto_update) { + document.getElementById("refreshDashboard").hidden = false + document.getElementById("refreshDashboard").addEventListener("click", function () { + document.getElementById("refreshDashboardSpinner").hidden = false + const xhr = new XMLHttpRequest(); + xhr.open("POST", "/refresh-dashboard"); + xhr.setRequestHeader("Content-Type", "application/json; charset=UTF-8"); + xhr.onload = () => { + document.getElementById("refreshDashboardSpinner").hidden = true + if (xhr.readyState == 4 && xhr.status == 200) { + const response = JSON.parse(xhr.responseText); + if (response.success == "1") { + console.log(response.console) + add_alert(`${response.message} Reloading dashboard in 3 seconds!`, "success") + setTimeout(() => { + location.reload(); + }, 3000); + } else { + add_alert(response.message, "danger") + console.log(response.console) + } + } else { + add_alert(`Error: ${xhr.status}, ${xhr.responseText}`, "danger") + } + }; + xhr.send(JSON.stringify({})); + }); + } } // fill the filters with default values setup_run_amount_filter(); @@ -351,11 +379,11 @@ function setup_settings_modal() { const element = document.getElementById(elementId); const isDarkMode = document.documentElement.classList.contains("dark-mode"); const themeMode = isDarkMode ? 'dark' : 'light'; - + // Check if user has custom colors for this theme mode const customColors = settings.theme_colors?.custom?.[themeMode]; const storedColor = customColors?.[colorKey]; - + if (storedColor) { element.value = to_hex_color(storedColor); } else { @@ -370,14 +398,14 @@ function setup_settings_modal() { const newColor = element.value; const isDarkMode = document.documentElement.classList.contains("dark-mode"); const themeMode = isDarkMode ? 'dark' : 'light'; - + if (!settings.theme_colors.custom) { settings.theme_colors.custom = { light: {}, dark: {} }; } if (!settings.theme_colors.custom[themeMode]) { settings.theme_colors.custom[themeMode] = {}; } - + settings.theme_colors.custom[themeMode][colorKey] = newColor; set_local_storage_item(`theme_colors.custom.${themeMode}.${colorKey}`, newColor); apply_theme_colors(); @@ -387,16 +415,16 @@ function setup_settings_modal() { const element = document.getElementById(elementId); const isDarkMode = document.documentElement.classList.contains("dark-mode"); const themeMode = isDarkMode ? 'dark' : 'light'; - + // Reset to default from settings const defaults = settings.theme_colors[themeMode]; element.value = to_hex_color(defaults[colorKey]); - + if (settings.theme_colors?.custom?.[themeMode]) { delete settings.theme_colors.custom[themeMode][colorKey]; set_local_storage_item('theme_colors.custom', settings.theme_colors.custom); } - + apply_theme_colors(); } @@ -636,8 +664,8 @@ function setup_sections_filters() { update_switch_local_storage("switch.suitePathsTestSection", settings.switch.suitePathsTestSection); update_graphs_with_loading( ["testStatisticsGraph", "testDurationGraph", "testDurationDeviationGraph", "testMessagesGraph", - "testMostFlakyGraph", "testRecentMostFlakyGraph", "testMostFailedGraph", - "testRecentMostFailedGraph", "testMostTimeConsumingGraph"], + "testMostFlakyGraph", "testRecentMostFlakyGraph", "testMostFailedGraph", + "testRecentMostFailedGraph", "testMostTimeConsumingGraph"], () => { setup_suites_in_test_select(); update_test_statistics_graph(); @@ -676,7 +704,7 @@ function setup_sections_filters() { document.getElementById("keywordSelect").addEventListener("change", () => { update_graphs_with_loading( ["keywordStatisticsGraph", "keywordTimesRunGraph", "keywordTotalDurationGraph", - "keywordAverageDurationGraph", "keywordMinDurationGraph", "keywordMaxDurationGraph"], + "keywordAverageDurationGraph", "keywordMinDurationGraph", "keywordMaxDurationGraph"], () => { update_keyword_statistics_graph(); update_keyword_times_run_graph(); @@ -693,8 +721,8 @@ function setup_sections_filters() { update_switch_local_storage("switch.useLibraryNames", settings.switch.useLibraryNames); update_graphs_with_loading( ["keywordStatisticsGraph", "keywordTimesRunGraph", "keywordTotalDurationGraph", - "keywordAverageDurationGraph", "keywordMinDurationGraph", "keywordMaxDurationGraph", - "keywordMostFailedGraph", "keywordMostTimeConsumingGraph", "keywordMostUsedGraph"], + "keywordAverageDurationGraph", "keywordMinDurationGraph", "keywordMaxDurationGraph", + "keywordMostFailedGraph", "keywordMostTimeConsumingGraph", "keywordMostUsedGraph"], () => { setup_keywords_in_select(); update_keyword_statistics_graph(); @@ -747,10 +775,10 @@ function save_section_filter_values() { const suiteFolder = document.getElementById("suiteFolder"); if (suiteFolder) saved.suiteFolder = suiteFolder.innerText; ["suiteSelectSuites", "suiteSelectTests", "testSelect", "testTagsSelect", "keywordSelect", - "compareRun1", "compareRun2", "compareRun3", "compareRun4"].forEach(id => { - const el = document.getElementById(id); - if (el) saved[id] = el.value; - }); + "compareRun1", "compareRun2", "compareRun3", "compareRun4"].forEach(id => { + const el = document.getElementById(id); + if (el) saved[id] = el.value; + }); return saved; } @@ -758,14 +786,14 @@ function restore_section_filter_values(saved) { const suiteFolder = document.getElementById("suiteFolder"); if (suiteFolder && saved.suiteFolder !== undefined) suiteFolder.innerText = saved.suiteFolder; ["suiteSelectSuites", "suiteSelectTests", "testSelect", "testTagsSelect", "keywordSelect", - "compareRun1", "compareRun2", "compareRun3", "compareRun4"].forEach(id => { - const el = document.getElementById(id); - if (el && saved[id] !== undefined) { - // Only restore if the saved value still exists as an option - const optionExists = Array.from(el.options).some(opt => opt.value === saved[id]); - if (optionExists) el.value = saved[id]; - } - }); + "compareRun1", "compareRun2", "compareRun3", "compareRun4"].forEach(id => { + const el = document.getElementById(id); + if (el && saved[id] !== undefined) { + // Only restore if the saved value still exists as an option + const optionExists = Array.from(el.options).some(opt => opt.value === saved[id]); + if (optionExists) el.value = saved[id]; + } + }); } // function to setup eventlisteners for changing the graph view buttons diff --git a/robotframework_dashboard/js/variables/data.js b/robotframework_dashboard/js/variables/data.js index 292212a..15f132d 100644 --- a/robotframework_dashboard/js/variables/data.js +++ b/robotframework_dashboard/js/variables/data.js @@ -19,6 +19,7 @@ var filteredAmount = "placeholder_amount" var filteredAmountDefault = 0 const use_logs = "placeholder_use_logs" const server = "placeholder_server" +const no_auto_update = "placeholder_no_autoupdate" if (!message_config.includes("placeholder_message_config")) { message_config = JSON.parse(message_config) } export { @@ -34,4 +35,5 @@ export { use_logs, server, unified_dashboard_title, + no_auto_update }; \ No newline at end of file diff --git a/robotframework_dashboard/main.py b/robotframework_dashboard/main.py index df125c1..fadd2e1 100644 --- a/robotframework_dashboard/main.py +++ b/robotframework_dashboard/main.py @@ -36,6 +36,7 @@ def main(): arguments.force_json_config, arguments.project_version, arguments.no_vacuum, + arguments.no_autoupdate, arguments.timezone, ) # If arguments.start_server is provided override some required args diff --git a/robotframework_dashboard/robotdashboard.py b/robotframework_dashboard/robotdashboard.py index 51a7568..f2e7111 100644 --- a/robotframework_dashboard/robotdashboard.py +++ b/robotframework_dashboard/robotdashboard.py @@ -30,6 +30,7 @@ def __init__( force_json_config: bool, project_version: str, no_vacuum: bool, + no_autoupdate: bool, timezone: str = "", ): """Sets the parameters provided in the command line""" @@ -50,6 +51,7 @@ def __init__( self.database = None self.project_version = project_version self.no_vacuum = no_vacuum + self.no_autoupdate = no_autoupdate self.timezone = timezone def initialize_database(self, suppress=True): @@ -242,6 +244,7 @@ def create_dashboard(self): self.use_logs, self.offline_dependencies, self.force_json_config, + self.no_autoupdate, ) end = time() console += self._print_console( diff --git a/robotframework_dashboard/server.py b/robotframework_dashboard/server.py index 314b1c8..d7c2e3a 100644 --- a/robotframework_dashboard/server.py +++ b/robotframework_dashboard/server.py @@ -303,7 +303,6 @@ def __init__( def _get_admin_page(self): admin_file = join(dirname(abspath(__file__)), "./templates", "admin.html") admin_html = open(admin_file, "r").read() - admin_html = admin_html.replace('"placeholder_version"', __version__) admin_html = admin_html.replace( "", "" if self.no_autoupdate else "hidden", @@ -312,7 +311,6 @@ def _get_admin_page(self): "", "true" if self.no_autoupdate else "false", ) - dependency_processor = DependencyProcessor(admin_page=True) admin_html = admin_html.replace( "", dependency_processor.get_js_block() diff --git a/robotframework_dashboard/templates/dashboard.html b/robotframework_dashboard/templates/dashboard.html index f1c8c1b..3c9ffde 100644 --- a/robotframework_dashboard/templates/dashboard.html +++ b/robotframework_dashboard/templates/dashboard.html @@ -30,6 +30,11 @@ Compare Tables Admin +