diff --git a/data/web/log/script.js b/data/web/log/script.js
new file mode 100644
index 0000000000..fea743117f
--- /dev/null
+++ b/data/web/log/script.js
@@ -0,0 +1,128 @@
+var levels = ['debug', 'info', 'warning', 'error', 'fatal'];
+var filterLevel = 0;
+
+function insertAfter(newNode, referenceNode) {
+ referenceNode.parentNode.insertBefore(newNode, referenceNode.nextSibling);
+}
+
+function remove(node) {
+ node.parentNode.removeChild(node);
+}
+
+function scrollTo(selector) {
+ var element = document.querySelector(selector);
+ if (element && element.scrollIntoView) {
+ element.scrollIntoView();
+ }
+}
+
+function getLevel(element) {
+ return levels.findIndex(function (levelString, level) {
+ var className = 'log-level-' + levelString;
+ if (element.classList.contains(className)) {
+ return true;
+ }
+ });
+}
+
+function pluralize(nItems, singular, plural) {
+ if (nItems === 1) {
+ return [1, singular].join(' ');
+ }
+ plural = plural || [singular, 's'].join('');
+ return [nItems.toString(), plural].join(' ');
+}
+
+function scrollLink(content, selector) {
+ var html = '' + content + "";
+ return html;
+}
+
+function getSummary() {
+ var nFatals = document.getElementsByClassName('log-level-fatal').length;
+ var nErrors = document.getElementsByClassName('log-level-error').length;
+ var nWarnings = document.getElementsByClassName('log-level-warning').length;
+
+ if (nFatals > 0) {
+ return '' + scrollLink(pluralize(nFatals, 'fatal error'), '.log-level-fatal') +
+ ', ' + scrollLink(pluralize(nErrors, 'other error'), '.log-level-error') + ' and ' + scrollLink(pluralize(nWarnings, 'warning'), '.log-level-warning') + '';
+ } else if (nErrors > 0) {
+ return '' + scrollLink(pluralize(nErrors, 'error'), '.log-level-error') + ' and ' +
+ scrollLink(pluralize(nWarnings, 'warning'), '.log-level-warning') + '';
+ } else if (nWarnings > 0) {
+ return '' + scrollLink(pluralize(nWarnings, 'warning'), '.log-level-warning') + '';
+ } else {
+ return 'No errors or warnings';
+ }
+}
+
+function updateFilter() {
+ var table = document.getElementsByTagName('table')[0];
+ table.classList.remove('hidden');
+
+ var noMessages = document.getElementById('no-messages');
+ if (noMessages) {
+ remove(noMessages);
+ }
+
+ var rows = document.getElementsByTagName('tr');
+ var nShown = 0;
+ [].forEach.call(rows, function (row) {
+ if (row.classList.length === 0) {
+ return;
+ }
+ var rowLevel = getLevel(row);
+ if (rowLevel >= filterLevel) {
+ row.classList.remove('hidden');
+ nShown++;
+ } else {
+ row.classList.add('hidden');
+ }
+ });
+ if (nShown === 0) {
+ var select = document.getElementsByTagName('select')[0];
+ var p = document.createElement("p");
+ p.id = "no-messages";
+ p.innerHTML = "There are no log messages with the level '" + levels[filterLevel] + "' or higher.";
+ insertAfter(p, select);
+ table.classList.add('hidden');
+ }
+}
+
+window.onload = function () {
+ var header = document.getElementsByTagName('h1')[0];
+ header.innerHTML = "OpenSpace Log";
+
+ var summary = document.createElement('p');
+ summary.innerHTML = getSummary();
+
+ var select = document.createElement('select');
+ select.id = 'filter-level-selector';
+
+ var selectLabel = document.createElement('label');
+ selectLabel.for = 'filter-level-selector';
+ selectLabel.innerHTML = "Lowest log level to show: ";
+
+ levels.forEach(function (level) {
+ var option = document.createElement('option');
+ option.value = level;
+ option.innerHTML = level;
+ select.appendChild(option);
+ });
+
+ insertAfter(summary, header);
+ insertAfter(selectLabel, summary);
+ insertAfter(select, selectLabel);
+
+ var preselectedIndex = levels.indexOf(window.location.hash.slice(1));
+ if (preselectedIndex >= 0) {
+ filterLevel = select.selectedIndex = preselectedIndex;
+ updateFilter();
+ }
+
+ select.onchange = function (evt) {
+ filterLevel = select.selectedIndex;
+ updateFilter();
+ window.location.hash = '#' + select.options[select.selectedIndex].value;
+ };
+}
\ No newline at end of file
diff --git a/data/web/log/style.css b/data/web/log/style.css
new file mode 100644
index 0000000000..3aa140b30e
--- /dev/null
+++ b/data/web/log/style.css
@@ -0,0 +1,93 @@
+table {
+ width: 100%;
+}
+
+td, th {
+ padding: 4px 15px 3px;
+}
+
+.log-date {
+ width: 8em;
+}
+
+h1 {
+ padding-left: 15px;
+}
+
+.hidden {
+ display: none;
+}
+
+p, label {
+ margin-left: 15px;
+ margin-bottom: 15px;
+}
+
+label {
+ margin-right: 0.5em;
+}
+
+.log-level-debug {
+ background-color: #bbdda9;
+ border-bottom: 1px solid #7bc142;
+ color: #265127;
+}
+.log-level-debug td:first-child {
+ border-left: 10px solid #7bc142;
+}
+
+thead, .log-level-info {
+ color: #333333;
+ background-color: #ffffff;
+ border-bottom: 1px solid #eaeaea;
+}
+.log-level-info td:first-child {
+ border-left: 10px solid #eaeaea;
+}
+
+thead th:first-child {
+ border-left 10px solid #fff;
+}
+
+.log-level-warning {
+ color: #3e3115;
+ background-color: #fef8c3;
+ border-bottom: 1px solid #efcd3f;
+}
+.log-level-warning td:first-child {
+ border-left: 10px solid #efcd3f;
+}
+
+.log-level-error {
+ color: #4c1315;
+ background-color: #fcdcdc;
+ border-bottom: 1px solid #d66767;
+}
+.log-level-error td:first-child {
+ border-left: 10px solid #d66767;
+}
+
+.log-level-fatal {
+ color: #220f21;
+ background-color: #ff7dc1;
+ border-bottom: 1px solid #b84398;
+}
+.log-level-fatal td:first-child {
+ border-left: 10px solid #b84398;
+}
+
+.summary {
+ padding: 5px;
+}
+.summary-warning {
+ background-color: #fef8c3;
+}
+.summary-error {
+ background-color: #fcdcdc;
+}
+.summary-fatal {
+ background-color: #ff7dc1;
+}
+.summary-ok {
+ background-color: #bbdda9;
+}
\ No newline at end of file
diff --git a/src/engine/logfactory.cpp b/src/engine/logfactory.cpp
index ffc145d95e..c1ec813a07 100644
--- a/src/engine/logfactory.cpp
+++ b/src/engine/logfactory.cpp
@@ -41,6 +41,10 @@ namespace {
const std::string valueHtmlLog = "html";
const std::string valueTextLog = "Text";
+
+ const std::string BootstrapPath = "${OPENSPACE_DATA}/web/common/bootstrap.min.css";
+ const std::string CssPath = "${OPENSPACE_DATA}/web/log/style.css";
+ const std::string JsPath = "${OPENSPACE_DATA}/web/log/script.js";
}
namespace openspace {
@@ -81,13 +85,18 @@ std::unique_ptr createLog(const ghoul::Dictionary& dictiona
using LogLevelStamping = ghoul::logging::Log::LogLevelStamping;
if (type == valueHtmlLog) {
+
+ std::vector cssFiles{absPath(BootstrapPath), absPath(CssPath)};
+ std::vector jsFiles{absPath(JsPath)};
+
return std::make_unique(
filename,
append ? Append::Yes : Append::No,
timeStamp ? TimeStamping::Yes : TimeStamping::No,
dateStamp ? DateStamping::Yes : DateStamping::No,
categoryStamp ? CategoryStamping::Yes : CategoryStamping::No,
- logLevelStamp ? LogLevelStamping::Yes : LogLevelStamping::No
+ logLevelStamp ? LogLevelStamping::Yes : LogLevelStamping::No,
+ cssFiles, jsFiles
);
}
else if (type == valueTextLog) {