Improved login page customization, improved Chinese.

This commit is contained in:
Ylian Saint-Hilaire 2020-09-03 02:19:17 -07:00
parent df4c302470
commit 64efc47129
8 changed files with 332 additions and 297 deletions

View File

@ -15,7 +15,7 @@
<p>服务器上的使用者[[[USERNAME]]] <a href="[[[SERVERURL]]]">[[[SERVERNAME]]]</a> 正在要求您安装软件以启动远程控制会话。</p>
<area-msg>
<p>
息: <b notrans="1">[[[MSG]]]</b>
息: <b notrans="1">[[[MSG]]]</b>
</p>
</area-msg>
<area-windows>
@ -34,7 +34,7 @@
</area-linux>
<area-link>
<p>
要安裝軟體 <a href="[[[SERVERURL]]][[[LINKURL]]]">点击这里</a> 并按照说明进行操作。
要安装软件 <a href="[[[SERVERURL]]][[[LINKURL]]]">点击这里</a> 并按照说明进行操作。
</p>
</area-link>
<p>如果您没有发起此请求,请不理此邮件。</p>

View File

@ -5,7 +5,7 @@
服务器[[[SERVERNAME]]][[[SERVERURL]]]/)上的用户[[[USERNAME]]]请求您安装软件以启动远程控制。
~<area-msg>
~
息:[[[MSG]]]
息:[[[MSG]]]
~
~</area-msg>
~<area-windows>

View File

@ -141,10 +141,11 @@
"items": {
"type": "object",
"properties": {
"siteStyle": { "type": "integer", "default": 1, "description": "Valid numbers are 1 and 2, changes the style of the login page and some secondary pages." }
"title": { "type": "string" },
"title2": { "type": "string" },
"titlePicture": { "type": "string" },
"siteStyle": { "type": "integer", "default": 1, "description": "Valid numbers are 1 and 2, changes the style of the login page and some secondary pages." },
"title": { "type": "string", "default": "MeshCentral", "description": "The title of this web site. All web pages will have this title." },
"title2": { "type": "string", "default": null, "description": "Secondary title text that is placed on the upper right on the title on many web pages." },
"titlePicture": { "type": "string", "default": null, "description": "Web site .png logo file that is 450x66 in size placed in meshcentral-data that is used on the top of many pages." },
"loginPicture": { "type": "string", "default": null, "description": "Web site .png logo file placed in meshcentral-data that used on the login page when sitestyle is 2." },
"userQuota": { "type": "integer" },
"meshQuota": { "type": "integer" },
"minify": { "type": "boolean", "default": false, "description": "When enabled, the server will send reduced sided web pages." },
@ -154,7 +155,7 @@
"newAccountEmailDomains": { "type": "array", "uniqueItems": true, "items": { "type": "string" } },
"newAccountsRights": { "type": "array", "uniqueItems": true, "items": { "type": "string" } },
"welcomeText": { "type": "string", "description": "Text that will be shown on the login screen." },
"welcomePicture": { "type": "string", "description": "Name of the JPG file that will be shown on the login screen. Put this file in the meshcentral-data folder and place the name here." },
"welcomePicture": { "type": "string", "description": "Name of the PNG or JPEG file that will be shown on the login screen. Put this file in the meshcentral-data folder and place the file name here." },
"hide": { "type": "integer" },
"footer": { "type": "string" },
"certUrl": { "type": "string", "format": "uri", "description": "https url when to get the TLS certificate that MeshAgent's will see when connecting to this server. This setting is used when a reverse proxy like NGINX is used in front of MeshCentral." },

View File

@ -118,6 +118,7 @@
"title": "MyServer",
"title2": "Servername",
"_titlePicture": "title-sample.png",
"_loginPicture": "title-sample.png",
"_userQuota": 1048576,
"_meshQuota": 248576,
"minify": true,

View File

@ -440,7 +440,7 @@ function startEx(argv) {
function totext(source, target, lang) {
// Load the source language file
var sourceLangFileData = null;
try { sourceLangFileData = JSON.parse(fs.readFileSync(source)); } catch (ex) { }
try { sourceLangFileData = JSON.parse(fs.readFileSync(source)); } catch (ex) { console.log(ex); }
if ((sourceLangFileData == null) || (sourceLangFileData.strings == null)) { log("Invalid source language file."); process.exit(); return; }
log('Writing ' + lang + '...');
@ -485,7 +485,7 @@ function totext(source, target, lang) {
function fromtext(source, target, lang) {
// Load the source language file
var sourceLangFileData = null;
try { sourceLangFileData = JSON.parse(fs.readFileSync(source)); } catch (ex) { }
try { sourceLangFileData = JSON.parse(fs.readFileSync(source)); } catch (ex) { console.log(ex); }
if ((sourceLangFileData == null) || (sourceLangFileData.strings == null)) { log("Invalid source language file."); process.exit(); return; }
log('Updating ' + lang + '...');
@ -514,12 +514,12 @@ function fromtext(source, target, lang) {
function merge(source, target, lang) {
// Load the source language file
var sourceLangFileData = null;
try { sourceLangFileData = JSON.parse(fs.readFileSync(source)); } catch (ex) { }
try { sourceLangFileData = JSON.parse(fs.readFileSync(source)); } catch (ex) { console.log(ex); }
if ((sourceLangFileData == null) || (sourceLangFileData.strings == null)) { log("Invalid source language file."); process.exit(); return; }
// Load the target language file
var targetLangFileData = null;
try { targetLangFileData = JSON.parse(fs.readFileSync(target)); } catch (ex) { }
try { targetLangFileData = JSON.parse(fs.readFileSync(target)); } catch (ex) { console.log(ex); }
if ((targetLangFileData == null) || (targetLangFileData.strings == null)) { log("Invalid target language file."); process.exit(); return; }
log('Merging ' + lang + '...');

File diff suppressed because it is too large Load Diff

View File

@ -25,11 +25,13 @@
</style>
</head>
<body id="body" onload="if (typeof(startup) !== 'undefined') startup();" class="arg_hide login">
<img style="position:absolute;left:0;bottom:0;z-index:-1;height:60%;opacity:0.1" src="images/login/back.png" />
<img style="position:absolute;left:0;bottom:0;z-index:-1;height:60%;opacity:0.1" src="welcome.png" />
<table id="centralTable" class="container" style="height:100%;z-index:1">
<tr>
<td id="logincell">
<div style="font-size:46px;font-family:Arial,Helvetica,sans-serif;font-weight:bold;padding-bottom:10px;color:#c8c8c8;text-shadow: 2px 2px 2px #000;">{{{title1}}}</div>
{{{titlehtml}}}
<img id="loginPicture" />
<div style="font-size:46px;font-family:Arial,Helvetica,sans-serif;font-weight:bold;padding-bottom:10px;color:#c8c8c8;text-shadow: 2px 2px 2px #000;">{{{title1}}}<sup style="font-size:20px"> {{{title2}}}</sup></div>
<div id=loginpanel style="display:none;box-shadow:1px 1px 4px #000">
<form method=post>
<input type=hidden name=action value=login />
@ -37,22 +39,22 @@
<table style="width:100%">
<tr>
<td>
<input id=username title="Username" style="width:250px;border:0;border-radius:8px;padding:8px;background-color:#FFF8CC" autocomplete="username" placeholder="Username" type=text maxlength=64 name=username onchange=validateLogin(1) onkeyup=validateLogin(1,event) />
<input id=username title="Username" style="box-sizing:border-box;width:280px;border:0;border-radius:4px;padding:8px;background-color:#FFF8CC" autocomplete="username" placeholder="Username" type=text maxlength=64 name=username onchange=validateLogin(1) onkeyup=validateLogin(1,event) />
</td>
</tr>
<tr>
<td>
<input id=password title="Password" style="width:250px;border:0;border-radius:8px;padding:8px;background-color:#FFF8CC" autocomplete="current-password" placeholder="Password" type=password maxlength=256 name=password onchange=validateLogin(2) onkeyup=validateLogin(2,event) />
<input id=password title="Password" style="box-sizing:border-box;width:280px;border:0;border-radius:4px;padding:8px;background-color:#FFF8CC" autocomplete="current-password" placeholder="Password" type=password maxlength=256 name=password onchange=validateLogin(2) onkeyup=validateLogin(2,event) />
</td>
</tr>
<tr>
<td>
<div id=showPassHintLink title="Password Hint" style="width:250px;border:0;border-radius:8px;padding:8px;background-color:#FFF8CC;display:none"><a onclick="return showPassHint(event);" href="#" style="cursor:pointer">Show Hint</a></div>
<div id=showPassHintLink title="Password Hint" style="box-sizing:border-box;width:280px;border:0;border-radius:4px;padding:8px;background-color:#FFF8CC;display:none"><a onclick="return showPassHint(event);" href="#" style="cursor:pointer">Show Hint</a></div>
</td>
</tr>
<tr>
<td>
<input id=loginButton style="width:250px;border:0;border-radius:4px;padding:6px" type=submit value="Log In" disabled="disabled" />
<input id=loginButton style="box-sizing:border-box;width:280px;border:0;border-radius:4px;padding:6px" type=submit value="Log In" disabled="disabled" />
</td>
</tr>
</table>
@ -132,12 +134,12 @@
<table style="width:100%;margin-top:4px;margin-bottom:4px">
<tr>
<td>
<input id=remail title="Email" style="width:250px;border:0;border-radius:8px;padding:8px;background-color:#FFF8CC" autocomplete="username" placeholder="Email" type=text maxlength=256 name=email onchange=validateReset() onkeyup=validateReset(event) />
<input id=remail title="Email" style="box-sizing:border-box;width:280px;border:0;border-radius:4px;padding:8px;background-color:#FFF8CC" autocomplete="username" placeholder="Email" type=text maxlength=256 name=email onchange=validateReset() onkeyup=validateReset(event) />
</td>
</tr>
<tr>
<td>
<input id=eresetButton style="width:250px;border:0;border-radius:4px;padding:6px" type=submit value="Reset Account" disabled="disabled" />
<input id=eresetButton style="box-sizing:border-box;width:280px;border:0;border-radius:4px;padding:6px" type=submit value="Reset Account" disabled="disabled" />
</td>
</tr>
</table>
@ -153,14 +155,14 @@
<table style="width:100%">
<tr>
<td>
<input id=tokenInput autocomplete="one-time-code" title="Token" style="width:250px;border:0;border-radius:8px;padding:8px;background-color:#FFF8CC" placeholder="Token" type=text maxlength=50 name=token onchange=checkToken(event) onpaste=resetCheckToken(event) onkeyup=checkToken(event) onkeydown=checkToken(event) /><br />
<input id=tokenInput autocomplete="one-time-code" title="Token" style="box-sizing:border-box;width:280px;border:0;border-radius:4px;padding:8px;background-color:#FFF8CC" placeholder="Token" type=text maxlength=50 name=token onchange=checkToken(event) onpaste=resetCheckToken(event) onkeyup=checkToken(event) onkeydown=checkToken(event) /><br />
<input id=hwtokenInput type=text name=hwtoken style="display:none" />
</td>
</tr>
<tr>
<td>
<div>
<input id=tokenOkButton style="width:250px;border:0;border-radius:4px;padding:6px" type=submit value="Log In" disabled="disabled" />
<input id=tokenOkButton style="box-sizing:border-box;width:280px;border:0;border-radius:4px;padding:6px" type=submit value="Log In" disabled="disabled" />
</div>
</td>
</tr>
@ -265,8 +267,8 @@
<tr style="height:20px">
<td>
<div>
<div id="flink" style="margin-left:4px">{{{footer}}}</div>
<div id="flink" style="float:right;margin-right:4px">{{{rootCertLink}}}&nbsp;<a href=terms>Terms &amp; Privacy</a></div>
<div id="flink" style="margin-left:4px">{{{footer}}}</div>
</div>
</td>
</tr>
@ -311,6 +313,8 @@
var authStrategies = '{{{authStrategies}}}'.split(',');
function startup() {
if (decodeURIComponent('{{{loginpicture}}}') == 'true') { Q('loginPicture').src = "loginlogo.png"; }
// Display the right server message
var i;
var messageid = parseInt('{{{messageid}}}');

View File

@ -2459,7 +2459,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
if (domain.customui != null) { customui = encodeURIComponent(JSON.stringify(domain.customui)); }
// Render the login page
render(req, res, getRenderPage((domain.sitestyle == 2)?'login2':'login', req, domain), getRenderArgs({ loginmode: loginmode, rootCertLink: getRootCertLink(), newAccount: newAccountsAllowed, newAccountPass: (((domain.newaccountspass == null) || (domain.newaccountspass == '')) ? 0 : 1), serverDnsName: obj.getWebServerName(domain), serverPublicPort: httpsPort, emailcheck: emailcheck, features: features, sessiontime: args.sessiontime, passRequirements: passRequirements, customui: customui, footer: (domain.footer == null) ? '' : domain.footer, hkey: encodeURIComponent(hardwareKeyChallenge).replace(/'/g, '%27'), messageid: msgid, passhint: passhint, welcometext: domain.welcometext ? encodeURIComponent(domain.welcometext).split('\'').join('\\\'') : null, hwstate: hwstate, otpemail: otpemail, otpsms: otpsms, twoFactorCookieDays: twoFactorCookieDays, authStrategies: authStrategies.join(',') }, req, domain));
render(req, res, getRenderPage((domain.sitestyle == 2) ? 'login2' : 'login', req, domain), getRenderArgs({ loginmode: loginmode, rootCertLink: getRootCertLink(), newAccount: newAccountsAllowed, newAccountPass: (((domain.newaccountspass == null) || (domain.newaccountspass == '')) ? 0 : 1), serverDnsName: obj.getWebServerName(domain), serverPublicPort: httpsPort, emailcheck: emailcheck, features: features, sessiontime: args.sessiontime, passRequirements: passRequirements, customui: customui, footer: (domain.footer == null) ? '' : domain.footer, hkey: encodeURIComponent(hardwareKeyChallenge).replace(/'/g, '%27'), messageid: msgid, passhint: passhint, welcometext: domain.welcometext ? encodeURIComponent(domain.welcometext).split('\'').join('\\\'') : null, hwstate: hwstate, otpemail: otpemail, otpsms: otpsms, twoFactorCookieDays: twoFactorCookieDays, authStrategies: authStrategies.join(','), loginpicture: (typeof domain.loginpicture == 'string') }, req, domain));
}
// Handle a post request on the root
@ -2822,7 +2822,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
if (domain.titlepicture) {
if ((parent.configurationFiles != null) && (parent.configurationFiles[domain.titlepicture] != null)) {
// Use the logo in the database
res.set({ 'Content-Type': 'image/jpeg' });
res.set({ 'Content-Type': domain.titlepicture.toLowerCase().endsWith('.png')?'image/png':'image/jpeg' });
res.send(parent.configurationFiles[domain.titlepicture]);
return;
} else {
@ -2843,6 +2843,25 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
}
}
// Handle login logo request
function handleLoginLogoRequest(req, res) {
const domain = checkUserIpAddress(req, res);
if (domain == null) { return; }
//res.set({ 'Cache-Control': 'max-age=86400' }); // 1 day
if (domain.loginpicture) {
if ((parent.configurationFiles != null) && (parent.configurationFiles[domain.loginpicture] != null)) {
// Use the logo in the database
res.set({ 'Content-Type': domain.loginpicture.toLowerCase().endsWith('.png') ? 'image/png' : 'image/jpeg' });
res.send(parent.configurationFiles[domain.loginpicture]);
return;
} else {
// Use the logo on file
try { res.sendFile(obj.path.join(obj.parent.datapath, domain.loginpicture)); return; } catch (ex) { res.sendStatus(404); }
}
}
}
// Handle translation request
function handleTranslationsRequest(req, res) {
const domain = checkUserIpAddress(req, res);
@ -2916,7 +2935,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
if (domain.welcomepicture) {
if ((parent.configurationFiles != null) && (parent.configurationFiles[domain.welcomepicture] != null)) {
// Use the welcome image in the database
res.set({ 'Content-Type': 'image/jpeg' });
res.set({ 'Content-Type': domain.welcomepicture.toLowerCase().endsWith('.png')?'image/png':'image/jpeg' });
res.send(parent.configurationFiles[domain.welcomepicture]);
return;
}
@ -2925,29 +2944,31 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
try { res.sendFile(obj.path.join(obj.parent.datapath, domain.welcomepicture)); return; } catch (ex) { }
}
var imagefile = 'images/mainwelcome.jpg';
if (domain.sitestyle == 2) { imagefile = 'images/login/back.png'; }
if (domain.webpublicpath != null) {
obj.fs.exists(obj.path.join(domain.webpublicpath, 'images/mainwelcome.jpg'), function (exists) {
obj.fs.exists(obj.path.join(domain.webpublicpath, imagefile), function (exists) {
if (exists) {
// Use the domain logo picture
try { res.sendFile(obj.path.join(domain.webpublicpath, 'images/mainwelcome.jpg')); } catch (ex) { res.sendStatus(404); }
try { res.sendFile(obj.path.join(domain.webpublicpath, imagefile)); } catch (ex) { res.sendStatus(404); }
} else {
// Use the default logo picture
try { res.sendFile(obj.path.join(obj.parent.webPublicPath, 'images/mainwelcome.jpg')); } catch (ex) { res.sendStatus(404); }
try { res.sendFile(obj.path.join(obj.parent.webPublicPath, imagefile)); } catch (ex) { res.sendStatus(404); }
}
});
} else if (parent.webPublicOverridePath) {
obj.fs.exists(obj.path.join(obj.parent.webPublicOverridePath, 'images/mainwelcome.jpg'), function (exists) {
obj.fs.exists(obj.path.join(obj.parent.webPublicOverridePath, imagefile), function (exists) {
if (exists) {
// Use the override logo picture
try { res.sendFile(obj.path.join(obj.parent.webPublicOverridePath, 'images/mainwelcome.jpg')); } catch (ex) { res.sendStatus(404); }
try { res.sendFile(obj.path.join(obj.parent.webPublicOverridePath, imagefile)); } catch (ex) { res.sendStatus(404); }
} else {
// Use the default logo picture
try { res.sendFile(obj.path.join(obj.parent.webPublicPath, 'images/mainwelcome.jpg')); } catch (ex) { res.sendStatus(404); }
try { res.sendFile(obj.path.join(obj.parent.webPublicPath, imagefile)); } catch (ex) { res.sendStatus(404); }
}
});
} else {
// Use the default logo picture
try { res.sendFile(obj.path.join(obj.parent.webPublicPath, 'images/mainwelcome.jpg')); } catch (ex) { res.sendStatus(404); }
try { res.sendFile(obj.path.join(obj.parent.webPublicPath, imagefile)); } catch (ex) { res.sendStatus(404); }
}
}
@ -4707,8 +4728,10 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
obj.app.ws(url + 'devicefile.ashx', function (ws, req) { obj.meshDeviceFileHandler.CreateMeshDeviceFile(obj, ws, null, req, domain); });
obj.app.get(url + 'devicefile.ashx', handleDeviceFile);
obj.app.get(url + 'logo.png', handleLogoRequest);
obj.app.get(url + 'loginlogo.png', handleLoginLogoRequest);
obj.app.post(url + 'translations', handleTranslationsRequest);
obj.app.get(url + 'welcome.jpg', handleWelcomeImageRequest);
obj.app.get(url + 'welcome.png', handleWelcomeImageRequest);
obj.app.get(url + 'recordings.ashx', handleGetRecordings);
obj.app.get(url + 'player.htm', handlePlayerRequest);
obj.app.get(url + 'player', handlePlayerRequest);