mirror of
https://github.com/Ylianst/MeshCentral.git
synced 2025-03-09 15:40:18 +00:00
Translation fixed, some french strings added.
This commit is contained in:
parent
8458f569b6
commit
c2d0562b39
17 changed files with 23984 additions and 8109 deletions
294
views/translations/agentinvite_fr.handlebars
Normal file
294
views/translations/agentinvite_fr.handlebars
Normal file
|
@ -0,0 +1,294 @@
|
|||
<!DOCTYPE html><html><head>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta content="text/html; charset=utf-8" http-equiv="Content-Type">
|
||||
<meta name="viewport" content="user-scalable=1.0,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="format-detection" content="telephone=no">
|
||||
<link type="text/css" href="styles/style.css" media="screen" rel="stylesheet" title="CSS">
|
||||
<script type="text/javascript" src="scripts/common-0.0.1.js"></script>
|
||||
<title>MeshCentral - Agent Installation</title>
|
||||
<style>
|
||||
.tab {
|
||||
overflow: hidden;
|
||||
border: 1px solid #ccc;
|
||||
background-color: #f1f1f1;
|
||||
}
|
||||
|
||||
.tab button {
|
||||
background-color: inherit;
|
||||
float: left;
|
||||
border: none;
|
||||
outline: none;
|
||||
cursor: pointer;
|
||||
padding: 14px 16px;
|
||||
transition: 0.3s;
|
||||
}
|
||||
|
||||
.tab button:hover {
|
||||
background-color: #ddd;
|
||||
}
|
||||
|
||||
.tab button.active {
|
||||
background-color: #8f8;
|
||||
}
|
||||
|
||||
.tabcontent {
|
||||
display: none;
|
||||
padding: 6px 12px;
|
||||
border: 1px solid #ccc;
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body id="body" onload="if (typeof(startup) !== 'undefined') startup();" style="display:none;overflow:hidden">
|
||||
<div id="container">
|
||||
<!-- Begin Masthead -->
|
||||
<div id="masthead" class="noselect" style="background:url(logo.png) 0px 0px;background-color:#036;background-repeat:no-repeat;height:66px;width:100%;overflow:hidden;">
|
||||
<div style="float:left; height: 66px; color:#c8c8c8; padding-left:20px; padding-top:8px">
|
||||
<strong><font style="font-size:46px; font-family: Arial, Helvetica, sans-serif;">{{{title}}}</font></strong>
|
||||
</div>
|
||||
<div style="float:left; height: 66px; color:#c8c8c8; padding-left:5px; padding-top:14px">
|
||||
<strong><font style="font-size:14px; font-family: Arial, Helvetica, sans-serif;">{{{title2}}}</font></strong>
|
||||
</div>
|
||||
<p id="logoutControl" style="color:white;font-size:11px;margin: 10px 10px 0;">{{{logoutControl}}}</p>
|
||||
</div>
|
||||
<div id="page_leftbar">
|
||||
<div style="height:16px"></div>
|
||||
</div>
|
||||
<div id="topbar" class="noselect style3" style="height:24px;position:relative">
|
||||
<div id="uiMenuButton" title="User interface selection" onclick="showUserInterfaceSelectMenu()">
|
||||
♦
|
||||
<div id="uiMenu" style="display:none">
|
||||
<div id="uiViewButton1" class="uiSelector" onclick="userInterfaceSelectMenu(1)" title="Left bar interface"><div class="uiSelector1"></div></div>
|
||||
<div id="uiViewButton2" class="uiSelector" onclick="userInterfaceSelectMenu(2)" title="Top bar interface"><div class="uiSelector2"></div></div>
|
||||
<div id="uiViewButton3" class="uiSelector" onclick="userInterfaceSelectMenu(3)" title="Fixed width interface"><div class="uiSelector3"></div></div>
|
||||
<div id="uiViewButton4" class="uiSelector" onclick="toggleNightMode()" title="Basculer mode nuit"><div class="uiSelector4"></div></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="column_l" style="max-height:calc(100vh - 135px);overflow-y:auto">
|
||||
<h1>Remote Agent Installation<span id="groupname"></span></h1>
|
||||
<p>
|
||||
You have been invited to install a software that will allow a remote operator to fully access your computer remotely including the desktop and files.
|
||||
Only follow the instructions below if this invitation was expected and you know who will be accessing your computer.
|
||||
Selecting your operation system and follow the instructions below.
|
||||
</p>
|
||||
<div>
|
||||
<div class="tab">
|
||||
<button id="twintab64" class="tablinks" onclick="openTab(event, 'wintab64')">Windows 64bit</button>
|
||||
<button id="twintab32" class="tablinks" onclick="openTab(event, 'wintab32')">Windows 32bit</button>
|
||||
<button id="tlinuxtab" class="tablinks" onclick="openTab(event, 'linuxtab')">Linux</button>
|
||||
<button id="tmacostab" class="tablinks" onclick="openTab(event, 'macostab')">MacOS</button>
|
||||
</div>
|
||||
|
||||
<div id="wintab64" class="tabcontent" style="background-color:white;color:black">
|
||||
<h3>Microsoft™ Windows 64bit</h3>
|
||||
<p><a id="win64url">Download the software here</a>, run it and press "Install" or "Connect".</p>
|
||||
<div style="text-align:center">
|
||||
<img class="winagent-img" src="images/winagent.png">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="wintab32" class="tabcontent" style="background-color:white;color:black">
|
||||
<h3>Microsoft™ Windows 32bit</h3>
|
||||
<p><a id="win32url">Download the software here</a>, run it and press "Install" or "Connect".</p>
|
||||
<div style="text-align:center">
|
||||
<img class="winagent-img" src="images/winagent.png">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="linuxtab" class="tabcontent" style="background-color:white;color:black">
|
||||
<h3>Linux</h3>
|
||||
<p>To install, cut and paste the following command in a root terminal.</p>
|
||||
<div id="linuxinstall" style="font-family:courier,'courier new',monospace;margin-left:30px"></div>
|
||||
<input type="button" value="Copy to clipboard" style="margin-left:30px;margin-top:4px" onclick="copyToClipLinuxInstall()">
|
||||
<p>To uninstall, cut and paste the following command as root.</p>
|
||||
<div id="unlinuxinstall" style="font-family:courier,'courier new',monospace;margin-left:30px"></div>
|
||||
<input type="button" value="Copy to clipboard" style="margin-left:30px;margin-top:4px" onclick="copyToClipLinuxUnInstall()">
|
||||
<br><br>
|
||||
</div>
|
||||
|
||||
<div id="macostab" class="tabcontent" style="background-color:white;color:black">
|
||||
<h3>Apple™ MacOS</h3>
|
||||
<p><a id="macosurl">Download the installer here</a>, right click on it or press "control" and click on the file. Then select "Open" and follow the instructions.</p>
|
||||
<div style="text-align:center">
|
||||
<img src="images/macosagent.png">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="footer">
|
||||
<table cellpadding="0" cellspacing="10" style="width: 100%">
|
||||
<tbody><tr>
|
||||
<td style="text-align:left"></td>
|
||||
<td style="text-align:right"></td>
|
||||
</tr>
|
||||
</tbody></table>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
'use strict';
|
||||
var uiMode = parseInt(getstore('uiMode', 1));
|
||||
var webPageStackMenu = false;
|
||||
var webPageFullScreen = true;
|
||||
var nightMode = (getstore('_nightMode', '0') == '1');
|
||||
var domain = "{{{domain}}}";
|
||||
var domainUrl = "{{{domainurl}}}";
|
||||
var meshid = "{{{meshid}}}";
|
||||
var serverPort = "{{{serverport}}}";
|
||||
var serverHttps = "{{{serverhttps}}}";
|
||||
var serverNoProxy = "{{{servernoproxy}}}";
|
||||
var installFlags = "{{{installflags}}}";
|
||||
var groupName = decodeURIComponent("{{{meshname}}}");
|
||||
if (groupName != '') { QH('groupname', ' for ' + groupName); }
|
||||
userInterfaceSelectMenu();
|
||||
setup();
|
||||
|
||||
// Toggle user interface menu
|
||||
function showUserInterfaceSelectMenu() {
|
||||
Q('uiViewButton1').classList.remove('uiSelectorSel');
|
||||
Q('uiViewButton2').classList.remove('uiSelectorSel');
|
||||
Q('uiViewButton3').classList.remove('uiSelectorSel');
|
||||
Q('uiViewButton4').classList.remove('uiSelectorSel');
|
||||
try { Q('uiViewButton' + uiMode).classList.add('uiSelectorSel'); } catch (ex) { }
|
||||
QV('uiMenu', (QS('uiMenu').display == 'none'));
|
||||
if (nightMode) { Q('uiViewButton4').classList.add('uiSelectorSel'); }
|
||||
}
|
||||
|
||||
function userInterfaceSelectMenu(s) {
|
||||
if (s) { uiMode = s; putstore('uiMode', uiMode); }
|
||||
webPageFullScreen = (uiMode < 3);
|
||||
webPageStackMenu = true;//(uiMode > 1);
|
||||
toggleFullScreen(0);
|
||||
toggleStackMenu(0);
|
||||
QC('column_l').add('room4submenu');
|
||||
}
|
||||
|
||||
function toggleNightMode() {
|
||||
nightMode = !nightMode;
|
||||
if (nightMode) { QC('body').add('night'); } else { QC('body').remove('night'); }
|
||||
putstore('_nightMode', nightMode ? '1' : '0');
|
||||
}
|
||||
|
||||
// Toggle the web page to full screen
|
||||
function toggleFullScreen(toggle) {
|
||||
if (toggle === 1) { webPageFullScreen = !webPageFullScreen; putstore('webPageFullScreen', webPageFullScreen); }
|
||||
var hide = 0;
|
||||
//if (args.hide) { hide = parseInt(args.hide); }
|
||||
if (webPageFullScreen == false) {
|
||||
QC('body').remove("menu_stack");
|
||||
QC('body').remove("fullscreen");
|
||||
QC('body').remove("arg_hide");
|
||||
//if (xxcurrentView >= 10) QC('column_l').add('room4submenu');
|
||||
//QV('UserDummyMenuSpan', false);
|
||||
//QV('page_leftbar', false);
|
||||
} else {
|
||||
QC('body').add("fullscreen");
|
||||
if (hide & 16) QC('body').add("arg_hide"); // This is replacement for QV('page_leftbar', !(hide & 16));
|
||||
//QV('UserDummyMenuSpan', (xxcurrentView < 10) && webPageFullScreen);
|
||||
//QV('page_leftbar', true);
|
||||
}
|
||||
QV('body', true);
|
||||
}
|
||||
|
||||
// If FullScreen, toggle menu to be horisontal or vertical
|
||||
function toggleStackMenu(toggle) {
|
||||
if (webPageFullScreen == true) {
|
||||
if (toggle === 1) {
|
||||
webPageStackMenu = !webPageStackMenu;
|
||||
putstore('webPageStackMenu', webPageStackMenu);
|
||||
}
|
||||
if (webPageStackMenu == false) {
|
||||
QC('body').remove("menu_stack");
|
||||
} else {
|
||||
QC('body').add("menu_stack");
|
||||
//if (xxcurrentView >= 10) QC('column_l').remove('room4submenu');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function putstore(name, val) { try { if (typeof (localStorage) === 'undefined') return; localStorage.setItem(name, val); } catch (e) { } }
|
||||
function getstore(name, val) { try { if (typeof (localStorage) === 'undefined') return val; var v = localStorage.getItem(name); if ((v == null) || (v == null)) return val; return v; } catch (e) { return val; } }
|
||||
|
||||
function openTab(evt, tabname) {
|
||||
// Declare all variables
|
||||
var i, tabcontent, tablinks;
|
||||
|
||||
// Get all elements with class="tabcontent" and hide them
|
||||
tabcontent = document.getElementsByClassName("tabcontent");
|
||||
for (i = 0; i < tabcontent.length; i++) {
|
||||
tabcontent[i].style.display = "none";
|
||||
}
|
||||
|
||||
// Get all elements with class="tablinks" and remove the class "active"
|
||||
tablinks = document.getElementsByClassName("tablinks");
|
||||
for (i = 0; i < tablinks.length; i++) {
|
||||
tablinks[i].className = tablinks[i].className.replace("actif", "");
|
||||
}
|
||||
|
||||
// Show the current tab, and add an "active" class to the button that opened the tab
|
||||
document.getElementById(tabname).style.display = "block";
|
||||
if (evt != null) { evt.currentTarget.className += "actif"; } else { document.getElementById('t' + tabname).className += "actif"; }
|
||||
}
|
||||
|
||||
var linuxInstall, linuxUnInstall;
|
||||
function setup() {
|
||||
var servername = window.location.hostname;
|
||||
var domainUrlNoSlash = domainUrl.substring(0, domainUrl.length - 1);
|
||||
|
||||
// Windows 64bit Setup
|
||||
var url = 'meshagents?id=4&meshid=' + meshid;
|
||||
if (installFlags != 0) { url += ('&installflags=' + installFlags); }
|
||||
Q('win64url').href = url;
|
||||
|
||||
// Windows 32bit Setup
|
||||
url = 'meshagents?id=3&meshid=' + meshid;
|
||||
if (installFlags != 0) { url += ('&installflags=' + installFlags); }
|
||||
Q('win32url').href = url;
|
||||
|
||||
// MacOS Setup
|
||||
url = 'meshosxagent?id=16&meshid=' + meshid;
|
||||
Q('macosurl').href = url;
|
||||
|
||||
// Linux Setup
|
||||
if (serverHttps == 1) {
|
||||
var portStr = (serverPort == 443) ? '' : (":" + serverPort);
|
||||
if (serverNoProxy == 0) {
|
||||
linuxInstall = "(wget https://" + servername + portStr + domainUrl + "meshagents?script=1 --no-check-certificate -O ./meshinstall.sh || wget https://" + servername + portStr + domainUrl + "meshagents?script=1 --no-proxy --no-check-certificate -O ./meshinstall.sh) && chmod 755 ./meshinstall.sh && sudo ./meshinstall.sh https://" + servername + portStr + domainUrlNoSlash + " '" + meshid + "'\r\n";
|
||||
linuxUnInstall = "(wget https://" + servername + portStr + domainUrl + "meshagents?script=1 --no-check-certificate -O ./meshinstall.sh || wget https://" + servername + portStr + domainUrl + "meshagents?script=1 --no-proxy --no-check-certificate -O ./meshinstall.sh) && chmod 755 ./meshinstall.sh && sudo ./meshinstall.sh uninstall\r\n";
|
||||
} else {
|
||||
// Server asked that agent be installed to preferably not use a HTTP proxy.
|
||||
linuxInstall = "wget https://" + servername + portStr + domainUrl + "meshagents?script=1 --no-proxy --no-check-certificate -O ./meshinstall.sh && chmod 755 ./meshinstall.sh && sudo ./meshinstall.sh https://" + servername + portStr + domainUrlNoSlash + " '" + meshid + "'\r\n";
|
||||
linuxUnInstall = "wget https://" + servername + portStr + domainUrl + "meshagents?script=1 --no-proxy --no-check-certificate -O ./meshinstall.sh && chmod 755 ./meshinstall.sh && sudo ./meshinstall.sh uninstall\r\n";
|
||||
}
|
||||
} else {
|
||||
var portStr = (serverPort == 80) ? '' : (":" + serverPort);
|
||||
if (serverNoProxy == 0) {
|
||||
linuxInstall = "(wget http://" + servername + portStr + domainUrl + "meshagents?script=1 -O ./meshinstall.sh || wget http://" + servername + portStr + domainUrl + "meshagents?script=1 --no-proxy -O ./meshinstall.sh) && chmod 755 ./meshinstall.sh && sudo ./meshinstall.sh http://" + servername + portStr + domainUrlNoSlash + " '" + meshid + "'\r\n";
|
||||
linuxUnInstall = "(wget http://" + servername + portStr + domainUrl + "meshagents?script=1 -O ./meshinstall.sh || wget http://" + servername + portStr + domainUrl + "meshagents?script=1 --no-proxy -O ./meshinstall.sh) && chmod 755 ./meshinstall.sh && sudo ./meshinstall.sh uninstall\r\n";
|
||||
} else {
|
||||
// Server asked that agent be installed to preferably not use a HTTP proxy.
|
||||
linuxInstall = "wget http://" + servername + portStr + domainUrl + "meshagents?script=1 --no-proxy -O ./meshinstall.sh && chmod 755 ./meshinstall.sh && sudo ./meshinstall.sh http://" + servername + portStr + domainUrlNoSlash + " '" + meshid + "'\r\n";
|
||||
linuxUnInstall = "wget http://" + servername + portStr + domainUrl + "meshagents?script=1 --no-proxy -O ./meshinstall.sh && chmod 755 ./meshinstall.sh && sudo ./meshinstall.sh uninstall\r\n";
|
||||
}
|
||||
}
|
||||
QH('linuxinstall', linuxInstall);
|
||||
QH('unlinuxinstall', linuxUnInstall);
|
||||
|
||||
// Attempt to detect the most likely operating system for this browser
|
||||
if (navigator.userAgent.indexOf('Win64') >= 0) { openTab(null, 'wintab64'); }
|
||||
else if (navigator.userAgent.indexOf('Windows') >= 0) { openTab(null, 'wintab32'); }
|
||||
else if (navigator.userAgent.indexOf('Linux') >= 0) { openTab(null, 'linuxtab'); }
|
||||
else if (navigator.userAgent.indexOf('Macintosh') >= 0) { openTab(null, 'macostab'); }
|
||||
else { openTab(null, 'wintab64'); }
|
||||
}
|
||||
|
||||
function copyToClipLinuxInstall() { copyTextToClip(linuxInstall); }
|
||||
function copyToClipLinuxUnInstall() { copyTextToClip(linuxUnInstall); }
|
||||
function copyTextToClip(txt) { function selectElementText(e) { if (document.selection) { var range = document.body.createTextRange(); range.moveToElementText(e); range.select(); } else if (window.getSelection) { var range = document.createRange(); range.selectNode(e); window.getSelection().removeAllRanges(); window.getSelection().addRange(range); } } var e = document.createElement('DIV'); e.textContent = txt; document.body.appendChild(e); selectElementText(e); document.execCommand('copy'); e.remove(); }
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
</body></html>
|
3379
views/translations/default-mobile_fr.handlebars
Normal file
3379
views/translations/default-mobile_fr.handlebars
Normal file
File diff suppressed because it is too large
Load diff
9308
views/translations/default_fr.handlebars
Normal file
9308
views/translations/default_fr.handlebars
Normal file
File diff suppressed because it is too large
Load diff
41
views/translations/download_fr.handlebars
Normal file
41
views/translations/download_fr.handlebars
Normal file
|
@ -0,0 +1,41 @@
|
|||
<!DOCTYPE html><html dir="ltr" xmlns="http://www.w3.org/1999/xhtml"><head>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta content="text/html;charset=utf-8" http-equiv="Content-Type">
|
||||
<meta name="viewport" content="user-scalable=1.0,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="format-detection" content="telephone=no">
|
||||
<link type="text/css" href="/styles/style.css" media="screen" rel="stylesheet" title="CSS">
|
||||
<title>MeshCentral - Download</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="container" style="max-height:100vh">
|
||||
<div id="mastheadx"></div>
|
||||
<div id="masthead" style="background:url(logo.png) 0px 0px;background-color:#036;background-repeat:no-repeat;height:66px;width:100%;overflow:hidden">
|
||||
<div style="float:left;height:66px;color:#c8c8c8;padding-left:20px;padding-top:8px">
|
||||
<strong><font style="font-size:46px;font-family:Arial,Helvetica,sans-serif">{{{title}}}</font></strong>
|
||||
</div>
|
||||
<div style="float:left;height:66px;color:#c8c8c8;padding-left:5px;padding-top:14px">
|
||||
<strong><font style="font-size:14px;font-family:Arial,Helvetica,sans-serif">{{{title2}}}</font></strong>
|
||||
</div>
|
||||
</div>
|
||||
<div id="page_content" style="max-height:calc(100vh-138px)">
|
||||
<div id="column_l">
|
||||
<h1>Download</h1>
|
||||
<p style="margin-left:20px">{{{message}}}</p>
|
||||
<br>
|
||||
</div>
|
||||
<div id="footer">
|
||||
<table cellpadding="0" cellspacing="10" style="width:100%">
|
||||
<tbody><tr>
|
||||
<td style="text-align:left"></td>
|
||||
<td style="text-align:right">
|
||||
{{{rootCertLink}}}
|
||||
<a href="terms">Terms & Privacy</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody></table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</body></html>
|
132
views/translations/error404_fr.handlebars
Normal file
132
views/translations/error404_fr.handlebars
Normal file
|
@ -0,0 +1,132 @@
|
|||
<!DOCTYPE html><html><head>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta content="text/html; charset=utf-8" http-equiv="Content-Type">
|
||||
<meta name="viewport" content="user-scalable=1.0,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="format-detection" content="telephone=no">
|
||||
<link type="text/css" href="styles/style.css" media="screen" rel="stylesheet" title="CSS">
|
||||
<script type="text/javascript" src="scripts/common-0.0.1.js"></script>
|
||||
<title>MeshCentral - Terms of use</title>
|
||||
</head>
|
||||
<body id="body" onload="if (typeof(startup) !== 'undefined') startup();" style="display:none;overflow:hidden">
|
||||
<div id="container">
|
||||
<!-- Begin Masthead -->
|
||||
<div id="masthead" class="noselect" style="background:url(logo.png) 0px 0px;background-color:#036;background-repeat:no-repeat;height:66px;width:100%;overflow:hidden;">
|
||||
<div style="float:left; height: 66px; color:#c8c8c8; padding-left:20px; padding-top:8px">
|
||||
<strong><font style="font-size:46px; font-family: Arial, Helvetica, sans-serif;">{{{title}}}</font></strong>
|
||||
</div>
|
||||
<div style="float:left; height: 66px; color:#c8c8c8; padding-left:5px; padding-top:14px">
|
||||
<strong><font style="font-size:14px; font-family: Arial, Helvetica, sans-serif;">{{{title2}}}</font></strong>
|
||||
</div>
|
||||
<p id="logoutControl" style="color:white;font-size:11px;margin: 10px 10px 0;">{{{logoutControl}}}</p>
|
||||
</div>
|
||||
<div id="page_leftbar">
|
||||
<div style="height:16px"></div>
|
||||
</div>
|
||||
<div id="topbar" class="noselect style3" style="height:24px;position:relative">
|
||||
<div id="uiMenuButton" title="User interface selection" onclick="showUserInterfaceSelectMenu()">
|
||||
♦
|
||||
<div id="uiMenu" style="display:none">
|
||||
<div id="uiViewButton1" class="uiSelector" onclick="userInterfaceSelectMenu(1)" title="Left bar interface"><div class="uiSelector1"></div></div>
|
||||
<div id="uiViewButton2" class="uiSelector" onclick="userInterfaceSelectMenu(2)" title="Top bar interface"><div class="uiSelector2"></div></div>
|
||||
<div id="uiViewButton3" class="uiSelector" onclick="userInterfaceSelectMenu(3)" title="Fixed width interface"><div class="uiSelector3"></div></div>
|
||||
<div id="uiViewButton4" class="uiSelector" onclick="toggleNightMode()" title="Basculer mode nuit"><div class="uiSelector4"></div></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="column_l" style="max-height:calc(100vh - 135px);overflow-y:auto">
|
||||
<div style="text-align:center;padding-top:30px;font-size:200px;font-family:Arial;color:#bbb"><b>404</b></div>
|
||||
<div style="text-align:center;font-size:20px;font-family:Arial;color:#999">This page does not exist</div>
|
||||
<div style="text-align:center;padding-top:20px;font-size:20px;font-family:Arial;color:#999"><a href="/" style="text-decoration:none"><b>Go to main site</b></a></div>
|
||||
</div>
|
||||
<div id="footer">
|
||||
<table cellpadding="0" cellspacing="10" style="width: 100%">
|
||||
<tbody><tr>
|
||||
<td style="text-align:left"></td>
|
||||
<td style="text-align:right"><a href="/">Retour</a></td>
|
||||
</tr>
|
||||
</tbody></table>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
'use strict';
|
||||
var uiMode = parseInt(getstore('uiMode', 1));
|
||||
var webPageStackMenu = false;
|
||||
var webPageFullScreen = true;
|
||||
var nightMode = (getstore('_nightMode', '0') == '1');
|
||||
|
||||
var terms = '{{{terms}}}';
|
||||
if (terms != '') { QH('column_l', decodeURIComponent(terms)); }
|
||||
QV('column_l', true);
|
||||
userInterfaceSelectMenu();
|
||||
|
||||
// Toggle user interface menu
|
||||
function showUserInterfaceSelectMenu() {
|
||||
Q('uiViewButton1').classList.remove('uiSelectorSel');
|
||||
Q('uiViewButton2').classList.remove('uiSelectorSel');
|
||||
Q('uiViewButton3').classList.remove('uiSelectorSel');
|
||||
Q('uiViewButton4').classList.remove('uiSelectorSel');
|
||||
try { Q('uiViewButton' + uiMode).classList.add('uiSelectorSel'); } catch (ex) { }
|
||||
QV('uiMenu', (QS('uiMenu').display == 'none'));
|
||||
if (nightMode) { Q('uiViewButton4').classList.add('uiSelectorSel'); }
|
||||
}
|
||||
|
||||
function userInterfaceSelectMenu(s) {
|
||||
if (s) { uiMode = s; putstore('uiMode', uiMode); }
|
||||
webPageFullScreen = (uiMode < 3);
|
||||
webPageStackMenu = true;//(uiMode > 1);
|
||||
toggleFullScreen(0);
|
||||
toggleStackMenu(0);
|
||||
QC('column_l').add('room4submenu');
|
||||
}
|
||||
|
||||
function toggleNightMode() {
|
||||
nightMode = !nightMode;
|
||||
if (nightMode) { QC('body').add('night'); } else { QC('body').remove('night'); }
|
||||
putstore('_nightMode', nightMode ? '1' : '0');
|
||||
}
|
||||
|
||||
// Toggle the web page to full screen
|
||||
function toggleFullScreen(toggle) {
|
||||
if (toggle === 1) { webPageFullScreen = !webPageFullScreen; putstore('webPageFullScreen', webPageFullScreen); }
|
||||
var hide = 0;
|
||||
//if (args.hide) { hide = parseInt(args.hide); }
|
||||
if (webPageFullScreen == false) {
|
||||
QC('body').remove('menu_stack');
|
||||
QC('body').remove('fullscreen');
|
||||
QC('body').remove('arg_hide');
|
||||
//if (xxcurrentView >= 10) QC('column_l').add('room4submenu');
|
||||
//QV('UserDummyMenuSpan', false);
|
||||
//QV('page_leftbar', false);
|
||||
} else {
|
||||
QC('body').add('fullscreen');
|
||||
if (hide & 16) QC('body').add('arg_hide'); // This is replacement for QV('page_leftbar', !(hide & 16));
|
||||
//QV('UserDummyMenuSpan', (xxcurrentView < 10) && webPageFullScreen);
|
||||
//QV('page_leftbar', true);
|
||||
}
|
||||
QV('body', true);
|
||||
}
|
||||
|
||||
// If FullScreen, toggle menu to be horisontal or vertical
|
||||
function toggleStackMenu(toggle) {
|
||||
if (webPageFullScreen == true) {
|
||||
if (toggle === 1) {
|
||||
webPageStackMenu = !webPageStackMenu;
|
||||
putstore('webPageStackMenu', webPageStackMenu);
|
||||
}
|
||||
if (webPageStackMenu == false) {
|
||||
QC('body').remove('menu_stack');
|
||||
} else {
|
||||
QC('body').add('menu_stack');
|
||||
//if (xxcurrentView >= 10) QC('column_l').remove('room4submenu');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function putstore(name, val) { try { if (typeof (localStorage) === 'undefined') return; localStorage.setItem(name, val); } catch (e) { } }
|
||||
function getstore(name, val) { try { if (typeof (localStorage) === 'undefined') return val; var v = localStorage.getItem(name); if ((v == null) || (v == null)) return val; return v; } catch (e) { return val; } }
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
</body></html>
|
631
views/translations/login-mobile_fr.handlebars
Normal file
631
views/translations/login-mobile_fr.handlebars
Normal file
|
@ -0,0 +1,631 @@
|
|||
<!DOCTYPE html><html dir="ltr" xmlns="http://www.w3.org/1999/xhtml"><head>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta content="text/html; charset=utf-8" http-equiv="Content-Type">
|
||||
<meta name="viewport" content="user-scalable=1.0,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="format-detection" content="telephone=no">
|
||||
<link rel="shortcut icon" type="image/x-icon" href="{{{domainurl}}}favicon.ico">
|
||||
<script type="text/javascript" src="scripts/common-0.0.1.js"></script>
|
||||
<script type="text/javascript" src="scripts/u2f-api.js"></script>
|
||||
<title>MeshCentral - Login</title>
|
||||
<style>
|
||||
a {
|
||||
color: #036;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
#footer a {
|
||||
color: #fff;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
#footer a:hover {
|
||||
color: #fff;
|
||||
text-decoration: none;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body onload="if (typeof(startup) !== 'undefined') startup();" style="overflow-y:hidden;margin:0;padding:0;border:0;color:black;font-size:13px;font-family:\'Trebuchet MS\', Arial, Helvetica, sans-serif">
|
||||
<div id="container">
|
||||
<div id="mastheadx"></div>
|
||||
<div id="masthead" style="background:url(logo.png) 0px 0px;background-size:341px 50px;background-color:#036;background-repeat:no-repeat;height:50px;width:100%;overflow:hidden">
|
||||
<div style="float:left;height:66px;color:#c8c8c8;padding-left:10px;padding-top:6px">
|
||||
<strong><font style="font-size:36px;font-family:Arial,Helvetica,sans-serif">{{{title}}}</font></strong>
|
||||
</div>
|
||||
<div style="float:left;height:66px;color:#c8c8c8;padding-left:5px;padding-top:10px">
|
||||
<strong><font style="font-size:12px;font-family:Arial,Helvetica,sans-serif">{{{title2}}}</font></strong>
|
||||
</div>
|
||||
</div>
|
||||
<div id="page_content" style="overflow-y:scroll;position:absolute;bottom:32px;top:50px;width:100%;display:flex;align-items:center">
|
||||
<div id="column_l" style="padding:10px;width:100%">
|
||||
<table style="width:100%">
|
||||
<tbody><tr>
|
||||
<td align="center">
|
||||
<div id="loginpanel" style="background-color:#979797;border-radius:16px;width:260px;padding:16px;text-align:center;clear:both;display:none">
|
||||
<form method="post">
|
||||
<input type="hidden" name="action" value="login">
|
||||
<div id="message1">
|
||||
{{{message}}}
|
||||
</div>
|
||||
<div>
|
||||
<b>Log In</b>
|
||||
</div>
|
||||
<table>
|
||||
<tbody><tr>
|
||||
<td id="loginusername" align="right" width="100">Nom d'utilisateur:</td>
|
||||
<td><input id="username" type="text" maxlength="64" name="username" onchange="validateLogin(1)" onkeyup="validateLogin(1,event)"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right">Password:</td>
|
||||
<td><input id="password" type="password" maxlength="256" name="password" autocomplete="off" onchange="validateLogin(2)" onkeyup="validateLogin(2,event)"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><div id="showPassHintLink" style="display:none"><a onclick="showPassHint()" style="cursor:pointer">Show Hint</a></div></td>
|
||||
<td align="right"><input id="loginButton" type="submit" value="Log In" disabled="disabled"></td>
|
||||
</tr>
|
||||
</tbody></table>
|
||||
<div id="hrAccountDiv" style="display:none"><hr></div>
|
||||
<div id="resetAccountDiv" style="display:none;padding:2px">
|
||||
<span id="resetAccountSpan">Forgot user/password?</span> <a onclick="xgo(3)" style="cursor:pointer">Reset account</a>.
|
||||
</div>
|
||||
<div id="newAccountDiv" style="display:none;padding:2px">
|
||||
Don't have an account? <a onclick="xgo(2)" style="cursor:pointer">Create one</a>.
|
||||
</div>
|
||||
<input id="loginformargs" name="urlargs" type="hidden" value="">
|
||||
</form>
|
||||
</div>
|
||||
<div id="createpanel" style="display:none">
|
||||
<div style="background-color:#979797;border-radius:16px;width:260px;padding:16px;text-align:center;clear:both;position:relative">
|
||||
<form method="post">
|
||||
<input type="hidden" name="action" value="createaccount">
|
||||
<div id="message2">
|
||||
{{{message}}}
|
||||
</div>
|
||||
<div>
|
||||
<b>Account Creation</b>
|
||||
</div>
|
||||
<div id="passwordPolicyCallout" style="left:-5px;top:10px;width:100px;position:absolute;background-color:#FFC;border-radius:5px;padding:5px;box-shadow:0px 0px 15px #666;font-size:10px"></div>
|
||||
<table>
|
||||
<tbody><tr id="nuUserRow">
|
||||
<td align="right" width="100">Nom d'utilisateur:</td>
|
||||
<td><input id="ausername" type="text" name="username" onchange="validateCreate(1)" maxlength="64" onkeydown="haltReturn(event)" onkeyup="validateCreate(1,event)"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right" width="100">Email:</td>
|
||||
<td><input id="aemail" type="text" name="email" onchange="validateCreate(2)" maxlength="256" onkeydown="haltReturn(event)" onkeyup="validateCreate(2,event)"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right">Password:</td>
|
||||
<td><input id="apassword1" type="password" name="password1" autocomplete="off" maxlength="256" onkeydown="haltReturn(event)" onchange="validateCreate(3)" onkeyup="validateCreate(3,event)"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right">Password:</td>
|
||||
<td><input id="apassword2" type="password" name="password2" autocomplete="off" maxlength="256" onkeydown="haltReturn(event)" onchange="validateCreate(4)" onkeyup="validateCreate(4,event)"></td>
|
||||
</tr>
|
||||
<tr id="createPanelHint" style="display:none">
|
||||
<td align="right">Pass Hint:</td>
|
||||
<td><input id="apasswordhint" type="text" name="apasswordhint" autocomplete="off" maxlength="256" onkeydown="haltReturn(event)" onchange="validateCreate(5)" onkeyup="validateCreate(5,event)"></td>
|
||||
</tr>
|
||||
<tr id="newAccountPass" title="Enter the account creation token">
|
||||
<td align="right">Creation Token:</td>
|
||||
<td><input id="anewaccountpass" type="password" name="anewaccountpass" autocomplete="off" maxlength="256" onkeydown="haltReturn(event)" onchange="validateCreate(6)" onkeyup="validateCreate(6,event)"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<div style="float:right"><input id="createButton" type="submit" value="Create Account" disabled="disabled"></div>
|
||||
<div id="passWarning" style="padding-top:6px"></div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody></table>
|
||||
<hr><a onclick="xgo(1)" style="cursor:pointer">Back to login</a>
|
||||
<input id="createformargs" name="urlargs" type="hidden" value="">
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div id="resetpanel" style="background-color:#979797;border-radius:16px;width:260px;padding:16px;text-align:center;display:none;clear:both">
|
||||
<form method="post">
|
||||
<input type="hidden" name="action" value="resetaccount">
|
||||
<div id="message3">
|
||||
{{{message}}}
|
||||
</div>
|
||||
<div>
|
||||
<b>Account Reset</b>
|
||||
</div>
|
||||
<table>
|
||||
<tbody><tr>
|
||||
<td align="right" width="100">Email:</td>
|
||||
<td><input id="remail" type="text" name="email" maxlength="256" onchange="validateReset()" onkeyup="validateReset(event)"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<div style="float:right"><input id="eresetButton" type="submit" value="Reset Account" disabled="disabled"></div>
|
||||
<div id="passWarning" style="padding-top:6px"></div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody></table>
|
||||
<hr><a onclick="xgo(1)" style="cursor:pointer">Back to login</a>
|
||||
<input id="resetformargs" name="urlargs" type="hidden" value="">
|
||||
</form>
|
||||
</div>
|
||||
<div id="tokenpanel" style="background-color:#979797;border-radius:16px;width:260px;padding:16px;text-align:center;display:none;clear:both">
|
||||
<form method="post" autocomplete="off">
|
||||
<input type="hidden" name="action" value="tokenlogin">
|
||||
<input type="hidden" name="hwstate" value="{{{hwstate}}}">
|
||||
<div id="message4">
|
||||
{{{message}}}
|
||||
</div>
|
||||
<table>
|
||||
<tbody><tr>
|
||||
<td align="right" width="100">Login token:</td>
|
||||
<td>
|
||||
<input id="tokenInput" type="text" name="token" maxlength="50" onchange="checkToken(event)" onkeyup="checkToken(event)" onkeydown="checkToken(event)" onfocus="checkTokenTimer(1)" onblur="checkTokenTimer(0)">
|
||||
<input id="hwtokenInput" type="text" name="hwtoken" style="display:none">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<div style="float:right"><input id="tokenOkButton" type="submit" value="Login" disabled="disabled"></div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody></table>
|
||||
<hr><a onclick="xgo(1)" style="cursor:pointer">Back to login</a>
|
||||
<input id="tokenformargs" name="urlargs" type="hidden" value="">
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div id="resettokenpanel" style="background-color:#979797;border-radius:16px;width:260px;padding:16px;text-align:center;display:none;clear:both">
|
||||
<form method="post" autocomplete="off">
|
||||
<input type="hidden" name="action" value="resetaccount">
|
||||
<div id="message5">
|
||||
{{{message}}}
|
||||
</div>
|
||||
<table>
|
||||
<tbody><tr>
|
||||
<td align="right" width="100">Login token:</td>
|
||||
<td>
|
||||
<input id="resetTokenInput" type="text" name="token" maxlength="50" onchange="resetCheckToken(event)" onpaste="resetCheckToken(event)" onkeyup="resetCheckToken(event)" onkeydown="resetCheckToken(event)">
|
||||
<input id="resetHwtokenInput" type="text" name="hwtoken" style="display:none">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<div style="float:right"><input id="resetTokenOkButton" type="submit" value="Login" disabled="disabled"></div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody></table>
|
||||
<hr><a onclick="xgo(1)" style="cursor:pointer">Back to login</a>
|
||||
<input id="resettokenformargs" name="urlargs" type="hidden" value="">
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div id="resetpasswordpanel" style="position:relative;background-color:#979797;border-radius:16px;width:300px;padding:16px;text-align:center;display:none">
|
||||
<form method="post">
|
||||
<input type="hidden" name="action" value="resetpassword">
|
||||
<div id="message6">
|
||||
{{{message}}}
|
||||
</div>
|
||||
<div id="rpasswordPolicyCallout" style="left:-10px;width:100px;display:none;position:absolute;background-color:#FFC;border-radius:5px;padding:5px;box-shadow:0px 0px 15px #666;font-size:10px"></div>
|
||||
<table>
|
||||
<tbody><tr>
|
||||
<td id="rnuPass1" width="100" align="right">Password:</td>
|
||||
<td><input id="rapassword1" type="password" name="rpassword1" autocomplete="off" maxlength="256" onkeydown="haltReturn(event)" onchange="validatePassReset(3,event)" onkeyup="validatePassReset(3,event)"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td id="rnuPass2" align="right">Password:</td>
|
||||
<td><input id="rapassword2" type="password" name="rpassword2" autocomplete="off" maxlength="256" onkeydown="haltReturn(event)" onchange="validatePassReset(4,event)" onkeyup="validatePassReset(4,event)"></td>
|
||||
</tr>
|
||||
<tr id="resetpasswordpanelHint" style="display:none">
|
||||
<td id="rnuHint" align="right">Password Hint:</td>
|
||||
<td><input id="rapasswordhint" type="text" name="rpasswordhint" autocomplete="off" maxlength="256" onkeydown="haltReturn(event)" onchange="validatePassReset(5,event)" onkeyup="validatePassReset(5,event)"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<div style="float:right"><input id="resetPassButton" type="submit" value="Reset Password" disabled="disabled"></div>
|
||||
<div id="rpassWarning" style="padding-top:6px"></div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody></table>
|
||||
<hr><a onclick="xgo(1)" style="cursor:pointer">Back to login</a>
|
||||
<input id="resetpasswordformargs" name="urlargs" type="hidden" value="">
|
||||
</form>
|
||||
</div>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</tbody></table>
|
||||
</div>
|
||||
</div>
|
||||
<div id="footer" style="height:32px;width:100%;text-align:center;background-color:#113962;position:absolute;bottom:0px">
|
||||
<table cellpadding="0" cellspacing="6" style="width:100%">
|
||||
<tbody><tr>
|
||||
<td style="text-align:left;color:white">{{{footer}}}</td>
|
||||
<td style="text-align:right">{{{rootCertLink}}} <a href="terms">Terms & Privacy</a></td>
|
||||
</tr>
|
||||
</tbody></table>
|
||||
</div>
|
||||
</div>
|
||||
<div id="dialog" style="z-index:1000;background-color:#EEE;box-shadow:0px 0px 15px #666;font-family:Arial,Helvetica,sans-serif;border-radius:5px;position:fixed;top:180px;width:400px;display:none">
|
||||
<div style="width:100%;background-color:#003366;color:#FFF;border-radius:5px 5px 0 0">
|
||||
<div id="id_dialogclose" style="float:right;padding:5px;cursor:pointer" onclick="setDialogMode()"><b>X</b></div>
|
||||
<div id="id_dialogtitle" style="padding:5px"></div>
|
||||
<div style="width:100%;margin:6px"></div>
|
||||
</div>
|
||||
<div style="margin-right:16px;margin-left:8px">
|
||||
<div id="dialog1" style="margin:auto;text-align:center;margin:3px">
|
||||
<div id="id_dialogMessage" style="padding:10px"></div>
|
||||
</div>
|
||||
<div id="dialog2" style="margin:auto;margin:3px">
|
||||
<div id="id_dialogOptions"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="idx_dlgButtonBar" style="padding:10px;margin-bottom:20px">
|
||||
<input id="idx_dlgCancelButton" type="button" value="Annuler" style="float:right;width:80px;margin-left:5px" onclick="dialogclose(0)">
|
||||
<input id="idx_dlgOkButton" type="button" value="OK" style="float:right;width:80px" onclick="dialogclose(1)">
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
'use strict';
|
||||
var passhint = '{{{passhint}}}';
|
||||
var newAccountPass = parseInt('{{{newAccountPass}}}');
|
||||
var emailCheck = ('{{{emailcheck}}}' == 'true');
|
||||
var features = parseInt('{{{features}}}');
|
||||
var passRequirements = '{{{passRequirements}}}';
|
||||
if (passRequirements != '') { passRequirements = JSON.parse(decodeURIComponent(passRequirements)); } else { passRequirements = {}; }
|
||||
var passRequirementsEx = ((passRequirements.min != null) || (passRequirements.max != null) || (passRequirements.upper != null) || (passRequirements.lower != null) || (passRequirements.numeric != null) || (passRequirements.nonalpha != null));
|
||||
var hardwareKeyChallenge = decodeURIComponent('{{{hkey}}}');
|
||||
var currentpanel = 0;
|
||||
|
||||
// If URL arguments are provided, add them to form posts
|
||||
if (window.location.href.indexOf('?') > 0) {
|
||||
var urlargs = window.location.href.substring(window.location.href.indexOf('?'));
|
||||
Q('loginformargs').value = urlargs;
|
||||
Q('createformargs').value = urlargs;
|
||||
Q('resetformargs').value = urlargs;
|
||||
Q('tokenformargs').value = urlargs;
|
||||
Q('resettokenformargs').value = urlargs;
|
||||
Q('resetpasswordformargs').value = urlargs;
|
||||
}
|
||||
|
||||
function startup() {
|
||||
if ((features & 32) == 0) {
|
||||
// Guard against other site's top frames (web bugs).
|
||||
var loc = null;
|
||||
try { loc = top.location.toString().toLowerCase(); } catch (e) { }
|
||||
if (top != self && (loc == null || top.active == false)) { top.location = self.location; return; }
|
||||
}
|
||||
|
||||
if (features & 0x200000) { // Email is username
|
||||
QH('loginusername', "Email:");
|
||||
QH('resetAccountSpan', "Forgot password?");
|
||||
QV('nuUserRow', false);
|
||||
}
|
||||
|
||||
QV('createPanelHint', passRequirements.hint === true);
|
||||
QV('resetpasswordpanelHint', passRequirements.hint === true);
|
||||
|
||||
window.onresize = center;
|
||||
center();
|
||||
validateLogin();
|
||||
validateCreate();
|
||||
if ('{{loginmode}}' != '') { go(parseInt('{{loginmode}}')); } else { go(1); }
|
||||
QV('newAccountDiv', ('{{{newAccount}}}' === '1') || ('{{{newAccount}}}' === 'true')); // If new accounts are not allowed, don't display the new account link.
|
||||
if ((passRequirements.hint === true) && (passhint != null) && (passhint.length > 0)) { QV('showPassHintLink', true); }
|
||||
QV('newAccountPass', (newAccountPass == 1));
|
||||
QV('resetAccountDiv', (emailCheck == true));
|
||||
QV('hrAccountDiv', (emailCheck == true) || (newAccountPass == 1));
|
||||
|
||||
if ('{{loginmode}}' == '4') {
|
||||
try { if (hardwareKeyChallenge.length > 0) { hardwareKeyChallenge = JSON.parse(hardwareKeyChallenge); } else { hardwareKeyChallenge = null; } } catch (ex) { hardwareKeyChallenge = null }
|
||||
if ((hardwareKeyChallenge != null) && (hardwareKeyChallenge.type == 'webAuthn')) {
|
||||
hardwareKeyChallenge.challenge = Uint8Array.from(atob(hardwareKeyChallenge.challenge), function (c) { return c.charCodeAt(0) }).buffer;
|
||||
|
||||
var publicKeyCredentialRequestOptions = { challenge: hardwareKeyChallenge.challenge, allowCredentials: [], timeout: hardwareKeyChallenge.timeout }
|
||||
for (var i = 0; i < hardwareKeyChallenge.keyIds.length; i++) {
|
||||
publicKeyCredentialRequestOptions.allowCredentials.push(
|
||||
{ id: Uint8Array.from(atob(hardwareKeyChallenge.keyIds[i]), function (c) { return c.charCodeAt(0) }), type: 'public-key', transports: ['usb', 'ble', 'nfc'], }
|
||||
);
|
||||
}
|
||||
|
||||
// New WebAuthn hardware keys
|
||||
navigator.credentials.get({ publicKey: publicKeyCredentialRequestOptions }).then(
|
||||
function (rawAssertion) {
|
||||
var assertion = {
|
||||
id: btoa(String.fromCharCode.apply(null, new Uint8Array(rawAssertion.rawId))),
|
||||
clientDataJSON: btoa(String.fromCharCode.apply(null, new Uint8Array(rawAssertion.response.clientDataJSON))),
|
||||
userHandle: btoa(String.fromCharCode.apply(null, new Uint8Array(rawAssertion.response.userHandle))),
|
||||
signature: btoa(String.fromCharCode.apply(null, new Uint8Array(rawAssertion.response.signature))),
|
||||
authenticatorData: btoa(String.fromCharCode.apply(null, new Uint8Array(rawAssertion.response.authenticatorData))),
|
||||
};
|
||||
Q('hwtokenInput').value = JSON.stringify(assertion);
|
||||
QE('tokenOkButton', true);
|
||||
Q('tokenOkButton').click();
|
||||
},
|
||||
function (error) { console.log('credentials-get error', error); }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if ('{{loginmode}}' == '5') {
|
||||
try { if (hardwareKeyChallenge.length > 0) { hardwareKeyChallenge = JSON.parse(hardwareKeyChallenge); } else { hardwareKeyChallenge = null; } } catch (ex) { hardwareKeyChallenge = null }
|
||||
if ((hardwareKeyChallenge != null) && (hardwareKeyChallenge.type == 'webAuthn')) {
|
||||
hardwareKeyChallenge.challenge = Uint8Array.from(atob(hardwareKeyChallenge.challenge), function (c) { return c.charCodeAt(0) }).buffer;
|
||||
|
||||
var publicKeyCredentialRequestOptions = { challenge: hardwareKeyChallenge.challenge, allowCredentials: [], timeout: hardwareKeyChallenge.timeout }
|
||||
for (var i = 0; i < hardwareKeyChallenge.keyIds.length; i++) {
|
||||
publicKeyCredentialRequestOptions.allowCredentials.push(
|
||||
{ id: Uint8Array.from(atob(hardwareKeyChallenge.keyIds[i]), function (c) { return c.charCodeAt(0) }), type: 'public-key', transports: ['usb', 'ble', 'nfc'], }
|
||||
);
|
||||
}
|
||||
|
||||
// New WebAuthn hardware keys
|
||||
navigator.credentials.get({ publicKey: publicKeyCredentialRequestOptions }).then(
|
||||
function (rawAssertion) {
|
||||
var assertion = {
|
||||
id: btoa(String.fromCharCode.apply(null, new Uint8Array(rawAssertion.rawId))),
|
||||
clientDataJSON: btoa(String.fromCharCode.apply(null, new Uint8Array(rawAssertion.response.clientDataJSON))),
|
||||
userHandle: btoa(String.fromCharCode.apply(null, new Uint8Array(rawAssertion.response.userHandle))),
|
||||
signature: btoa(String.fromCharCode.apply(null, new Uint8Array(rawAssertion.response.signature))),
|
||||
authenticatorData: btoa(String.fromCharCode.apply(null, new Uint8Array(rawAssertion.response.authenticatorData))),
|
||||
};
|
||||
Q('resetHwtokenInput').value = JSON.stringify(assertion);
|
||||
QE('resetTokenOkButton', true);
|
||||
Q('resetTokenOkButton').click();
|
||||
},
|
||||
function (error) { console.log('credentials-get error', error); }
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function showPassHint() {
|
||||
if (passRequirements.hint === true) { messagebox("Password Hint", passhint); }
|
||||
}
|
||||
|
||||
function xgo(x) {
|
||||
QV('message1', false);
|
||||
QV('message2', false);
|
||||
QV('message3', false);
|
||||
QV('message4', false);
|
||||
QV('message5', false);
|
||||
QV('message6', false);
|
||||
go(x);
|
||||
}
|
||||
|
||||
function go(x) {
|
||||
currentpanel = x;
|
||||
setDialogMode(0);
|
||||
QV('showPassHintLink', false);
|
||||
QV('loginpanel', x == 1);
|
||||
QV('createpanel', x == 2);
|
||||
QV('resetpanel', x == 3);
|
||||
QV('tokenpanel', x == 4);
|
||||
QV('resettokenpanel', x == 5);
|
||||
QV('resetpasswordpanel', x == 6);
|
||||
if (x == 1) { Q('username').focus(); }
|
||||
if (x == 2) { if (features & 0x200000) { Q('aemail').focus(); } else { Q('ausername').focus(); } } // Email is username
|
||||
if (x == 3) { Q('remail').focus(); }
|
||||
if (x == 4) { Q('tokenInput').focus(); }
|
||||
if (x == 5) { Q('resetTokenInput').focus(); }
|
||||
if (x == 6) { Q('rapassword1').focus(); }
|
||||
}
|
||||
|
||||
function validateLogin(box, e) {
|
||||
var ok = ((Q('username').value.length > 0) && (Q('username').value.indexOf(' ') == -1) && (Q('password').value.length > 0));
|
||||
QE('loginButton', ok);
|
||||
setDialogMode(0);
|
||||
if ((e != null) && (e.keyCode == 13)) { if (box == 1) { Q('password').focus(); } else if (box == 2) { Q('loginButton').click(); } }
|
||||
if (e != null) { haltEvent(e); }
|
||||
}
|
||||
|
||||
function validateCreate(box,e) {
|
||||
setDialogMode(0);
|
||||
var ok = false;
|
||||
if (features & 0x200000) { ok = true; } else { ok = (Q('ausername').value.length > 0) && (Q('ausername').value.indexOf(' ') == -1) && (Q('ausername').value.indexOf('"') == -1) && (Q('ausername').value.indexOf(',') == -1); }
|
||||
ok &= ((validateEmail(Q('aemail').value) == true) && (Q('apassword1').value.length > 0) && (Q('apassword2').value == Q('apassword1').value));
|
||||
if ((newAccountPass == 1) && (Q('anewaccountpass').value.length == 0)) { ok = false; }
|
||||
if (Q('apassword1').value == '') {
|
||||
QH('passWarning', '');
|
||||
QV('passwordPolicyCallout', false);
|
||||
} else {
|
||||
if (!passRequirementsEx) {
|
||||
// No password requirements, display password strength
|
||||
var passStrength = checkPasswordStrength(Q('apassword1').value);
|
||||
if (passStrength >= 80) { QH('passWarning', '<span style=color:green><b>' + "Mot de passe fort" + '</b><span>'); }
|
||||
else if (passStrength >= 60) { QH('passWarning', '<span style=color:blue><b>' + "Good Password" + '</b><span>'); }
|
||||
else { QH('passWarning', '<span style=color:red><b>' + "Mot de passe faible" + '</b><span>'); }
|
||||
} else {
|
||||
// Password requirements provided, use that
|
||||
var passReq = checkPasswordRequirements(Q('apassword1').value, passRequirements);
|
||||
if (passReq == false) {
|
||||
ok = false;
|
||||
//QS('nuPass1').color = '#7b241c';
|
||||
//QS('nuPass2').color = '#7b241c';
|
||||
QH('passWarning', '<span style=color:red><b>' + "Password Policy" + '</b><span>'); // TODO: Display problem hint
|
||||
QV('passwordPolicyCallout', true);
|
||||
QH('passwordPolicyCallout', passwordPolicyText(Q('apassword1').value));
|
||||
} else {
|
||||
QH('passWarning', '');
|
||||
QV('passwordPolicyCallout', false);
|
||||
}
|
||||
}
|
||||
}
|
||||
QE('createButton', ok);
|
||||
if ((e != null) && (e.keyCode == 13)) {
|
||||
if (box == 1) { Q('aemail').focus(); }
|
||||
if (box == 2) { Q('apassword1').focus(); }
|
||||
if (box == 3) { Q('apassword2').focus(); }
|
||||
if (box == 4) { Q('apasswordhint').focus(); }
|
||||
if (box == 5) { if (newAccountPass == 1) { Q('anewaccountpass').focus(); } else { Q('createButton').click(); } }
|
||||
if (box == 6) { Q('createButton').click(); }
|
||||
}
|
||||
if (e != null) { haltEvent(e); }
|
||||
}
|
||||
|
||||
function validatePassReset(box, e) {
|
||||
setDialogMode(0);
|
||||
var pass1ok = (Q('rapassword1').value.length > 0);
|
||||
var pass2ok = (Q('rapassword2').value.length > 0) && (Q('rapassword2').value == Q('rapassword1').value);
|
||||
var ok = (pass1ok && pass2ok);
|
||||
|
||||
// Color the fields
|
||||
QS('rnuPass1').color = pass1ok ? 'black' : '#7b241c';
|
||||
QS('rnuPass2').color = pass2ok ? 'black' : '#7b241c';
|
||||
|
||||
if (Q('rapassword1').value == '') {
|
||||
QH('rpassWarning', '');
|
||||
QV('rpasswordPolicyCallout', false);
|
||||
} else {
|
||||
if (!passRequirementsEx) {
|
||||
// No password requirements, display password strength
|
||||
var passStrength = checkPasswordStrength(Q('rapassword1').value);
|
||||
if (passStrength >= 80) { QH('rpassWarning', '<span style=color:green><b>' + "Mot de passe fort" + '</b><span>'); }
|
||||
else if (passStrength >= 60) { QH('rpassWarning', '<span style=color:blue><b>' + "Good Password" + '</b><span>'); }
|
||||
else { QH('rpassWarning', '<span style=color:red><b>' + "Mot de passe faible" + '</b><span>'); }
|
||||
} else {
|
||||
// Password requirements provided, use that
|
||||
var passReq = checkPasswordRequirements(Q('rapassword1').value, passRequirements);
|
||||
if (passReq == false) {
|
||||
ok = false;
|
||||
QS('rnuPass1').color = '#7b241c';
|
||||
QS('rnuPass2').color = '#7b241c';
|
||||
QH('rpassWarning', '<div style=color:red;cursor:pointer onclick=showPasswordPolicy()><b>' + "Password Policy" + '</b><div>'); // This is also a link to the password policy
|
||||
QV('rpasswordPolicyCallout', true);
|
||||
QH('rpasswordPolicyCallout', passwordPolicyText(Q('rapassword1').value));
|
||||
} else {
|
||||
QH('rpassWarning', '');
|
||||
QV('rpasswordPolicyCallout', false);
|
||||
}
|
||||
}
|
||||
}
|
||||
if ((e != null) && (e.keyCode == 13)) {
|
||||
if (box == 2) { Q('rapassword1').focus(); }
|
||||
if (box == 3) { Q('rapassword2').focus(); }
|
||||
if (box == 4) { Q('rapasswordhint').focus(); }
|
||||
if (box == 6) { Q('resetPassButton').click(); }
|
||||
}
|
||||
if (e != null) { haltEvent(e); }
|
||||
QE('resetPassButton', ok);
|
||||
}
|
||||
|
||||
function validateReset(e) {
|
||||
setDialogMode(0);
|
||||
var x = validateEmail(Q('remail').value);
|
||||
QE('eresetButton', x);
|
||||
if ((e != null) && (e.keyCode == 13) && (x == true)) { Q('eresetButton').click(); }
|
||||
if (e != null) { haltEvent(e); }
|
||||
}
|
||||
|
||||
function passwordPolicyText(pass) {
|
||||
var policy = '<div style=text-align:left>';
|
||||
var counts = strCount(pass);
|
||||
if (passRequirements.min && ((pass == null) || (pass.length < passRequirements.min))) { policy += format("Minimum length of {0}", passRequirements.min) + '<br />'; }
|
||||
if (passRequirements.max && ((pass == null) || (pass.length > passRequirements.max))) { policy += format("Maximum length of {0}", passRequirements.max) + '<br />'; }
|
||||
if (passRequirements.upper && ((pass == null) || (counts.upper < passRequirements.upper))) { policy += format("{0} upper case", passRequirements.upper) + '<br />'; }
|
||||
if (passRequirements.lower && ((pass == null) || (counts.lower < passRequirements.lower))) { policy += format("{0} lower case", passRequirements.lower) + '<br />'; }
|
||||
if (passRequirements.numeric && ((pass == null) || (counts.numeric < passRequirements.numeric))) { policy += format("{0} numeric", passRequirements.numeric) + '<br />'; }
|
||||
if (passRequirements.nonalpha && ((pass == null) || (counts.nonalpha < passRequirements.nonalpha))) { policy += format("{0} non-alphanumeric", passRequirements.nonalpha) + '<br />'; }
|
||||
policy += '</div>';
|
||||
return policy;
|
||||
}
|
||||
|
||||
// Return a password strength score
|
||||
function checkPasswordStrength(password) {
|
||||
var r = 0, letters = {}, varCount = 0, variations = { digits: /\d/.test(password), lower: /[a-z]/.test(password), upper: /[A-Z]/.test(password), nonWords: /\W/.test(password) }
|
||||
if (!password) return 0;
|
||||
for (var i = 0; i< password.length; i++) { letters[password[i]] = (letters[password[i]] || 0) + 1; r += 5.0 / letters[password[i]]; }
|
||||
for (var c in variations) { varCount += (variations[c] == true) ? 1 : 0; }
|
||||
return parseInt(r + (varCount - 1) * 10);
|
||||
}
|
||||
|
||||
// Check password requirements
|
||||
function checkPasswordRequirements(password, requirements) {
|
||||
if ((requirements == null) || (requirements == '') || (typeof requirements != 'object')) return true;
|
||||
if (requirements.min) { if (password.length < requirements.min) return false; }
|
||||
if (requirements.max) { if (password.length > requirements.max) return false; }
|
||||
var counts = strCount(password);
|
||||
if (requirements.numeric && (counts.numeric < requirements.numeric)) return false;
|
||||
if (requirements.lower && (counts.lower < requirements.lower)) return false;
|
||||
if (requirements.upper && (counts.upper < requirements.upper)) return false;
|
||||
if (requirements.nonalpha && (counts.nonalpha < requirements.nonalpha)) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
function strCount(password) {
|
||||
var counts = { numeric: 0, lower: 0, upper: 0, nonalpha: 0 };
|
||||
if (typeof password != 'string') return counts;
|
||||
for (var i = 0; i < password.length; i++) {
|
||||
if (/\d/.test(password[i])) { counts.numeric++; }
|
||||
if (/[a-z]/.test(password[i])) { counts.lower++; }
|
||||
if (/[A-Z]/.test(password[i])) { counts.upper++; }
|
||||
if (/\W/.test(password[i])) { counts.nonalpha++; }
|
||||
}
|
||||
return counts;
|
||||
}
|
||||
|
||||
var xcheckTokenTimer = null;
|
||||
function checkTokenTimer(enter) {
|
||||
if ((enter == 0) && (xcheckTokenTimer != null)) { clearInterval(xcheckTokenTimer); xcheckTokenTimer = null; }
|
||||
if ((enter == 1) && (xcheckTokenTimer == null)) { xcheckTokenTimer = setInterval(checkToken, 200); }
|
||||
}
|
||||
|
||||
function checkToken() {
|
||||
var t1 = Q('tokenInput').value, t2 = t1.split(' ').join('');
|
||||
if (t1 != t2) { Q('tokenInput').value = t2; }
|
||||
QE('tokenOkButton', (Q('tokenInput').value.length == 6) || (Q('tokenInput').value.length == 8) || (Q('tokenInput').value.length == 44));
|
||||
}
|
||||
|
||||
function resetCheckToken() {
|
||||
var t1 = Q('resetTokenInput').value, t2 = t1.split(' ').join('');
|
||||
if (t1 != t2) { Q('resetTokenInput').value = t2; }
|
||||
QE('resetTokenOkButton', (Q('resetTokenInput').value.length == 6) || (Q('resetTokenInput').value.length == 8) || (Q('resetTokenInput').value.length == 44));
|
||||
}
|
||||
|
||||
//
|
||||
// POPUP DIALOG
|
||||
//
|
||||
|
||||
// undefined = Hidden, 1 = Generic Message
|
||||
var xxdialogMode;
|
||||
var xxdialogFunc;
|
||||
var xxdialogButtons;
|
||||
var xxdialogTag;
|
||||
var xxcurrentView = 0;
|
||||
|
||||
// Display a dialog box
|
||||
// Parameters: Dialog Mode (0 = none), Dialog Title, Buttons (1 = OK, 2 = Cancel, 3 = OK & Cancel), Call back function(0 = Cancel, 1 = OK), Dialog Content (Mode 2 only)
|
||||
function setDialogMode(x, y, b, f, c, tag) {
|
||||
xxdialogMode = x;
|
||||
xxdialogFunc = f;
|
||||
xxdialogButtons = b;
|
||||
xxdialogTag = tag;
|
||||
QE('idx_dlgOkButton', true);
|
||||
QV('idx_dlgOkButton', b & 1);
|
||||
QV('idx_dlgCancelButton', b & 2);
|
||||
QV('id_dialogclose', (b & 2) || (b & 8));
|
||||
QV('idx_dlgButtonBar', b & 7);
|
||||
if (y) QH('id_dialogtitle', y);
|
||||
for (var i = 1; i < 24; i++) { QV('dialog' + i, i == x); } // Edit this line when more dialogs are added
|
||||
QV('dialog', x);
|
||||
if (c) { if (x == 2) { QH('id_dialogOptions', c); } else { QH('id_dialogMessage', c); } }
|
||||
}
|
||||
|
||||
function dialogclose(x) {
|
||||
var f = xxdialogFunc;
|
||||
var b = xxdialogButtons;
|
||||
var t = xxdialogTag;
|
||||
setDialogMode();
|
||||
if (((b & 8) || x) && f) f(x, t);
|
||||
}
|
||||
|
||||
function center() { QS('dialog').left = ((((getDocWidth() - 400) / 2)) + 'px'); }
|
||||
function messagebox(t, m) { QH('id_dialogMessage', m); setDialogMode(1, t, 1); }
|
||||
function statusbox(t, m) { QH('id_dialogMessage', m); setDialogMode(1, t); }
|
||||
function getDocWidth() { if (window.innerWidth) return window.innerWidth; if (document.documentElement && document.documentElement.clientWidth && document.documentElement.clientWidth != 0) return document.documentElement.clientWidth; return document.getElementsByTagName('body')[0].clientWidth; }
|
||||
function haltEvent(e) { if (e.preventDefault) e.preventDefault(); if (e.stopPropagation) e.stopPropagation(); return false; }
|
||||
function haltReturn(e) { if (e.keyCode == 13) { haltEvent(e); } }
|
||||
function validateEmail(v) { var emailReg = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; return emailReg.test(v); } // New version
|
||||
function format(format) { var args = Array.prototype.slice.call(arguments, 1); return format.replace(/{(\d+)}/g, function (match, number) { return typeof args[number] != 'undefined' ? args[number] : match; }); };
|
||||
|
||||
</script>
|
||||
|
||||
</body></html>
|
712
views/translations/login_fr.handlebars
Normal file
712
views/translations/login_fr.handlebars
Normal file
|
@ -0,0 +1,712 @@
|
|||
<!DOCTYPE html><html dir="ltr" xmlns="http://www.w3.org/1999/xhtml"><head>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta content="text/html; charset=utf-8" http-equiv="Content-Type">
|
||||
<meta name="viewport" content="user-scalable=1.0,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="format-detection" content="telephone=no">
|
||||
<link rel="shortcut icon" type="image/x-icon" href="{{{domainurl}}}favicon.ico">
|
||||
<link keeplink="1" type="text/css" href="styles/style.css" media="screen" rel="stylesheet" title="CSS">
|
||||
<script type="text/javascript" src="scripts/common-0.0.1.js"></script>
|
||||
<script keeplink="1" type="text/javascript" src="scripts/u2f-api.js"></script>
|
||||
<title>{{{title}}} - Login</title>
|
||||
</head>
|
||||
<body id="body" onload="if (typeof(startup) !== 'undefined') startup();" class="arg_hide login">
|
||||
<div id="container">
|
||||
<div id="masthead">
|
||||
<div class="title">{{{title}}}</div>
|
||||
<div class="title2">{{{title2}}}</div>
|
||||
</div>
|
||||
<div id="topbar" class="noselect style3" style="height:24px">
|
||||
<div id="uiMenuButton" title="User interface selection" onclick="showUserInterfaceSelectMenu()">
|
||||
♦
|
||||
<div id="uiMenu" style="display:none">
|
||||
<div id="uiViewButton1" class="uiSelector" onclick="userInterfaceSelectMenu(1)" title="Left bar interface"><div class="uiSelector1"></div></div>
|
||||
<div id="uiViewButton2" class="uiSelector" onclick="userInterfaceSelectMenu(2)" title="Top bar interface"><div class="uiSelector2"></div></div>
|
||||
<div id="uiViewButton3" class="uiSelector" onclick="userInterfaceSelectMenu(3)" title="Fixed width interface"><div class="uiSelector3"></div></div>
|
||||
<div id="uiViewButton4" class="uiSelector" onclick="toggleNightMode()" title="Basculer mode nuit"><div class="uiSelector4"></div></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="column_l">
|
||||
<h1>Bienvenue</h1>
|
||||
<div id="welcomeText" style="display:none">Connect to your home or office devices from anywhere in the world using <a href="http://www.meshcommander.com/meshcentral2">MeshCentral</a>, the real time, open source remote monitoring and management web site. You will need to download and install a management agent on your computers. Once installed, computers will show up in the "My Devices" section of this web site and you will be able to monitor them and take control of them.</div>
|
||||
<table id="centralTable" style="">
|
||||
<tbody><tr>
|
||||
<td id="welcomeimage">
|
||||
<picture>
|
||||
<img alt="" src="welcome.jpg" style="border-radius:20px">
|
||||
</picture>
|
||||
</td>
|
||||
<td id="logincell">
|
||||
<div id="loginpanel" style="display:none">
|
||||
<form method="post">
|
||||
<input type="hidden" name="action" value="login">
|
||||
<div id="message1">
|
||||
{{{message}}}
|
||||
</div>
|
||||
<div>
|
||||
<b>Log In</b>
|
||||
</div>
|
||||
<table>
|
||||
<tbody><tr>
|
||||
<td id="loginusername" align="right" width="100">Nom d'utilisateur:</td>
|
||||
<td><input id="username" type="text" maxlength="64" name="username" onchange="validateLogin(1)" onkeyup="validateLogin(1,event)"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right">Password:</td>
|
||||
<td><input id="password" type="password" maxlength="256" name="password" autocomplete="off" onchange="validateLogin(2)" onkeyup="validateLogin(2,event)"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><div id="showPassHintLink" style="display:none"><a onclick="return showPassHint(event);" href="#" style="cursor:pointer">Show Hint</a></div></td>
|
||||
<td align="right"><input id="loginButton" type="submit" value="Log In" disabled="disabled"></td>
|
||||
</tr>
|
||||
</tbody></table>
|
||||
<div id="hrAccountDiv" style="display:none"><hr></div>
|
||||
<div id="resetAccountDiv" style="display:none;padding:2px">
|
||||
<span id="resetAccountSpan">Forgot username/password?</span> <a onclick="return xgo(3,event);" href="#" style="cursor:pointer">Reset account</a>.
|
||||
</div>
|
||||
<div id="newAccountDiv" style="display:none;padding:2px">
|
||||
Don't have an account? <a onclick="return xgo(2,event);" href="#" style="cursor:pointer">Create one</a>.
|
||||
</div>
|
||||
<input id="loginformargs" name="urlargs" type="hidden" value="">
|
||||
</form>
|
||||
</div>
|
||||
<div id="createpanel" style="display:none;position:relative">
|
||||
<form method="post">
|
||||
<input type="hidden" name="action" value="createaccount">
|
||||
<div id="message2">
|
||||
{{{message}}}
|
||||
</div>
|
||||
<div>
|
||||
<b>Account Creation</b>
|
||||
</div>
|
||||
<div id="passwordPolicyCallout" style="display:none"></div>
|
||||
<table>
|
||||
<tbody><tr id="nuUserRow">
|
||||
<td id="nuUser" align="right" width="100">Nom d'utilisateur:</td>
|
||||
<td><input id="ausername" type="text" name="username" onchange="validateCreate(1)" maxlength="64" onkeydown="haltReturn(event)" onkeyup="validateCreate(1,event)"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td id="nuEmail" align="right" width="100">Email:</td>
|
||||
<td><input id="aemail" type="text" name="email" onchange="validateCreate(2)" maxlength="256" onkeydown="haltReturn(event)" onkeyup="validateCreate(2,event)"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td id="nuPass1" align="right">Password:</td>
|
||||
<td><input id="apassword1" type="password" name="password1" autocomplete="off" maxlength="256" onkeydown="haltReturn(event)" onchange="validateCreate(3,event)" onkeyup="validateCreate(3,event)"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td id="nuPass2" align="right">Password:</td>
|
||||
<td><input id="apassword2" type="password" name="password2" autocomplete="off" maxlength="256" onkeydown="haltReturn(event)" onchange="validateCreate(4,event)" onkeyup="validateCreate(4,event)"></td>
|
||||
</tr>
|
||||
<tr id="createPanelHint" style="display:none">
|
||||
<td id="nuHint" align="right">Password Hint:</td>
|
||||
<td><input id="apasswordhint" type="text" name="apasswordhint" autocomplete="off" maxlength="256" onkeydown="haltReturn(event)" onchange="validateCreate(5,event)" onkeyup="validateCreate(5,event)"></td>
|
||||
</tr>
|
||||
<tr id="newAccountPass" title="Enter the account creation token">
|
||||
<td id="nuToken" align="right">Creation Token:</td>
|
||||
<td><input id="anewaccountpass" type="password" name="anewaccountpass" autocomplete="off" maxlength="256" onkeydown="haltReturn(event)" onchange="validateCreate(6,event)" onkeyup="validateCreate(6,event)"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<div style="float:right"><input id="createButton" type="submit" value="Create Account" disabled="disabled"></div>
|
||||
<div id="passWarning" style="padding-top:6px"></div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody></table>
|
||||
<hr><a onclick="return xgo(1,event);" href="#" style="cursor:pointer">Back to login</a>
|
||||
<input id="createformargs" name="urlargs" type="hidden" value="">
|
||||
</form>
|
||||
</div>
|
||||
<div id="resetpanel" style="display:none">
|
||||
<form method="post">
|
||||
<input type="hidden" name="action" value="resetaccount">
|
||||
<div id="message3">
|
||||
{{{message}}}
|
||||
</div>
|
||||
<div>
|
||||
<b>Account Reset</b>
|
||||
</div>
|
||||
<table>
|
||||
<tbody><tr>
|
||||
<td align="right" width="100">Email:</td>
|
||||
<td><input id="remail" type="text" name="email" maxlength="256" onchange="validateReset()" onkeyup="validateReset(event)"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<div style="float:right"><input id="eresetButton" type="submit" value="Reset Account" disabled="disabled"></div>
|
||||
<div id="passWarning" style="padding-top:6px"></div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody></table>
|
||||
<hr><a onclick="return xgo(1,event);" href="#" style="cursor:pointer">Back to login</a>
|
||||
<input id="resetformargs" name="urlargs" type="hidden" value="">
|
||||
</form>
|
||||
</div>
|
||||
<div id="tokenpanel" style="display:none">
|
||||
<form method="post" autocomplete="off">
|
||||
<input type="hidden" name="action" value="tokenlogin">
|
||||
<input type="hidden" name="hwstate" value="{{{hwstate}}}">
|
||||
<div id="message4">
|
||||
{{{message}}}
|
||||
</div>
|
||||
<table>
|
||||
<tbody><tr>
|
||||
<td align="right" width="100">Login token:</td>
|
||||
<td>
|
||||
<input id="tokenInput" type="text" name="token" maxlength="50" onchange="checkToken(event)" onpaste="resetCheckToken(event)" onkeyup="checkToken(event)" onkeydown="checkToken(event)">
|
||||
<input id="hwtokenInput" type="text" name="hwtoken" style="display:none">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<div style="float:right"><input id="tokenOkButton" type="submit" value="Login" disabled="disabled"></div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody></table>
|
||||
<hr><a onclick="return xgo(1,event);" href="#" style="cursor:pointer">Back to login</a>
|
||||
<input id="tokenformargs" name="urlargs" type="hidden" value="">
|
||||
</form>
|
||||
</div>
|
||||
<div id="resettokenpanel" style="display:none">
|
||||
<form method="post">
|
||||
<input type="hidden" name="action" value="resetaccount">
|
||||
<div id="message5">
|
||||
{{{message}}}
|
||||
</div>
|
||||
<table>
|
||||
<tbody><tr>
|
||||
<td align="right" width="100">Login token:</td>
|
||||
<td>
|
||||
<input id="resetTokenInput" type="text" name="token" maxlength="50" onchange="resetCheckToken(event)" onkeyup="resetCheckToken(event)" onkeydown="resetCheckToken(event)">
|
||||
<input id="resetHwtokenInput" type="text" name="hwtoken" style="display:none">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<div style="float:right"><input id="resetTokenOkButton" type="submit" value="Login" disabled="disabled"></div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody></table>
|
||||
<hr><a onclick="return xgo(1,event);" href="#" style="cursor:pointer">Back to login</a>
|
||||
<input id="resettokenformargs" name="urlargs" type="hidden" value="">
|
||||
</form>
|
||||
</div>
|
||||
<div id="resetpasswordpanel" style="display:none;position:relative">
|
||||
<form method="post">
|
||||
<input type="hidden" name="action" value="resetpassword">
|
||||
<div id="message6">
|
||||
{{{message}}}
|
||||
</div>
|
||||
<div id="rpasswordPolicyCallout" style="display:none"></div>
|
||||
<table>
|
||||
<tbody><tr>
|
||||
<td id="rnuPass1" width="100" align="right">Password:</td>
|
||||
<td><input id="rapassword1" type="password" name="rpassword1" autocomplete="off" maxlength="256" onkeydown="haltReturn(event)" onchange="validatePassReset(3,event)" onkeyup="validatePassReset(3,event)"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td id="rnuPass2" align="right">Password:</td>
|
||||
<td><input id="rapassword2" type="password" name="rpassword2" autocomplete="off" maxlength="256" onkeydown="haltReturn(event)" onchange="validatePassReset(4,event)" onkeyup="validatePassReset(4,event)"></td>
|
||||
</tr>
|
||||
<tr id="resetpasswordpanelHint" style="display:none">
|
||||
<td id="rnuHint" align="right">Password Hint:</td>
|
||||
<td><input id="rapasswordhint" type="text" name="rpasswordhint" autocomplete="off" maxlength="256" onkeydown="haltReturn(event)" onchange="validatePassReset(5,event)" onkeyup="validatePassReset(5,event)"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<div style="float:right"><input id="resetPassButton" type="submit" value="Reset Password" disabled="disabled"></div>
|
||||
<div id="rpassWarning" style="padding-top:6px"></div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody></table>
|
||||
<hr><a onclick="return xgo(1,event);" href="#" style="cursor:pointer">Back to login</a>
|
||||
<input id="resetpasswordformargs" name="urlargs" type="hidden" value="">
|
||||
</form>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody></table>
|
||||
<br>
|
||||
</div>
|
||||
<div id="footer">
|
||||
<div class="footer1">{{{footer}}}</div>
|
||||
<div class="footer2">
|
||||
{{{rootCertLink}}}
|
||||
<a href="terms">Terms & Privacy</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div id="dialog" style="display:none">
|
||||
<div id="dialogHeader">
|
||||
<div id="id_dialogclose" style="float:right;padding:5px;cursor:pointer" onclick="setDialogMode()"><b>X</b></div>
|
||||
<div id="id_dialogtitle" style="padding:5px"></div>
|
||||
<div style="width:100%;margin:6px"></div>
|
||||
</div>
|
||||
<div id="dialogBody">
|
||||
<div id="dialog1">
|
||||
<div id="id_dialogMessage" style=""></div>
|
||||
</div>
|
||||
<div id="dialog2" style="">
|
||||
<div id="id_dialogOptions"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="idx_dlgButtonBar" style="">
|
||||
<input id="idx_dlgCancelButton" type="button" value="Annuler" style="" onclick="dialogclose(0)">
|
||||
<input id="idx_dlgOkButton" type="button" value="OK" style="" onclick="dialogclose(1)">
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
'use strict';
|
||||
var passhint = '{{{passhint}}}';
|
||||
var newAccountPass = parseInt('{{{newAccountPass}}}');
|
||||
var emailCheck = ('{{{emailcheck}}}' == 'true');
|
||||
var passRequirements = '{{{passRequirements}}}';
|
||||
var hardwareKeyChallenge = decodeURIComponent('{{{hkey}}}');
|
||||
if (passRequirements != '') { passRequirements = JSON.parse(decodeURIComponent(passRequirements)); } else { passRequirements = {}; }
|
||||
var passRequirementsEx = ((passRequirements.min != null) || (passRequirements.max != null) || (passRequirements.upper != null) || (passRequirements.lower != null) || (passRequirements.numeric != null) || (passRequirements.nonalpha != null));
|
||||
var features = parseInt('{{{features}}}');
|
||||
var welcomeText = decodeURIComponent('{{{welcometext}}}');
|
||||
var currentpanel = 0;
|
||||
var uiMode = parseInt(getstore('uiMode', '1'));
|
||||
var webPageFullScreen = true;
|
||||
var nightMode = (getstore('_nightMode', '0') == '1');
|
||||
var publicKeyCredentialRequestOptions = null;
|
||||
|
||||
// If URL arguments are provided, add them to form posts
|
||||
if (window.location.href.indexOf('?') > 0) {
|
||||
var urlargs = window.location.href.substring(window.location.href.indexOf('?'));
|
||||
Q('loginformargs').value = urlargs;
|
||||
Q('createformargs').value = urlargs;
|
||||
Q('resetformargs').value = urlargs;
|
||||
Q('tokenformargs').value = urlargs;
|
||||
Q('resettokenformargs').value = urlargs;
|
||||
Q('resetpasswordformargs').value = urlargs;
|
||||
}
|
||||
|
||||
//var webPageFullScreen = getstore('webPageFullScreen', true);
|
||||
//if (webPageFullScreen == 'false') { webPageFullScreen = false; }
|
||||
//if (webPageFullScreen == 'true') { webPageFullScreen = true; }
|
||||
//toggleFullScreen();
|
||||
|
||||
function startup() {
|
||||
if ((features & 32) == 0) {
|
||||
// Guard against other site's top frames (web bugs).
|
||||
var loc = null;
|
||||
try { loc = top.location.toString().toLowerCase(); } catch (e) { }
|
||||
if (top != self && (loc == null || top.active == false)) { top.location = self.location; return; }
|
||||
}
|
||||
|
||||
if (features & 0x200000) { // Email is username
|
||||
QH('loginusername', "Email:");
|
||||
QH('resetAccountSpan', "Forgot password?");
|
||||
QV('nuUserRow', false);
|
||||
}
|
||||
|
||||
if (nightMode) { QC('body').add('night'); }
|
||||
|
||||
QV('createPanelHint', passRequirements.hint === true);
|
||||
QV('resetpasswordpanelHint', passRequirements.hint === true);
|
||||
|
||||
// Display the welcome text
|
||||
if (welcomeText) { QH('welcomeText', welcomeText); }
|
||||
QV('welcomeText', true);
|
||||
|
||||
window.onresize = center;
|
||||
center();
|
||||
|
||||
validateLogin();
|
||||
validateCreate();
|
||||
if ('{{loginmode}}' != '') { go(parseInt('{{loginmode}}')); } else { go(1); }
|
||||
QV('newAccountDiv', ('{{{newAccount}}}' === '1') || ('{{{newAccount}}}' === 'true')); // If new accounts are not allowed, don't display the new account link.
|
||||
if ((passhint != null) && (passhint.length > 0)) { QV('showPassHintLink', true); }
|
||||
QV('newAccountPass', (newAccountPass == 1));
|
||||
QV('resetAccountDiv', (emailCheck == true));
|
||||
QV('hrAccountDiv', (emailCheck == true) || (newAccountPass == 1));
|
||||
|
||||
if ('{{loginmode}}' == '4') {
|
||||
try { if (hardwareKeyChallenge.length > 0) { hardwareKeyChallenge = JSON.parse(hardwareKeyChallenge); } else { hardwareKeyChallenge = null; } } catch (ex) { hardwareKeyChallenge = null }
|
||||
if ((hardwareKeyChallenge != null) && (hardwareKeyChallenge.type == 'webAuthn')) {
|
||||
hardwareKeyChallenge.challenge = Uint8Array.from(atob(hardwareKeyChallenge.challenge), function (c) { return c.charCodeAt(0) }).buffer;
|
||||
|
||||
publicKeyCredentialRequestOptions = { challenge: hardwareKeyChallenge.challenge, allowCredentials: [], timeout: hardwareKeyChallenge.timeout }
|
||||
for (var i = 0; i < hardwareKeyChallenge.keyIds.length; i++) {
|
||||
publicKeyCredentialRequestOptions.allowCredentials.push(
|
||||
{ id: Uint8Array.from(atob(hardwareKeyChallenge.keyIds[i]), function (c) { return c.charCodeAt(0) }), type: 'public-key', transports: ['usb', 'ble', 'nfc'], }
|
||||
);
|
||||
}
|
||||
|
||||
// New WebAuthn hardware keys
|
||||
navigator.credentials.get({ publicKey: publicKeyCredentialRequestOptions }).then(
|
||||
function (rawAssertion) {
|
||||
var assertion = {
|
||||
id: btoa(String.fromCharCode.apply(null, new Uint8Array(rawAssertion.rawId))),
|
||||
clientDataJSON: btoa(String.fromCharCode.apply(null, new Uint8Array(rawAssertion.response.clientDataJSON))),
|
||||
userHandle: btoa(String.fromCharCode.apply(null, new Uint8Array(rawAssertion.response.userHandle))),
|
||||
signature: btoa(String.fromCharCode.apply(null, new Uint8Array(rawAssertion.response.signature))),
|
||||
authenticatorData: btoa(String.fromCharCode.apply(null, new Uint8Array(rawAssertion.response.authenticatorData))),
|
||||
};
|
||||
Q('hwtokenInput').value = JSON.stringify(assertion);
|
||||
QE('tokenOkButton', true);
|
||||
Q('tokenOkButton').click();
|
||||
},
|
||||
function (error) {
|
||||
console.log('credentials-get error', error);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if ('{{loginmode}}' == '5') {
|
||||
try { if (hardwareKeyChallenge.length > 0) { hardwareKeyChallenge = JSON.parse(hardwareKeyChallenge); } else { hardwareKeyChallenge = null; } } catch (ex) { hardwareKeyChallenge = null }
|
||||
if ((hardwareKeyChallenge != null) && (hardwareKeyChallenge.type == 'webAuthn')) {
|
||||
hardwareKeyChallenge.challenge = Uint8Array.from(atob(hardwareKeyChallenge.challenge), function (c) { return c.charCodeAt(0) }).buffer;
|
||||
|
||||
publicKeyCredentialRequestOptions = { challenge: hardwareKeyChallenge.challenge, allowCredentials: [], timeout: hardwareKeyChallenge.timeout }
|
||||
for (var i = 0; i < hardwareKeyChallenge.keyIds.length; i++) {
|
||||
publicKeyCredentialRequestOptions.allowCredentials.push(
|
||||
{ id: Uint8Array.from(atob(hardwareKeyChallenge.keyIds[i]), function (c) { return c.charCodeAt(0) }), type: 'public-key', transports: ['usb', 'ble', 'nfc'], }
|
||||
);
|
||||
}
|
||||
|
||||
// New WebAuthn hardware keys
|
||||
navigator.credentials.get({ publicKey: publicKeyCredentialRequestOptions }).then(
|
||||
function (rawAssertion) {
|
||||
var assertion = {
|
||||
id: btoa(String.fromCharCode.apply(null, new Uint8Array(rawAssertion.rawId))),
|
||||
clientDataJSON: btoa(String.fromCharCode.apply(null, new Uint8Array(rawAssertion.response.clientDataJSON))),
|
||||
userHandle: btoa(String.fromCharCode.apply(null, new Uint8Array(rawAssertion.response.userHandle))),
|
||||
signature: btoa(String.fromCharCode.apply(null, new Uint8Array(rawAssertion.response.signature))),
|
||||
authenticatorData: btoa(String.fromCharCode.apply(null, new Uint8Array(rawAssertion.response.authenticatorData))),
|
||||
};
|
||||
Q('resetHwtokenInput').value = JSON.stringify(assertion);
|
||||
QE('resetTokenOkButton', true);
|
||||
Q('resetTokenOkButton').click();
|
||||
},
|
||||
function (error) { console.log('credentials-get error', error); }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Setup the user interface in the right mode
|
||||
userInterfaceSelectMenu();
|
||||
}
|
||||
|
||||
function showPassHint(e) {
|
||||
messagebox("Password Hint", passhint);
|
||||
haltEvent(e);
|
||||
return false;
|
||||
}
|
||||
|
||||
function xgo(x, e) {
|
||||
QV('message1', false);
|
||||
QV('message2', false);
|
||||
QV('message3', false);
|
||||
QV('message4', false);
|
||||
QV('message5', false);
|
||||
QV('message6', false);
|
||||
go(x);
|
||||
haltEvent(e);
|
||||
return false;
|
||||
}
|
||||
|
||||
function go(x) {
|
||||
currentpanel = x;
|
||||
setDialogMode(0);
|
||||
QV('showPassHintLink', false);
|
||||
QV('loginpanel', x == 1);
|
||||
QV('createpanel', x == 2);
|
||||
QV('resetpanel', x == 3);
|
||||
QV('tokenpanel', x == 4);
|
||||
QV('resettokenpanel', x == 5);
|
||||
QV('resetpasswordpanel', x == 6);
|
||||
if (x == 1) { Q('username').focus(); }
|
||||
if (x == 2) { if (features & 0x200000) { Q('aemail').focus(); } else { Q('ausername').focus(); } } // Email is username
|
||||
if (x == 3) { Q('remail').focus(); }
|
||||
if (x == 4) { Q('tokenInput').focus(); }
|
||||
if (x == 5) { Q('resetTokenInput').focus(); }
|
||||
if (x == 6) { Q('rapassword1').focus(); }
|
||||
}
|
||||
|
||||
function validateLogin(box, e) {
|
||||
var ok = ((Q('username').value.length > 0) && (Q('username').value.indexOf(' ') == -1) && (Q('password').value.length > 0));
|
||||
QE('loginButton', ok);
|
||||
setDialogMode(0);
|
||||
if ((e != null) && (e.keyCode == 13)) { if ((box == 1) && (Q('username').value != '')) { Q('password').focus(); } else if ((box == 2) && (Q('password').value != '')) { Q('loginButton').click(); } }
|
||||
if (e != null) { haltEvent(e); }
|
||||
}
|
||||
|
||||
function validateCreate(box, e) {
|
||||
setDialogMode(0);
|
||||
var userok = false;
|
||||
if (features & 0x200000) { userok = true; } else { userok = (Q('ausername').value.length > 0) && (Q('ausername').value.indexOf(' ') == -1) && (Q('ausername').value.indexOf('"') == -1) && (Q('ausername').value.indexOf(',') == -1); }
|
||||
var emailok = (validateEmail(Q('aemail').value) == true);
|
||||
var pass1ok = (Q('apassword1').value.length > 0);
|
||||
var pass2ok = (Q('apassword2').value.length > 0) && (Q('apassword2').value == Q('apassword1').value);
|
||||
var newAccOk = (newAccountPass == 0) || (Q('anewaccountpass').value.length > 0);
|
||||
var ok = (userok && emailok && pass1ok && pass2ok && newAccOk);
|
||||
|
||||
// Color the fields
|
||||
QS('nuUser').color = userok?'black':'#7b241c';
|
||||
QS('nuEmail').color = emailok?'black':'#7b241c';
|
||||
QS('nuPass1').color = pass1ok?'black':'#7b241c';
|
||||
QS('nuPass2').color = pass2ok?'black':'#7b241c';
|
||||
QS('nuToken').color = newAccOk?'black':'#7b241c';
|
||||
|
||||
if (Q('apassword1').value == '') {
|
||||
QH('passWarning', '');
|
||||
QV('passwordPolicyCallout', false);
|
||||
} else {
|
||||
if (!passRequirementsEx) {
|
||||
// No password requirements, display password strength
|
||||
var passStrength = checkPasswordStrength(Q('apassword1').value);
|
||||
if (passStrength >= 80) { QH('passWarning', '<span style=color:green><b>' + "Mot de passe fort" + '</b><span>'); }
|
||||
else if (passStrength >= 60) { QH('passWarning', '<span style=color:blue><b>' + "Good Password" + '</b><span>'); }
|
||||
else { QH('passWarning', '<span style=color:red><b>' + "Mot de passe faible" + '</b><span>'); }
|
||||
} else {
|
||||
// Password requirements provided, use that
|
||||
var passReq = checkPasswordRequirements(Q('apassword1').value, passRequirements);
|
||||
if (passReq == false) {
|
||||
ok = false;
|
||||
QS('nuPass1').color = '#7b241c';
|
||||
QS('nuPass2').color = '#7b241c';
|
||||
QH('passWarning', '<div style=color:red;cursor:pointer onclick=showPasswordPolicy()><b>' + "Password Policy" + '</b><div>'); // This is also a link to the password policy
|
||||
QV('passwordPolicyCallout', true);
|
||||
QH('passwordPolicyCallout', passwordPolicyText(Q('apassword1').value));
|
||||
} else {
|
||||
QH('passWarning', '');
|
||||
QV('passwordPolicyCallout', false);
|
||||
}
|
||||
}
|
||||
}
|
||||
if ((e != null) && (e.keyCode == 13)) {
|
||||
|
||||
if ((box == 1) && userok) { Q('aemail').focus(); }
|
||||
if ((box == 2) && emailok) { Q('apassword1').focus(); }
|
||||
if ((box == 3) && pass1ok) { Q('apassword2').focus(); }
|
||||
if ((box == 4) && pass2ok) { if (passRequirements.hint === true) { Q('apasswordhint').focus(); } else { box = 5; } }
|
||||
if (box == 5) { if (newAccountPass == 1) { Q('anewaccountpass').focus(); } else { box = 6; } }
|
||||
if (box == 6) { Q('createButton').click(); }
|
||||
}
|
||||
if (e != null) { haltEvent(e); }
|
||||
QE('createButton', ok);
|
||||
}
|
||||
|
||||
function validatePassReset(box, e) {
|
||||
setDialogMode(0);
|
||||
var pass1ok = (Q('rapassword1').value.length > 0);
|
||||
var pass2ok = (Q('rapassword2').value.length > 0) && (Q('rapassword2').value == Q('rapassword1').value);
|
||||
var ok = (pass1ok && pass2ok);
|
||||
|
||||
// Color the fields
|
||||
QS('rnuPass1').color = pass1ok ? 'black' : '#7b241c';
|
||||
QS('rnuPass2').color = pass2ok ? 'black' : '#7b241c';
|
||||
|
||||
if (Q('rapassword1').value == '') {
|
||||
QH('rpassWarning', '');
|
||||
QV('rpasswordPolicyCallout', false);
|
||||
} else {
|
||||
if (!passRequirementsEx) {
|
||||
// No password requirements, display password strength
|
||||
var passStrength = checkPasswordStrength(Q('rapassword1').value);
|
||||
if (passStrength >= 80) { QH('rpassWarning', '<span style=color:green><b>' + "Mot de passe fort" + '</b><span>'); }
|
||||
else if (passStrength >= 60) { QH('rpassWarning', '<span style=color:blue><b>' + "Good Password" + '</b><span>'); }
|
||||
else { QH('rpassWarning', '<span style=color:red><b>' + "Mot de passe faible" + '</b><span>'); }
|
||||
} else {
|
||||
// Password requirements provided, use that
|
||||
var passReq = checkPasswordRequirements(Q('rapassword1').value, passRequirements);
|
||||
if (passReq == false) {
|
||||
ok = false;
|
||||
QS('rnuPass1').color = '#7b241c';
|
||||
QS('rnuPass2').color = '#7b241c';
|
||||
QH('rpassWarning', '<div style=color:red;cursor:pointer onclick=showPasswordPolicy()><b>' + "Password Policy" + '</b><div>'); // This is also a link to the password policy
|
||||
QV('rpasswordPolicyCallout', true);
|
||||
QH('rpasswordPolicyCallout', passwordPolicyText(Q('rapassword1').value));
|
||||
} else {
|
||||
QH('rpassWarning', '');
|
||||
QV('rpasswordPolicyCallout', false);
|
||||
}
|
||||
}
|
||||
}
|
||||
if ((e != null) && (e.keyCode == 13)) {
|
||||
if (box == 2) { Q('rapassword1').focus(); }
|
||||
if (box == 3) { Q('rapassword2').focus(); }
|
||||
if (box == 4) { Q('rapasswordhint').focus(); }
|
||||
if (box == 6) { Q('resetPassButton').click(); }
|
||||
}
|
||||
if (e != null) { haltEvent(e); }
|
||||
QE('resetPassButton', ok);
|
||||
}
|
||||
|
||||
function passwordPolicyText(pass) {
|
||||
var policy = '<div style=text-align:left>';
|
||||
var counts = strCount(pass);
|
||||
if (passRequirements.min && ((pass == null) || (pass.length < passRequirements.min))) { policy += format("Minimum length of {0}", passRequirements.min) + '<br />'; }
|
||||
if (passRequirements.max && ((pass == null) || (pass.length > passRequirements.max))) { policy += format("Maximum length of {0}", passRequirements.max) + '<br />'; }
|
||||
if (passRequirements.upper && ((pass == null) || (counts.upper < passRequirements.upper))) { policy += format("{0} upper case", passRequirements.upper) + '<br />'; }
|
||||
if (passRequirements.lower && ((pass == null) || (counts.lower < passRequirements.lower))) { policy += format("{0} lower case", passRequirements.lower) + '<br />'; }
|
||||
if (passRequirements.numeric && ((pass == null) || (counts.numeric < passRequirements.numeric))) { policy += format("{0} numeric", passRequirements.numeric) + '<br />'; }
|
||||
if (passRequirements.nonalpha && ((pass == null) || (counts.nonalpha < passRequirements.nonalpha))) { policy += format("{0} non-alphanumeric", passRequirements.nonalpha) + '<br />'; }
|
||||
policy += '</div>';
|
||||
return policy;
|
||||
}
|
||||
|
||||
function showPasswordPolicy() {
|
||||
messagebox("Password Policy", passwordPolicyText());
|
||||
}
|
||||
|
||||
function validateReset(e) {
|
||||
setDialogMode(0);
|
||||
var x = validateEmail(Q('remail').value);
|
||||
QE('eresetButton', x);
|
||||
if ((e != null) && (e.keyCode == 13) && (x == true)) {
|
||||
Q('eresetButton').click();
|
||||
}
|
||||
if (e != null) { haltEvent(e); }
|
||||
}
|
||||
|
||||
// Return a password strength score
|
||||
function checkPasswordStrength(password) {
|
||||
var r = 0, letters = {}, varCount = 0, variations = { digits: /\d/.test(password), lower: /[a-z]/.test(password), upper: /[A-Z]/.test(password), nonWords: /\W/.test(password) }
|
||||
if (!password) return 0;
|
||||
for (var i = 0; i< password.length; i++) { letters[password[i]] = (letters[password[i]] || 0) + 1; r += 5.0 / letters[password[i]]; }
|
||||
for (var c in variations) { varCount += (variations[c] == true) ? 1 : 0; }
|
||||
return parseInt(r + (varCount - 1) * 10);
|
||||
}
|
||||
|
||||
// Check password requirements
|
||||
function checkPasswordRequirements(password, requirements) {
|
||||
if ((requirements == null) || (requirements == '') || (typeof requirements != 'object')) return true;
|
||||
if (requirements.min) { if (password.length < requirements.min) return false; }
|
||||
if (requirements.max) { if (password.length > requirements.max) return false; }
|
||||
var counts = strCount(password);
|
||||
if (requirements.numeric && (counts.numeric < requirements.numeric)) return false;
|
||||
if (requirements.lower && (counts.lower < requirements.lower)) return false;
|
||||
if (requirements.upper && (counts.upper < requirements.upper)) return false;
|
||||
if (requirements.nonalpha && (counts.nonalpha < requirements.nonalpha)) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
function strCount(password) {
|
||||
var counts = { numeric: 0, lower: 0, upper: 0, nonalpha: 0 };
|
||||
if (typeof password != 'string') return counts;
|
||||
for (var i = 0; i < password.length; i++) {
|
||||
if (/\d/.test(password[i])) { counts.numeric++; }
|
||||
if (/[a-z]/.test(password[i])) { counts.lower++; }
|
||||
if (/[A-Z]/.test(password[i])) { counts.upper++; }
|
||||
if (/\W/.test(password[i])) { counts.nonalpha++; }
|
||||
}
|
||||
return counts;
|
||||
}
|
||||
|
||||
function checkToken() {
|
||||
var t1 = Q('tokenInput').value;
|
||||
var t2 = t1.split(' ').join('');
|
||||
if (t1 != t2) { Q('tokenInput').value = t2; }
|
||||
QE('tokenOkButton', (Q('tokenInput').value.length == 6) || (Q('tokenInput').value.length == 8) || (Q('tokenInput').value.length == 44));
|
||||
}
|
||||
|
||||
function resetCheckToken() {
|
||||
var t1 = Q('resetTokenInput').value;
|
||||
var t2 = t1.split(' ').join('');
|
||||
if (t1 != t2) { Q('resetTokenInput').value = t2; }
|
||||
QE('resetTokenOkButton', (Q('resetTokenInput').value.length == 6) || (Q('resetTokenInput').value.length == 8) || (Q('resetTokenInput').value.length == 44));
|
||||
}
|
||||
|
||||
//
|
||||
// POPUP DIALOG
|
||||
//
|
||||
|
||||
// undefined = Hidden, 1 = Generic Message
|
||||
var xxdialogMode;
|
||||
var xxdialogFunc;
|
||||
var xxdialogButtons;
|
||||
var xxdialogTag;
|
||||
var xxcurrentView = 0;
|
||||
|
||||
// Display a dialog box
|
||||
// Parameters: Dialog Mode (0 = none), Dialog Title, Buttons (1 = OK, 2 = Cancel, 3 = OK & Cancel), Call back function(0 = Cancel, 1 = OK), Dialog Content (Mode 2 only)
|
||||
function setDialogMode(x, y, b, f, c, tag) {
|
||||
xxdialogMode = x;
|
||||
xxdialogFunc = f;
|
||||
xxdialogButtons = b;
|
||||
xxdialogTag = tag;
|
||||
QE('idx_dlgOkButton', true);
|
||||
QV('idx_dlgOkButton', b & 1);
|
||||
QV('idx_dlgCancelButton', b & 2);
|
||||
QV('id_dialogclose', (b & 2) || (b & 8));
|
||||
QV('idx_dlgButtonBar', b & 7);
|
||||
if (y) QH('id_dialogtitle', y);
|
||||
for (var i = 1; i < 24; i++) { QV('dialog' + i, i == x); } // Edit this line when more dialogs are added
|
||||
QV('dialog', x);
|
||||
if (c) { if (x == 2) { QH('id_dialogOptions', c); } else { QH('id_dialogMessage', c); } }
|
||||
}
|
||||
|
||||
function dialogclose(x) {
|
||||
var f = xxdialogFunc;
|
||||
var b = xxdialogButtons;
|
||||
var t = xxdialogTag;
|
||||
setDialogMode();
|
||||
if (((b & 8) || x) && f) f(x, t);
|
||||
}
|
||||
|
||||
// Toggle the web page to full screen
|
||||
function toggleFullScreen(toggle) {
|
||||
//if (toggle === 1) { webPageFullScreen = !webPageFullScreen; putstore('webPageFullScreen', webPageFullScreen); }
|
||||
if (webPageFullScreen == false) {
|
||||
// By adding body class, it will change a style of all ellements using CSS selector
|
||||
// No need for JS anymore and it will be consistent style for all the templates.
|
||||
QC('body').remove('fullscreen');
|
||||
} else {
|
||||
QC('body').add('fullscreen');
|
||||
}
|
||||
QV('body', true);
|
||||
center();
|
||||
}
|
||||
|
||||
// Toggle user interface menu
|
||||
function showUserInterfaceSelectMenu() {
|
||||
Q('uiViewButton1').classList.remove('uiSelectorSel');
|
||||
Q('uiViewButton2').classList.remove('uiSelectorSel');
|
||||
Q('uiViewButton3').classList.remove('uiSelectorSel');
|
||||
try { Q('uiViewButton' + uiMode).classList.add('uiSelectorSel'); } catch (ex) { }
|
||||
QV('uiMenu', (QS('uiMenu').display == 'none'));
|
||||
if (nightMode) { Q('uiViewButton4').classList.add('uiSelectorSel'); }
|
||||
}
|
||||
|
||||
function userInterfaceSelectMenu(s) {
|
||||
if (s) { uiMode = s; putstore('uiMode', uiMode); }
|
||||
webPageFullScreen = (uiMode < 3);
|
||||
//webPageStackMenu = (uiMode > 1);
|
||||
toggleFullScreen(0);
|
||||
//toggleStackMenu(0);
|
||||
}
|
||||
|
||||
function toggleNightMode() {
|
||||
nightMode = !nightMode;
|
||||
if (nightMode) { QC('body').add('night'); } else { QC('body').remove('night'); }
|
||||
putstore('_nightMode', (nightMode ? '1' : '0'));
|
||||
}
|
||||
|
||||
function center() {
|
||||
/* Now we use CSS media to achive the same effect as deleted JS */
|
||||
if (webPageFullScreen == false) {
|
||||
QS('centralTable')['margin-top'] = '';
|
||||
} else {
|
||||
var h = ((Q('column_l').clientHeight) / 2) - 220;
|
||||
if (h < 0) h = 0;
|
||||
QS('centralTable')['margin-top'] = h + 'px';
|
||||
}
|
||||
}
|
||||
function messagebox(t, m) { QH('id_dialogMessage', m); setDialogMode(1, t, 1); }
|
||||
function statusbox(t, m) { QH('id_dialogMessage', m); setDialogMode(1, t); }
|
||||
function getDocWidth() { if (window.innerWidth) return window.innerWidth; if (document.documentElement && document.documentElement.clientWidth && document.documentElement.clientWidth != 0) return document.documentElement.clientWidth; return document.getElementsByTagName('body')[0].clientWidth; }
|
||||
function haltEvent(e) { if (e.preventDefault) e.preventDefault(); if (e.stopPropagation) e.stopPropagation(); return false; }
|
||||
function haltReturn(e) { if (e.keyCode == 13) { haltEvent(e); } }
|
||||
function validateEmail(v) { var emailReg = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; return emailReg.test(v); } // New version
|
||||
function putstore(name, val) { try { if (typeof (localStorage) === 'undefined') return; localStorage.setItem(name, val); } catch (e) { } }
|
||||
function getstore(name, val) { try { if (typeof (localStorage) === 'undefined') return val; var v = localStorage.getItem(name); if ((v == null) || (v == null)) return val; return v; } catch (e) { return val; } }
|
||||
function format(format) { var args = Array.prototype.slice.call(arguments, 1); return format.replace(/{(\d+)}/g, function (match, number) { return typeof args[number] != 'undefined' ? args[number] : match; }); };
|
||||
|
||||
</script>
|
||||
|
||||
</body></html>
|
40
views/translations/message_fr.handlebars
Normal file
40
views/translations/message_fr.handlebars
Normal file
|
@ -0,0 +1,40 @@
|
|||
<!DOCTYPE html><html dir="ltr" xmlns="http://www.w3.org/1999/xhtml"><head>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta content="text/html;charset=utf-8" http-equiv="Content-Type">
|
||||
<meta name="viewport" content="user-scalable=1.0,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="format-detection" content="telephone=no">
|
||||
<link type="text/css" href="/styles/style.css" media="screen" rel="stylesheet" title="CSS">
|
||||
<title>MeshCentral - {{{title3}}}</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="container" style="max-height:100vh">
|
||||
<div id="mastheadx"></div>
|
||||
<div id="masthead" style="background:url(logo.png) 0px 0px;background-color:#036;background-repeat:no-repeat;height:66px;width:100%;overflow:hidden">
|
||||
<div style="float:left;height:66px;color:#c8c8c8;padding-left:20px;padding-top:8px">
|
||||
<strong><font style="font-size:46px;font-family:Arial,Helvetica,sans-serif">{{{title}}}</font></strong>
|
||||
</div>
|
||||
<div style="float:left;height:66px;color:#c8c8c8;padding-left:5px;padding-top:14px">
|
||||
<strong><font style="font-size:14px;font-family:Arial,Helvetica,sans-serif">{{{title2}}}</font></strong>
|
||||
</div>
|
||||
</div>
|
||||
<div id="page_content" style="max-height:calc(100vh-138px)">
|
||||
<div id="column_l">
|
||||
<h1>{{{title3}}}</h1>
|
||||
<p style="margin-left:20px">{{{message}}}</p>
|
||||
<br>
|
||||
</div>
|
||||
<div id="footer">
|
||||
<table cellpadding="0" cellspacing="10" style="width:100%">
|
||||
<tbody><tr>
|
||||
<td style="text-align:left"></td>
|
||||
<td style="text-align:right">
|
||||
<a href="terms">Terms & Privacy</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody></table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</body></html>
|
641
views/translations/messenger_fr.handlebars
Normal file
641
views/translations/messenger_fr.handlebars
Normal file
|
@ -0,0 +1,641 @@
|
|||
<!DOCTYPE html><html style="height:100%"><head>
|
||||
<title>MeshMessenger</title>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta content="text/html;charset=utf-8" http-equiv="Content-Type">
|
||||
<meta name="format-detection" content="telephone=no">
|
||||
<link type="text/css" href="styles/style.css" media="screen" rel="stylesheet" title="CSS">
|
||||
<link type="text/css" href="styles/messenger.css" media="screen" rel="stylesheet" title="CSS">
|
||||
<script type="text/javascript" src="scripts/common-0.0.1.js"></script>
|
||||
<script type="text/javascript" src="scripts/filesaver.js"></script>
|
||||
</head>
|
||||
<body style="font-family:Arial,Helvetica,sans-serif">
|
||||
<div id="xtop" style="position:absolute;left:0;right:0;top:0;height:38px;background-color:#036;color:#c8c8c8;box-shadow:3px 3px 10px gray">
|
||||
<div id="notifyButton" class="icon13 topButton" style="margin-right:4px;display:none" title="Enable browser notification" onclick="enableNotificationsButtonClick()"></div>
|
||||
<div id="fileButton" class="icon4 topButton" title="Share a file" style="display:none" onclick="fileButtonClick()"></div>
|
||||
<div id="camButton" class="icon2 topButton" title="Activer caméra et microphone" style="display:none" onclick="camButtonClick()"></div>
|
||||
<div id="micButton" class="icon6 topButton" title="Activer le microphone" style="display:none" onclick="micButtonClick()"></div>
|
||||
<div id="hangupButton" class="icon11 topRedButton" title="Hang up" style="display:none" onclick="hangUpButtonClick(1)"></div>
|
||||
<div style="display:inline-block;width:2px"></div>
|
||||
<div style="padding-top:9px;padding-left:6px;font-size:20px;display:inline-block"><b>MeshMessenger<span id="xtitle"></span></b></div>
|
||||
</div>
|
||||
<div id="xmiddle" style="position:absolute;left:0;right:0;top:38px;bottom:30px">
|
||||
<div style="position:absolute;left:0;right:0;top:0;bottom:0;overflow-y:scroll">
|
||||
<div id="xmsg" style="position:absolute;left:0;right:0;bottom:0;padding:5px"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="xbottom" style="position:absolute;left:0;right:0;bottom:0px;height:30px;background-color:#036">
|
||||
<div style="position:absolute;left:5px;right:215px;bottom:4px;top:4px;background-color:aliceblue"><input id="xouttext" type="text" style="width:calc(100% - 5px)" onfocus="onUserInputFocus(1)" onblur="onUserInputFocus(0)"></div>
|
||||
<input type="button" id="sendButton" value="Send" style="position:absolute;right:110px;width:100px;top:4px;" onclick="xsend(event)">
|
||||
<input type="button" id="clearButton" value="Clear" style="position:absolute;right:5px;width:100px;top:4px;" onclick="displayClear()">
|
||||
</div>
|
||||
<div id="remoteVideo" style="position:absolute;right:24px;top:45px;width:320px;height:calc(240px + 30px);background-color:gray;border-radius:12px 12px 12px 12px;box-shadow:3px 3px 10px gray;display:none">
|
||||
<div style="position:absolute;right:0;left:0;top:2.5px;text-align:center">Remote</div>
|
||||
<video id="remoteVideoCanvas" autoplay="" style="position:absolute;top:20px;left:0;width:100%;height:calc(100% - 30px);background-color:black"></video>
|
||||
</div>
|
||||
<div id="localVideo" style="position:absolute;right:24px;top:320px;width:160px;height:calc(120px + 30px);background-color:gray;border-radius:12px 12px 12px 12px;box-shadow:3px 3px 10px gray;display:none">
|
||||
<div style="position:absolute;right:0;left:0;top:2.5px;text-align:center">Local</div>
|
||||
<video id="localVideoCanvas" autoplay="" muted="" style="position:absolute;top:20px;left:0;width:100%;height:calc(100% - 30px);background-color:black"></video>
|
||||
</div>
|
||||
<input id="uploadFileInput" type="file" multiple="" style="display:none">
|
||||
<script type="text/javascript" onunload="onUnLoad()">
|
||||
var userInputFocus = 0;
|
||||
var args = parseUriArgs();
|
||||
var socket = null; // Websocket object
|
||||
var state = 0; // Connection state. 0 = Disconnected, 1 = Connecting, 2 = Connected.
|
||||
|
||||
// WebRTC sessions and data, audio and video channels
|
||||
var random = Math.random(); // Selected random, larger value initiates WebRTC.
|
||||
var webrtcSessions = { }; // WebRTC objects: 0 for data, 1 for outbound audio/video, 2 for inbound audio/video
|
||||
var webchannel = null; // WebRTC data channel
|
||||
var localStream = null;
|
||||
var remoteStream = null;
|
||||
var multiWebRtc = true; // if set to true, multiple WebRTC sessions will be setup. If false, everything uses one session.
|
||||
var userMediaSupport = 0;
|
||||
var notification = null;
|
||||
getUserMediaSupport(function (x) { userMediaSupport = x; })
|
||||
var webrtcconfiguration = '{{{webrtconfig}}}';
|
||||
if (webrtcconfiguration == '') { webrtcconfiguration = null; } else { try { webrtcconfiguration = JSON.parse(decodeURIComponent(webrtcconfiguration)); } catch (ex) { console.log('Invalid WebRTC config: \"' + webrtcconfiguration + '\".'); webrtcconfiguration = null; } }
|
||||
|
||||
// File transfer state
|
||||
var fileUploads = [];
|
||||
var fileDownloads = {};
|
||||
var currentFileUpload = null;
|
||||
var currentFileDownload = null;
|
||||
|
||||
// Set the title
|
||||
if (args.title) { QH('xtitle', ' - ' + args.title); document.title = document.title + ' - ' + args.title; }
|
||||
|
||||
// Setup web notifications
|
||||
if (Notification) { QV('notifyButton', Notification.permission != 'granted'); }
|
||||
|
||||
// Listen to drag & drop events
|
||||
document.addEventListener('dragover', haltEvent, false);
|
||||
document.addEventListener('dragleave', haltEvent, false);
|
||||
document.addEventListener('drop', fileDrop, false);
|
||||
|
||||
document.onclick = function (e) {
|
||||
if (Notification) { QV('notifyButton', Notification.permission != 'granted'); }
|
||||
if (notification != null) { notification.close(); notification = null; }
|
||||
}
|
||||
|
||||
// Trap document key up events
|
||||
document.onkeyup = function ondockeypress(e) {
|
||||
if (Notification) { QV('notifyButton', Notification.permission != 'granted'); }
|
||||
if (notification != null) { notification.close(); notification = null; }
|
||||
if (state == 2) {
|
||||
if ((e.keyCode == 8) && (userInputFocus == 0)) {
|
||||
// Backspace
|
||||
var outtext = Q('xouttext').value;
|
||||
if (outtext.length > 0) { Q('xouttext').value = outtext.substring(0, outtext.length - 1); }
|
||||
}
|
||||
}
|
||||
if (userInputFocus == 0) { haltEvent(e); return false; }
|
||||
}
|
||||
|
||||
// Trap document key presses
|
||||
document.onkeypress = function ondockeypress(e) {
|
||||
if (Notification) { QV('notifyButton', Notification.permission != 'granted'); }
|
||||
if (notification != null) { notification.close(); notification = null; }
|
||||
if (state == 2) {
|
||||
if (e.keyCode == 13) {
|
||||
// Return
|
||||
xsend(e);
|
||||
} else {
|
||||
// Any other key
|
||||
if ((userInputFocus == 0) && (e.key.length == 1)) { Q('xouttext').value = Q('xouttext').value + e.key; }
|
||||
}
|
||||
}
|
||||
if (userInputFocus == 0) { haltEvent(e); return false; }
|
||||
}
|
||||
|
||||
function onUserInputFocus(x) { userInputFocus = x; }
|
||||
function displayClear() { QH('xmsg', ''); cancelAllFileTransfers(); fileUploads = [], fileDownloads = {}; }
|
||||
|
||||
// Polyfill FileReader if needed
|
||||
if (!FileReader.prototype.readAsBinaryString) {
|
||||
FileReader.prototype.readAsBinaryString = function (fileData) {
|
||||
var binary = '', self = this, reader = new FileReader();
|
||||
reader.onload = function (e) {
|
||||
var bytes = new Uint8Array(reader.result);
|
||||
for (var i = 0; i < bytes.byteLength; i++) { binary += String.fromCharCode(bytes[i]); }
|
||||
self.onload({ target: { result: binary } });
|
||||
}
|
||||
reader.readAsArrayBuffer(fileData);
|
||||
}
|
||||
}
|
||||
|
||||
// Detect if microphone & camera are present
|
||||
// 0 = nomedia, 1 = miconly, 2 = mic&cam
|
||||
function getUserMediaSupport(func) {
|
||||
try {
|
||||
navigator.mediaDevices.enumerateDevices().then(function (devices) {
|
||||
try {
|
||||
var mic = 0, cam = 0;
|
||||
devices.forEach(function (device) {
|
||||
if (device.kind === 'audioinput') { mic = 1; }
|
||||
if (device.kind === 'videoinput') { cam = 1; }
|
||||
});
|
||||
if (mic == 0) { func(0); }
|
||||
func(mic + cam);
|
||||
} catch (ex) { }
|
||||
})
|
||||
} catch (ex) { }
|
||||
}
|
||||
|
||||
// Display a control message
|
||||
function displayControl(msg) {
|
||||
QA('xmsg', '<div style="clear:both"><div style="color:gray;float:left;margin-bottom:2px">' + msg + '</div><div></div></div>');
|
||||
Q('xmsg').scrollTop = Q('xmsg').scrollHeight;
|
||||
}
|
||||
|
||||
function displayLocalVideo(active) { QV('localVideo', active); adjustVideoWindows(); }
|
||||
function displayRemoteVideo(active) { QV('remoteVideo', active); adjustVideoWindows(); }
|
||||
function adjustVideoWindows() {
|
||||
//var lv = (QS('localVideo')['display'] != 'none');
|
||||
var rv = (QS('remoteVideo')['display'] != 'none');
|
||||
QS('localVideo')['top'] = rv ? '320px' : '45px';
|
||||
}
|
||||
|
||||
// Display a message from the remote user
|
||||
function displayRemote(msg) {
|
||||
QA('xmsg', '<div style="clear:both"><div class="remoteBubble">' + msg + '</div><div></div></div>');
|
||||
Q('xmsg').scrollTop = Q('xmsg').scrollHeight;
|
||||
|
||||
// If web notifications are granted, use it.
|
||||
if (Notification) { QV('notifyButton', Notification.permission != 'granted'); }
|
||||
if (Notification && (Notification.permission == 'granted')) {
|
||||
if (notification != null) { notification.close(); notification = null; }
|
||||
if (args.title) {
|
||||
notification = new Notification("MeshMessenger" + ' - ' + args.title, { body: msg });
|
||||
} else {
|
||||
notification = new Notification("MeshMessenger", { body: msg });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Display and send a message from the local user
|
||||
function xsend(event) {
|
||||
if (notification != null) { notification.close(); notification = null; }
|
||||
if (Notification) { QV('notifyButton', Notification.permission != 'granted'); }
|
||||
var outtext = Q('xouttext').value;
|
||||
if (outtext.length > 0) {
|
||||
Q('xouttext').value = '';
|
||||
QA('xmsg', '<div style="clear:both"><div class="localBubble">' + outtext + '</div><div></div></div>');
|
||||
Q('xmsg').scrollTop = Q('xmsg').scrollHeight;
|
||||
send({ action: 'chat', msg: outtext });
|
||||
}
|
||||
}
|
||||
|
||||
function haltEvent(e) { if (e.preventDefault) e.preventDefault(); if (e.stopPropagation) e.stopPropagation(); return false; }
|
||||
function parseUriArgs() { var name, r = {}, parsedUri = window.document.location.href.split(/[\?&|\=]/); parsedUri.splice(0, 1); for (x in parsedUri) { switch (x % 2) { case 0: { name = decodeURIComponent(parsedUri[x]); break; } case 1: { r[name] = decodeURIComponent(parsedUri[x]); var x = parseInt(r[name]); if (x == r[name]) { r[name] = x; } break; } default: { break; } } } return r; }
|
||||
|
||||
// Update user controls
|
||||
function updateControls() {
|
||||
QE('sendButton', state == 2);
|
||||
QE('clearButton', state == 2);
|
||||
QE('xouttext', state == 2);
|
||||
QV('fileButton', state == 2);
|
||||
QV('camButton', webchannel && webchannel.ok && !localStream && (userMediaSupport == 2));
|
||||
QV('micButton', webchannel && webchannel.ok && !localStream && (userMediaSupport > 0));
|
||||
QV('hangupButton', webchannel && webchannel.ok && localStream);
|
||||
}
|
||||
|
||||
// This is the WebRTC setup
|
||||
function startWebRTC(id, startDataChannel) {
|
||||
if ((webrtcSessions[0] != null) && (multiWebRtc == false)) { return webrtcSessions[0]; };
|
||||
|
||||
// Setup the WebRTC object
|
||||
var webrtc = null;
|
||||
if (typeof RTCPeerConnection !== 'undefined') { webrtc = new RTCPeerConnection(webrtcconfiguration); }
|
||||
else if (typeof webkitRTCPeerConnection !== 'undefined') { webrtc = new webkitRTCPeerConnection(webrtcconfiguration); }
|
||||
if (webrtc == null) return null; // No WebRTC support.
|
||||
|
||||
webrtc.id = id;
|
||||
webrtc.onicecandidate = function (e) { try { if (e.candidate != null) { sendws({ action: 'webRtcIce', ice: e.candidate, id: this.id }); } } catch (ex) { } }
|
||||
webrtc.oniceconnectionstatechange = function () { if (webrtc && webrtc.iceConnectionState == 'failed') { webrtc.close(); if (webrtcSessions[webrtc.id]) { delete webrtcSessions[webrtc.id]; } } }
|
||||
webrtc.ondatachannel = function (ev) {
|
||||
//console.log('ondatachannel');
|
||||
webchannel = ev.channel;
|
||||
webchannel.onmessage = function (event) { processMessage(event.data, 2); };
|
||||
webchannel.onopen = function () { webchannel.ok = true; updateControls(); sendws({ action: 'rtcSwitch', v: 0 }); };
|
||||
webchannel.onclose = function (event) { if (webchannel && webchannel.ok) { disconnect(); } else { hangUpButtonClick(0); } }
|
||||
}
|
||||
webrtc.onnegotiationneeded = function (event) {
|
||||
if (webrtc.holdTimer != null) return;
|
||||
webrtc.holdTimer = setTimeout(function () { // This time is needed to keep Chrome from being to excited. Wait until we add all tracks before kicking this off.
|
||||
//console.log('onnegotiationneeded', id);
|
||||
webrtc.holdTimer = null;
|
||||
webrtc.createOffer(function (offer) { /*console.log('offer', offer.sdp.length);*/ webrtc.setLocalDescription(offer, function () { sendws({ action: 'webRtcSdp', sdp: offer, id: id }); }, function () { hangUpButtonClick(id); }); }, function () { hangUpButtonClick(id); });
|
||||
}, 20);
|
||||
}
|
||||
webrtc.ontrack = function (event) {
|
||||
//console.log('ontrack', id);
|
||||
var video = Q('remoteVideoCanvas');
|
||||
video.srcObject = remoteStream = event.streams[0];
|
||||
video.onloadedmetadata = function (e) { video.play(); };
|
||||
displayRemoteVideo(true);
|
||||
}
|
||||
//webrtc.onremovetrack = function (event) { console.log('onremovetrack'); }
|
||||
//webrtc.onicegatheringstatechange = function (event) { console.log('onicegatheringstatechange', event); }
|
||||
//webrtc.onsignalingstatechange = function (event) { console.log('onsignalingstatechange', event); }
|
||||
|
||||
// Initiate the WebRTC offer or handle the offer from the peer.
|
||||
if (startDataChannel == true) {
|
||||
webchannel = webrtc.createDataChannel('DataChannel', {}); // { ordered: false, maxRetransmits: 2 }
|
||||
webchannel.onmessage = function (event) { processMessage(event.data, 2); };
|
||||
webchannel.onopen = function () { webchannel.ok = true; updateControls(); sendws({ action: 'rtcSwitch', v: 0 }); };
|
||||
webchannel.onclose = function (event) { if (webchannel && webchannel.ok) { disconnect(); } else { hangUpButtonClick(0); } }
|
||||
}
|
||||
|
||||
webrtcSessions[id] = webrtc;
|
||||
return webrtc;
|
||||
}
|
||||
|
||||
function webRtcHandleOffer(id, description) {
|
||||
//console.log('webRtcHandleOffer', description.sdp.length);
|
||||
var webrtc = webrtcSessions[id];
|
||||
if (webrtc) {
|
||||
webrtc.setRemoteDescription(new RTCSessionDescription(description), function () {
|
||||
if (description.type == 'offer') {
|
||||
webrtc.createAnswer(function (answer) {
|
||||
webrtc.setLocalDescription(answer, function (a, b) {
|
||||
try { sendws({ action: 'webRtcSdp', sdp: answer, id: id }); } catch (ex) { }
|
||||
}, function () { hangUpButtonClick(id); });
|
||||
}, function () { hangUpButtonClick(id); });
|
||||
}
|
||||
}, function () { hangUpButtonClick(id); });
|
||||
}
|
||||
}
|
||||
|
||||
// Indicate to peer that data traffic will no longer be sent over websocket and start holding traffic.
|
||||
function performWebRtcSwitch() {
|
||||
if (webchannel && webchannel.ok) { sendws({ action: 'rtcSwitch', v: 1 }); webchannel.xoutBuffer = []; }
|
||||
}
|
||||
|
||||
// Disconnect everything
|
||||
function disconnect() {
|
||||
if (state > 0) { displayControl("Connection closed."); }
|
||||
if (state > 1) { setTimeout(start, 500); }
|
||||
cancelAllFileTransfers();
|
||||
hangUpButtonClick(0, true); // Data channel
|
||||
hangUpButtonClick(1, true); // Local audio/video
|
||||
hangUpButtonClick(2, true); // Remote audio/video
|
||||
if (socket != null) { socket.close(); socket = null; }
|
||||
updateControls();
|
||||
state = 0;
|
||||
}
|
||||
|
||||
// Send data over the current transport (WebRTC first)
|
||||
function send(data) {
|
||||
if (state != 2) return; // If not in connected state, ignore this.
|
||||
if (typeof data == 'object') { data = JSON.stringify(data); } // If this is an object, convert it to a string.
|
||||
if (webchannel && webchannel.ok) { if (webchannel.xoutBuffer != null) { webchannel.xoutBuffer.push(data); } else { webchannel.send(data); } } // If WebRTC channel is possible, use it or hold until we can use it.
|
||||
else { if (socket != null) { try { socket.send(data); } catch (ex) { } } } // If a websocket channel is present, use that.
|
||||
}
|
||||
|
||||
// Send data over the websocket transport (WebSocket only)
|
||||
function sendws(data) {
|
||||
if (state != 2) return;
|
||||
//console.log('SEND', data);
|
||||
if (typeof data == 'object') { data = JSON.stringify(data); }
|
||||
if (socket != null) { socket.send(data); }
|
||||
}
|
||||
|
||||
// WebRTC id switcher (0 -> 0, 1 -> 2, 2 -> 1)
|
||||
function webRtcIdSwitch(id) { if (id == 0) { return 0; } return 3 - id; }
|
||||
|
||||
// Process incoming messages
|
||||
function processMessage(data, transport) {
|
||||
if (typeof data == 'string') {
|
||||
try { data = JSON.parse(data); } catch (ex) { console.log('Unable to parse', data); return; }
|
||||
//console.log('RECV', data);
|
||||
switch (data.action) {
|
||||
case 'chat': { displayRemote(data.msg); break; } // Incoming chat message.
|
||||
case 'random': { if (random > data.random) { startWebRTC(0, true); } break; } // If we have a larger random value, we start WebRTC.
|
||||
case 'webRtcSdp': { if (!webrtcSessions[webRtcIdSwitch(data.id)]) { startWebRTC(webRtcIdSwitch(data.id), false); } webRtcHandleOffer(webRtcIdSwitch(data.id), data.sdp); break; } // Remote WebRTC offer or answer.
|
||||
case 'webRtcIce': { var webrtc = webrtcSessions[webRtcIdSwitch(data.id)]; if (webrtc) { try { webrtc.addIceCandidate(new RTCIceCandidate(data.ice)); } catch (ex) { } } break; } // Remote ICE candidate
|
||||
case 'videoStop': { hangUpButtonClick(webRtcIdSwitch(data.id), true); break; }
|
||||
case 'rtcSwitch': { // WebRTC switch over commands.
|
||||
switch (data.v) {
|
||||
case 0: { performWebRtcSwitch(); break; } // Other side is ready for switch over to WebRTC
|
||||
case 1: { sendws({ action: 'rtcSwitch', v: 2 }); break; } // Other side no longer sending data on websocket, confirm we got the end marker
|
||||
case 2: { for (var i in webchannel.xoutBuffer) { webchannel.send(webchannel.xoutBuffer[i]); } delete webchannel.xoutBuffer; break; } // Send any pending data over WebRTC and start using WebRTC with all traffic
|
||||
default: { console.log('Unknown rtcSwitch value: ' + data.action); break; } //
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'file': { startFileDownload(data); break; }
|
||||
case 'fileUploadCancel': { cancelFileTransfer(data.id); break; }
|
||||
case 'fileUploadStart': {
|
||||
if (fileDownloads[data.id]) {
|
||||
currentFileDownload = fileDownloads[data.id];
|
||||
currentFileDownload.data = '';
|
||||
changeFileInfo(data.id, 2, 0);
|
||||
continueFileDownload(data);
|
||||
send({ action: 'fileUploadAck', id: data.id });
|
||||
} break;
|
||||
}
|
||||
case 'fileUploadEnd': {
|
||||
if (currentFileDownload && (currentFileDownload.id == data.id)) {
|
||||
changeFileInfo(data.id, 3, 200);
|
||||
currentFileDownload.done = 1;
|
||||
currentFileDownload = null;
|
||||
send({ action: 'fileUploadAck', id: data.id });
|
||||
}
|
||||
currentFileDownload = null;
|
||||
break;
|
||||
}
|
||||
case 'fileUploadAck': {
|
||||
continueFileUpload();
|
||||
break;
|
||||
}
|
||||
case 'fileData': {
|
||||
if (currentFileDownload && (currentFileDownload.id == data.id)) {
|
||||
currentFileDownload.data += data.data;
|
||||
changeFileInfo(data.id, 2, (currentFileDownload.data.length * 200 / currentFileDownload.size));
|
||||
send({ action: 'fileUploadAck', id: data.id });
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: { console.log('Unhandled object data', data); break; }
|
||||
}
|
||||
} else {
|
||||
console.log('Unhandled data', typeof data, data);
|
||||
}
|
||||
}
|
||||
|
||||
// File sharing button
|
||||
function fileButtonClick() {
|
||||
var chooser = Q('uploadFileInput');
|
||||
if (chooser.getAttribute('eventset') != 1) {
|
||||
chooser.setAttribute('eventset', '1');
|
||||
chooser.addEventListener('change', fileSelect, false);
|
||||
}
|
||||
chooser.value = null;
|
||||
chooser.click();
|
||||
}
|
||||
|
||||
// User selected one or more files to upload to remote user.
|
||||
function fileSelect() {
|
||||
if (state != 2) return;
|
||||
var x = Q('uploadFileInput');
|
||||
if (x.files.length > 10) {
|
||||
displayControl("Limit of 10 file uploads at the same time.");
|
||||
} else {
|
||||
for (var i = 0; i < x.files.length; i++) {
|
||||
if (x.files[i].size > 0) {
|
||||
var reader = new FileReader();
|
||||
reader.onload = function (e) { this.xfile.data = e.target.result; startFileUpload(this.xfile); };
|
||||
reader.xfile = x.files[i];
|
||||
reader.readAsBinaryString(x.files[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// User drag & droped one or more files to upload to remote user.
|
||||
function fileDrop(e) {
|
||||
haltEvent(e);
|
||||
if ((state != 2) || (e.dataTransfer == null)) return;
|
||||
if (e.dataTransfer.files.length > 10) {
|
||||
displayControl("Limit of 10 file uploads at the same time.");
|
||||
} else {
|
||||
for (var i = 0; i < e.dataTransfer.files.length; i++) {
|
||||
if (e.dataTransfer.files[i].size > 0) {
|
||||
var reader = new FileReader();
|
||||
reader.onload = function (e) { this.xfile.data = e.target.result; startFileUpload(this.xfile); };
|
||||
reader.xfile = e.dataTransfer.files[i];
|
||||
reader.readAsBinaryString(e.dataTransfer.files[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function startFileUpload(file) {
|
||||
if (state != 2) return;
|
||||
file.id = Math.random();
|
||||
fileUploads.push(file);
|
||||
QA('xmsg', '<div style="clear:both"></div><div id="FILEUP-' + file.id + '" class="localBubble" style="width:240px;cursor:pointer" onclick="cancelFileTransfer(\'' + file.id + '\')"><div id="FILEUP-ICON-' + file.id + '" class="fileicon" style="float:left;width:32px;height:32px"></div><div><div id="FILEUP-NAME-' + file.id + '" style="height:16px;overflow:hidden;white-space:nowrap;" title="' + file.name + '">' + file.name + '</div><div style="width:200px;background-color:lightgray;margin-left:32px;border-radius:3px;margin-top:3px;height:11px"><div id="FILEUP-PROGRESS-' + file.id + '" style="width:0px;background-color:green;border-radius:3px;height:11px"> </div></div></div></div>');
|
||||
Q('xmsg').scrollTop = Q('xmsg').scrollHeight;
|
||||
send({ action: 'file', size: file.size, id: file.id, type: file.type, name: file.name });
|
||||
if (currentFileUpload == null) continueFileUpload();
|
||||
}
|
||||
|
||||
function startFileDownload(file) {
|
||||
if (state != 2) return;
|
||||
fileDownloads[file.id] = file;
|
||||
QA('xmsg', '<div style="clear:both"></div><div id="FILEUP-' + file.id + '" class="remoteBubble" style="width:240px;cursor:pointer" onclick="saveFileTransfer(\'' + file.id + '\')"><div id="FILEUP-ICON-' + file.id + '" class="fileicon" style="float:left;width:32px;height:32px"></div><div><div id="FILEUP-NAME-' + file.id + '" style="height:16px;overflow:hidden;white-space:nowrap;" title="' + file.name + '">' + file.name + '</div><div style="width:200px;background-color:lightgray;margin-left:32px;border-radius:3px;margin-top:3px;height:11px"><div id="FILEUP-PROGRESS-' + file.id + '" style="width:0px;background-color:green;border-radius:3px;height:11px"> </div></div></div></div>');
|
||||
Q('xmsg').scrollTop = Q('xmsg').scrollHeight;
|
||||
}
|
||||
|
||||
// Change the file icon and progress
|
||||
function changeFileInfo(id, icon, progress, progressColor) {
|
||||
if (icon) {
|
||||
Q('FILEUP-ICON-' + id).classList.remove('fileicon');
|
||||
Q('FILEUP-ICON-' + id).classList.remove('fileiconx');
|
||||
Q('FILEUP-ICON-' + id).classList.remove('fileicontransfer');
|
||||
Q('FILEUP-ICON-' + id).classList.remove('fileicondone');
|
||||
Q('FILEUP-ICON-' + id).classList.add(['fileicon', 'fileiconx', 'fileicontransfer', 'fileicondone'][icon]);
|
||||
}
|
||||
if (progress) { QS('FILEUP-PROGRESS-' + id)['width'] = progress + 'px'; }
|
||||
if (progressColor) { QS('FILEUP-PROGRESS-' + id)['background-color'] = progressColor; }
|
||||
}
|
||||
|
||||
// Convert a string into a blob
|
||||
function data2blob(data) {
|
||||
var bytes = new Array(data.length);
|
||||
for (var i = 0; i < data.length; i++) bytes[i] = data.charCodeAt(i);
|
||||
return new Blob([new Uint8Array(bytes)]);
|
||||
};
|
||||
|
||||
function saveFileTransfer(id) {
|
||||
var f = fileDownloads[id];
|
||||
if (f && f.done == 1) { saveAs(data2blob(f.data), f.name); }
|
||||
}
|
||||
|
||||
function cancelFileTransfer(id) {
|
||||
if ((currentFileUpload != null) && (currentFileUpload.id == id)) { currentFileUpload = null; }
|
||||
if ((currentFileDownload != null) && (currentFileDownload.id == id)) { currentFileDownload = null; }
|
||||
|
||||
var found = false;
|
||||
if (fileDownloads[id] && (fileDownloads[id].done != 1)) {
|
||||
delete fileDownloads[id];
|
||||
found = true;
|
||||
} else {
|
||||
for (var i in fileUploads) {
|
||||
if (fileUploads[i].id == id) {
|
||||
send({ action: 'fileUploadCancel', id: id });
|
||||
fileUploads.splice(i, 1);
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (found) { changeFileInfo(id, 1, 200, 'gray'); } // Only cancel a file if it was in the file queue.
|
||||
}
|
||||
|
||||
function cancelAllFileTransfers() {
|
||||
for (var i in fileDownloads) { cancelFileTransfer(fileDownloads[i].id); }
|
||||
for (var i in fileUploads) { cancelFileTransfer(fileUploads[i].id); }
|
||||
}
|
||||
|
||||
function continueFileUpload() {
|
||||
if (currentFileUpload == null) {
|
||||
// Select the next file to upload
|
||||
if (fileUploads.length == 0) { return; } // Nothing to do
|
||||
currentFileUpload = fileUploads[0];
|
||||
currentFileUpload.ptr = 0;
|
||||
|
||||
// Indicate that we are sending this file
|
||||
send({ action: 'fileUploadStart', size: currentFileUpload.size, id: currentFileUpload.id, type: currentFileUpload.type, name: currentFileUpload.name });
|
||||
} else {
|
||||
if (currentFileUpload.size <= currentFileUpload.ptr) {
|
||||
// If we are done, send the end marker
|
||||
send({ action: 'fileUploadEnd', size: currentFileUpload.size, id: currentFileUpload.id, type: currentFileUpload.type, name: currentFileUpload.name });
|
||||
changeFileInfo(currentFileUpload.id, 3, 200);
|
||||
fileUploads.splice(0, 1);
|
||||
currentFileUpload = null;
|
||||
continueFileUpload(); // Send the next file
|
||||
} else {
|
||||
// Send the next block
|
||||
var nextBlockLen = Math.min(4000, currentFileUpload.data.length - currentFileUpload.ptr);
|
||||
var data = currentFileUpload.data.substring(currentFileUpload.ptr, currentFileUpload.ptr + nextBlockLen);
|
||||
send({ action: 'fileData', id: currentFileUpload.id, data: data });
|
||||
currentFileUpload.ptr += nextBlockLen;
|
||||
changeFileInfo(currentFileUpload.id, 0, (currentFileUpload.ptr * 200 / currentFileUpload.size));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function continueFileDownload(msg) {
|
||||
send({ action: 'fileUploadAck', id: msg.id });
|
||||
}
|
||||
|
||||
// Toggle notification
|
||||
function enableNotificationsButtonClick() {
|
||||
if (Notification) { Notification.requestPermission().then(function (permission) { QV('notifyButton', permission != 'granted'); }); }
|
||||
return false;
|
||||
}
|
||||
|
||||
// Camera button
|
||||
function camButtonClick() {
|
||||
if (localStream == null) { startLocalStream({ video: true, audio: true }); }
|
||||
}
|
||||
|
||||
// Microphone
|
||||
function micButtonClick() {
|
||||
if (localStream == null) { startLocalStream({ video: false, audio: true }); }
|
||||
}
|
||||
|
||||
function hangUpButtonClick(id, fromRemote) {
|
||||
//console.log('hangUpButtonClick', id);
|
||||
var localVideo = Q('localVideoCanvas');
|
||||
var remoteVideo = Q('remoteVideoCanvas');
|
||||
var webrtc = webrtcSessions[(multiWebRtc == true)? id : 0];
|
||||
|
||||
if ((id == 0) && (webchannel != null)) { try { webchannel.close(); } catch (e) { } webchannel = null; }
|
||||
|
||||
if (webrtc) {
|
||||
if ((multiWebRtc == true) || (id == 0)) {
|
||||
webrtc.ontrack = null;
|
||||
webrtc.onremovetrack = null;
|
||||
webrtc.onremovestream = null;
|
||||
webrtc.onnicecandidate = null;
|
||||
webrtc.oniceconnectionstatechange = null;
|
||||
webrtc.onsignalingstatechange = null;
|
||||
webrtc.onicegatheringstatechange = null;
|
||||
webrtc.onnotificationneeded = null;
|
||||
}
|
||||
|
||||
if ((id == 1) && localStream) { var tracks = localStream.getTracks(); for (var i in tracks) { tracks[i].stop(); } localStream = null; }
|
||||
if ((id == 2) && remoteStream) { var tracks = remoteStream.getTracks(); for (var i in tracks) { tracks[i].stop(); } remoteStream = null; }
|
||||
|
||||
if ((multiWebRtc == true) || (id == 0)) {
|
||||
webrtc.close();
|
||||
delete webrtcSessions[id];
|
||||
}
|
||||
}
|
||||
|
||||
if (id == 1) {
|
||||
localVideo.removeAttribute('src');
|
||||
localVideo.removeAttribute('srcObject');
|
||||
if (localStream != null) { localStream = null; }
|
||||
displayLocalVideo(false);
|
||||
} else if (id == 2) {
|
||||
remoteVideo.removeAttribute('src');
|
||||
remoteVideo.removeAttribute('srcObject');
|
||||
displayRemoteVideo(false);
|
||||
}
|
||||
|
||||
if (fromRemote != true) { send({ action: 'videoStop', id: id }); }
|
||||
updateControls();
|
||||
}
|
||||
|
||||
// Setup local audio/video
|
||||
function startLocalStream(constraints) {
|
||||
var channel = (multiWebRtc == true) ? 1 : 0;
|
||||
if (localStream != null) return;
|
||||
if ((multiWebRtc == true) && (webrtcSessions[1] != null)) return;
|
||||
if (navigator.mediaDevices.getUserMedia) {
|
||||
localStream = 1;
|
||||
updateControls();
|
||||
navigator.mediaDevices.getUserMedia(constraints)
|
||||
.then(function (stream) {
|
||||
localStream = stream;
|
||||
var tracks = localStream.getTracks();
|
||||
var webrtc = startWebRTC(channel);
|
||||
if (constraints.video == true) {
|
||||
var video = Q('localVideoCanvas');
|
||||
video.srcObject = stream;
|
||||
video.onloadedmetadata = function (e) { video.play(); };
|
||||
displayLocalVideo(true);
|
||||
}
|
||||
for (var i in tracks) { webrtc.addTrack(tracks[i], localStream); }
|
||||
}, function (err) {
|
||||
displayControl(err.message + '.');
|
||||
hangUpButtonClick(1);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// This is the main start
|
||||
function start() {
|
||||
// Get started
|
||||
updateControls();
|
||||
if ((typeof args.id == 'string') && (args.id.length > 0)) {
|
||||
var url = window.location.protocol.replace('http', 'ws') + '//' + window.location.host + window.location.pathname.substring(0, window.location.pathname.lastIndexOf('/')) + '/meshrelay.ashx?id=' + args.id;
|
||||
if ((args.auth != null) && (args.auth != '')) { url += '&auth=' + args.auth; }
|
||||
socket = new WebSocket(url);
|
||||
socket.onopen = function () { state = 1; displayControl("Waiting for other user..."); }
|
||||
socket.onerror = function (e) { /*console.error(e);*/ }
|
||||
socket.onclose = function () { disconnect(); }
|
||||
socket.onmessage = function (msg) {
|
||||
if ((state < 2) && (typeof msg.data == 'string') && ((msg.data == 'c') || (msg.data == 'cr'))) {
|
||||
hangUpButtonClick(0, true);
|
||||
hangUpButtonClick(1, true);
|
||||
hangUpButtonClick(2, true);
|
||||
displayControl("Connected.");
|
||||
state = 2;
|
||||
updateControls();
|
||||
sendws({ action: 'random', random: random }); // Send a random number. Higher number starts the WebRTC session.
|
||||
return;
|
||||
}
|
||||
if (state == 2) { processMessage(msg.data, 1); }
|
||||
}
|
||||
} else {
|
||||
displayControl("Error: No connection key specified.");
|
||||
}
|
||||
}
|
||||
|
||||
start();
|
||||
|
||||
function onUnLoad() {
|
||||
for (var i = 0; i < 3; i++) { if (webrtcSessions[i]) { webrtcSessions[i].close(); delete webrtcSessions[i]; } }
|
||||
if (webchannel != null) { try { webchannel.close(); } catch (e) { } webchannel = null; }
|
||||
if (socket != null) { try { socket.close(); } catch (e) { } socket = null; }
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
</body></html>
|
Loading…
Add table
Add a link
Reference in a new issue