Add advanced custom CSS/JS file support per template (#7361)
Introduces a new 'customFiles' array in the config schema for granular domain-level CSS and JavaScript customization. Updates webserver logic to inject custom CSS/JS tags based on template scope, replacing static custom.css/custom.js references in all Handlebars views. Also updates meshctrl.js to support the new config property.
TheDevRyan committed
Oct 24, 2025 at 11:53 UTC
c7d1c0e18f290749f5a080d309d4c97f27f59df6
27 files changed
+176
-21
meshcentral-config-schema.json
+35
@@ -1987,6 +1987,41 @@
1987
"customUI": {
1988
"type": "object"
1989
},
1990
+ "customFiles": {
1991
+ "type": "array",
1992
+ "description": "Advanced customization system allowing multiple CSS and JavaScript file configurations per domain. Each configuration can target specific templates using the 'scope' property, enabling granular control over where custom files are loaded. All custom files are loaded after the default custom.css and custom.js files.",
1993
+ "items": {
1994
+ "type": "object",
1995
+ "properties": {
1996
+ "name": {
1997
+ "type": "string",
1998
+ "description": "Unique name for this custom files configuration. Used for identification and debugging purposes."
1999
+ },
2000
+ "css": {
2001
+ "type": "array",
2002
+ "items": {
2003
+ "type": "string"
2004
+ },
2005
+ "description": "Ordered list of custom CSS files to load. Files are loaded in the specified order after the default custom.css. Place files in the meshcentral-data/public/styles/ directory. Example: [\"theme.css\", \"overrides.css\", \"mobile.css\"]"
2006
+ },
2007
+ "js": {
2008
+ "type": "array",
2009
+ "items": {
2010
+ "type": "string"
2011
+ },
2012
+ "description": "Ordered list of custom JavaScript files to load. Files are loaded in the specified order after the default custom.js. Place files in the meshcentral-data/public/scripts/ directory. Example: [\"analytics.js\", \"custom-functions.js\", \"ui-enhancements.js\"]"
2013
+ },
2014
+ "scope": {
2015
+ "type": "array",
2016
+ "items": {
2017
+ "type": "string"
2018
+ },
2019
+ "description": "List of template names where these custom files should be loaded. If not specified or empty, files will NOT be loaded anywhere. Use 'all' to load on all templates. Available template names: all, default, default3, default-mobile, login, login2, login-mobile, messenger, message, message2, invite, agentinvite, player, xterm, mstsc, ssh, sharing, sharing-mobile, download, download2, terms, terms-mobile, error404, error4042, error404-mobile. Example: [\"default3\", \"login2\", \"player\"] or [\"all\"]"
2020
+ }
2021
+ },
2022
+ "required": ["name"]
2023
+ }
2024
+ },
2025
"consentMessages": {
2026
"type": "object",
2027
"description": "This section is used to customize user consent prompts, these show up when asking if a remote session is allowed or not.",
meshctrl.js
+2
-2
@@ -1195,8 +1195,8 @@ function displayConfigHelp() {
1195
}
1196
1197
function performConfigOperations(args) {
1198
- var domainValues = ['title', 'title2', 'titlepicture', 'trustedcert', 'welcomepicture', 'welcometext', 'userquota', 'meshquota', 'newaccounts', 'usernameisemail', 'newaccountemaildomains', 'newaccountspass', 'newaccountsrights', 'geolocation', 'lockagentdownload', 'userconsentflags', 'Usersessionidletimeout', 'auth', 'ldapoptions', 'ldapusername', 'ldapuserbinarykey', 'ldapuseremail', 'footer', 'certurl', 'loginKey', 'userallowedip', 'agentallowedip', 'agentnoproxy', 'agentconfig', 'orphanagentuser', 'httpheaders', 'yubikey', 'passwordrequirements', 'limits', 'amtacmactivation', 'redirects', 'sessionrecording', 'hide'];
1199
- var domainObjectValues = ['ldapoptions', 'httpheaders', 'yubikey', 'passwordrequirements', 'limits', 'amtacmactivation', 'redirects', 'sessionrecording'];
1198
+ var domainValues = ['title', 'title2', 'titlepicture', 'trustedcert', 'welcomepicture', 'welcometext', 'userquota', 'meshquota', 'newaccounts', 'usernameisemail', 'newaccountemaildomains', 'newaccountspass', 'newaccountsrights', 'geolocation', 'lockagentdownload', 'userconsentflags', 'Usersessionidletimeout', 'auth', 'ldapoptions', 'ldapusername', 'ldapuserbinarykey', 'ldapuseremail', 'footer', 'certurl', 'loginKey', 'userallowedip', 'agentallowedip', 'agentnoproxy', 'agentconfig', 'orphanagentuser', 'httpheaders', 'yubikey', 'passwordrequirements', 'limits', 'amtacmactivation', 'redirects', 'sessionrecording', 'hide', 'customFiles'];
1199
+ var domainObjectValues = ['ldapoptions', 'httpheaders', 'yubikey', 'passwordrequirements', 'limits', 'amtacmactivation', 'redirects', 'sessionrecording', 'customFiles'];
1200
var domainArrayValues = ['newaccountemaildomains', 'newaccountsrights', 'loginkey', 'agentconfig'];
1201
var configChange = false;
1202
var fs = require('fs');
views/agentinvite.handlebars
+2
-2
@@ -8,11 +8,11 @@
8
<meta name="format-detection" content="telephone=no" />
9
<meta name="robots" content="noindex,nofollow" />
10
<link type="text/css" href="styles/style.css" media="screen" rel="stylesheet" title="CSS" />
11
- <link type="text/css" href="styles/custom.css" media="screen" rel="stylesheet" title="CSS" />
11
+ {{{customCSSTags}}}
12
<link rel="apple-touch-icon" href="/favicon-303x303.png" />
13
<script type="text/javascript" src="scripts/common-0.0.1{{min}}.js"></script>
14
<script type="text/javascript" src="scripts/qrcode.min.js"></script>
15
- <script type="text/javascript" src="scripts/custom.js"></script>
15
+ {{{customJSTags}}}
16
<title>Agent Installation</title>
17
<style>
18
.tab {
views/default-mobile.handlebars
+2
@@ -13,6 +13,7 @@
13
<link rel="icon" type="image/png" sizes="32x32" href="{{{domainurl}}}favicon-32x32.png">
14
<link rel="apple-touch-icon" href="/favicon-303x303.png" />
15
<link type="text/css" href="styles/xterm.css" media="screen" rel="stylesheet" title="CSS" />
16
+ {{{customCSSTags}}}
17
<meta name="apple-mobile-web-app-capable" content="yes">
18
<meta name="apple-mobile-web-app-status-bar-style" content="#ffffff">
19
<meta name="apple-mobile-web-app-title" content="{{{title}}}">
@@ -30,6 +31,7 @@
31
<script type="text/javascript" src="scripts/zlib-adler32{{{min}}}.js"></script>
32
<script type="text/javascript" src="scripts/zlib-crc32{{{min}}}.js"></script>
33
<script keeplink=1 type="text/javascript" src="scripts/filesaver.min.js"></script>
34
+ {{{customJSTags}}}
35
<meta name="msapplication-TileColor" content="#00aba9">
36
<meta name="theme-color" content="#ffffff">
37
<title>{{{title}}}</title>
views/default.handlebars
+2
-2
@@ -14,7 +14,7 @@
14
<link type="text/css" href="styles/ol3-contextmenu.min.css" media="screen" rel="stylesheet" title="CSS" />
15
<link type="text/css" href="styles/xterm.css" media="screen" rel="stylesheet" title="CSS" />
16
<link type="text/css" href="styles/flatpickr.min.css" media="screen" rel="stylesheet" title="CSS">
17
- <link type="text/css" href="styles/custom.css" media="screen" rel="stylesheet" title="CSS" />
17
+ {{{customCSSTags}}}
18
<link rel="apple-touch-icon" href="/favicon-303x303.png" />
19
<script type="text/javascript" src="scripts/common-0.0.1{{{min}}}.js"></script>
20
<script type="text/javascript" src="scripts/meshcentral{{{min}}}.js"></script>
@@ -50,7 +50,7 @@
50
<script keeplink=1 type="text/javascript" src="scripts/ol3-contextmenu{{{min}}}.js"></script>
51
<script keeplink=1 type="text/javascript" src="scripts/purify{{{min}}}.js"></script>
52
<script keeplink=1 type="text/javascript" src="scripts/marked{{{min}}}.js"></script>
53
- <script type="text/javascript" src="scripts/custom.js"></script>
53
+ {{{customJSTags}}}
54
<title>{{{title}}}</title>
55
</head>
56
<body id="body" oncontextmenu="handleContextMenu(event)" style="display:none;min-width:495px" onload="if (typeof(startup) !== 'undefined') startup();">
views/default3.handlebars
+2
-2
@@ -18,7 +18,7 @@
18
<link id="theme-stylesheet" href="styles/bootstrap{{{min}}}.css" rel="stylesheet" title="CSS">
19
<link href="styles/select2.min.css" rel="stylesheet" title="CSS">
20
<link href="styles/select2-bootstrap-5-theme.min.css" rel="stylesheet" title="CSS">
21
- <link type="text/css" href="styles/custom.css" media="screen" rel="stylesheet" title="CSS" />
21
+ {{{customCSSTags}}}
22
<link rel="apple-touch-icon" href="/favicon-303x303.png" />
23
<script type="text/javascript" src="scripts/common-0.0.1{{{min}}}.js"></script>
24
<script type="text/javascript" src="scripts/meshcentral{{{min}}}.js"></script>
@@ -59,7 +59,7 @@
59
<script keeplink=1 type="text/javascript" src="scripts/ol3-contextmenu{{{min}}}.js"></script>
60
<script keeplink=1 type="text/javascript" src="scripts/purify{{{min}}}.js"></script>
61
<script keeplink=1 type="text/javascript" src="scripts/marked{{{min}}}.js"></script>
62
- <script type="text/javascript" src="scripts/custom.js"></script>
62
+ {{{customJSTags}}}
63
<title>{{{title}}}</title>
64
</head>
65
views/download.handlebars
+2
@@ -8,7 +8,9 @@
8
<meta name=format-detection content="telephone=no" />
9
<meta name="robots" content="noindex,nofollow">
10
<link type=text/css href="/styles/style.css" media="screen" rel="stylesheet" title="CSS" />
11
+ {{{customCSSTags}}}
12
<link rel="apple-touch-icon" href="/favicon-303x303.png" />
13
+ {{{customJSTags}}}
14
<title>{{{title1}}} - Download</title>
15
</head>
16
<body>
views/download2.handlebars
+2
@@ -8,7 +8,9 @@
8
<meta name=format-detection content="telephone=no" />
9
<meta name="robots" content="noindex,nofollow">
10
<link type=text/css href="/styles/style.css" media="screen" rel="stylesheet" title="CSS" />
11
+ {{{customCSSTags}}}
12
<link rel="apple-touch-icon" href="/favicon-303x303.png" />
13
+ {{{customJSTags}}}
14
<title>{{{title1}}} - Download</title>
15
<style>
16
body {
views/error404-mobile.handlebars
+2
@@ -7,7 +7,9 @@
7
<meta name="apple-mobile-web-app-capable" content="yes" />
8
<meta name="format-detection" content="telephone=no" />
9
<meta name="robots" content="noindex,nofollow" />
10
+ {{{customCSSTags}}}
11
<link rel="apple-touch-icon" href="/favicon-303x303.png" />
12
+ {{{customJSTags}}}
13
<title>{{{title}}}</title>
14
<style nonce="{{{cspNonce}}}">
15
body {
views/error404.handlebars
+2
@@ -8,7 +8,9 @@
8
<meta name="format-detection" content="telephone=no" />
9
<meta name="robots" content="noindex,nofollow" />
10
<link type="text/css" href="/styles/style.css" media="screen" rel="stylesheet" title="CSS" />
11
+ {{{customCSSTags}}}
12
<link rel="apple-touch-icon" href="/favicon-303x303.png" />
13
+ {{{customJSTags}}}
14
<title>{{{title}}}</title>
15
<style nonce="{{{cspNonce}}}">
16
#xbody {
views/error4042.handlebars
+2
@@ -8,7 +8,9 @@
8
<meta name=format-detection content="telephone=no" />
9
<meta name="robots" content="noindex,nofollow">
10
<link type=text/css href="/styles/style.css" media="screen" rel="stylesheet" title="CSS" />
11
+ {{{customCSSTags}}}
12
<link rel="apple-touch-icon" href="/favicon-303x303.png" />
13
+ {{{customJSTags}}}
14
<title>{{{title1}}} - Download</title>
15
<style nonce="{{{cspNonce}}}">
16
body {
views/invite.handlebars
+2
-2
@@ -8,10 +8,10 @@
8
<meta name="format-detection" content="telephone=no" />
9
<meta name="robots" content="noindex,nofollow" />
10
<link type="text/css" href="styles/style.css" media="screen" rel="stylesheet" title="CSS" />
11
- <link type="text/css" href="styles/custom.css" media="screen" rel="stylesheet" title="CSS" />
11
+ {{{customCSSTags}}}
12
<link rel="apple-touch-icon" href="/favicon-303x303.png" />
13
<script type="text/javascript" src="scripts/common-0.0.1{{min}}.js"></script>
14
- <script type="text/javascript" src="scripts/custom.js"></script>
14
+ {{{customJSTags}}}
15
<title>Agent Installation</title>
16
<style>
17
.tab {
views/login-mobile.handlebars
+2
@@ -12,10 +12,12 @@
12
<link rel="icon" type="image/png" sizes="16x16" href="{{{domainurl}}}favicon-16x16.png">
13
<link rel="icon" type="image/png" sizes="32x32" href="{{{domainurl}}}favicon-32x32.png">
14
<link rel="apple-touch-icon" href="/favicon-303x303.png" />
15
+ {{{customCSSTags}}}
16
<meta name="apple-mobile-web-app-capable" content="yes">
17
<meta name="apple-mobile-web-app-status-bar-style" content="#ffffff">
18
<meta name="apple-mobile-web-app-title" content="{{{title}}}">
19
<script type="text/javascript" src="scripts/common-0.0.1{{min}}.js"></script>
20
+ {{{customJSTags}}}
21
<script type="text/javascript" src="scripts/u2f-api{{min}}.js"></script>
22
<meta name="msapplication-TileColor" content="#00aba9">
23
<meta name="theme-color" content="#ffffff">
views/login.handlebars
+2
@@ -10,8 +10,10 @@
10
<link rel="manifest" href="{{{domainurl}}}manifest.json">
11
<link rel="shortcut icon" type="image/x-icon" href="{{{domainurl}}}favicon.ico" />
12
<link keeplink=1 type="text/css" href="styles/style.css" media="screen" rel="stylesheet" title="CSS" />
13
+ {{{customCSSTags}}}
14
<link rel="apple-touch-icon" href="/favicon-303x303.png" />
15
<script type="text/javascript" src="scripts/common-0.0.1{{min}}.js"></script>
16
+ {{{customJSTags}}}
17
<script keeplink=1 type="text/javascript" src="scripts/u2f-api{{min}}.js"></script>
18
<title>{{{title}}} - Login</title>
19
</head>
views/login2.handlebars
+2
-2
@@ -10,11 +10,11 @@
10
<link rel="manifest" href="{{{domainurl}}}manifest.json">
11
<link rel="shortcut icon" type="image/x-icon" href="{{{domainurl}}}favicon.ico" />
12
<link keeplink=1 type="text/css" href="styles/style.css" media="screen" rel="stylesheet" title="CSS" />
13
- <link type="text/css" href="styles/custom.css" media="screen" rel="stylesheet" title="CSS" />
13
+ {{{customCSSTags}}}
14
<link rel="apple-touch-icon" href="/favicon-303x303.png" />
15
<script type="text/javascript" src="scripts/common-0.0.1{{min}}.js"></script>
16
<script keeplink=1 type="text/javascript" src="scripts/u2f-api{{min}}.js"></script>
17
- <script type="text/javascript" src="scripts/custom.js"></script>
17
+ {{{customJSTags}}}
18
<title>{{{title}}} - Login</title>
19
<style>
20
#body {
views/message.handlebars
+2
@@ -8,8 +8,10 @@
8
<meta name=format-detection content="telephone=no" />
9
<meta name="robots" content="noindex,nofollow">
10
<link type=text/css href="/styles/style.css" media="screen" rel="stylesheet" title="CSS" />
11
+ {{{customCSSTags}}}
12
<link rel="apple-touch-icon" href="/favicon-303x303.png" />
13
<script type="text/javascript" src="scripts/common-0.0.1{{min}}.js"></script>
14
+ {{{customJSTags}}}
15
<title id="topTitle">{{{title}}}</title>
16
</head>
17
<body>
views/message2.handlebars
+2
-2
@@ -8,10 +8,10 @@
8
<meta name=format-detection content="telephone=no" />
9
<meta name="robots" content="noindex,nofollow">
10
<link type=text/css href="/styles/style.css" media="screen" rel="stylesheet" title="CSS" />
11
- <link type="text/css" href="styles/custom.css" media="screen" rel="stylesheet" title="CSS" />
11
+ {{{customCSSTags}}}
12
<link rel="apple-touch-icon" href="/favicon-303x303.png" />
13
<script type="text/javascript" src="scripts/common-0.0.1{{min}}.js"></script>
14
- <script type="text/javascript" src="scripts/custom.js"></script>
14
+ {{{customJSTags}}}
15
<title id="topTitle">{{{title1}}}</title>
16
<style>
17
body {
views/messenger.handlebars
+2
-2
@@ -9,11 +9,11 @@
9
<meta name="robots" content="noindex,nofollow">
10
<link type="text/css" href="styles/style.css" media="screen" rel="stylesheet" title="CSS" />
11
<link type="text/css" href="styles/messenger.css" media="screen" rel="stylesheet" title="CSS" />
12
- <link type="text/css" href="styles/custom.css" media="screen" rel="stylesheet" title="CSS" />
12
+ {{{customCSSTags}}}
13
<link rel="apple-touch-icon" href="/favicon-303x303.png" />
14
<script type="text/javascript" src="scripts/common-0.0.1{{min}}.js"></script>
15
<script type="text/javascript" src="scripts/filesaver.min.js"></script>
16
- <script type="text/javascript" src="scripts/custom.js"></script>
16
+ {{{customJSTags}}}
17
</head>
18
<body style="font-family:Arial,Helvetica,sans-serif">
19
<div id="xtop" style="position:absolute;left:0;right:0;top:0;height:38px;background-color:#036;color:#EEE;box-shadow:3px 3px 10px gray">
views/mstsc.handlebars
+2
@@ -7,12 +7,14 @@
7
<meta name="robots" content="noindex,nofollow">
8
<link rel="icon" href="/favicon.ico">
9
<link rel="apple-touch-icon" href="/favicon-303x303.png" />
10
+ {{{customCSSTags}}}
11
<title>RDP</title>
12
<script type="text/javascript" src="mstsc/mstsc.js"></script>
13
<script type="text/javascript" src="mstsc/keyboard.js"></script>
14
<script type="text/javascript" src="mstsc/rle.js"></script>
15
<script type="text/javascript" src="mstsc/client.js"></script>
16
<script type="text/javascript" src="mstsc/canvas.js"></script>
17
+ {{{customJSTags}}}
18
<style>
19
:focus {
20
outline: 0;
views/player.handlebars
+2
-2
@@ -9,7 +9,7 @@
9
<meta name="robots" content="noindex,nofollow">
10
<link type="text/css" href="styles/style.css" media="screen" rel="stylesheet" title="CSS" />
11
<link type="text/css" href="styles/xterm.css" media="screen" rel="stylesheet" title="CSS" />
12
- <link type="text/css" href="styles/custom.css" media="screen" rel="stylesheet" title="CSS" />
12
+ {{{customCSSTags}}}
13
<link rel="apple-touch-icon" href="/favicon-303x303.png" />
14
<script type="text/javascript" src="scripts/common-0.0.1{{min}}.js"></script>
15
<script type="text/javascript" src="scripts/agent-desktop-0.0.2{{min}}.js"></script>
@@ -23,7 +23,7 @@
23
<script type="text/javascript" src="scripts/xterm-addon-fit-min.js"></script>
24
<script keeplink=1 type="text/javascript" src="scripts/webm-writer.js"></script>
25
<script keeplink=1 type="text/javascript" src="scripts/filesaver.min.js"></script>
26
- <script type="text/javascript" src="scripts/custom.js"></script>
26
+ {{{customJSTags}}}
27
</head>
28
<body style="overflow:hidden;background-color:black">
29
<div id=p11 class="noselect" style="overflow:hidden">
views/sharing-mobile.handlebars
+2
@@ -13,6 +13,7 @@
13
<link rel="icon" type="image/png" sizes="32x32" href="{{{domainurl}}}favicon-32x32.png">
14
<link rel="apple-touch-icon" href="/favicon-303x303.png" />
15
<link type="text/css" href="styles/xterm.css" media="screen" rel="stylesheet" title="CSS" />
16
+ {{{customCSSTags}}}
17
<meta name="apple-mobile-web-app-capable" content="yes">
18
<meta name="apple-mobile-web-app-status-bar-style" content="#ffffff">
19
<meta name="apple-mobile-web-app-title" content="{{{title}}}">
@@ -30,6 +31,7 @@
31
<script type="text/javascript" src="scripts/zlib-adler32{{{min}}}.js"></script>
32
<script type="text/javascript" src="scripts/zlib-crc32{{{min}}}.js"></script>
33
<script keeplink=1 type="text/javascript" src="scripts/filesaver.min.js"></script>
34
+ {{{customJSTags}}}
35
<meta name="msapplication-TileColor" content="#00aba9">
36
<meta name="theme-color" content="#ffffff">
37
<title>{{{title}}}</title>
views/sharing.handlebars
+2
-2
@@ -10,7 +10,7 @@
10
<link type="text/css" href="styles/style.css" media="screen" rel="stylesheet" title="CSS" />
11
<link type="text/css" href="styles/style-sharing.css" media="screen" rel="stylesheet" title="CSS" />
12
<link type="text/css" href="styles/xterm.css" media="screen" rel="stylesheet" title="CSS" />
13
- <link type="text/css" href="styles/custom.css" media="screen" rel="stylesheet" title="CSS" />
13
+ {{{customCSSTags}}}
14
<link rel="apple-touch-icon" href="/favicon-303x303.png" />
15
<script type="text/javascript" src="scripts/common-0.0.1{{min}}.js"></script>
16
<script type="text/javascript" src="scripts/amt-redir-ws-0.1.0{{{min}}}.js"></script>
@@ -28,7 +28,7 @@
28
<script type="text/javascript" src="scripts/xterm{{{min}}}.js"></script>
29
<script type="text/javascript" src="scripts/xterm-addon-fit{{{min}}}.js"></script>
30
<script keeplink=1 type="text/javascript" src="scripts/filesaver.min.js"></script>
31
- <script type="text/javascript" src="scripts/custom.js"></script>
31
+ {{{customJSTags}}}
32
<title>{{{title}}}</title>
33
</head>
34
<body style="overflow:hidden;background-color:black">
views/ssh.handlebars
+2
@@ -9,6 +9,7 @@
9
<meta name="robots" content="noindex,nofollow">
10
<link type="text/css" href="styles/style.css" media="screen" rel="stylesheet" title="CSS" />
11
<link type="text/css" href="styles/xterm.css" media="screen" rel="stylesheet" title="CSS" />
12
+ {{{customCSSTags}}}
13
<link rel="apple-touch-icon" href="/favicon-303x303.png" />
14
<script type="text/javascript" src="scripts/common-0.0.1{{min}}.js"></script>
15
<script type="text/javascript" src="scripts/meshcentral{{min}}.js"></script>
@@ -16,6 +17,7 @@
17
<script type="text/javascript" src="scripts/agent-redir-rtc-0.1.0{{min}}.js"></script>
18
<script type="text/javascript" src="scripts/xterm-min.js"></script>
19
<script type="text/javascript" src="scripts/xterm-addon-fit-min.js"></script>
20
+ {{{customJSTags}}}
21
<title>SSH</title>
22
</head>
23
<body style="overflow:hidden;background-color:black" onload="start()">
views/terms-mobile.handlebars
+2
@@ -8,6 +8,8 @@
8
<meta name="format-detection" content="telephone=no" />
9
<meta name="robots" content="noindex,nofollow" />
10
<link rel="apple-touch-icon" href="/favicon-303x303.png" />
11
+ {{{customCSSTags}}}
12
+ {{{customJSTags}}}
13
<title>{{{title}}} - Terms of use</title>
14
<style type="text/css">
15
:focus {
views/terms.handlebars
+2
@@ -8,8 +8,10 @@
8
<meta name="format-detection" content="telephone=no" />
9
<meta name="robots" content="noindex,nofollow" />
10
<link type="text/css" href="styles/style.css" media="screen" rel="stylesheet" title="CSS" />
11
+ {{{customCSSTags}}}
12
<link rel="apple-touch-icon" href="/favicon-303x303.png" />
13
<script type="text/javascript" src="scripts/common-0.0.1{{min}}.js"></script>
14
+ {{{customJSTags}}}
15
<title>{{{title}}} - Terms of use</title>
16
</head>
17
<body id="body" onload="if (typeof(startup) !== 'undefined') startup();" style="display:none;overflow:hidden">
views/xterm.handlebars
+2
@@ -9,6 +9,7 @@
9
<meta name="robots" content="noindex,nofollow">
10
<link type="text/css" href="styles/style.css" media="screen" rel="stylesheet" title="CSS" />
11
<link type="text/css" href="styles/xterm.css" media="screen" rel="stylesheet" title="CSS" />
12
+ {{{customCSSTags}}}
13
<link rel="apple-touch-icon" href="/favicon-303x303.png" />
14
<script type="text/javascript" src="scripts/common-0.0.1{{min}}.js"></script>
15
<script type="text/javascript" src="scripts/meshcentral{{min}}.js"></script>
@@ -16,6 +17,7 @@
17
<script type="text/javascript" src="scripts/agent-redir-rtc-0.1.0{{min}}.js"></script>
18
<script type="text/javascript" src="scripts/xterm-min.js"></script>
19
<script type="text/javascript" src="scripts/xterm-addon-fit-min.js"></script>
20
+ {{{customJSTags}}}
21
<title>{{{name}}}</title>
22
</head>
23
<body style="overflow:hidden;background-color:black" oncontextmenu="handleContextMenu(event)">
webserver.js
+91
-1
@@ -3188,6 +3188,14 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
3188
var customui = '';
3189
if (domain.customui != null) { customui = encodeURIComponent(JSON.stringify(domain.customui)); }
3190
3191
+ // Custom files (CSS and JS)
3192
+ var customFiles = '';
3193
+ if (domain.customFiles != null) {
3194
+ customFiles = encodeURIComponent(JSON.stringify(domain.customFiles));
3195
+ } else if (domain.customfiles != null) {
3196
+ customFiles = encodeURIComponent(JSON.stringify(domain.customfiles));
3197
+ }
3198
+
3199
// Server features
3200
var serverFeatures = 255;
3201
if (domain.myserver === false) { serverFeatures = 0; } // 64 = Show "My Server" tab
@@ -3239,6 +3247,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
3247
mpspass: args.mpspass,
3248
passRequirements: passRequirements,
3249
customui: customui,
3250
+ customFiles: customFiles,
3251
webcerthash: Buffer.from(obj.webCertificateFullHashs[domain.id], 'binary').toString('base64').replace(/\+/g, '@').replace(/\//g, '$'),
3252
footer: (domain.footer == null) ? '' : obj.common.replacePlaceholders(domain.footer, {
3253
'serverversion': obj.parent.currentVer,
@@ -3258,7 +3267,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
3267
showNotesPanel: (domain.shownotespanel ? 'true' : 'false'),
3268
userSessionsSort: (domain.usersessionssort ? domain.usersessionssort : 'SessionId'),
3269
webrtcconfig: webRtcConfig
3261
- }, dbGetFunc.req, domain), user);
3270
+ }, dbGetFunc.req, domain, uiViewMode), user);
3271
}
3272
xdbGetFunc.req = req;
3273
xdbGetFunc.res = res;
@@ -3455,6 +3464,14 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
3464
var customui = '';
3465
if (domain.customui != null) { customui = encodeURIComponent(JSON.stringify(domain.customui)); }
3466
3467
+ // Custom files (CSS and JS)
3468
+ var customFiles = '';
3469
+ if (domain.customFiles != null) {
3470
+ customFiles = encodeURIComponent(JSON.stringify(domain.customFiles));
3471
+ } else if (domain.customfiles != null) {
3472
+ customFiles = encodeURIComponent(JSON.stringify(domain.customfiles));
3473
+ }
3474
+
3475
// Get two-factor screen timeout
3476
var twoFactorTimeout = 300000; // Default is 5 minutes, 0 for no timeout.
3477
if ((typeof domain.passwordrequirements == 'object') && (typeof domain.passwordrequirements.twofactortimeout == 'number')) {
@@ -3493,6 +3510,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
3510
sessiontime: (args.sessiontime) ? args.sessiontime : 60, // Session time in minutes, 60 minutes is the default
3511
passRequirements: passRequirements,
3512
customui: customui,
3513
+ customFiles: customFiles,
3514
footer: (domain.loginfooter == null) ? '' : obj.common.replacePlaceholders(domain.loginfooter, {
3515
'serverversion': obj.parent.currentVer,
3516
'servername': obj.getWebServerName(domain, req),
@@ -9396,6 +9414,63 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
9414
return null;
9415
}
9416
9417
+ function generateCustomCSSTags(customFilesArray, currentTemplate) {
9418
+ var cssTags = '';
9419
+
9420
+ cssTags += '<link keeplink=1 type="text/css" href="styles/custom.css" media="screen" rel="stylesheet" title="CSS" />\n ';
9421
+
9422
+
9423
+ if (customFilesArray) {
9424
+ if (Array.isArray(customFilesArray)) {
9425
+ for (var i = 0; i < customFilesArray.length; i++) {
9426
+ var customFileConfig = customFilesArray[i];
9427
+ if (customFileConfig && customFileConfig.css && Array.isArray(customFileConfig.css)) {
9428
+ if ((customFileConfig.scope && customFileConfig.scope.indexOf('all') !== -1) ||
9429
+ (currentTemplate && customFileConfig.scope && customFileConfig.scope.indexOf(currentTemplate) !== -1)) {
9430
+ for (var j = 0; j < customFileConfig.css.length; j++) {
9431
+ cssTags += '<link keeplink=1 type="text/css" href="styles/' + customFileConfig.css[j] + '" media="screen" rel="stylesheet" title="CSS" />\n ';
9432
+ }
9433
+ }
9434
+ }
9435
+ }
9436
+ } else if (customFilesArray.css && Array.isArray(customFilesArray.css)) {
9437
+ for (var i = 0; i < customFilesArray.css.length; i++) {
9438
+ cssTags += '<link keeplink=1 type="text/css" href="styles/' + customFilesArray.css[i] + '" media="screen" rel="stylesheet" title="CSS" />\n ';
9439
+ }
9440
+ }
9441
+ }
9442
+
9443
+ return cssTags.trim();
9444
+ }
9445
+
9446
+ function generateCustomJSTags(customFilesArray, currentTemplate) {
9447
+ var jsTags = '';
9448
+
9449
+ jsTags += '<script keeplink=1 type="text/javascript" src="scripts/custom.js"></script>\n ';
9450
+
9451
+ if (customFilesArray) {
9452
+ if (Array.isArray(customFilesArray)) {
9453
+ for (var i = 0; i < customFilesArray.length; i++) {
9454
+ var customFileConfig = customFilesArray[i];
9455
+ if (customFileConfig && customFileConfig.js && Array.isArray(customFileConfig.js)) {
9456
+ if ((customFileConfig.scope && customFileConfig.scope.indexOf('all') !== -1) ||
9457
+ (currentTemplate && customFileConfig.scope && customFileConfig.scope.indexOf(currentTemplate) !== -1)) {
9458
+ for (var j = 0; j < customFileConfig.js.length; j++) {
9459
+ jsTags += '<script keeplink=1 type="text/javascript" src="scripts/' + customFileConfig.js[j] + '"></script>\n ';
9460
+ }
9461
+ }
9462
+ }
9463
+ }
9464
+ } else if (customFilesArray.js && Array.isArray(customFilesArray.js)) {
9465
+ for (var i = 0; i < customFilesArray.js.length; i++) {
9466
+ jsTags += '<script keeplink=1 type="text/javascript" src="scripts/' + customFilesArray.js[i] + '"></script>\n ';
9467
+ }
9468
+ }
9469
+ }
9470
+
9471
+ return jsTags.trim();
9472
+ }
9473
+
9474
// Return the correct render page arguments.
9475
function getRenderArgs(xargs, req, domain, page) {
9476
var minify = (domain.minify == true);
@@ -9435,6 +9510,21 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
9510
// To mitigate any possible BREACH attack, we generate a random 0 to 255 bytes length string here.
9511
xargs.randomlength = (args.webpagelengthrandomization !== false) ? parent.crypto.randomBytes(parent.crypto.randomBytes(1)[0]).toString('base64') : '';
9512
9513
+ // Generate custom CSS and JS tags
9514
+ if (xargs.customFiles) {
9515
+ try {
9516
+ var customFiles = JSON.parse(decodeURIComponent(xargs.customFiles));
9517
+ xargs.customCSSTags = generateCustomCSSTags(customFiles, page);
9518
+ xargs.customJSTags = generateCustomJSTags(customFiles, page);
9519
+ } catch (ex) {
9520
+ xargs.customCSSTags = generateCustomCSSTags(null, page);
9521
+ xargs.customJSTags = generateCustomJSTags(null, page);
9522
+ }
9523
+ } else {
9524
+ xargs.customCSSTags = generateCustomCSSTags(null, page);
9525
+ xargs.customJSTags = generateCustomJSTags(null, page);
9526
+ }
9527
+
9528
return xargs;
9529
}
9530