Merge branch 'master' into dockerrewrite

This commit is contained in:
DaanSelen 2025-05-28 15:09:25 +02:00 committed by GitHub
commit ae0aa7e9ac
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 129 additions and 12 deletions

View File

@ -420,3 +420,18 @@ module.exports.uniqueArray = function (a) {
}
return out;
}
// Replace placeholders in a string with values from an object or a function
module.exports.replacePlaceholders = function (template, values) {
return template.replace(/\{(\w+)\}/g, (match, key) => {
if (typeof values === 'function') {
return values(key);
}
else if (values && typeof values === 'object') {
return values[key] !== undefined ? values[key] : match;
}
else {
return values !== undefined ? values : match;
}
});
}

View File

@ -168,7 +168,6 @@ RUN cd meshcentral && npm install
# Expose needed ports
EXPOSE 80 443
# These volumes will be created by default even without any declaration, this allows default persistence in Docker/Podman.
VOLUME /opt/meshcentral/meshcentral-data
VOLUME /opt/meshcentral/meshcentral-files

View File

@ -116,6 +116,27 @@ module.exports.CreateMeshMail = function (parent, domain) {
}
}
// If no email template found, use the default translated email template
if (((htmlfile == null) || (txtfile == null)) && (lang != null) && (lang != 'en')) {
var translationsPath = obj.parent.path.join(obj.parent.webEmailsPath, 'translations');
var translationsPathHtml = obj.parent.path.join(obj.parent.webEmailsPath, 'translations', name + '_' + lang + '.html');
var translationsPathTxt = obj.parent.path.join(obj.parent.webEmailsPath, 'translations', name + '_' + lang + '.txt');
if (obj.parent.fs.existsSync(translationsPath) && obj.parent.fs.existsSync(translationsPathHtml) && obj.parent.fs.existsSync(translationsPathTxt)) {
htmlfile = obj.parent.fs.readFileSync(translationsPathHtml).toString();
txtfile = obj.parent.fs.readFileSync(translationsPathTxt).toString();
}
}
// If no translated email template found, use the default email template
if ((htmlfile == null) || (txtfile == null)) {
var pathHtml = obj.parent.path.join(obj.parent.webEmailsPath, name + '.html');
var pathTxt = obj.parent.path.join(obj.parent.webEmailsPath, name + '.txt');
if (obj.parent.fs.existsSync(pathHtml) && obj.parent.fs.existsSync(pathTxt)) {
htmlfile = obj.parent.fs.readFileSync(pathHtml).toString();
txtfile = obj.parent.fs.readFileSync(pathTxt).toString();
}
}
// No email templates
if ((htmlfile == null) || (txtfile == null)) { return null; }

View File

@ -397,6 +397,7 @@ module.exports.CreateServer = function (parent) {
function getTemplate(templateNumber, domain, lang) {
parent.debug('email', 'Getting SMS template #' + templateNumber + ', lang: ' + lang);
if (Array.isArray(lang)) { lang = lang[0]; } // TODO: For now, we only use the first language given.
if (lang != null) { lang = lang.split('-')[0]; } // Take the first part of the language, "xx-xx"
var r = {}, emailsPath = null;
if ((domain != null) && (domain.webemailspath != null)) { emailsPath = domain.webemailspath; }
@ -404,7 +405,7 @@ module.exports.CreateServer = function (parent) {
else if (obj.parent.webEmailsPath != null) { emailsPath = obj.parent.webEmailsPath; }
if ((emailsPath == null) || (obj.parent.fs.existsSync(emailsPath) == false)) { return null }
// Get the non-english email if needed
// Get the non-english sms if needed
var txtfile = null;
if ((lang != null) && (lang != 'en')) {
var translationsPath = obj.parent.path.join(emailsPath, 'translations');
@ -414,7 +415,7 @@ module.exports.CreateServer = function (parent) {
}
}
// Get the english email
// Get the english sms
if (txtfile == null) {
var pathTxt = obj.parent.path.join(emailsPath, 'sms-messages.txt');
if (obj.parent.fs.existsSync(pathTxt)) {
@ -422,6 +423,23 @@ module.exports.CreateServer = function (parent) {
}
}
// If no english sms and a non-english language is requested, try to get the default translated sms
if (txtfile == null && (lang != null) && (lang != 'en')) {
var translationsPath = obj.parent.path.join(obj.parent.webEmailsPath, 'translations');
var translationsPathTxt = obj.parent.path.join(obj.parent.webEmailsPath, 'translations', 'sms-messages_' + lang + '.txt');
if (obj.parent.fs.existsSync(translationsPath) && obj.parent.fs.existsSync(translationsPathTxt)) {
txtfile = obj.parent.fs.readFileSync(translationsPathTxt).toString();
}
}
// If no default translated sms, try to get the default english sms
if (txtfile == null) {
var pathTxt = obj.parent.path.join(obj.parent.webEmailsPath, 'sms-messages.txt');
if (obj.parent.fs.existsSync(pathTxt)) {
txtfile = obj.parent.fs.readFileSync(pathTxt).toString();
}
}
// No email templates
if (txtfile == null) { return null; }

View File

@ -162,7 +162,19 @@ module.exports.CreateMeshScanner = function (parent) {
try {
if ((typeof obj.parent.config.domains[''].title == 'string') && (obj.parent.config.domains[''].title.length > 0)) {
name = obj.parent.config.domains[''].title; info = '';
try { if ((typeof obj.parent.config.domains[''].title2 == 'string') && (obj.parent.config.domains[''].title2.length > 0)) { info = obj.parent.config.domains[''].title2; } } catch (ex) { }
try {
if ((typeof obj.parent.config.domains[''].title2 == 'string') && (obj.parent.config.domains[''].title2.length > 0)) {
info = obj.common.replacePlaceholders(obj.parent.config.domains[''].title2, {
'serverversion': obj.parent.currentVer,
'servername': obj.getWebServerName(domain, req),
'agentsessions': Object.keys(parent.webserver.wsagents).length,
'connectedusers': Object.keys(parent.webserver.wssessions).length,
'userssessions': Object.keys(parent.webserver.wssessions2).length,
'relaysessions': parent.webserver.relaySessionCount,
'relaycount': Object.keys(parent.webserver.wsrelays).length
});
}
} catch (ex) { }
}
} catch (ex) { }
try {

View File

@ -169,6 +169,7 @@ module.exports.CreateMeshSMS = function (parent) {
function getTemplate(templateNumber, domain, lang) {
parent.debug('email', 'Getting SMS template #' + templateNumber + ', lang: ' + lang);
if (Array.isArray(lang)) { lang = lang[0]; } // TODO: For now, we only use the first language given.
if (lang != null) { lang = lang.split('-')[0]; } // Take the first part of the language, "xx-xx"
var r = {}, emailsPath = null;
if ((domain != null) && (domain.webemailspath != null)) { emailsPath = domain.webemailspath; }
@ -194,6 +195,23 @@ module.exports.CreateMeshSMS = function (parent) {
}
}
// If no english sms and a non-english language is requested, try to get the default translated sms
if (txtfile == null && (lang != null) && (lang != 'en')) {
var translationsPath = obj.parent.path.join(obj.parent.webEmailsPath, 'translations');
var translationsPathTxt = obj.parent.path.join(obj.parent.webEmailsPath, 'translations', 'sms-messages_' + lang + '.txt');
if (obj.parent.fs.existsSync(translationsPath) && obj.parent.fs.existsSync(translationsPathTxt)) {
txtfile = obj.parent.fs.readFileSync(translationsPathTxt).toString();
}
}
// If no default translated sms, try to get the default english sms
if (txtfile == null) {
var pathTxt = obj.parent.path.join(obj.parent.webEmailsPath, 'sms-messages.txt');
if (obj.parent.fs.existsSync(pathTxt)) {
txtfile = obj.parent.fs.readFileSync(pathTxt).toString();
}
}
// No email templates
if (txtfile == null) { return null; }

View File

@ -3217,7 +3217,15 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
passRequirements: passRequirements,
customui: customui,
webcerthash: Buffer.from(obj.webCertificateFullHashs[domain.id], 'binary').toString('base64').replace(/\+/g, '@').replace(/\//g, '$'),
footer: (domain.footer == null) ? '' : domain.footer,
footer: (domain.footer == null) ? '' : obj.common.replacePlaceholders(domain.footer, {
'serverversion': obj.parent.currentVer,
'servername': obj.getWebServerName(domain, req),
'agentsessions': Object.keys(parent.webserver.wsagents).length,
'connectedusers': Object.keys(parent.webserver.wssessions).length,
'userssessions': Object.keys(parent.webserver.wssessions2).length,
'relaysessions': parent.webserver.relaySessionCount,
'relaycount': Object.keys(parent.webserver.wsrelays).length
}),
webstate: encodeURIComponent(webstate).replace(/'/g, '%27'),
amtscanoptions: amtscanoptions,
pluginHandler: (parent.pluginHandler == null) ? 'null' : parent.pluginHandler.prepExports(),
@ -3462,12 +3470,29 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
sessiontime: (args.sessiontime) ? args.sessiontime : 60, // Session time in minutes, 60 minutes is the default
passRequirements: passRequirements,
customui: customui,
footer: (domain.loginfooter == null) ? '' : domain.loginfooter,
footer: (domain.loginfooter == null) ? '' : obj.common.replacePlaceholders(domain.loginfooter, {
'serverversion': obj.parent.currentVer,
'servername': obj.getWebServerName(domain, req),
'agentsessions': Object.keys(parent.webserver.wsagents).length,
'connectedusers': Object.keys(parent.webserver.wssessions).length,
'userssessions': Object.keys(parent.webserver.wssessions2).length,
'relaysessions': parent.webserver.relaySessionCount,
'relaycount': Object.keys(parent.webserver.wsrelays).length
}),
hkey: encodeURIComponent(hardwareKeyChallenge).replace(/'/g, '%27'),
messageid: msgid,
flashErrors: JSON.stringify(flashErrors),
passhint: passhint,
welcometext: domain.welcometext ? encodeURIComponent(domain.welcometext).split('\'').join('\\\'') : null,
welcometext: domain.welcometext ? encodeURIComponent(obj.common.replacePlaceholders(domain.welcometext, {
'serverversion': obj.parent.currentVer,
'servername': obj.getWebServerName(domain, req),
'agentsessions': Object.keys(parent.webserver.wsagents).length,
'connectedusers': Object.keys(parent.webserver.wssessions).length,
'userssessions': Object.keys(parent.webserver.wssessions2).length,
'relaysessions': parent.webserver.relaySessionCount,
'relaycount': Object.keys(parent.webserver.wsrelays).length
})).split('\'').join('\\\'') : null,
welcomePictureFullScreen: ((typeof domain.welcomepicturefullscreen == 'boolean') ? domain.welcomepicturefullscreen : false),
hwstate: hwstate,
otpemail: otpemail,
@ -4208,7 +4233,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
if (typeof c.pid != 'string') { res.sendStatus(404); return; }
// Check the expired time, expire message.
if ((c.e != null) && (c.e <= Date.now())) { render(req, res, getRenderPage((domain.sitestyle >= 2) ? 'message2' : 'message', req, domain), getRenderArgs({ titleid: 2, msgid: 12, domainurl: encodeURIComponent(domain.url).replace(/'/g, '%27') }, req, domain)); return; }
if ((c.e != null) && (c.e <= Date.now())) { res.status(404); render(req, res, getRenderPage((domain.sitestyle >= 2) ? 'message2' : 'message', req, domain), getRenderArgs({ titleid: 2, msgid: 12, domainurl: encodeURIComponent(domain.url).replace(/'/g, '%27') }, req, domain)); return; }
obj.db.Get('deviceshare-' + c.pid, function (err, docs) {
if ((err != null) || (docs == null) || (docs.length != 1)) { res.sendStatus(404); return; }
@ -4254,17 +4279,17 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
// Serve the guest sharing page
function handleSharingRequestEx(req, res, domain, c) {
// Check the expired time, expire message.
if ((c.expire != null) && (c.expire <= Date.now())) { render(req, res, getRenderPage((domain.sitestyle >= 2) ? 'message2' : 'message', req, domain), getRenderArgs({ titleid: 2, msgid: 12, domainurl: encodeURIComponent(domain.url).replace(/'/g, '%27') }, req, domain)); return; }
if ((c.expire != null) && (c.expire <= Date.now())) { res.status(404); render(req, res, getRenderPage((domain.sitestyle >= 2) ? 'message2' : 'message', req, domain), getRenderArgs({ titleid: 2, msgid: 12, domainurl: encodeURIComponent(domain.url).replace(/'/g, '%27') }, req, domain)); return; }
// Check the public id
obj.db.GetAllTypeNodeFiltered([c.nid], domain.id, 'deviceshare', null, function (err, docs) {
// Check if any sharing links are present, expire message.
if ((err != null) || (docs.length == 0)) { render(req, res, getRenderPage((domain.sitestyle >= 2) ? 'message2' : 'message', req, domain), getRenderArgs({ titleid: 2, msgid: 12, domainurl: encodeURIComponent(domain.url).replace(/'/g, '%27') }, req, domain)); return; }
if ((err != null) || (docs.length == 0)) { res.status(404); render(req, res, getRenderPage((domain.sitestyle >= 2) ? 'message2' : 'message', req, domain), getRenderArgs({ titleid: 2, msgid: 12, domainurl: encodeURIComponent(domain.url).replace(/'/g, '%27') }, req, domain)); return; }
// Search for the device share public identifier, expire message.
var found = false;
for (var i = 0; i < docs.length; i++) { if ((docs[i].publicid == c.pid) && ((docs[i].extrakey == null) || (docs[i].extrakey === c.k))) { found = true; } }
if (found == false) { render(req, res, getRenderPage((domain.sitestyle >= 2) ? 'message2' : 'message', req, domain), getRenderArgs({ titleid: 2, msgid: 12, domainurl: encodeURIComponent(domain.url).replace(/'/g, '%27') }, req, domain)); return; }
if (found == false) { res.status(404); render(req, res, getRenderPage((domain.sitestyle >= 2) ? 'message2' : 'message', req, domain), getRenderArgs({ titleid: 2, msgid: 12, domainurl: encodeURIComponent(domain.url).replace(/'/g, '%27') }, req, domain)); return; }
// Get information about this node
obj.db.Get(c.nid, function (err, nodes) {
@ -4272,7 +4297,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
var node = nodes[0];
// Check the start time, not yet valid message.
if ((c.start != null) && (c.expire != null) && ((c.start > Date.now()) || (c.start > c.expire))) { render(req, res, getRenderPage((domain.sitestyle >= 2) ? 'message2' : 'message', req, domain), getRenderArgs({ titleid: 2, msgid: 11, domainurl: encodeURIComponent(domain.url).replace(/'/g, '%27') }, req, domain)); return; }
if ((c.start != null) && (c.expire != null) && ((c.start > Date.now()) || (c.start > c.expire))) { res.status(404); render(req, res, getRenderPage((domain.sitestyle >= 2) ? 'message2' : 'message', req, domain), getRenderArgs({ titleid: 2, msgid: 11, domainurl: encodeURIComponent(domain.url).replace(/'/g, '%27') }, req, domain)); return; }
// If this is a web relay share, check if this feature is active
if ((c.p == 8) || (c.p == 16)) {
@ -9360,6 +9385,15 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
xargs.title1 = domain.title1 ? domain.title1 : '';
xargs.title2 = (domain.title1 && domain.title2) ? domain.title2 : '';
}
xargs.title2 = obj.common.replacePlaceholders(xargs.title2, {
'serverversion': obj.parent.currentVer,
'servername': obj.getWebServerName(domain, req),
'agentsessions': Object.keys(parent.webserver.wsagents).length,
'connectedusers': Object.keys(parent.webserver.wssessions).length,
'userssessions': Object.keys(parent.webserver.wssessions2).length,
'relaysessions': parent.webserver.relaySessionCount,
'relaycount': Object.keys(parent.webserver.wsrelays).length
});
xargs.extitle = encodeURIComponent(xargs.title).split('\'').join('\\\'');
xargs.domainurl = domain.url;
xargs.autocomplete = (domain.autocomplete === false) ? 'autocomplete=off x' : 'autocomplete'; // This option allows autocomplete to be turned off on the login page.