diff --git a/meshcentral-config-schema.json b/meshcentral-config-schema.json index b43e4fdd..5c5bf358 100644 --- a/meshcentral-config-schema.json +++ b/meshcentral-config-schema.json @@ -1173,6 +1173,11 @@ "default": 2, "description": "Valid numbers are 1 and 2, changes the style of the login page and some secondary pages." }, + "showModernUIToggle": { + "type": "boolean", + "default": false, + "description": "When set to true, the user will be able to toggle between the modern and classic UI." + }, "title": { "type": "string", "default": "MeshCentral", diff --git a/public/styles/style-bootstrap.css b/public/styles/style-bootstrap.css index fb00e82c..cf6fe678 100644 --- a/public/styles/style-bootstrap.css +++ b/public/styles/style-bootstrap.css @@ -266,6 +266,16 @@ body { right: 3px; } +.textnewui { + color: white; + font-weight: bold; + padding-top: 5px; + cursor: pointer; + position: absolute; + right: 0; + margin-right: 10px; +} + .LogoffLinkColor { color:white; } @@ -2877,7 +2887,7 @@ nav .lbbuttonsel2 { width: 28px; } -.viewSelector3 { +.viewSelector3, .uiSelector7 { margin-left: 2px; margin-top: 2px; background: url(../images/views.png) -56px 0px; @@ -2977,6 +2987,13 @@ nav .lbbuttonsel2 { background-color: #AAA; } +.uiSelector_end { + width: 32px; + height: 32px; + float: left; + margin: 3px; +} + .uiSelectorSel { background-color: #BBB; opacity: 0.8; diff --git a/public/styles/style.css b/public/styles/style.css index b60847a2..4df3ac40 100644 --- a/public/styles/style.css +++ b/public/styles/style.css @@ -263,6 +263,16 @@ body { right: 3px; } +.textnewui { + color: white; + font-weight: bold; + padding-top: 5px; + cursor: pointer; + position: absolute; + right: 0; + margin-right: 10px; +} + .LogoffLinkColor { color:white; } @@ -2896,7 +2906,7 @@ a { width: 28px; } -.viewSelector3 { +.viewSelector3, .uiSelector7 { margin-left: 2px; margin-top: 2px; background: url(../images/views.png) -56px 0px; @@ -2996,6 +3006,13 @@ a { background-color: #AAA; } +.uiSelector_end { + width: 32px; + height: 32px; + float: left; + margin: 3px; +} + .uiSelectorSel { background-color: #BBB; opacity: 0.8; diff --git a/sample-config-advanced.json b/sample-config-advanced.json index 936b0875..13c53654 100644 --- a/sample-config-advanced.json +++ b/sample-config-advanced.json @@ -186,6 +186,7 @@ "domains": { "": { "_siteStyle": 2, + "_showModernUIToggle": true, "title": "MyServer", "title2": "Servername", "_titlePicture": "title-sample.png", diff --git a/views/default.handlebars b/views/default.handlebars index dc73f027..6dc33569 100644 --- a/views/default.handlebars +++ b/views/default.handlebars @@ -164,6 +164,9 @@

+
+ Try the new MeshCentral UI +
@@ -199,11 +202,13 @@
+
+
 
@@ -1723,6 +1728,9 @@ }); } + // Show the modern ui switcher + QV('textnewui', ((features2 & 0x40000000) == 0) ? false : true); + // Connect to the mesh server meshserver = MeshServerCreateControl(domainUrl); meshserver.onStateChanged = onStateChanged; @@ -2183,6 +2191,24 @@ QV('body', true); } + function saveUserInterfaceMode() { + var nUiViewMode = 2; + if (Q('ui1').checked) { nUiViewMode = 3; } + if (getstore('uiViewMode', 2) != nUiViewMode) { + putstore('uiViewMode', nUiViewMode); + reload(); + } + } + + function toggleBootstrapUIMode() { + if (xxdialogMode) return; + var uiViewMode = getstore('uiViewMode', 2); + var x = '
'; + x += '
'; + setDialogMode(2, "User Interface", 3, saveUserInterfaceMode, x); + QV('uiMenu', false); + } + function getNodeFromId(id) { if (nodes != null) { for (var i in nodes) { if (nodes[i]._id == id) return nodes[i]; } } return null; } function reload() { var x = window.location.href; diff --git a/views/default3.handlebars b/views/default3.handlebars index b34bf161..83fd67d5 100644 --- a/views/default3.handlebars +++ b/views/default3.handlebars @@ -153,6 +153,9 @@ +
+ Try the new MeshCentral UI +
+
+
+
+
 
@@ -2149,6 +2158,9 @@ }); } + // Show the modern ui switcher + QV('textnewui', ((features2 & 0x40000000) == 0) ? false : true); + // Connect to the mesh server meshserver = MeshServerCreateControl(domainUrl); meshserver.onStateChanged = onStateChanged; @@ -2622,6 +2634,31 @@ QV('body', true); } + function saveUserInterfaceMode() { + var nUiViewMode = 3; + if (Q('ui0').checked) { nUiViewMode = 2; } + if (getstore('uiViewMode', 3) != nUiViewMode) { + putstore('uiViewMode', nUiViewMode); + // Check if the URL includes 'sitestyle=' and remove it + var url = new URL(window.location.href); + if (url.searchParams.has('sitestyle')) { + url.searchParams.delete('sitestyle'); + window.history.replaceState({}, document.title, url.toString()); + } + reload(); + } + } + + function toggleBootstrapUIMode() { + if (xxdialogMode) return; + var uiViewMode = getstore('uiViewMode', 3); + var x = '
'; + x += '
'; + setModalContent('xxAddAgent', "User Interface", x); + showModal('xxAddAgentModal', 'idx_dlgOkButton', saveUserInterfaceMode); + QV('uiMenu', false); + } + function getNodeFromId(id) { if (nodes != null) { for (var i in nodes) { if (nodes[i]._id == id) return nodes[i]; } } return null; } function reload() { var x = window.location.href; diff --git a/webserver.js b/webserver.js index 879701d7..4d469d8d 100644 --- a/webserver.js +++ b/webserver.js @@ -3184,8 +3184,14 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF if (obj.parent.config.settings && obj.parent.config.settings.webrtcconfig && (typeof obj.parent.config.settings.webrtcconfig == 'object')) { webRtcConfig = encodeURIComponent(JSON.stringify(obj.parent.config.settings.webrtcconfig)).replace(/'/g, '%27'); } else if (args.webrtcconfig && (typeof args.webrtcconfig == 'object')) { webRtcConfig = encodeURIComponent(JSON.stringify(args.webrtcconfig)).replace(/'/g, '%27'); } + // Load default page style or new modern ui + var uiViewMode = 'default'; + var webstateJSON = JSON.parse(webstate); + if (webstateJSON && webstateJSON.uiViewMode == 3) { uiViewMode = 'default3'; } + if (domain.sitestyle == 3) { uiViewMode = 'default3'; } + if (req.query.sitestyle == 3) { uiViewMode = 'default3'; } // Refresh the session - render(dbGetFunc.req, dbGetFunc.res, getRenderPage(((domain.sitestyle == 3) || (req.query.sitestyle == 3) ? 'default3' : 'default'), dbGetFunc.req, domain), getRenderArgs({ + render(dbGetFunc.req, dbGetFunc.res, getRenderPage(uiViewMode, dbGetFunc.req, domain), getRenderArgs({ authCookie: authCookie, authRelayCookie: authRelayCookie, viewmode: viewmode, @@ -3335,6 +3341,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF if (domain.scrolltotop == true) { features2 += 0x08000000; } // Show the "Scroll to top" button if (domain.devicesearchbargroupname === true) { features2 += 0x10000000; } // Search bar will find by group name too if (((typeof domain.passwordrequirements != 'object') || (domain.passwordrequirements.duo2factor != false)) && (typeof domain.duo2factor == 'object') && (typeof domain.duo2factor.integrationkey == 'string') && (typeof domain.duo2factor.secretkey == 'string') && (typeof domain.duo2factor.apihostname == 'string')) { features2 += 0x20000000; } // using Duo for 2FA is allowed + if (domain.showmodernuitoggle == true) { features2 += 0x40000000; } // Indicates that the new UI should be shown return { features: features, features2: features2 }; } @@ -9045,7 +9052,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF } // Filter the user web site and only output state that we need to keep - const acceptableUserWebStateStrings = ['webPageStackMenu', 'notifications', 'deviceView', 'nightMode', 'webPageFullScreen', 'search', 'showRealNames', 'sort', 'deskAspectRatio', 'viewsize', 'DeskControl', 'uiMode', 'footerBar','loctag','theme','lastThemes']; + const acceptableUserWebStateStrings = ['webPageStackMenu', 'notifications', 'deviceView', 'nightMode', 'webPageFullScreen', 'search', 'showRealNames', 'sort', 'deskAspectRatio', 'viewsize', 'DeskControl', 'uiMode', 'footerBar','loctag','theme','lastThemes','uiViewMode']; const acceptableUserWebStateDesktopStrings = ['encoding', 'showfocus', 'showmouse', 'showcad', 'limitFrameRate', 'noMouseRotate', 'quality', 'scaling', 'agentencoding'] obj.filterUserWebState = function (state) { if (typeof state == 'string') { try { state = JSON.parse(state); } catch (ex) { return null; } }