1
0
Fork 0
mirror of https://github.com/Ylianst/MeshCentral.git synced 2025-02-12 11:01:52 +00:00

UI Toggle between Classic and Modern (#6763)

* [ENH] Add toggle switch for new MeshCentral UI in settings and top header of the classic UI

* [ENH] Add toggle for new MeshCentral UI in settings and in top headbar of the modern UI

* add showModernUIToggle and store uiviewmode in db

---------

Co-authored-by: kambereBr <brunokambere@gmail.com>
This commit is contained in:
Simon Smith 2025-02-09 19:41:47 +00:00 committed by GitHub
parent 1310c57397
commit 5734bcc33a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 114 additions and 4 deletions

View file

@ -1173,6 +1173,11 @@
"default": 2, "default": 2,
"description": "Valid numbers are 1 and 2, changes the style of the login page and some secondary pages." "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": { "title": {
"type": "string", "type": "string",
"default": "MeshCentral", "default": "MeshCentral",

View file

@ -266,6 +266,16 @@ body {
right: 3px; right: 3px;
} }
.textnewui {
color: white;
font-weight: bold;
padding-top: 5px;
cursor: pointer;
position: absolute;
right: 0;
margin-right: 10px;
}
.LogoffLinkColor { .LogoffLinkColor {
color:white; color:white;
} }
@ -2877,7 +2887,7 @@ nav .lbbuttonsel2 {
width: 28px; width: 28px;
} }
.viewSelector3 { .viewSelector3, .uiSelector7 {
margin-left: 2px; margin-left: 2px;
margin-top: 2px; margin-top: 2px;
background: url(../images/views.png) -56px 0px; background: url(../images/views.png) -56px 0px;
@ -2977,6 +2987,13 @@ nav .lbbuttonsel2 {
background-color: #AAA; background-color: #AAA;
} }
.uiSelector_end {
width: 32px;
height: 32px;
float: left;
margin: 3px;
}
.uiSelectorSel { .uiSelectorSel {
background-color: #BBB; background-color: #BBB;
opacity: 0.8; opacity: 0.8;

View file

@ -263,6 +263,16 @@ body {
right: 3px; right: 3px;
} }
.textnewui {
color: white;
font-weight: bold;
padding-top: 5px;
cursor: pointer;
position: absolute;
right: 0;
margin-right: 10px;
}
.LogoffLinkColor { .LogoffLinkColor {
color:white; color:white;
} }
@ -2896,7 +2906,7 @@ a {
width: 28px; width: 28px;
} }
.viewSelector3 { .viewSelector3, .uiSelector7 {
margin-left: 2px; margin-left: 2px;
margin-top: 2px; margin-top: 2px;
background: url(../images/views.png) -56px 0px; background: url(../images/views.png) -56px 0px;
@ -2996,6 +3006,13 @@ a {
background-color: #AAA; background-color: #AAA;
} }
.uiSelector_end {
width: 32px;
height: 32px;
float: left;
margin: 3px;
}
.uiSelectorSel { .uiSelectorSel {
background-color: #BBB; background-color: #BBB;
opacity: 0.8; opacity: 0.8;

View file

@ -186,6 +186,7 @@
"domains": { "domains": {
"": { "": {
"_siteStyle": 2, "_siteStyle": 2,
"_showModernUIToggle": true,
"title": "MyServer", "title": "MyServer",
"title2": "Servername", "title2": "Servername",
"_titlePicture": "title-sample.png", "_titlePicture": "title-sample.png",

View file

@ -164,6 +164,9 @@
<div id=notificationCount onclick="clickNotificationIcon()" class="unselectable" style="display: none;" title="Click to view current notifications">0</div> <div id=notificationCount onclick="clickNotificationIcon()" class="unselectable" style="display: none;" title="Click to view current notifications">0</div>
</div> </div>
<p id="logoutControl"><span id=logoutControlSpan class="logoncontrolspan"></span><span id=idleTimeoutNotify style="color:yellow"></span></p> <p id="logoutControl"><span id=logoutControlSpan class="logoncontrolspan"></span><span id=idleTimeoutNotify style="color:yellow"></span></p>
<div class=textnewui id=textnewui onmouseup=toggleBootstrapUIMode() onkeypress="if (event.key=='Enter') { toggleBootstrapUIMode(); }">
<b>Try the new MeshCentral UI</b>
</div>
</div> </div>
<div id="page_leftbar"> <div id="page_leftbar">
<div style="height:16px"></div> <div style="height:16px"></div>
@ -199,11 +202,13 @@
<div tabindex=0 id=uiViewButton1 class=uiSelector onclick=userInterfaceSelectMenu(1) title="Left bar interface" onkeypress="if (event.key == 'Enter') userInterfaceSelectMenu(1)"><div class="uiSelector1"></div></div> <div tabindex=0 id=uiViewButton1 class=uiSelector onclick=userInterfaceSelectMenu(1) title="Left bar interface" onkeypress="if (event.key == 'Enter') userInterfaceSelectMenu(1)"><div class="uiSelector1"></div></div>
<div tabindex=0 id=uiViewButton2 class=uiSelector onclick=userInterfaceSelectMenu(2) title="Top bar interface" onkeypress="if (event.key == 'Enter') userInterfaceSelectMenu(2)"><div class="uiSelector2"></div></div> <div tabindex=0 id=uiViewButton2 class=uiSelector onclick=userInterfaceSelectMenu(2) title="Top bar interface" onkeypress="if (event.key == 'Enter') userInterfaceSelectMenu(2)"><div class="uiSelector2"></div></div>
<div tabindex=0 id=uiViewButton3 class=uiSelector onclick=userInterfaceSelectMenu(3) title="Fixed width interface" onkeypress="if (event.key == 'Enter') userInterfaceSelectMenu(3)"><div class="uiSelector3"></div></div> <div tabindex=0 id=uiViewButton3 class=uiSelector onclick=userInterfaceSelectMenu(3) title="Fixed width interface" onkeypress="if (event.key == 'Enter') userInterfaceSelectMenu(3)"><div class="uiSelector3"></div></div>
<div tabindex=0 id=uiViewButton7 class=uiSelector onclick=toggleBootstrapUIMode() title="Toggle Bootstrap UI" onkeypress="if (event.key == 'Enter') toggleBootstrapUIMode()"><div class="uiSelector7"></div></div>
</td> </td>
<td> <td>
<div tabindex=0 id=uiViewButton6 class=uiSelector onclick="showNotes(false)" title="Personal Notes" onkeypress="if (event.key == 'Enter') showNotes(false)"><div class="uiSelector6"></div></div> <div tabindex=0 id=uiViewButton6 class=uiSelector onclick="showNotes(false)" title="Personal Notes" onkeypress="if (event.key == 'Enter') showNotes(false)"><div class="uiSelector6"></div></div>
<div tabindex=0 id=uiViewButton4 class=uiSelector onclick=toggleNightMode() title="Toggle night mode" onkeypress="if (event.key == 'Enter') toggleNightMode()"><div class="uiSelector4"></div></div> <div tabindex=0 id=uiViewButton4 class=uiSelector onclick=toggleNightMode() title="Toggle night mode" onkeypress="if (event.key == 'Enter') toggleNightMode()"><div class="uiSelector4"></div></div>
<div tabindex=0 id=uiViewButton5 class=uiSelector onclick=toggleFooterBarMode() title="Toggle footer bar" onkeypress="if (event.key == 'Enter') toggleFooterBarMode()"><div class="uiSelector5"></div></div> <div tabindex=0 id=uiViewButton5 class=uiSelector onclick=toggleFooterBarMode() title="Toggle footer bar" onkeypress="if (event.key == 'Enter') toggleFooterBarMode()"><div class="uiSelector5"></div></div>
<div class=uiSelector_end>&nbsp;</div>
</td> </td>
</tr> </tr>
</table> </table>
@ -1723,6 +1728,9 @@
}); });
} }
// Show the modern ui switcher
QV('textnewui', ((features2 & 0x40000000) == 0) ? false : true);
// Connect to the mesh server // Connect to the mesh server
meshserver = MeshServerCreateControl(domainUrl); meshserver = MeshServerCreateControl(domainUrl);
meshserver.onStateChanged = onStateChanged; meshserver.onStateChanged = onStateChanged;
@ -2183,6 +2191,24 @@
QV('body', true); 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 = '<input type=radio id=ui0 name=uiradio value=2 ' + ((uiViewMode == 2)?'checked':'') + '><label for=ui0>' + "Classic" + '</label><br>';
x += '<input type=radio id=ui1 name=uiradio value=3 ' + ((uiViewMode == 3)?'checked':'') + '><label for=ui1>' + "Modern" + '</label><br>';
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 getNodeFromId(id) { if (nodes != null) { for (var i in nodes) { if (nodes[i]._id == id) return nodes[i]; } } return null; }
function reload() { function reload() {
var x = window.location.href; var x = window.location.href;

View file

@ -153,6 +153,9 @@
<img id="topMenuIcon" class=noselect <img id="topMenuIcon" class=noselect
style="position:absolute;right:0;top:10px;color:#c8c8c8;font-size:44px;margin-right:8px;cursor:pointer;display:none" style="position:absolute;right:0;top:10px;color:#c8c8c8;font-size:44px;margin-right:8px;cursor:pointer;display:none"
onclick=topMenu() src="/images/3bars-30.png" width=30 height=30 /> onclick=topMenu() src="/images/3bars-30.png" width=30 height=30 />
<div class=textnewui id=textnewui onmouseup=toggleBootstrapUIMode() onkeypress="if (event.key=='Enter') { toggleBootstrapUIMode(); }">
<b>Try the new MeshCentral UI</b>
</div>
</div> </div>
<div class="sidebar flex-column" id="page_leftbar"> <div class="sidebar flex-column" id="page_leftbar">
<div style="height:24px"></div> <div style="height:24px"></div>
@ -190,6 +193,11 @@
onkeypress="if (event.key == 'Enter') userInterfaceSelectMenu(3)"> onkeypress="if (event.key == 'Enter') userInterfaceSelectMenu(3)">
<div class="uiSelector3"></div> <div class="uiSelector3"></div>
</div> </div>
<div tabindex=0 id=uiViewButton7 class=uiSelector
onclick=toggleBootstrapUIMode() title="Toggle Modern UI"
onkeypress="if (event.key == 'Enter') toggleBootstrapUIMode()">
<div class="uiSelector7"></div>
</div>
</td> </td>
<td> <td>
<div tabindex=0 id=uiViewButton6 class=uiSelector onclick="showNotes(false)" <div tabindex=0 id=uiViewButton6 class=uiSelector onclick="showNotes(false)"
@ -207,6 +215,7 @@
onkeypress="if (event.key == 'Enter') toggleFooterBarMode()"> onkeypress="if (event.key == 'Enter') toggleFooterBarMode()">
<div class="uiSelector5"></div> <div class="uiSelector5"></div>
</div> </div>
<div class=uiSelector_end>&nbsp;</div>
</td> </td>
</tr> </tr>
</table> </table>
@ -2149,6 +2158,9 @@
}); });
} }
// Show the modern ui switcher
QV('textnewui', ((features2 & 0x40000000) == 0) ? false : true);
// Connect to the mesh server // Connect to the mesh server
meshserver = MeshServerCreateControl(domainUrl); meshserver = MeshServerCreateControl(domainUrl);
meshserver.onStateChanged = onStateChanged; meshserver.onStateChanged = onStateChanged;
@ -2622,6 +2634,31 @@
QV('body', true); 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 = '<div class=form-check><input type=radio class=form-check-input id=ui0 name=uiradio value=2 ' + ((uiViewMode == 2)?'checked':'') + '><label class=form-check-label for=ui0>' + "Classic" + '</label></div>';
x += '<div class=form-check><input type=radio class=form-check-input id=ui1 name=uiradio value=3 ' + ((uiViewMode == 3)?'checked':'') + '><label class=form-check-label for=ui1>' + "Modern" + '</label></div>';
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 getNodeFromId(id) { if (nodes != null) { for (var i in nodes) { if (nodes[i]._id == id) return nodes[i]; } } return null; }
function reload() { function reload() {
var x = window.location.href; var x = window.location.href;

View file

@ -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'); } 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'); } 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 // 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, authCookie: authCookie,
authRelayCookie: authRelayCookie, authRelayCookie: authRelayCookie,
viewmode: viewmode, 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.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 (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 (((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 }; 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 // 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'] const acceptableUserWebStateDesktopStrings = ['encoding', 'showfocus', 'showmouse', 'showcad', 'limitFrameRate', 'noMouseRotate', 'quality', 'scaling', 'agentencoding']
obj.filterUserWebState = function (state) { obj.filterUserWebState = function (state) {
if (typeof state == 'string') { try { state = JSON.parse(state); } catch (ex) { return null; } } if (typeof state == 'string') { try { state = JSON.parse(state); } catch (ex) { return null; } }