Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions docs/dashboard-server.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
26 changes: 13 additions & 13 deletions robotframework_dashboard/dashboard.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand All @@ -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"',
Expand All @@ -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)
Expand Down
78 changes: 53 additions & 25 deletions robotframework_dashboard/js/eventlisteners.js
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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 {
Expand All @@ -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();
Expand All @@ -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();
}

Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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();
Expand All @@ -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();
Expand Down Expand Up @@ -747,25 +775,25 @@ 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;
}

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
Expand Down
2 changes: 2 additions & 0 deletions robotframework_dashboard/js/variables/data.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -34,4 +35,5 @@ export {
use_logs,
server,
unified_dashboard_title,
no_auto_update
};
1 change: 1 addition & 0 deletions robotframework_dashboard/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions robotframework_dashboard/robotdashboard.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"""
Expand All @@ -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):
Expand Down Expand Up @@ -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(
Expand Down
2 changes: 0 additions & 2 deletions robotframework_dashboard/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
"<!-- placeholder_refresh_card_visibility -->",
"" if self.no_autoupdate else "hidden",
Expand All @@ -312,7 +311,6 @@ def _get_admin_page(self):
"<!-- placeholder_noautoupdate -->",
"true" if self.no_autoupdate else "false",
)

dependency_processor = DependencyProcessor(admin_page=True)
admin_html = admin_html.replace(
"<!-- placeholder_javascript -->", dependency_processor.get_js_block()
Expand Down
Loading
Loading