mirror of
https://github.com/pgadmin-org/pgadmin4.git
synced 2025-02-25 18:55:31 -06:00
Implemented runtime using NWjs to open pgAdmin4 in a standalone window
instead of the system tray and web browser. Used NWjs to get rid of QT and C++. Fixes #5967 Use cheroot as the default production server for pgAdmin4. Fixes #5017
This commit is contained in:
44
runtime/src/css/pgadmin-desktop.css
Normal file
44
runtime/src/css/pgadmin-desktop.css
Normal file
@@ -0,0 +1,44 @@
|
||||
@import "../../node_modules/bootstrap/dist/css/bootstrap.css";
|
||||
|
||||
.card {
|
||||
margin: 0.25rem !important;
|
||||
}
|
||||
|
||||
#status-text {
|
||||
font-size: medium;
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
#server_log_label {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
color: #fff;
|
||||
background-color: #326690;
|
||||
border-color: #326690;
|
||||
}
|
||||
.btn-primary:hover {
|
||||
color: #fff;
|
||||
background-color: #285274;
|
||||
border-color: #254b6a;
|
||||
}
|
||||
.btn-primary:focus, .btn-primary.focus {
|
||||
color: #fff;
|
||||
background-color: #285274;
|
||||
border-color: #254b6a;
|
||||
box-shadow: 0 0 0 0 rgba(81, 125, 161, 0.5);
|
||||
}
|
||||
.btn-primary.disabled, .btn-primary:disabled {
|
||||
color: #fff;
|
||||
background-color: #326690;
|
||||
border-color: #326690;
|
||||
}
|
||||
.btn-primary:not(:disabled):not(.disabled):active, .btn-primary:not(:disabled):not(.disabled).active, .show > .btn-primary.dropdown-toggle {
|
||||
color: #fff;
|
||||
background-color: #254b6a;
|
||||
border-color: #224461;
|
||||
}
|
||||
.btn-primary:not(:disabled):not(.disabled):active:focus, .btn-primary:not(:disabled):not(.disabled).active:focus, .show > .btn-primary.dropdown-toggle:focus {
|
||||
box-shadow: 0 0 0 0 rgba(81, 125, 161, 0.5);
|
||||
}
|
||||
60
runtime/src/html/configure.html
Normal file
60
runtime/src/html/configure.html
Normal file
@@ -0,0 +1,60 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||
<title>pgAdmin 4 Configuration</title>
|
||||
<link rel="stylesheet" href="../css/pgadmin-desktop.css"/>
|
||||
<style>
|
||||
body, html {
|
||||
font-size: 14px;
|
||||
background-color: #ebeef3;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-header h6">Fixed Port</div>
|
||||
<div class="card-body">
|
||||
<div class="form-group">
|
||||
<label>By default, the pgAdmin 4 uses a random port number to ensure it can always run successfully. If you need to use a predictable port number, you can set one here. Note that if the port is already in use, the application will be unable to start.</label>
|
||||
</div>
|
||||
<div class="form-inline">
|
||||
<div class="form-check mr-3">
|
||||
<label class="form-check-label mr-2" for="fixedPortCheck">Fixed port number?</label>
|
||||
<input type="checkbox" class="form-check-input" id="fixedPortCheck"
|
||||
data-name="fixed_port"/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="mr-1" for="portNo">Port Number</label>
|
||||
<input type="number" class="form-control" id="portNo" min="1025" max="65535" value="5050" style="min-width: 100px;">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card shadow-sm mt-3">
|
||||
<div class="card-header h6">Connection Timeout</div>
|
||||
<div class="card-body">
|
||||
<div class="form-group">
|
||||
<label>Connection Timeout will define how long to wait for pgAdmin to start before throwing the error. By default, pgAdmin wait for 90 seconds.
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-inline">
|
||||
<div class="form-group">
|
||||
<label class="mr-2">Timeout</label>
|
||||
<input type="number" class="form-control" id="timeOut" min="10" max="600" value="90" style="min-width: 80px;">
|
||||
<label class="ml-1">seconds</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="p-2 d-flex fixed-bottom shadow bg-white">
|
||||
<div class="mr-auto" id="status-text"></div>
|
||||
<div class="ml-auto">
|
||||
<button id="btnSave" type="submit" class="btn btn-primary" disabled>Save</button>
|
||||
</div>
|
||||
</div>
|
||||
<script type="text/javascript" src="../js/configure.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
42
runtime/src/html/pgadmin.html
Normal file
42
runtime/src/html/pgadmin.html
Normal file
@@ -0,0 +1,42 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>pgAdmin 4</title>
|
||||
<style>
|
||||
body, html {
|
||||
height: 100%;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
||||
font-size: 14px;
|
||||
margin: 0 10px;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.bg {
|
||||
background-image: url("../../assets/welcome_logo.svg");
|
||||
height: 100%;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
background-size: contain;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.loader-text {
|
||||
margin-top: auto;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
</style>
|
||||
<script type="text/javascript">
|
||||
let platform = require("os").platform;
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="bg">
|
||||
<span id="loader-text-status" class="loader-text"></span>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript" src="../js/pgadmin.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
31
runtime/src/html/server_error.html
Normal file
31
runtime/src/html/server_error.html
Normal file
@@ -0,0 +1,31 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||
<title>Fatal Error</title>
|
||||
<link rel="stylesheet" href="../css/pgadmin-desktop.css"/>
|
||||
<style>
|
||||
body, html {
|
||||
font-size: 14px;
|
||||
background-color: #ebeef3;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-body">
|
||||
<label id="server_error_label" style=" color: red; font-size: large;"></label>
|
||||
<textarea id="server_error_log" style="min-width: 750px; min-height: 300px; resize: none;" readonly></textarea>
|
||||
</div>
|
||||
<div class="p-2 d-flex fixed-bottom shadow bg-white">
|
||||
<div class="mr-auto" id="status-text"></div>
|
||||
<div class="ml-auto">
|
||||
<button id="btnConfigure" class="btn btn-primary">Configure...</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script type="text/javascript" src="../js/server_error.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
31
runtime/src/html/view_log.html
Normal file
31
runtime/src/html/view_log.html
Normal file
@@ -0,0 +1,31 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||
<title>pgAdmin 4 Log</title>
|
||||
<link rel="stylesheet" href="../css/pgadmin-desktop.css"/>
|
||||
<style>
|
||||
body, html {
|
||||
font-size: 14px;
|
||||
background-color: #ebeef3;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-body">
|
||||
<label id="server_log_label">Server Log: </label>
|
||||
<textarea id="server_log" style="min-width: 750px; min-height: 300px;resize: none;" readonly></textarea>
|
||||
</div>
|
||||
<div class="p-2 d-flex fixed-bottom shadow bg-white">
|
||||
<div class="mr-auto" id="status-text"></div>
|
||||
<div class="ml-auto">
|
||||
<button id="btnReload" class="btn btn-primary">Reload</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script type="text/javascript" src="../js/view_log.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
91
runtime/src/js/configure.js
Normal file
91
runtime/src/js/configure.js
Normal file
@@ -0,0 +1,91 @@
|
||||
/////////////////////////////////////////////////////////////
|
||||
//
|
||||
// pgAdmin 4 - PostgreSQL Tools
|
||||
//
|
||||
// Copyright (C) 2013 - 2021, The pgAdmin Development Team
|
||||
// This software is released under the PostgreSQL Licence
|
||||
//
|
||||
//////////////////////////////////////////////////////////////
|
||||
|
||||
const misc = require('../js/misc.js');
|
||||
|
||||
// Get the window object of view log window
|
||||
var gui = require('nw.gui');
|
||||
var configWindow = gui.Window.get();
|
||||
|
||||
function checkConfiguration() {
|
||||
if (document.getElementById('fixedPortCheck').checked) {
|
||||
var fixedPort = parseInt(document.getElementById('portNo').value);
|
||||
// get the available TCP port
|
||||
misc.getAvailablePort(fixedPort)
|
||||
.then(() => {
|
||||
saveConfiguration();
|
||||
})
|
||||
.catch(() => {
|
||||
alert('The specified fixed port is already in use. Please provide any other valid port.');
|
||||
});
|
||||
} else {
|
||||
saveConfiguration();
|
||||
}
|
||||
}
|
||||
|
||||
function saveConfiguration() {
|
||||
misc.ConfigureStore.set('fixedPort', document.getElementById('fixedPortCheck').checked);
|
||||
misc.ConfigureStore.set('portNo', parseInt(document.getElementById('portNo').value));
|
||||
misc.ConfigureStore.set('connectionTimeout', parseInt(document.getElementById('timeOut').value));
|
||||
|
||||
misc.ConfigureStore.saveConfig();
|
||||
|
||||
document.getElementById('status-text').innerHTML = 'Configuration Saved';
|
||||
|
||||
if (confirm('The pgAdmin 4 must be restarted for changes to take effect.\n\n Do you want to quit the application?') == true) {
|
||||
misc.cleanupAndQuitApp();
|
||||
}
|
||||
configWindow.close();
|
||||
}
|
||||
|
||||
function onCheckChange() {
|
||||
if (this.checked) {
|
||||
document.getElementById('portNo').removeAttribute('disabled');
|
||||
} else {
|
||||
document.getElementById('portNo').setAttribute('disabled', 'disabled');
|
||||
}
|
||||
|
||||
// Enable/Disable Save button
|
||||
enableDisableSaveButton();
|
||||
}
|
||||
|
||||
function enableDisableSaveButton() {
|
||||
var configData = misc.ConfigureStore.getConfigData();
|
||||
|
||||
if (configData['fixedPort'] != document.getElementById('fixedPortCheck').checked ||
|
||||
configData['portNo'] != document.getElementById('portNo').value ||
|
||||
configData['connectionTimeout'] != document.getElementById('timeOut').value) {
|
||||
document.getElementById('btnSave').removeAttribute('disabled');
|
||||
} else {
|
||||
document.getElementById('btnSave').setAttribute('disabled', 'disabled');
|
||||
}
|
||||
}
|
||||
|
||||
configWindow.on('loaded', function() {
|
||||
document.getElementById('status-text').innerHTML = '';
|
||||
// Get the config data from the file.
|
||||
var configData = misc.ConfigureStore.getConfigData();
|
||||
|
||||
// Set the GUI value as per configuration.
|
||||
if (configData['fixedPort']) {
|
||||
document.getElementById('fixedPortCheck').checked = true;
|
||||
document.getElementById('portNo').disabled = false;
|
||||
} else {
|
||||
document.getElementById('fixedPortCheck').checked = false;
|
||||
document.getElementById('portNo').disabled = true;
|
||||
}
|
||||
document.getElementById('portNo').value = configData['portNo'];
|
||||
document.getElementById('timeOut').value = configData['connectionTimeout'];
|
||||
|
||||
// Add event listeners
|
||||
document.getElementById('btnSave').addEventListener('click', checkConfiguration);
|
||||
document.getElementById('fixedPortCheck').addEventListener('change', onCheckChange);
|
||||
document.getElementById('portNo').addEventListener('change', enableDisableSaveButton);
|
||||
document.getElementById('timeOut').addEventListener('change', enableDisableSaveButton);
|
||||
});
|
||||
316
runtime/src/js/misc.js
Normal file
316
runtime/src/js/misc.js
Normal file
@@ -0,0 +1,316 @@
|
||||
/////////////////////////////////////////////////////////////
|
||||
//
|
||||
// pgAdmin 4 - PostgreSQL Tools
|
||||
//
|
||||
// Copyright (C) 2013 - 2021, The pgAdmin Development Team
|
||||
// This software is released under the PostgreSQL Licence
|
||||
//
|
||||
//////////////////////////////////////////////////////////////
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const net = require('net');
|
||||
const {platform, homedir} = require('os');
|
||||
var pgadminServerProcess = null;
|
||||
var pgAdminWindowObject = null;
|
||||
|
||||
// This function is used to check whether directory is present or not
|
||||
// if not present then create it recursively
|
||||
const createDir = (dirName) => {
|
||||
if (!fs.existsSync(dirName)) {
|
||||
fs.mkdirSync(dirName, {recursive: true});
|
||||
}
|
||||
};
|
||||
|
||||
// This function is used to get the python executable path
|
||||
// based on the platform. Use this for deployment.
|
||||
const getPythonPath = () => {
|
||||
var pythonPath = '';
|
||||
switch (platform()) {
|
||||
case 'win32':
|
||||
pythonPath = '../python/python.exe';
|
||||
break;
|
||||
case 'darwin':
|
||||
pythonPath = '../../Frameworks/Python.framework/Versions/Current/bin/python3';
|
||||
break;
|
||||
case 'linux':
|
||||
pythonPath = '../venv/bin/python3';
|
||||
break;
|
||||
default:
|
||||
if (platform().startsWith('win')) {
|
||||
pythonPath = '../python/python.exe';
|
||||
} else {
|
||||
pythonPath = '../venv/bin/python3';
|
||||
}
|
||||
}
|
||||
|
||||
return pythonPath;
|
||||
};
|
||||
|
||||
// This function is used to get the [roaming] app data path
|
||||
// based on the platform. Use this for config etc.
|
||||
const getAppDataPath = () => {
|
||||
var appDataPath = '';
|
||||
switch (platform()) {
|
||||
case 'win32':
|
||||
appDataPath = path.join(process.env.APPDATA, 'pgadmin');
|
||||
break;
|
||||
case 'darwin':
|
||||
appDataPath = path.join(homedir(), 'Library', 'Preferences', 'pgadmin');
|
||||
break;
|
||||
case 'linux':
|
||||
if ('XDG_CONFIG_HOME' in process.env) {
|
||||
appDataPath = path.join(process.env.XDG_CONFIG_HOME, 'pgadmin');
|
||||
} else {
|
||||
appDataPath = path.join(homedir(), '.config', 'pgadmin');
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if (platform().startsWith('win')) {
|
||||
appDataPath = path.join(process.env.APPDATA, 'pgadmin');
|
||||
} else {
|
||||
if ('XDG_CONFIG_HOME' in process.env) {
|
||||
appDataPath = path.join(process.env.XDG_CONFIG_HOME, 'pgadmin');
|
||||
} else {
|
||||
appDataPath = path.join(homedir(), '.config', 'pgadmin');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create directory if not exists
|
||||
createDir(appDataPath);
|
||||
|
||||
return appDataPath;
|
||||
};
|
||||
|
||||
// This function is used to get the [local] app data path
|
||||
// based on the platform. Use this for logs etc.
|
||||
const getLocalAppDataPath = () => {
|
||||
var localAppDataPath = '';
|
||||
switch (platform()) {
|
||||
case 'win32':
|
||||
localAppDataPath = path.join(process.env.LOCALAPPDATA, 'pgadmin');
|
||||
break;
|
||||
case 'darwin':
|
||||
localAppDataPath = path.join(homedir(), 'Library', 'Application Support', 'pgadmin');
|
||||
break;
|
||||
case 'linux':
|
||||
if ('XDG_DATA_HOME' in process.env) {
|
||||
localAppDataPath = path.join(process.env.XDG_DATA_HOME, 'pgadmin');
|
||||
} else {
|
||||
localAppDataPath = path.join(homedir(), '.local', 'share', 'pgadmin');
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if (platform().startsWith('win')) {
|
||||
localAppDataPath = path.join(process.env.LOCALAPPDATA, 'pgadmin');
|
||||
} else {
|
||||
if ('XDG_DATA_HOME' in process.env) {
|
||||
localAppDataPath = path.join(process.env.XDG_DATA_HOME, 'pgadmin');
|
||||
} else {
|
||||
localAppDataPath = path.join(homedir(), '.local', 'share', 'pgadmin');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create directory if not exists
|
||||
createDir(localAppDataPath);
|
||||
|
||||
return localAppDataPath;
|
||||
};
|
||||
|
||||
// This function is used to get the random available TCP port
|
||||
// if fixedPort is set to 0. Else check whether port is in used or not.
|
||||
const getAvailablePort = (fixedPort) => {
|
||||
return new Promise(function(resolve, reject) {
|
||||
const server = net.createServer();
|
||||
|
||||
server.listen(fixedPort, '127.0.0.1');
|
||||
|
||||
server.on('error', (e) => {
|
||||
reject(e.code);
|
||||
});
|
||||
|
||||
server.on('listening', () => {
|
||||
var serverPort = server.address().port;
|
||||
server.close();
|
||||
resolve(serverPort);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// Get the app data folder path
|
||||
const currentTime = (new Date()).getTime();
|
||||
const serverLogFile = path.join(getLocalAppDataPath(), 'pgadmin4.' + currentTime.toString() + '.log');
|
||||
const configFileName = path.join(getAppDataPath(), 'runtime_config.json');
|
||||
const DEFAULT_CONFIG_DATA = {'fixedPort': false, 'portNo': 5050, 'connectionTimeout': 90, 'windowWidth': 1300, 'windowHeight': 900};
|
||||
|
||||
// This function is used to read the file and return the content
|
||||
const readServerLog = () => {
|
||||
var data = null;
|
||||
|
||||
if (fs.existsSync(serverLogFile)) {
|
||||
data = fs.readFileSync(serverLogFile, 'utf8');
|
||||
} else {
|
||||
var errMsg = 'Unable to read file ' + serverLogFile + ' not found.';
|
||||
console.warn(errMsg);
|
||||
return errMsg;
|
||||
}
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
// This function is used to write the data into the log file
|
||||
const writeServerLog = (data) => {
|
||||
data = data + '\n';
|
||||
if (fs.existsSync(serverLogFile)) {
|
||||
fs.writeFileSync(serverLogFile, data, {flag: 'a+'});
|
||||
} else {
|
||||
fs.writeFileSync(serverLogFile, data, {flag: 'w'});
|
||||
}
|
||||
};
|
||||
|
||||
// This function is used to remove the log file
|
||||
const removeLogFile = () => {
|
||||
if (fs.existsSync(serverLogFile)) {
|
||||
fs.rmSync(serverLogFile);
|
||||
}
|
||||
};
|
||||
|
||||
// This function used to set the object of pgAdmin server process.
|
||||
const setProcessObject = (processObject) => {
|
||||
pgadminServerProcess = processObject;
|
||||
};
|
||||
|
||||
// This function used to set the object of pgAdmin window.
|
||||
const setPgAdminWindowObject = (windowObject) => {
|
||||
pgAdminWindowObject = windowObject;
|
||||
};
|
||||
|
||||
// This function is used to get the server log file.
|
||||
const getServerLogFile = () => {
|
||||
return serverLogFile;
|
||||
};
|
||||
|
||||
// This function is used to get the runtime config file.
|
||||
const getRunTimeConfigFile = () => {
|
||||
return configFileName;
|
||||
};
|
||||
|
||||
// This function is used to kill the server process, remove the log files
|
||||
// and quit the application.
|
||||
const cleanupAndQuitApp = () => {
|
||||
// Remove the server log file on exit
|
||||
removeLogFile();
|
||||
|
||||
// Killing pgAdmin4 server process if application quits
|
||||
if (pgadminServerProcess != null) {
|
||||
try {
|
||||
process.kill(pgadminServerProcess.pid);
|
||||
}
|
||||
catch (e) {
|
||||
console.warn('Failed to kill server process.');
|
||||
}
|
||||
}
|
||||
|
||||
if (pgAdminWindowObject != null) {
|
||||
// Close the window.
|
||||
pgAdminWindowObject.close(true);
|
||||
|
||||
// Remove all the cookies.
|
||||
pgAdminWindowObject.cookies.getAll({}, function(cookies) {
|
||||
try {
|
||||
cookies.forEach(function(cookie) {
|
||||
pgAdminWindowObject.cookies.remove({url: 'http://' + cookie.domain, name: cookie.name});
|
||||
});
|
||||
} catch (error) {
|
||||
console.warn('Failed to remove cookies.');
|
||||
} finally {
|
||||
pgAdminWindowObject = null;
|
||||
// Quit Application
|
||||
nw.App.quit();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// Quit Application
|
||||
nw.App.quit();
|
||||
}
|
||||
};
|
||||
|
||||
var ConfigureStore = {
|
||||
fileName: configFileName,
|
||||
jsonData: {},
|
||||
|
||||
init: function() {
|
||||
if (!this.readConfig()){
|
||||
this.jsonData = DEFAULT_CONFIG_DATA;
|
||||
this.saveConfig();
|
||||
}
|
||||
},
|
||||
|
||||
// This function is used to write configuration data
|
||||
saveConfig: function() {
|
||||
fs.writeFileSync(this.fileName, JSON.stringify(this.jsonData, null, 4), {flag: 'w'});
|
||||
},
|
||||
|
||||
// This function is used to read the configuration data
|
||||
readConfig: function() {
|
||||
if (fs.existsSync(this.fileName)) {
|
||||
try {
|
||||
this.jsonData = JSON.parse(fs.readFileSync(this.fileName));
|
||||
} catch (error) {
|
||||
/* If the file is not present or invalid JSON data in file */
|
||||
this.jsonData = {};
|
||||
}
|
||||
} else {
|
||||
var errMsg = 'Unable to read file ' + this.fileName + ' not found.';
|
||||
console.warn(errMsg);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
getConfigData: function() {
|
||||
return this.jsonData;
|
||||
},
|
||||
|
||||
get: function(key, if_not_value) {
|
||||
if(this.jsonData[key] != undefined) {
|
||||
return this.jsonData[key];
|
||||
} else {
|
||||
return if_not_value;
|
||||
}
|
||||
},
|
||||
|
||||
set: function(key, value) {
|
||||
if(typeof key === 'object'){
|
||||
this.jsonData = {
|
||||
...this.jsonData,
|
||||
...key,
|
||||
};
|
||||
} else {
|
||||
if(value === '' || value == null || typeof(value) == 'undefined') {
|
||||
if(this.jsonData[key] != undefined) {
|
||||
delete this.jsonData[key];
|
||||
}
|
||||
} else {
|
||||
this.jsonData[key] = value;
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
module.exports = {
|
||||
readServerLog: readServerLog,
|
||||
writeServerLog: writeServerLog,
|
||||
getAvailablePort: getAvailablePort,
|
||||
getPythonPath: getPythonPath,
|
||||
setProcessObject: setProcessObject,
|
||||
cleanupAndQuitApp: cleanupAndQuitApp,
|
||||
getServerLogFile: getServerLogFile,
|
||||
getRunTimeConfigFile: getRunTimeConfigFile,
|
||||
setPgAdminWindowObject: setPgAdminWindowObject,
|
||||
ConfigureStore: ConfigureStore,
|
||||
};
|
||||
300
runtime/src/js/pgadmin.js
Normal file
300
runtime/src/js/pgadmin.js
Normal file
@@ -0,0 +1,300 @@
|
||||
/////////////////////////////////////////////////////////////
|
||||
//
|
||||
// pgAdmin 4 - PostgreSQL Tools
|
||||
//
|
||||
// Copyright (C) 2013 - 2021, The pgAdmin Development Team
|
||||
// This software is released under the PostgreSQL Licence
|
||||
//
|
||||
//////////////////////////////////////////////////////////////
|
||||
|
||||
const axios = require('axios');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const misc = require('../js/misc.js');
|
||||
const spawn = require('child_process').spawn;
|
||||
|
||||
var pgadminServerProcess = null;
|
||||
var startPageUrl = null;
|
||||
var serverCheckUrl = null;
|
||||
|
||||
var serverPort = 5050;
|
||||
|
||||
// Paths to the rest of the app
|
||||
var pythonPath = misc.getPythonPath();
|
||||
var pgadminFile = '../web/pgAdmin4.py';
|
||||
var configFile = '../web/config.py';
|
||||
|
||||
// Override the paths above, if a developer needs to
|
||||
if (fs.existsSync('dev_config.json')) {
|
||||
try {
|
||||
var dev_config = JSON.parse(fs.readFileSync('dev_config.json'));
|
||||
pythonPath = dev_config['pythonPath'];
|
||||
pgadminFile = dev_config['pgadminFile'];
|
||||
} catch (error) {
|
||||
// Meh.
|
||||
}
|
||||
}
|
||||
|
||||
// This function is used to create UUID
|
||||
function createUUID() {
|
||||
var dt = new Date().getTime();
|
||||
var uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
|
||||
var r = (dt + Math.random()*16)%16 | 0;
|
||||
dt = Math.floor(dt/16);
|
||||
return (c=='x' ? r :(r&0x3|0x8)).toString(16);
|
||||
});
|
||||
|
||||
return uuid;
|
||||
}
|
||||
|
||||
// This functions is used to start the pgAdmin4 server by spawning a
|
||||
// separate process.
|
||||
function startDesktopMode() {
|
||||
// Return if pgadmin server process is already spawned
|
||||
// Added check for debugging purpose.
|
||||
if (pgadminServerProcess != null)
|
||||
return;
|
||||
|
||||
var UUID = createUUID();
|
||||
// Set the environment variable so that pgAdmn 4 server
|
||||
// start listening on that port.
|
||||
process.env.PGADMIN_INT_PORT = serverPort;
|
||||
process.env.PGADMIN_INT_KEY = UUID;
|
||||
process.env.PGADMIN_SERVER_MODE = 'OFF';
|
||||
|
||||
// Start Page URL
|
||||
startPageUrl = 'http://127.0.0.1:' + serverPort + '/?key=' + UUID;
|
||||
serverCheckUrl = 'http://127.0.0.1:' + serverPort + '/misc/ping?key=' + UUID;
|
||||
|
||||
document.getElementById('loader-text-status').innerHTML = 'Starting pgAdmin 4...';
|
||||
|
||||
// Write Python Path, pgAdmin file path and command in log file.
|
||||
var command = path.resolve(pythonPath) + ' ' + path.resolve(pgadminFile);
|
||||
misc.writeServerLog('Python Path: "' + path.resolve(pythonPath) + '"');
|
||||
misc.writeServerLog('Runtime Config File: "' + path.resolve(misc.getRunTimeConfigFile()) + '"');
|
||||
misc.writeServerLog('pgAdmin Config File: "' + path.resolve(configFile) + '"');
|
||||
misc.writeServerLog('Webapp Path: "' + path.resolve(pgadminFile) + '"');
|
||||
misc.writeServerLog('pgAdmin Command: "' + command + '"');
|
||||
|
||||
// Spawn the process to start pgAdmin4 server.
|
||||
pgadminServerProcess = spawn(pythonPath, [pgadminFile]);
|
||||
pgadminServerProcess.on('error', function(err) {
|
||||
// Log the error into the log file if process failed to launch
|
||||
misc.writeServerLog('Failed to lauch pgAdmin4 with below error:');
|
||||
misc.writeServerLog(err);
|
||||
});
|
||||
|
||||
pgadminServerProcess.stdout.setEncoding('utf8');
|
||||
pgadminServerProcess.stdout.on('data', (chunk) => {
|
||||
misc.writeServerLog(chunk);
|
||||
});
|
||||
|
||||
pgadminServerProcess.stderr.setEncoding('utf8');
|
||||
pgadminServerProcess.stderr.on('data', (chunk) => {
|
||||
if (chunk.indexOf('Runtime Open Configuration') > -1) {
|
||||
// Create and launch new window and open pgAdmin url
|
||||
nw.Window.open('src/html/configure.html', {
|
||||
'frame': true,
|
||||
'width': 600,
|
||||
'height': 420,
|
||||
'position': 'center',
|
||||
'resizable': false,
|
||||
'focus': true,
|
||||
'show': true,
|
||||
});
|
||||
} else if (chunk.indexOf('Runtime Open View Log') > -1) {
|
||||
// Create and launch new window and open pgAdmin url
|
||||
nw.Window.open('src/html/view_log.html', {
|
||||
'frame': true,
|
||||
'width': 790,
|
||||
'height': 425,
|
||||
'position': 'center',
|
||||
'resizable': false,
|
||||
'focus': true,
|
||||
'show': true,
|
||||
});
|
||||
} else {
|
||||
misc.writeServerLog(chunk);
|
||||
}
|
||||
});
|
||||
|
||||
// This function is used to ping the pgAdmin4 server whether it
|
||||
// it is started or not.
|
||||
function pingServer() {
|
||||
return axios.get(serverCheckUrl);
|
||||
}
|
||||
|
||||
var connectionTimeout = misc.ConfigureStore.get('connectionTimeout', 90) * 1000;
|
||||
var currentTime = (new Date).getTime();
|
||||
var endTime = currentTime + connectionTimeout;
|
||||
var midTime1 = currentTime + (connectionTimeout/2);
|
||||
var midTime2 = currentTime + (connectionTimeout*2/3);
|
||||
var pingInProgress = false;
|
||||
|
||||
// ping pgAdmin server every 1 second.
|
||||
var intervalID = setInterval(function() {
|
||||
// If ping request is already send and response is not
|
||||
// received no need to send another request.
|
||||
if (pingInProgress)
|
||||
return;
|
||||
|
||||
pingServer().then(() => {
|
||||
pingInProgress = false;
|
||||
document.getElementById('loader-text-status').innerHTML = 'pgAdmin 4 started';
|
||||
// Set the pgAdmin process object to misc
|
||||
misc.setProcessObject(pgadminServerProcess);
|
||||
|
||||
clearInterval(intervalID);
|
||||
launchPgAdminWindow();
|
||||
}).catch(() => {
|
||||
pingInProgress = false;
|
||||
var curTime = (new Date).getTime();
|
||||
// if the connection timeout has lapsed then throw an error
|
||||
// and stop pinging the server.
|
||||
if (curTime >= endTime) {
|
||||
clearInterval(intervalID);
|
||||
splashWindow.hide();
|
||||
|
||||
nw.Window.open('src/html/server_error.html', {
|
||||
'frame': true,
|
||||
'width': 790,
|
||||
'height': 430,
|
||||
'position': 'center',
|
||||
'resizable': false,
|
||||
'focus': true,
|
||||
'show': true,
|
||||
});
|
||||
}
|
||||
|
||||
if (curTime > midTime1) {
|
||||
if(curTime < midTime2) {
|
||||
document.getElementById('loader-text-status').innerHTML = 'Taking longer than usual...';
|
||||
} else {
|
||||
document.getElementById('loader-text-status').innerHTML = 'Almost there...';
|
||||
}
|
||||
} else {
|
||||
document.getElementById('loader-text-status').innerHTML = 'Waiting for pgAdmin 4 to start...';
|
||||
}
|
||||
});
|
||||
|
||||
pingInProgress = true;
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
// This function is used to hide the splash screen and create/launch
|
||||
// new window to render pgAdmin4 page.
|
||||
function launchPgAdminWindow() {
|
||||
// Create and launch new window and open pgAdmin url
|
||||
misc.writeServerLog('Application Server URL: ' + startPageUrl);
|
||||
var winWidth = misc.ConfigureStore.get('windowWidth', 1300);
|
||||
var winHeight = misc.ConfigureStore.get('windowHeight', 900);
|
||||
|
||||
nw.Window.open(startPageUrl, {
|
||||
'icon': '../../assets/pgAdmin4.png',
|
||||
'frame': true,
|
||||
'width': winWidth,
|
||||
'height': winHeight,
|
||||
'position': 'center',
|
||||
'resizable': true,
|
||||
'min_width': 400,
|
||||
'min_height': 200,
|
||||
'focus': true,
|
||||
'show': false,
|
||||
}, (pgadminWindow)=> {
|
||||
// Set pgAdmin4 Windows Object
|
||||
misc.setPgAdminWindowObject(pgadminWindow);
|
||||
|
||||
pgadminWindow.on('close', function() {
|
||||
misc.cleanupAndQuitApp();
|
||||
});
|
||||
|
||||
// set up handler for new-win-policy event.
|
||||
// Set the width and height for the new window.
|
||||
pgadminWindow.on('new-win-policy', function(frame, url, policy) {
|
||||
policy.setNewWindowManifest({
|
||||
'icon': '../../assets/pgAdmin4.png',
|
||||
'frame': true,
|
||||
'width': winWidth,
|
||||
'height': winHeight,
|
||||
'position': 'center',
|
||||
});
|
||||
});
|
||||
|
||||
pgadminWindow.on('loaded', function() {
|
||||
// Hide the splash screen
|
||||
splashWindow.hide();
|
||||
|
||||
/* Make the new window opener to null as it is
|
||||
* nothing but a splash screen. We will have to make it null,
|
||||
* so that open in new browser tab will work.
|
||||
*/
|
||||
pgadminWindow.window.opener = null;
|
||||
|
||||
// Show new window
|
||||
pgadminWindow.show();
|
||||
pgadminWindow.focus();
|
||||
});
|
||||
|
||||
pgadminWindow.on('resize', function(width, height) {
|
||||
// Set the width and height for the new window on resize.
|
||||
pgadminWindow.on('new-win-policy', function(frame, url, policy) {
|
||||
policy.setNewWindowManifest({
|
||||
'icon': '../../assets/pgAdmin4.png',
|
||||
'frame': true,
|
||||
'width': width,
|
||||
'height': height,
|
||||
'position': 'center',
|
||||
});
|
||||
});
|
||||
|
||||
misc.ConfigureStore.set('windowWidth', width);
|
||||
misc.ConfigureStore.set('windowHeight', height);
|
||||
misc.ConfigureStore.saveConfig();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Get the gui object of NW.js
|
||||
var gui = require('nw.gui');
|
||||
var splashWindow = gui.Window.get();
|
||||
|
||||
// Always clear the cache before starting the application.
|
||||
nw.App.clearCache();
|
||||
|
||||
// Create Mac Builtin Menu
|
||||
if (platform() == 'darwin') {
|
||||
var macMenu = new gui.Menu({type: 'menubar'});
|
||||
macMenu.createMacBuiltin('pgAdmin 4');
|
||||
gui.Window.get().menu = macMenu;
|
||||
}
|
||||
|
||||
splashWindow.on('loaded', function() {
|
||||
// Initialize the ConfigureStore
|
||||
misc.ConfigureStore.init();
|
||||
|
||||
var fixedPortCheck = misc.ConfigureStore.get('fixedPort', false);
|
||||
if (fixedPortCheck) {
|
||||
serverPort = misc.ConfigureStore.get('portNo');
|
||||
//Start the pgAdmin in Desktop mode.
|
||||
startDesktopMode();
|
||||
} else {
|
||||
// get the available TCP port by sending port no to 0.
|
||||
misc.getAvailablePort(0)
|
||||
.then((pythonApplicationPort) => {
|
||||
serverPort = pythonApplicationPort;
|
||||
//Start the pgAdmin in Desktop mode.
|
||||
startDesktopMode();
|
||||
})
|
||||
.catch((errCode) => {
|
||||
if (fixedPortCheck && errCode == 'EADDRINUSE') {
|
||||
alert('The specified fixed port is already in use. Please provide any other valid port.');
|
||||
} else {
|
||||
alert(errCode);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
splashWindow.on('close', function() {
|
||||
misc.cleanupAndQuitApp();
|
||||
});
|
||||
34
runtime/src/js/server_error.js
Normal file
34
runtime/src/js/server_error.js
Normal file
@@ -0,0 +1,34 @@
|
||||
/////////////////////////////////////////////////////////////
|
||||
//
|
||||
// pgAdmin 4 - PostgreSQL Tools
|
||||
//
|
||||
// Copyright (C) 2013 - 2021, The pgAdmin Development Team
|
||||
// This software is released under the PostgreSQL Licence
|
||||
//
|
||||
//////////////////////////////////////////////////////////////
|
||||
|
||||
const misc = require('../js/misc.js');
|
||||
|
||||
// Get the window object of server error window
|
||||
var gui = require('nw.gui');
|
||||
var errorWindow = gui.Window.get();
|
||||
|
||||
errorWindow.on('loaded', function() {
|
||||
document.getElementById('server_error_label').innerHTML = 'The pgAdmin 4 server could not be contacted:';
|
||||
document.getElementById('server_error_log').innerHTML = misc.readServerLog();
|
||||
document.getElementById('btnConfigure').addEventListener('click', function() {
|
||||
nw.Window.open('src/html/configure.html', {
|
||||
'frame': true,
|
||||
'width': 600,
|
||||
'height': 420,
|
||||
'position': 'center',
|
||||
'resizable': false,
|
||||
'focus': true,
|
||||
'show': true,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
errorWindow.on('close', function() {
|
||||
misc.cleanupAndQuitApp();
|
||||
});
|
||||
27
runtime/src/js/view_log.js
Normal file
27
runtime/src/js/view_log.js
Normal file
@@ -0,0 +1,27 @@
|
||||
/////////////////////////////////////////////////////////////
|
||||
//
|
||||
// pgAdmin 4 - PostgreSQL Tools
|
||||
//
|
||||
// Copyright (C) 2013 - 2021, The pgAdmin Development Team
|
||||
// This software is released under the PostgreSQL Licence
|
||||
//
|
||||
//////////////////////////////////////////////////////////////
|
||||
|
||||
const misc = require('../js/misc.js');
|
||||
|
||||
// Get the window object of view log window
|
||||
var gui = require('nw.gui');
|
||||
var logWindow = gui.Window.get();
|
||||
|
||||
logWindow.on('loaded', function() {
|
||||
document.getElementById('status-text').innerHTML = '';
|
||||
document.getElementById('server_log_label').innerHTML = 'Server Log: ' + '(' + misc.getServerLogFile() + ')';
|
||||
document.getElementById('server_log').innerHTML = misc.readServerLog();
|
||||
document.getElementById('btnReload').addEventListener('click', function() {
|
||||
document.getElementById('server_log').innerHTML = 'Loading logs...';
|
||||
setTimeout(function() {
|
||||
document.getElementById('server_log').innerHTML = misc.readServerLog();
|
||||
}, 500);
|
||||
document.getElementById('status-text').innerHTML = 'Logs reloaded successfully';
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user