vn-access/src/main.js

759 lines
20 KiB
JavaScript
Executable File

var Conf = {
appName: 'Verdnatura',
dsName: 'verdnatura',
odbcPath: 'HKCU\\Software\\ODBC\\ODBC.INI\\',
regPath: 'HKCU\\SOFTWARE\\Verdnatura\\vn-access',
defaultModule: 'vn',
defaultLocale: 'es',
defaultBranch: 'master',
defaultDatasource: 'production',
defaultRemoteURL: 'https://salix.verdnatura.es',
dbName: 'vn2008',
maxCorruptSize: 600,
odbcDriver: 'MySQL ODBC 8.0 Unicode Driver',
driverPath: '\\MySQL\\Connector ODBC 8.0\\myodbc8w.dll',
version: 4,
cdnURL: 'https://cdn.verdnatura.es/vn-access',
datasources: {
production: {
DESCRIPTION: 'production',
SERVER: 'db.verdnatura.es',
PORT: '3306',
SSLMODE: 'VERIFY_IDENTITY'
},
test: {
DESCRIPTION: 'test',
SERVER: 'test-db.verdnatura.es',
PORT: '3306',
SSLMODE: 'VERIFY_IDENTITY'
},
dev: {
DESCRIPTION: 'dev',
SERVER: 'dev-db.verdnatura.es',
PORT: '3307',
SSLMODE: 'DISABLED'
},
localhost: {
DESCRIPTION: 'local',
SERVER: 'localhost',
PORT: '3306',
SSLMODE: 'DISABLED'
}
}
};
var Locale = {
es: {
"Enter a username":
"Introduce un nombre de usuario",
"Enter a password":
"Introduce una contraseña",
"Server can't be reached":
"No se ha podido conectar con el servidor",
"Updating":
"Actualizando",
"Bad login":
"Usuario o contraseña incorrectos, recuerda que se hace distinción entre mayúsculas y minúsculas",
"Application it's already open":
"La aplicación ya está abierta",
"Loading":
"Cargando",
"Error while updating":
"Error al actualizar",
"Microsoft Access 2003 is not installed":
"Microsoft Access 2003 no está instalado en el sistema",
"MDB file not found":
"No se encontró el fichero MDB",
"Cache files have been deleted":
"Se han borrado todos los ficheros almacenados en la caché"
}
};
var App = {
shell: new ActiveXObject('WScript.Shell'),
fso: new ActiveXObject('scripting.filesystemobject'),
init: function() {
// Specify the size of window
var width = 420;
var height = 570;
window.resizeTo(width, height);
window.moveTo((screen.width - width) / 2, (screen.height - height) / 2);
},
onUnload: function() {
this.disableUi(false);
},
onLoad: function() {
// Initializes the global variables
var split = Verdnatura.commandLine.match(/(?:[^\s"]+|"[^"]*")+/g);
if (split.length > 1)
this.module = split[1].replace(/^"+|"+$/g, '');
if (!this.module)
this.module = Conf.defaultModule;
this.appDir = this.getEnv('ProgramFiles') + '\\' + Conf.appName;
this.moduleDir = this.shell.SpecialFolders('AppData') + '\\' + Conf.appName;
this.compressFile = this.getEnv('TEMP') + '\\' + this.module + '.7z';
this.mdbFile = this.moduleDir + '\\' + this.module + '.mdb';
this.lockFile = this.moduleDir + '\\' + this.module + '.ldb';
this.certFile = this.appDir + '\\cacert.pem';
// Creates the necessary registry entries
var lastVersion = this.regRead(Conf.regPath, 'lastExecutedVersion');
if (!lastVersion || lastVersion != Conf.version) {
var path = 'HKCU\\Software\\Microsoft\\Office\\11.0\\Access\\';
// Creates the Access configuration entries
this.regWrites(path + 'Settings', {
'Confirm Document Deletions': false,
'Confirm Action Queries': false,
'Confirm Record Changes': false
});
this.regWrite(path + 'Security', 'Level', 1);
// Creates the MySQL ODBC connection
this.createODBC(
Conf.odbcPath,
Conf.dsName,
Conf.odbcDriver
);
this.createDatasources();
this.regWrite(Conf.regPath, 'remoteURL', Conf.defaultRemoteURL);
this.regWrite(Conf.regPath, 'lastExecutedVersion', Conf.version);
// Delete old strings values (REMOVE IN VERSION 5)
this.regDelete(Conf.regPath + 'configured');
this.regDelete(Conf.regPath + 'remember');
}
var notSignOut = this.regRead(Conf.regPath, 'notSignOut');
var password = this.regRead(Conf.odbcPath + Conf.dsName, 'PWD');
this.$('user').value = this.regRead(Conf.odbcPath + Conf.dsName, 'UID');
this.$('password').value = password;
// Branch options
this.$('branch').value = this.getBranch();
this.onChangeBranch();
// Datasource options
this.$('datasource').value = this.getDatasource();
if (notSignOut && password) {
this.$('password').value = password;
this.$('notSignOut').checked = true;
this.onChangeDatasource(true, false);
this.onEnterClick();
} else {
this.resetForm(true);
this.onChangeDatasource(true, true);
}
},
resetForm: function(clearPassword) {
if (clearPassword)
this.$('password').value = '';
this.$('user').focus();
this.$('user').select();
},
createODBC: function(path, dsName, driverName) {
var driverPath = this.getEnv('ProgramFiles') + Conf.driverPath;
var datasource = Conf.defaultDatasource;
var params = {
Driver: driverPath,
DESCRIPTION: Conf.appName,
SERVER: Conf.datasources[datasource].SERVER,
DATABASE: Conf.dbName,
SSLCA: this.certFile,
SSLMODE: Conf.datasources[datasource].SSLMODE,
SSLCIPHER: 'AES256-SHA',
AUTO_RECONNECT: 1,
NO_PROMPT: 1,
ENABLE_CLEARTEXT_PLUGIN: 1
};
this.regWrite(path + 'ODBC Data Sources', dsName, driverName);
this.regWrites(path + dsName, params);
},
updateODBC: function(path, dsName, datasource) {
var params = Conf.datasources[datasource]
var datasourcePath = this.getDatasourcePath();
var myUID = this.regRead(datasourcePath, 'UID');
var myPWD = this.regRead(datasourcePath, 'PWD');
if (myUID && myPWD) {
params['UID'] = myUID;
params['PWD'] = myPWD;
}
this.regWrites(path + dsName, params);
},
disableUi: function(disabled, loadMessage) {
if (disabled)
this.hideMessage();
else
loadMessage = '';
this.$('loading-message').innerHTML = loadMessage;
this.$('user').disabled = disabled;
this.$('password').disabled = disabled;
this.$('notSignOut').disabled = disabled;
this.$('enter').disabled = disabled;
var display = disabled ? 'block' : 'none';
this.$('background').style.display = display;
this.$('spinner').style.display = display;
},
onCleanCacheClick: function() {
setTimeout(function() { App.cleanCache(); });
},
onShowBranchOptionsClick: function() {
var style = this.$("branchSelector").style;
style.display = style.display == 'none' || !style.display
? 'inline'
: 'none';
},
onShowDatasourceOptionsClick: function() {
var style = this.$("datasourceSelector").style;
style.display = style.display == 'none' || !style.display
? 'inline'
: 'none';
if (!this.$("datasourceLogo").className)
this.$("datasourceLogo").className = "on";
else
this.$("datasourceLogo").className = "";
},
onChangeBranch: function() {
if (this.$("branch").value == "master"||"test"||"dev") {
this.$("branchButton").className = this.$("branch").value;
this.$("branch").className = this.$("branch").value;
} else {
this.$("branchButton").className = null;
this.$("branch").className = null;
}
this.$('user').focus();
},
/**
* Changes the datasource, and optionally do the following
*
* @param {Boolean} hasUpdate
* @param {Boolean} hasChangeCredentials
*/
onChangeDatasource: function(hasUpdate, hasChangeCredentials) {
var myDatasource = this.$("datasource").value;
if (myDatasource == "production"||"test"||"dev") {
this.$("datasourceButton").className = myDatasource;
this.$("datasource").className = myDatasource;
} else {
this.$("datasourceButton").className = null;
this.$("datasource").className = null;
};
if (!hasUpdate) {
this.updateDatasource(myDatasource);
this.updateODBC(
Conf.odbcPath,
Conf.dsName,
myDatasource
);
};
if (hasChangeCredentials) {
var datasourcePath = this.getDatasourcePath();
var myUID = this.regRead(datasourcePath, 'UID');
var myPWD = this.regRead(datasourcePath, 'PWD');
if (myUID && myPWD) {
this.$('user').value = myUID;
this.$('password').value = myPWD;
};
};
this.$('user').focus();
},
cleanCache: function() {
if (this.fso.folderExists(this.moduleDir)) {
var folder = this.fso.getFolder(this.moduleDir);
var files = new Enumerator(folder.files);
for (; !files.atEnd(); files.moveNext()) {
var file = files.item();
if (/\.mdb$/.test(file.name) && file.name != 'config.mdb')
try {
file.Delete();
} catch (e) {}
}
}
this.showMessage(_('Cache files have been deleted'), 'notice');
},
onKeyPress: function(event) {
switch (event.keyCode) {
case 13: // Enter
this.onEnterPress(event);
break;
case 27: // Esc
window.close();
break;
}
},
onEnterPress: function(event) {
var target = event.target || event.srcElement;
if (target && target.id == 'user' && this.$('password').value == '') {
this.$('password').focus();
return;
}
this.onEnterClick();
},
onEnterClick: function() {
this.disableUi(true, _('Loading'));
setTimeout(function() { App.login(); });
},
login: function() {
try {
var user = this.$('user').value;
if (!user)
throw new Error(_('Enter a username'));
var password = this.$('password').value;
if (!password)
throw new Error(_('Enter a password'));
this.regWrite(Conf.odbcPath + Conf.dsName, 'UID', user);
this.regWrite(Conf.odbcPath + Conf.dsName, 'PWD', password);
// Check the cretentials and return the last version number
var version = this.fetchVersion();
// Check if there is a new version, and if there is, download it
if (version) {
this.disableUi(true, _('Updating'));
var remoteFile = version
? '.archive/'+ this.module +'/'+ version +'.7z'
: this.module +'.7z?'+ new Date().getTime();
remoteFile = Conf.cdnURL +'/'+ remoteFile;
var request = new ActiveXObject("MSXML2.XMLHTTP.6.0");
request.open('GET', remoteFile, true);
request.onreadystatechange = function() {
App.onRequestReady(request);
};
request.send();
} else
this.openMdb();
} catch (err) {
this.catchError(err);
}
},
/**
* Gets information about the version to download.
* cmdle
* @return {Number|Boolean} Version number, %false if cannot
* fetch or %null if local is up-to-date
*/
fetchVersion: function() {
var mysqlConn = new ActiveXObject('ADODB.Connection');
var datasource = this.getDatasource();
var user = this.$('user').value;
var password = this.$('password').value;
// FIXME: Can't login in dev-db
if (datasource == 'dev') {
datasource = Conf.defaultDatasource
};
// Check credentials
try {
mysqlConn.open(this.getODBCString({
Driver: '{'+ Conf.odbcDriver +'}',
SERVER: Conf.datasources[datasource].SERVER,
DATABASE: Conf.dbName,
UID: user,
PWD: password,
SSLCA: this.certFile,
SSLMODE: Conf.datasources[datasource].SSLMODE,
SSLCIPHER: 'AES256-SHA',
ENABLE_CLEARTEXT_PLUGIN : 1
}));
} catch (err) {
var dbErrors = mysqlConn && mysqlConn.errors;
if (dbErrors && dbErrors.count > 0) {
var dbError = dbErrors.item(0);
switch (dbError.NativeError) {
case 1045: // Access denied
clearPassword = true;
err = new Error(_('Bad login'));
err.name = 'BadLogin';
break;
case 2003: // Can't connect
err = new Error(_('Server can\'t be reached'));
break;
default:
err = new Error(dbError.description);
}
dbErrors.clear();
}
throw err;
}
mysqlConn.close();
// Request to obtain the token and lastest version of this module
try {
var request = new ActiveXObject("MSXML2.XMLHTTP.6.0");
urlLoginMethod = this.getRemoteURL() + '/api/Accounts/login'
request.open('POST', urlLoginMethod, false);
request.setRequestHeader('Content-Type', 'application/json')
var params = JSON.stringify({
"user": user,
"password": password
});
request.onreadystatechange = function() {
App.onRequestTokenReady(request);
}
request.send(params);
} catch (err) {
throw err;
};
// Checks if it's already open
if (this.fso.fileExists(this.lockFile))
try {
this.fso.deleteFile(this.lockFile);
return this.lastVersion;
} catch (e) {
throw new Error(_('Application it\'s already open'));
}
// Checks if MDB exists
if (!this.fso.fileExists(this.mdbFile))
return this.lastVersion;
// If it's abnormaly bigger, maybe is corrupted, so force download
var file = this.fso.getFile(this.mdbFile);
if (file.size > Conf.maxCorruptSize * 1024 * 1024)
return this.lastVersion;
// Obtains the local version number from the MDB file
var localVersion = this.mdbGetValue(
'SELECT Version FROM tblVariables',
'Version', parseInt
);
if (!localVersion)
localVersion = false;
// Determines if should download
return !localVersion || this.lastVersion === false || localVersion != this.lastVersion
? this.lastVersion
: null;
},
mdbGetValue: function(query, field, parseFn) {
var value;
try {
if (this.fso.fileExists(this.mdbFile)) {
var mdbConn = new ActiveXObject('ADODB.Connection');
mdbConn.open(this.getODBCString({
'Provider': 'Microsoft.Jet.OLEDB.4.0',
'Data Source': this.mdbFile
}));
try {
var rs = new ActiveXObject('ADODB.Recordset');
rs.Open(query, mdbConn);
value = rs.EOF ? null : parseFn(rs(field));
rs.close();
} catch (e) {}
mdbConn.close();
}
} catch (e) {}
return value;
},
getODBCString: function(options) {
var optionsArray = [];
for (var option in options)
optionsArray.push(option +'='+ options[option]);
return optionsArray.join(';');
},
onRequestReady: function(request) {
if (request.readyState !== 4)
return;
try {
if (request.status !== 200)
throw new Error('HTTP: '+ request.statusText + ' ' + request.status);
if (this.fso.fileExists(this.compressFile))
this.fso.deleteFile(this.compressFile);
var stream = new ActiveXObject('ADODB.Stream');
stream.open();
stream.Type = 1; // adTypeBinary
stream.write(request.responseBody);
stream.Position = 0;
stream.saveToFile(this.compressFile, 2);
stream.close();
if (this.fso.fileExists(this.mdbFile))
this.fso.deleteFile(this.mdbFile);
this.run('7za e "'+ this.compressFile +'" -o"'+ this.moduleDir +'"', true);
this.fso.deleteFile(this.compressFile);
} catch (e) {
alert(_('Error while updating') +': '+ e.message);
}
try {
if (!this.fso.fileExists(this.mdbFile))
throw new Error(_('MDB file not found'));
this.openMdb();
} catch (e) {
this.catchError(e);
}
},
onRequestTokenReady: function(request) {
if (request.readyState !== 4)
return;
try {
if (request.status == 401) // Unhauthorized
throw new Error('Bad login in the remoteURL "' + this.getRemoteURL() +
'":\nThe latest version could not be obtained');
else if (request.status !== 200 )
throw new Error('HTTP: '+ request.statusText + ' ' + request.status);
this.response = JSON.parse(request.responseText)
if (this.response) {
var request = new ActiveXObject("MSXML2.ServerXMLHTTP");
var filter = '&filter=' + JSON.stringify({
"fields":["version"],
"where":{
"app": this.module,
"branchFk": this.$('branch').value
},
});
var urlVersionMethod = this.getRemoteURL() + '/api/MdbVersions?access_token=' + this.response.token + filter
request.open('GET', urlVersionMethod, false);
request.onreadystatechange = function() {
App.onRequestVersionReady(request);
};
request.send();
}
} catch (err) {
throw err;
}
},
onRequestVersionReady: function(request) {
if (request.readyState !== 4)
return;
try {
if (request.status !== 200 )
throw new Error('HTTP: '+ request.statusText + ' ' + request.status);
var response = JSON.parse(request.responseText);
this.lastVersion = response[0].version
} catch (err) {
throw err;
}
},
openMdb: function() {
var notSignOut = !!this.$('notSignOut').checked;
this.regWrite(Conf.regPath, 'notSignOut', notSignOut);
var programFiles = this.getEnv('ProgramFiles');
var accessBin = programFiles +'\\Microsoft Office\\OFFICE11\\MSACCESS.EXE';
if (!this.fso.fileExists(accessBin))
throw new Error(_('Microsoft Access 2003 is not installed'));
this.shell.exec('"'+ accessBin +'" "'+ this.mdbFile +'"');
window.close();
},
catchError: function(err) {
var clearPassword = err.name == 'BadLogin';
if (!this.$('notSignOut').checked || clearPassword)
this.regWrite(Conf.odbcPath + Conf.dsName, 'PWD', '');
this.disableUi(false);
this.showMessage(err.message, 'error');
this.resetForm(clearPassword);
},
/**
* Displays a non-intrusive message.
*
* @param {String} message Message to display
* @param {String<error|notice>} className Message type
*/
showMessage: function(message, className) {
setTimeout(function() {
App.showMessageAsync(message, className);
});
},
showMessageAsync: function(message, className) {
if (this.messageTimeout)
clearTimeout(this.messageTimeout);
var messageDiv = this.$('message');
messageDiv.className = className;
messageDiv.innerHTML = message;
messageDiv.style.display = 'block';
this.messageTimeout = setTimeout(function() {
App.hideMessage();
}, 10000);
},
onBodyClick: function() {
this.hideMessage();
},
/**
* Hides the last displayed non-intrusive message.
*/
hideMessage: function() {
if (this.messageTimeout) {
this.$('message').style.display = 'none';
clearTimeout(this.messageTimeout);
this.messageTimeout = null;
}
},
/**
* Obtains a DOM element by it's identifier.
*
* @param {String} id The element id
*/
$: function(id) {
return document.getElementById(id);
},
run: function(command, wait) {
if (!wait)
wait = false;
this.shell.run(command, 0, wait);
},
getEnv: function(varName) {
return this.shell.expandEnvironmentStrings('%'+ varName +'%');
},
regRead: function(path, key) {
try {
var value = this.shell.regRead(path +'\\'+ key);
} catch (e) {
var value = null;
}
return value;
},
regWrite: function(path, key, value, type) {
if (!type)
switch (typeof (value))
{
case 'boolean':
type = 'REG_DWORD';
value = value ? 1 : 0;
break;
case 'number':
type = 'REG_DWORD';
break;
default:
type = 'REG_SZ';
if (!value)
value = '';
}
this.shell.regWrite(path + '\\' + key, value.toString(), type);
},
regWrites: function(path, values, type) {
for(var key in values)
this.regWrite(path, key, values[key], type);
},
regDelete: function(path) {
try {
this.shell.regDelete(path);
} catch (e) {}
},
/**
* Gets information about the branch config in access
*
* @return {string} Branch name, master if cannot
*/
getBranch: function() {
var branch = this.mdbGetValue(
'SELECT branch FROM tblVariables',
'branch', String
);
return branch || Conf.defaultBranch;
},
/**
* Get the current datasource used
*
* @return {string} Datasource name
*/
getDatasource: function() {
var datasource = this.regRead(
Conf.regPath,
'currentDatasource'
);
return datasource || Conf.defaultDatasource;
},
/**
* Get the remote URL
*
* @return {string} Remote server
*/
getRemoteURL: function() {
var remoteURL = this.regRead(
Conf.regPath,
'remoteURL'
);
return remoteURL || Conf.defaultRemoteURL;
},
/**
* Get the current datasource path
*
* @return {string} Datasource path
*/
getDatasourcePath: function() {
var datasourcePath = Conf.regPath + '\\datasources\\' + this.getDatasource();
return datasourcePath;
},
/**
* Update the datasource
*
* @param {String} datasource The datasource
*/
updateDatasource: function(datasource) {
this.regWrite(Conf.regPath, 'currentDatasource', datasource);
},
/**
* Create all datasources structure
*/
createDatasources: function() {
var params = {
'UID': null,
'PWD': null
}
for (var datasource in Conf.datasources) {
var myPath = Conf.regPath + '\\datasources\\' + datasource
for (var stringValues in Conf.datasources[datasource]) {
this.regWrite(myPath,
stringValues,
Conf.datasources[datasource][stringValues]
);
}
this.regWrites(myPath, params);
};
},
};
App.init();
function _(string) {
var translation = Locale[Conf.defaultLocale][string];
return translation ? translation : string;
}