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:
6
runtime/.eslintignore
Normal file
6
runtime/.eslintignore
Normal file
@@ -0,0 +1,6 @@
|
||||
generated
|
||||
node_modules
|
||||
vendor
|
||||
templates/
|
||||
templates\
|
||||
ycache
|
||||
53
runtime/.eslintrc.js
Normal file
53
runtime/.eslintrc.js
Normal file
@@ -0,0 +1,53 @@
|
||||
/////////////////////////////////////////////////////////////
|
||||
//
|
||||
// pgAdmin 4 - PostgreSQL Tools
|
||||
//
|
||||
// Copyright (C) 2013 - 2021, The pgAdmin Development Team
|
||||
// This software is released under the PostgreSQL Licence
|
||||
//
|
||||
//////////////////////////////////////////////////////////////
|
||||
|
||||
module.exports = {
|
||||
'env': {
|
||||
'browser': true,
|
||||
'es6': true,
|
||||
'amd': true,
|
||||
'jasmine': true,
|
||||
},
|
||||
'extends': [
|
||||
'eslint:recommended',
|
||||
],
|
||||
'parserOptions': {
|
||||
'ecmaVersion': 2018,
|
||||
"sourceType": "module",
|
||||
},
|
||||
'globals': {
|
||||
'_': true,
|
||||
'module': true,
|
||||
'process': true,
|
||||
'nw': true,
|
||||
'platform': true
|
||||
},
|
||||
'rules': {
|
||||
'indent': [
|
||||
'error',
|
||||
2
|
||||
],
|
||||
'linebreak-style': 0,
|
||||
'quotes': [
|
||||
'error',
|
||||
'single'
|
||||
],
|
||||
'semi': [
|
||||
'error',
|
||||
'always'
|
||||
],
|
||||
'comma-dangle': [
|
||||
'error',
|
||||
'always-multiline'
|
||||
],
|
||||
'no-console': ["error", { allow: ["warn", "error"] }],
|
||||
// We need to exclude below for RegEx case
|
||||
"no-useless-escape": 0,
|
||||
},
|
||||
};
|
||||
13
runtime/.gitignore
vendored
13
runtime/.gitignore
vendored
@@ -1,12 +1 @@
|
||||
.qmake.cache
|
||||
.qmake.stash
|
||||
Makefile
|
||||
moc_*.cpp
|
||||
moc_*.h
|
||||
pgAdmin4
|
||||
pgAdmin4.app/
|
||||
pgAdmin4.pro.user*
|
||||
qrc_breeze.cpp
|
||||
qrc_pgAdmin4.cpp
|
||||
ui_*.h
|
||||
object_script.*
|
||||
dev_config.json
|
||||
@@ -1,136 +0,0 @@
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// pgAdmin 4 - PostgreSQL Tools
|
||||
//
|
||||
// Copyright (C) 2013 - 2021, The pgAdmin Development Team
|
||||
// This software is released under the PostgreSQL Licence
|
||||
//
|
||||
// ConfigWindow.h - Configuration window
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "pgAdmin4.h"
|
||||
#include "ConfigWindow.h"
|
||||
#include "ui_ConfigWindow.h"
|
||||
|
||||
#include <QSettings>
|
||||
#include <QTcpSocket>
|
||||
#include <QtWidgets>
|
||||
|
||||
ConfigWindow::ConfigWindow(QWidget *parent) :
|
||||
QDialog(parent)
|
||||
{
|
||||
initConfigWindow();
|
||||
}
|
||||
|
||||
void ConfigWindow::initConfigWindow()
|
||||
{
|
||||
ui = new Ui::ConfigWindow;
|
||||
ui->setupUi(this);
|
||||
|
||||
m_needRestart = false;
|
||||
|
||||
setConfigValues();
|
||||
}
|
||||
|
||||
void ConfigWindow::setConfigValues()
|
||||
{
|
||||
QSettings settings;
|
||||
|
||||
ui->browserCommandLineEdit->setText(settings.value("BrowserCommand").toString());
|
||||
|
||||
if(settings.value("FixedPort").toBool())
|
||||
{
|
||||
ui->chkFixedPort->setCheckState(Qt::Checked);
|
||||
ui->spinPortNumber->setEnabled(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
ui->chkFixedPort->setCheckState(Qt::Unchecked);
|
||||
ui->spinPortNumber->setEnabled(false);
|
||||
}
|
||||
|
||||
ui->spinPortNumber->setValue(settings.value("PortNumber").toInt());
|
||||
|
||||
if (settings.value("OpenTabAtStartup", true).toBool())
|
||||
{
|
||||
ui->chkOpenTabAtStartup->setCheckState(Qt::Checked);
|
||||
}
|
||||
else
|
||||
{
|
||||
ui->chkOpenTabAtStartup->setCheckState(Qt::Unchecked);
|
||||
}
|
||||
|
||||
ui->pythonPathLineEdit->setText(settings.value("PythonPath").toString());
|
||||
ui->applicationPathLineEdit->setText(settings.value("ApplicationPath").toString());
|
||||
}
|
||||
|
||||
void ConfigWindow::on_buttonBox_accepted()
|
||||
{
|
||||
QSettings settings;
|
||||
|
||||
// Save the settings, and return true if a restart is required, otherwise false.
|
||||
QString browsercommand = ui->browserCommandLineEdit->text();
|
||||
bool fixedport = ui->chkFixedPort->isChecked();
|
||||
int portnumber = ui->spinPortNumber->value();
|
||||
bool opentabatstartup = ui->chkOpenTabAtStartup->isChecked();
|
||||
QString pythonpath = ui->pythonPathLineEdit->text();
|
||||
QString applicationpath = ui->applicationPathLineEdit->text();
|
||||
|
||||
if (fixedport && (settings.value("FixedPort").toBool() != fixedport ||
|
||||
settings.value("PortNumber").toInt() != portnumber) && isPortInUse(portnumber))
|
||||
{
|
||||
QString error = QString(QWidget::tr("The specified fixed port is already in use. Please provide any other valid port."));
|
||||
QMessageBox::critical(Q_NULLPTR, QString(QWidget::tr("Fatal Error")), error);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_needRestart = (settings.value("FixedPort").toBool() != fixedport ||
|
||||
settings.value("PortNumber").toInt() != portnumber ||
|
||||
settings.value("PythonPath").toString() != pythonpath ||
|
||||
settings.value("ApplicationPath").toString() != applicationpath);
|
||||
|
||||
settings.setValue("BrowserCommand", browsercommand);
|
||||
settings.setValue("FixedPort", fixedport);
|
||||
settings.setValue("PortNumber", portnumber);
|
||||
settings.setValue("OpenTabAtStartup", opentabatstartup);
|
||||
settings.setValue("PythonPath", pythonpath);
|
||||
settings.setValue("ApplicationPath", applicationpath);
|
||||
|
||||
settings.sync();
|
||||
}
|
||||
|
||||
emit accepted(m_needRestart);
|
||||
emit closing(true);
|
||||
|
||||
this->close();
|
||||
}
|
||||
|
||||
void ConfigWindow::on_buttonBox_rejected()
|
||||
{
|
||||
emit closing(false);
|
||||
this->close();
|
||||
}
|
||||
|
||||
void ConfigWindow::on_chkFixedPort_stateChanged(int state)
|
||||
{
|
||||
if (state == Qt::Checked)
|
||||
ui->spinPortNumber->setEnabled(true);
|
||||
else
|
||||
ui->spinPortNumber->setEnabled(false);
|
||||
}
|
||||
|
||||
bool ConfigWindow::isPortInUse(const quint16 port) const
|
||||
{
|
||||
QTcpSocket socket;
|
||||
|
||||
// Bind the socket on the specified port.
|
||||
socket.bind(port, QTcpSocket::DontShareAddress);
|
||||
|
||||
// Returns the host port number of the local socket if available; otherwise returns 0
|
||||
quint16 tmpPort = socket.localPort();
|
||||
if (tmpPort == 0)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// pgAdmin 4 - PostgreSQL Tools
|
||||
//
|
||||
// Copyright (C) 2013 - 2021, The pgAdmin Development Team
|
||||
// This software is released under the PostgreSQL Licence
|
||||
//
|
||||
// ConfigWindow.h - Configuration window
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef CONFIGWINDOW_H
|
||||
#define CONFIGWINDOW_H
|
||||
|
||||
#include <QDialog>
|
||||
|
||||
namespace Ui {
|
||||
class ConfigWindow;
|
||||
}
|
||||
|
||||
class ConfigWindow : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit ConfigWindow(QWidget *parent = Q_NULLPTR);
|
||||
void setConfigValues();
|
||||
|
||||
signals:
|
||||
void accepted(bool needRestart);
|
||||
void closing(bool accepted);
|
||||
|
||||
private slots:
|
||||
void on_buttonBox_accepted();
|
||||
void on_buttonBox_rejected();
|
||||
void on_chkFixedPort_stateChanged(int state);
|
||||
|
||||
private:
|
||||
Ui::ConfigWindow *ui;
|
||||
bool m_needRestart;
|
||||
|
||||
void initConfigWindow();
|
||||
bool isPortInUse(const quint16 port) const;
|
||||
};
|
||||
|
||||
#endif // CONFIGWINDOW_H
|
||||
@@ -1,652 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>ConfigWindow</class>
|
||||
<widget class="QDialog" name="ConfigWindow">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>800</width>
|
||||
<height>415</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Minimum">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>800</width>
|
||||
<height>415</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>pgAdmin 4 Configuration</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QTabWidget" name="tabWidget">
|
||||
<property name="currentIndex">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<widget class="QWidget" name="tab">
|
||||
<attribute name="title">
|
||||
<string>Runtime</string>
|
||||
</attribute>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_4">
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<property name="sizeConstraint">
|
||||
<enum>QLayout::SetMaximumSize</enum>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>16777215</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Enter a command line to be used to start the browser. If blank, the system default browser will be used. %URL% will be replaced with the appropriate URL when executing the browser.</string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="margin">
|
||||
<number>4</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<property name="sizeConstraint">
|
||||
<enum>QLayout::SetMaximumSize</enum>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QLabel" name="pythonPathLabel_2">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>250</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>16777215</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Browser Command</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="browserCommandLineEdit">
|
||||
<property name="placeholderText">
|
||||
<string>/usr/bin/firefox %URL%</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer_4">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Fixed</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_6">
|
||||
<property name="text">
|
||||
<string>By default the runtime 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.</string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_5">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_5">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>250</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>16777215</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Fixed Port Number?</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="chkFixedPort">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Fixed</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_7">
|
||||
<property name="text">
|
||||
<string>Port number</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QSpinBox" name="spinPortNumber">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>65535</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>5050</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Fixed</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_8">
|
||||
<property name="text">
|
||||
<string>By default, when the pgAdmin server is started a browser tab will be automatically opened to display the user interface. Un-check this option to run the server without automatically opening the browser.</string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_9">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>250</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Open a Browser Window/Tab at Startup?</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="chkOpenTabAtStartup">
|
||||
<property name="layoutDirection">
|
||||
<enum>Qt::RightToLeft</enum>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_3">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="tab_2">
|
||||
<attribute name="title">
|
||||
<string>Python</string>
|
||||
</attribute>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="0">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_7">
|
||||
<item>
|
||||
<spacer name="verticalSpacer_7">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Fixed</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>13</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_7">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>The options below are intended for expert users only, and may not behave as expected as they modify fixed search paths and are not alternate values. Modify with care!</string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer_3">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Fixed</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>17</width>
|
||||
<height>13</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_6">
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_6">
|
||||
<item>
|
||||
<widget class="QLabel" name="pythonPathLabel">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>250</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="layoutDirection">
|
||||
<enum>Qt::LeftToRight</enum>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Python Path</string>
|
||||
</property>
|
||||
<property name="scaledContents">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="margin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer_9">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Expanding</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_5">
|
||||
<item>
|
||||
<widget class="QLineEdit" name="pythonPathLineEdit">
|
||||
<property name="placeholderText">
|
||||
<string>/usr/pgadmin4/lib/python3.8;/usr/pgadmin4/lib/python3.8/site-packages</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Enter a PYTHONPATH if desired. Path elements should be semi-colon delimited.</string>
|
||||
</property>
|
||||
<property name="textFormat">
|
||||
<enum>Qt::AutoText</enum>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Fixed</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>13</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_4">
|
||||
<item>
|
||||
<widget class="QLabel" name="applicationPathLabel">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>250</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Application Path</string>
|
||||
</property>
|
||||
<property name="textFormat">
|
||||
<enum>Qt::PlainText</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer_5">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Expanding</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||
<item>
|
||||
<widget class="QLineEdit" name="applicationPathLineEdit">
|
||||
<property name="placeholderText">
|
||||
<string>/usr/pgadmin4/web</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Enter the path to the directory containing pgAdmin.py if desired.</string>
|
||||
</property>
|
||||
<property name="scaledContents">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer_8">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Expanding</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>60</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>accepted()</signal>
|
||||
<receiver>ConfigWindow</receiver>
|
||||
<slot>accept()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>248</x>
|
||||
<y>254</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>157</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>rejected()</signal>
|
||||
<receiver>ConfigWindow</receiver>
|
||||
<slot>reject()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>316</x>
|
||||
<y>260</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>286</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>chkFixedPort</sender>
|
||||
<signal>stateChanged(int)</signal>
|
||||
<receiver>ConfigWindow</receiver>
|
||||
<slot>on_chkFixedPort_stateChanged(int)</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>202</x>
|
||||
<y>213</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>365</x>
|
||||
<y>149</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
<slots>
|
||||
<slot>on_chkFixedPort_stateChanged(int)</slot>
|
||||
</slots>
|
||||
</ui>
|
||||
@@ -1,147 +0,0 @@
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// pgAdmin 4 - PostgreSQL Tools
|
||||
//
|
||||
// Copyright (C) 2013 - 2021, The pgAdmin Development Team
|
||||
// This software is released under the PostgreSQL Licence
|
||||
//
|
||||
// FloatingWindow.cpp - For GNOME 3.26 and above floating window will be used.
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "pgAdmin4.h"
|
||||
#include "FloatingWindow.h"
|
||||
#include "ui_FloatingWindow.h"
|
||||
|
||||
#include <QMenu>
|
||||
#include <QMenuBar>
|
||||
|
||||
|
||||
FloatingWindow::FloatingWindow(QWidget *parent) :
|
||||
QMainWindow(parent)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
bool FloatingWindow::Init()
|
||||
{
|
||||
ui = new Ui::FloatingWindow;
|
||||
ui->setupUi(this);
|
||||
|
||||
// Creating Menu
|
||||
createMenu();
|
||||
|
||||
// Setup the icon itself. For convenience, we'll also use it for the dialogue.
|
||||
#ifdef Q_OS_MAC
|
||||
QIcon icon(":pgAdmin4-mac.png");
|
||||
#else
|
||||
QIcon icon(":pgAdmin4.png");
|
||||
#endif
|
||||
|
||||
setWindowIcon(icon);
|
||||
setWindowTitle(tr("pgAdmin"));
|
||||
setFixedSize(300, 230);
|
||||
setWindowFlags(Qt::Window | Qt::WindowTitleHint | Qt::WindowMinimizeButtonHint);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// Create the menu
|
||||
void FloatingWindow::createMenu()
|
||||
{
|
||||
createActions();
|
||||
|
||||
m_floatingWindowMenu = menuBar()->addMenu(tr("&pgAdmin 4"));
|
||||
m_floatingWindowMenu->addAction(m_newAction);
|
||||
m_floatingWindowMenu->addAction(m_copyUrlAction);
|
||||
m_floatingWindowMenu->addSeparator();
|
||||
m_floatingWindowMenu->addAction(m_configAction);
|
||||
m_floatingWindowMenu->addAction(m_logAction);
|
||||
m_floatingWindowMenu->addSeparator();
|
||||
m_floatingWindowMenu->addAction(m_quitAction);
|
||||
}
|
||||
|
||||
|
||||
// Create the menu actions
|
||||
void FloatingWindow::createActions()
|
||||
{
|
||||
m_newAction = new QAction(tr("&New pgAdmin 4 window..."), this);
|
||||
m_newAction->setEnabled(false);
|
||||
connect(m_newAction, SIGNAL(triggered()), m_menuActions, SLOT(onNew()));
|
||||
|
||||
m_copyUrlAction = new QAction(tr("&Copy server URL"), this);
|
||||
m_copyUrlAction->setEnabled(false);
|
||||
connect(m_copyUrlAction, SIGNAL(triggered()), m_menuActions, SLOT(onCopyUrl()));
|
||||
|
||||
m_configAction = new QAction(tr("C&onfigure..."), this);
|
||||
m_configAction->setEnabled(false);
|
||||
connect(m_configAction, SIGNAL(triggered()), m_menuActions, SLOT(onConfig()));
|
||||
|
||||
m_logAction = new QAction(tr("&View log..."), this);
|
||||
m_logAction->setEnabled(false);
|
||||
connect(m_logAction, SIGNAL(triggered()), m_menuActions, SLOT(onLog()));
|
||||
|
||||
m_quitAction = new QAction(tr("&Shut down server"), this);
|
||||
m_quitAction->setEnabled(false);
|
||||
connect(m_quitAction, SIGNAL(triggered()), m_menuActions, SLOT(onQuit()));
|
||||
}
|
||||
|
||||
|
||||
void FloatingWindow::enablePostStartOptions()
|
||||
{
|
||||
if (m_newAction != Q_NULLPTR)
|
||||
m_newAction->setEnabled(true);
|
||||
|
||||
if (m_copyUrlAction != Q_NULLPTR)
|
||||
m_copyUrlAction->setEnabled(true);
|
||||
|
||||
if (m_configAction != Q_NULLPTR)
|
||||
m_configAction->setEnabled(true);
|
||||
|
||||
if (m_logAction != Q_NULLPTR)
|
||||
m_logAction->setEnabled(true);
|
||||
|
||||
if (m_quitAction != Q_NULLPTR)
|
||||
m_quitAction->setEnabled(true);
|
||||
}
|
||||
|
||||
void FloatingWindow::setMenuActions(MenuActions * menuActions)
|
||||
{
|
||||
m_menuActions = menuActions;
|
||||
}
|
||||
|
||||
// Enable the View Log option
|
||||
void FloatingWindow::enableViewLogOption()
|
||||
{
|
||||
if (m_logAction != Q_NULLPTR)
|
||||
m_logAction->setEnabled(true);
|
||||
}
|
||||
|
||||
// Disable the View Log option
|
||||
void FloatingWindow::disableViewLogOption()
|
||||
{
|
||||
if (m_logAction != Q_NULLPTR)
|
||||
m_logAction->setEnabled(false);
|
||||
}
|
||||
|
||||
// Enable the configure option
|
||||
void FloatingWindow::enableConfigOption()
|
||||
{
|
||||
if (m_configAction != Q_NULLPTR)
|
||||
m_configAction->setEnabled(true);
|
||||
}
|
||||
|
||||
// Disable the configure option
|
||||
void FloatingWindow::disableConfigOption()
|
||||
{
|
||||
if (m_configAction != Q_NULLPTR)
|
||||
m_configAction->setEnabled(false);
|
||||
}
|
||||
|
||||
void FloatingWindow::closeEvent(QCloseEvent * event)
|
||||
{
|
||||
// Emit the signal to shut down the python server.
|
||||
emit shutdownSignal(m_menuActions->getAppServerUrl());
|
||||
event->accept();
|
||||
exit(0);
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// pgAdmin 4 - PostgreSQL Tools
|
||||
//
|
||||
// Copyright (C) 2013 - 2021, The pgAdmin Development Team
|
||||
// This software is released under the PostgreSQL Licence
|
||||
//
|
||||
// FloatingWindow.h - For GNOME 3.26 and above floating window will be used.
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef FLOATINGWINDOW_H
|
||||
#define FLOATINGWINDOW_H
|
||||
|
||||
#include "MenuActions.h"
|
||||
|
||||
#include <QMainWindow>
|
||||
|
||||
namespace Ui {
|
||||
class FloatingWindow;
|
||||
}
|
||||
|
||||
class FloatingWindow : public QMainWindow
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit FloatingWindow(QWidget *parent = Q_NULLPTR);
|
||||
|
||||
bool Init();
|
||||
void enablePostStartOptions();
|
||||
void enableViewLogOption();
|
||||
void disableViewLogOption();
|
||||
void enableConfigOption();
|
||||
void disableConfigOption();
|
||||
void setMenuActions(MenuActions * menuActions);
|
||||
|
||||
private:
|
||||
Ui::FloatingWindow *ui;
|
||||
|
||||
void createMenu();
|
||||
void createActions();
|
||||
void closeEvent(QCloseEvent * event);
|
||||
|
||||
QAction *m_newAction = Q_NULLPTR;
|
||||
QAction *m_copyUrlAction = Q_NULLPTR;
|
||||
QAction *m_configAction = Q_NULLPTR;
|
||||
QAction *m_logAction = Q_NULLPTR;
|
||||
QAction *m_quitAction = Q_NULLPTR;
|
||||
|
||||
QMenu *m_floatingWindowMenu = Q_NULLPTR;
|
||||
MenuActions *m_menuActions = Q_NULLPTR;
|
||||
|
||||
signals:
|
||||
void shutdownSignal(QUrl);
|
||||
};
|
||||
|
||||
#endif // FLOATINGWINDOW_H
|
||||
@@ -1,155 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>FloatingWindow</class>
|
||||
<widget class="QMainWindow" name="FloatingWindow">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>300</width>
|
||||
<height>230</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>pgAdmin 4</string>
|
||||
</property>
|
||||
<widget class="QWidget" name="centralwidget">
|
||||
<widget class="QWidget" name="verticalLayoutWidget">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>10</x>
|
||||
<y>10</y>
|
||||
<width>281</width>
|
||||
<height>201</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>100</width>
|
||||
<height>100</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>100</width>
|
||||
<height>100</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">border-image:url(":pgAdmin4.png");</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="scaledContents">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_4">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Minimum">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>16777215</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="layoutDirection">
|
||||
<enum>Qt::LeftToRight</enum>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Note: Installing a system tray plugin will prevent this window being shown.</string>
|
||||
</property>
|
||||
<property name="textFormat">
|
||||
<enum>Qt::AutoText</enum>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_3">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
@@ -1,35 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist SYSTEM "file://localhost/System/Library/DTDs/PropertyList.dtd">
|
||||
<plist version="0.9">
|
||||
<dict>
|
||||
<!-- start of standard entries -->
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>pgAdmin 4</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>4.30.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>4.30.0</string>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>10.10</string>
|
||||
<key>NSHumanReadableCopyright</key>
|
||||
<string>Copyright (C) 2013 - 2021, The pgAdmin Development Team</string>
|
||||
<key>CFBundleIconFile</key>
|
||||
<string>@ICON@</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleGetInfoString</key>
|
||||
<string>pgAdmin 4 - PostgreSQL Tools</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>@TYPEINFO@</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>@EXECUTABLE@</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>org.pgadmin.@EXECUTABLE@</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
<string>NSApplication</string>
|
||||
<key>LSUIElement</key>
|
||||
<string>1</string>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -1,103 +0,0 @@
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// pgAdmin 4 - PostgreSQL Tools
|
||||
//
|
||||
// Copyright (C) 2013 - 2021, The pgAdmin Development Team
|
||||
// This software is released under the PostgreSQL Licence
|
||||
//
|
||||
// LogWindow.cpp - Log viewer window
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "pgAdmin4.h"
|
||||
#include "LogWindow.h"
|
||||
#include "ui_LogWindow.h"
|
||||
|
||||
#include <QStandardPaths>
|
||||
#include <QTime>
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
LogWindow::LogWindow(QWidget *parent) :
|
||||
QDialog(parent)
|
||||
{
|
||||
initLogWindow();
|
||||
}
|
||||
|
||||
void LogWindow::initLogWindow()
|
||||
{
|
||||
ui = new Ui::LogWindow;
|
||||
ui->setupUi(this);
|
||||
}
|
||||
|
||||
void LogWindow::LoadLog()
|
||||
{
|
||||
int startupLines;
|
||||
int serverLines;
|
||||
|
||||
ui->lblStatus->setText(tr("Loading logfiles..."));
|
||||
|
||||
ui->lblStartupLog->setText(tr("Startup Log (%1):").arg(g_startupLogFile));
|
||||
ui->lblServerLog->setText(tr("Server Log (%1):").arg(g_serverLogFile));
|
||||
|
||||
startupLines = this->readLog(g_startupLogFile, ui->textStartupLog);
|
||||
serverLines = this->readLog(g_serverLogFile, ui->textServerLog);
|
||||
|
||||
ui->lblStatus->setText(QString(tr("Loaded startup log (%1 lines) and server log (%2 lines).")).arg(startupLines).arg(serverLines));
|
||||
}
|
||||
|
||||
|
||||
void LogWindow::reload()
|
||||
{
|
||||
this->LoadLog();
|
||||
}
|
||||
|
||||
|
||||
// Read the logfile
|
||||
int LogWindow::readLog(QString logFile, QPlainTextEdit *logWidget)
|
||||
{
|
||||
FILE *log;
|
||||
char *buffer;
|
||||
long len = 0;
|
||||
int i;
|
||||
int lines = 0;
|
||||
|
||||
// Look busy!
|
||||
QApplication::setOverrideCursor(Qt::WaitCursor);
|
||||
this->setDisabled(true);
|
||||
QCoreApplication::processEvents( QEventLoop::AllEvents, 100 );
|
||||
|
||||
logWidget->clear();
|
||||
|
||||
// Attempt to open the file
|
||||
log = fopen(logFile.toUtf8().data(), "r");
|
||||
if (log == Q_NULLPTR)
|
||||
{
|
||||
logWidget->setPlainText(QString(tr("The log file (%1) could not be opened.")).arg(g_serverLogFile));
|
||||
this->setDisabled(false);
|
||||
QApplication::restoreOverrideCursor();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Get the file size, and read the data
|
||||
fseek(log, 0, SEEK_END);
|
||||
len = ftell(log);
|
||||
rewind(log);
|
||||
buffer = static_cast<char *>(malloc((len + 1) * sizeof(char)));
|
||||
|
||||
for (i = 0; i < len; i++) {
|
||||
if (fread(buffer + i, 1, 1, log) > 0 && buffer[i] == '\n')
|
||||
lines++;
|
||||
}
|
||||
|
||||
buffer[i] = 0;
|
||||
|
||||
fclose(log);
|
||||
logWidget->setPlainText(buffer);
|
||||
|
||||
// And... relax
|
||||
this->setDisabled(false);
|
||||
QApplication::restoreOverrideCursor();
|
||||
|
||||
return lines;
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// pgAdmin 4 - PostgreSQL Tools
|
||||
//
|
||||
// Copyright (C) 2013 - 2021, The pgAdmin Development Team
|
||||
// This software is released under the PostgreSQL Licence
|
||||
//
|
||||
// LogWindow.h - Log viewer window
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef LOGWINDOW_H
|
||||
#define LOGWINDOW_H
|
||||
|
||||
#include <QDialog>
|
||||
#include <QPlainTextEdit>
|
||||
|
||||
namespace Ui {
|
||||
class LogWindow;
|
||||
}
|
||||
|
||||
class LogWindow : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit LogWindow(QWidget *parent = Q_NULLPTR);
|
||||
void LoadLog();
|
||||
|
||||
private slots:
|
||||
void reload();
|
||||
|
||||
private:
|
||||
Ui::LogWindow *ui;
|
||||
|
||||
void initLogWindow();
|
||||
int readLog(QString logFile, QPlainTextEdit *logWidget);
|
||||
};
|
||||
|
||||
#endif // LOGWINDOW_H
|
||||
@@ -1,138 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>LogWindow</class>
|
||||
<widget class="QDialog" name="LogWindow">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>800</width>
|
||||
<height>500</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>pgAdmin 4 Log</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="lblStartupLog">
|
||||
<property name="text">
|
||||
<string>Startup Log:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPlainTextEdit" name="textStartupLog">
|
||||
<property name="font">
|
||||
<font>
|
||||
<family>Courier</family>
|
||||
</font>
|
||||
</property>
|
||||
<property name="readOnly">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="plainText">
|
||||
<string notr="true"/>
|
||||
</property>
|
||||
<property name="centerOnScroll">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="lblServerLog">
|
||||
<property name="text">
|
||||
<string>Server Log:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPlainTextEdit" name="textServerLog">
|
||||
<property name="font">
|
||||
<font>
|
||||
<family>Courier</family>
|
||||
</font>
|
||||
</property>
|
||||
<property name="readOnly">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="plainText">
|
||||
<string notr="true"/>
|
||||
</property>
|
||||
<property name="centerOnScroll">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QPushButton" name="btnReload">
|
||||
<property name="text">
|
||||
<string>Reload</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="lblStatus">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="btnClose">
|
||||
<property name="text">
|
||||
<string>Close</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>btnReload</sender>
|
||||
<signal>clicked()</signal>
|
||||
<receiver>LogWindow</receiver>
|
||||
<slot>reload()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>53</x>
|
||||
<y>471</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>399</x>
|
||||
<y>249</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>btnClose</sender>
|
||||
<signal>clicked()</signal>
|
||||
<receiver>LogWindow</receiver>
|
||||
<slot>close()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>731</x>
|
||||
<y>471</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>399</x>
|
||||
<y>249</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
<slots>
|
||||
<slot>reload()</slot>
|
||||
</slots>
|
||||
</ui>
|
||||
@@ -1,63 +0,0 @@
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// pgAdmin 4 - PostgreSQL Tools
|
||||
//
|
||||
// Copyright (C) 2013 - 2021, The pgAdmin Development Team
|
||||
// This software is released under the PostgreSQL Licence
|
||||
//
|
||||
// Logger.cpp - Logger Utility
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "pgAdmin4.h"
|
||||
#include "Logger.h"
|
||||
|
||||
#include <QDateTime>
|
||||
#include <QTextStream>
|
||||
#include <QStandardPaths>
|
||||
|
||||
Logger* Logger::m_pThis = Q_NULLPTR;
|
||||
QFile* Logger::m_Logfile = Q_NULLPTR;
|
||||
|
||||
Logger::Logger()
|
||||
{
|
||||
}
|
||||
|
||||
Logger::~Logger()
|
||||
{
|
||||
}
|
||||
|
||||
Logger* Logger::GetLogger()
|
||||
{
|
||||
if (m_pThis == Q_NULLPTR)
|
||||
{
|
||||
m_pThis = new Logger();
|
||||
m_Logfile = new QFile;
|
||||
m_Logfile->setFileName(g_startupLogFile);
|
||||
m_Logfile->open(QIODevice::WriteOnly | QIODevice::Text);
|
||||
m_Logfile->setPermissions(QFile::ReadOwner|QFile::WriteOwner);
|
||||
}
|
||||
|
||||
return m_pThis;
|
||||
}
|
||||
|
||||
void Logger::Log(const QString& sMessage) const
|
||||
{
|
||||
QString text = QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss: ") + sMessage + "\n";
|
||||
if (m_Logfile != Q_NULLPTR)
|
||||
{
|
||||
QTextStream out(m_Logfile);
|
||||
out << text;
|
||||
}
|
||||
}
|
||||
|
||||
void Logger::ReleaseLogger()
|
||||
{
|
||||
if (m_pThis != Q_NULLPTR)
|
||||
{
|
||||
if(m_Logfile != Q_NULLPTR)
|
||||
m_Logfile->close();
|
||||
delete m_pThis;
|
||||
m_pThis = Q_NULLPTR;
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// pgAdmin 4 - PostgreSQL Tools
|
||||
//
|
||||
// Copyright (C) 2013 - 2021, The pgAdmin Development Team
|
||||
// This software is released under the PostgreSQL Licence
|
||||
//
|
||||
// Logger.h - Logger Utility
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef LOGGER_H
|
||||
#define LOGGER_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QFile>
|
||||
|
||||
class Logger : public QObject
|
||||
{
|
||||
public:
|
||||
static Logger* GetLogger();
|
||||
static void ReleaseLogger();
|
||||
void Log(const QString& sMessage) const;
|
||||
|
||||
private:
|
||||
Logger();
|
||||
virtual ~Logger();
|
||||
|
||||
static Logger* m_pThis;
|
||||
static QFile *m_Logfile;
|
||||
};
|
||||
|
||||
#endif // LOGGER_H
|
||||
@@ -1,118 +0,0 @@
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// pgAdmin 4 - PostgreSQL Tools
|
||||
//
|
||||
// Copyright (C) 2013 - 2021, The pgAdmin Development Team
|
||||
// This software is released under the PostgreSQL Licence
|
||||
//
|
||||
// MenuActions.cpp - Common file for menu actions.
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "pgAdmin4.h"
|
||||
#include "MenuActions.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QClipboard>
|
||||
#include <QDesktopServices>
|
||||
#include <QEventLoop>
|
||||
#include <QMessageBox>
|
||||
#include <QProcess>
|
||||
#include <QSettings>
|
||||
|
||||
MenuActions::MenuActions()
|
||||
{
|
||||
}
|
||||
|
||||
void MenuActions::setAppServerUrl(QString appServerUrl)
|
||||
{
|
||||
m_appServerUrl = appServerUrl;
|
||||
}
|
||||
|
||||
|
||||
// Create a new application browser window on user request
|
||||
void MenuActions::onNew() const
|
||||
{
|
||||
QSettings settings;
|
||||
QString cmd = settings.value("BrowserCommand").toString();
|
||||
|
||||
if (!cmd.isEmpty())
|
||||
{
|
||||
cmd.replace("%URL%", m_appServerUrl);
|
||||
QProcess::startDetached(cmd);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!QDesktopServices::openUrl(m_appServerUrl))
|
||||
{
|
||||
QString error(QWidget::tr("Failed to open the system default web browser. Is one installed?."));
|
||||
QMessageBox::critical(Q_NULLPTR, QString(QWidget::tr("Fatal Error")), error);
|
||||
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Copy the application server URL to the clipboard
|
||||
void MenuActions::onCopyUrl() const
|
||||
{
|
||||
QClipboard *clipboard = QApplication::clipboard();
|
||||
clipboard->setText(m_appServerUrl);
|
||||
}
|
||||
|
||||
|
||||
// Show the config dialogue
|
||||
void MenuActions::onConfig()
|
||||
{
|
||||
if (!m_configWindow)
|
||||
m_configWindow = new ConfigWindow();
|
||||
|
||||
m_configWindow->setConfigValues();
|
||||
m_configWindow->show();
|
||||
m_configWindow->raise();
|
||||
m_configWindow->activateWindow();
|
||||
connect(m_configWindow, SIGNAL(accepted(bool)), this, SLOT(onConfigDone(bool)));
|
||||
}
|
||||
|
||||
|
||||
void MenuActions::onConfigDone(bool needRestart) const
|
||||
{
|
||||
if (needRestart && QMessageBox::Yes == QMessageBox::question(Q_NULLPTR,
|
||||
tr("Shut down server?"),
|
||||
tr("The pgAdmin 4 server must be restarted for changes to take effect. Do you want to shut down the server now?"),
|
||||
QMessageBox::Yes | QMessageBox::No))
|
||||
{
|
||||
exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Show the log window
|
||||
void MenuActions::onLog()
|
||||
{
|
||||
QSettings settings;
|
||||
|
||||
if (!m_logWindow)
|
||||
m_logWindow = new LogWindow();
|
||||
|
||||
m_logWindow->show();
|
||||
m_logWindow->raise();
|
||||
m_logWindow->activateWindow();
|
||||
|
||||
QCoreApplication::processEvents(QEventLoop::AllEvents, 100);
|
||||
|
||||
m_logWindow->LoadLog();
|
||||
}
|
||||
|
||||
|
||||
// Exit
|
||||
void MenuActions::onQuit()
|
||||
{
|
||||
if (QMessageBox::Yes == QMessageBox::question(Q_NULLPTR, tr("Shut down server?"), tr("Are you sure you want to shut down the pgAdmin 4 server?"), QMessageBox::Yes | QMessageBox::No))
|
||||
{
|
||||
// Emit the signal to shut down the python server.
|
||||
emit shutdownSignal(m_appServerUrl);
|
||||
QApplication::quit();
|
||||
}
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// pgAdmin 4 - PostgreSQL Tools
|
||||
//
|
||||
// Copyright (C) 2013 - 2021, The pgAdmin Development Team
|
||||
// This software is released under the PostgreSQL Licence
|
||||
//
|
||||
// MenuActions.h - Common file for menu actions.
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef MENUACTIONS_H
|
||||
#define MENUACTIONS_H
|
||||
|
||||
#include "LogWindow.h"
|
||||
#include "ConfigWindow.h"
|
||||
|
||||
class MenuActions: public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
MenuActions();
|
||||
|
||||
void setAppServerUrl(QString appServerUrl);
|
||||
QString getAppServerUrl() const { return m_appServerUrl; }
|
||||
|
||||
private:
|
||||
QString m_appServerUrl = "";
|
||||
LogWindow *m_logWindow = Q_NULLPTR;
|
||||
ConfigWindow *m_configWindow = Q_NULLPTR;
|
||||
|
||||
public slots:
|
||||
void onConfigDone(bool needRestart) const;
|
||||
|
||||
protected slots:
|
||||
void onNew() const;
|
||||
void onCopyUrl() const;
|
||||
void onConfig();
|
||||
void onLog();
|
||||
void onQuit();
|
||||
|
||||
signals:
|
||||
void shutdownSignal(QUrl);
|
||||
};
|
||||
|
||||
#endif // MENUACTIONS_H
|
||||
@@ -1,660 +0,0 @@
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// pgAdmin 4 - PostgreSQL Tools
|
||||
//
|
||||
// Copyright (C) 2013 - 2021, The pgAdmin Development Team
|
||||
// This software is released under the PostgreSQL Licence
|
||||
//
|
||||
// Runtime.cpp - Core of the runtime
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "pgAdmin4.h"
|
||||
#include "Runtime.h"
|
||||
#include "Server.h"
|
||||
#include "TrayIcon.h"
|
||||
#include "MenuActions.h"
|
||||
#include "ConfigWindow.h"
|
||||
#include "FloatingWindow.h"
|
||||
#include "Logger.h"
|
||||
|
||||
#ifdef Q_OS_MAC
|
||||
#include "macos.h"
|
||||
#endif
|
||||
|
||||
// Must be before QT
|
||||
#include <Python.h>
|
||||
|
||||
#include <QtWidgets>
|
||||
#include <QNetworkProxyFactory>
|
||||
#include <QNetworkRequest>
|
||||
#include <QNetworkReply>
|
||||
#include <QTime>
|
||||
|
||||
|
||||
Runtime::Runtime()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
bool Runtime::go(int argc, char *argv[])
|
||||
{
|
||||
// Before starting main application, need to set 'QT_X11_NO_MITSHM=1'
|
||||
// to make the runtime work with IBM PPC machines.
|
||||
#if defined (Q_OS_LINUX)
|
||||
QByteArray val("1");
|
||||
qputenv("QT_X11_NO_MITSHM", val);
|
||||
#endif
|
||||
|
||||
// Create the QT application
|
||||
QApplication app(argc, argv);
|
||||
app.setQuitOnLastWindowClosed(false);
|
||||
|
||||
// Setup look n feel
|
||||
setupStyling(&app);
|
||||
|
||||
// Setup the settings management
|
||||
QCoreApplication::setOrganizationName("pgadmin");
|
||||
QCoreApplication::setOrganizationDomain("pgadmin.org");
|
||||
QCoreApplication::setApplicationName("pgadmin4");
|
||||
|
||||
QSettings settings;
|
||||
|
||||
// Interlock
|
||||
if (alreadyRunning())
|
||||
exit(0);
|
||||
|
||||
// Proxy config
|
||||
configureProxy();
|
||||
|
||||
// Display the spash screen
|
||||
m_splash = displaySplash(&app);
|
||||
|
||||
// Generate a random key to authenticate the client to the server
|
||||
QString key = QUuid::createUuid().toString();
|
||||
key = key.mid(1, key.length() - 2);
|
||||
|
||||
// Create Menu Actions
|
||||
MenuActions *menuActions = new MenuActions();
|
||||
|
||||
// Create the control object (tray icon or floating window
|
||||
m_splash->showMessage(QString(QWidget::tr("Checking for system tray...")), Qt::AlignBottom | Qt::AlignCenter);
|
||||
|
||||
if (QSystemTrayIcon::isSystemTrayAvailable())
|
||||
m_trayIcon = createTrayIcon(menuActions);
|
||||
else
|
||||
m_floatingWindow = createFloatingWindow(menuActions);
|
||||
|
||||
// Fire up the app server
|
||||
const Server *server = startServerLoop(key);
|
||||
|
||||
// Ensure we'll cleanup
|
||||
QObject::connect(server, SIGNAL(finished()), server, SLOT(deleteLater()));
|
||||
atexit(cleanup);
|
||||
|
||||
// Generate the app server URL
|
||||
QString url = QString("http://127.0.0.1:%1/?key=%2").arg(m_port).arg(key);
|
||||
Logger::GetLogger()->Log(QString(QWidget::tr("Application Server URL: %1")).arg(url));
|
||||
|
||||
// Check the server is running
|
||||
checkServer(url);
|
||||
|
||||
// Stash the URL for any duplicate processes to open
|
||||
createAddressFile(url);
|
||||
|
||||
// Go!
|
||||
menuActions->setAppServerUrl(url);
|
||||
|
||||
// Enable the shutdown server menu as server started successfully.
|
||||
if (m_trayIcon != Q_NULLPTR)
|
||||
m_trayIcon->enablePostStartOptions();
|
||||
if (m_floatingWindow != Q_NULLPTR)
|
||||
m_floatingWindow->enablePostStartOptions();
|
||||
|
||||
// Open the browser if needed
|
||||
if (settings.value("OpenTabAtStartup", true).toBool())
|
||||
openBrowserTab(url);
|
||||
|
||||
// Make sure the server is shutdown if the server is quit by the user
|
||||
QObject::connect(menuActions, SIGNAL(shutdownSignal(QUrl)), server, SLOT(shutdown(QUrl)));
|
||||
|
||||
// Final cleanup
|
||||
m_splash->finish(Q_NULLPTR);
|
||||
|
||||
if (m_floatingWindow != Q_NULLPTR)
|
||||
m_floatingWindow->show();
|
||||
|
||||
Logger::GetLogger()->Log("Everything works fine, successfully started pgAdmin4.");
|
||||
Logger::ReleaseLogger();
|
||||
return app.exec();
|
||||
}
|
||||
|
||||
|
||||
// Setup the styling
|
||||
void Runtime::setupStyling(QApplication *app) const
|
||||
{
|
||||
// Setup the styling
|
||||
#ifndef Q_OS_LINUX
|
||||
QFile stylesheet;
|
||||
|
||||
#ifdef Q_OS_WIN32
|
||||
QSettings registry("HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize", QSettings::Registry64Format);
|
||||
if (!registry.value("AppsUseLightTheme", true).toBool())
|
||||
{
|
||||
qDebug( "Windows Dark Mode..." );
|
||||
stylesheet.setFileName(":/qdarkstyle/style.qss");
|
||||
stylesheet.open(QFile::ReadOnly | QFile::Text);
|
||||
QTextStream stream(&stylesheet);
|
||||
app->setStyleSheet(stream.readAll());
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef Q_OS_MAC
|
||||
if (IsDarkMode())
|
||||
{
|
||||
qDebug( "macOS Dark Mode...");
|
||||
stylesheet.setFileName(":/qdarkstyle/style.qss");
|
||||
stylesheet.open(QFile::ReadOnly | QFile::Text);
|
||||
QTextStream stream(&stylesheet);
|
||||
app->setStyleSheet(stream.readAll());
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// Set high DPI pixmap to display icons clear on Qt widget.
|
||||
QApplication::setAttribute(Qt::AA_UseHighDpiPixmaps);
|
||||
}
|
||||
|
||||
// Check if we're already running. If we are, open a new browser tab.
|
||||
bool Runtime::alreadyRunning()
|
||||
{
|
||||
// Create a system-wide semaphore keyed by app name, exe hash and the username
|
||||
// to ensure instances are unique to the user and path
|
||||
QString userName = qgetenv("USER"); // *nix
|
||||
if (userName.isEmpty())
|
||||
userName = qgetenv("USERNAME"); // Windows
|
||||
|
||||
QString semaName = QString("pgadmin4-%1-%2-sema").arg(userName).arg(getExeHash());
|
||||
QString shmemName = QString("pgadmin4-%1-%2-shmem").arg(userName).arg(getExeHash());
|
||||
qDebug() << "Semaphore name:" << semaName;
|
||||
qDebug() << "Shared memory segment name:" << shmemName;
|
||||
|
||||
QSystemSemaphore sema(semaName, 1);
|
||||
sema.acquire();
|
||||
|
||||
#ifndef Q_OS_WIN32
|
||||
// We may need to clean up stale shmem segments on *nix. Attaching and detaching
|
||||
// should remove the segment if it is orphaned.
|
||||
QSharedMemory stale_shmem(shmemName);
|
||||
if (stale_shmem.attach())
|
||||
stale_shmem.detach();
|
||||
#endif
|
||||
|
||||
m_shmem = new QSharedMemory(shmemName);
|
||||
bool is_running;
|
||||
if (m_shmem->attach())
|
||||
is_running = true;
|
||||
else
|
||||
{
|
||||
m_shmem->create(1);
|
||||
is_running = false;
|
||||
}
|
||||
sema.release();
|
||||
|
||||
if (is_running)
|
||||
{
|
||||
QFile addressFile(g_addressFile);
|
||||
addressFile.open(QIODevice::ReadOnly | QIODevice::Text);
|
||||
QTextStream in(&addressFile);
|
||||
QString url = in.readLine();
|
||||
|
||||
qDebug() << "Already running. Opening browser tab to: " << url << "and exiting.";
|
||||
openBrowserTab(url);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
void Runtime::configureProxy() const
|
||||
{
|
||||
// In windows and linux, it is required to set application level proxy
|
||||
// because socket bind logic to find free port gives socket creation error
|
||||
// when system proxy is configured. We are also setting
|
||||
// "setUseSystemConfiguration"=true to use the system proxy which will
|
||||
// override this application level proxy. As this bug is fixed in Qt 5.9 so
|
||||
// need to set application proxy for Qt version < 5.9.
|
||||
//
|
||||
#if defined (Q_OS_WIN) && QT_VERSION <= 0x050800
|
||||
// Give dummy URL required to find proxy server configured in windows.
|
||||
QNetworkProxyQuery proxyQuery(QUrl("https://www.pgadmin.org"));
|
||||
QNetworkProxy l_proxy;
|
||||
QList<QNetworkProxy> listOfProxies = QNetworkProxyFactory::systemProxyForQuery(proxyQuery);
|
||||
|
||||
if (listOfProxies.size())
|
||||
{
|
||||
l_proxy = listOfProxies[0];
|
||||
|
||||
// If host name is not empty means proxy server is configured.
|
||||
if (!l_proxy.hostName().isEmpty()) {
|
||||
QNetworkProxy::setApplicationProxy(QNetworkProxy());
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined (Q_OS_LINUX) && QT_VERSION <= 0x050800
|
||||
QByteArray proxy_env;
|
||||
proxy_env = qgetenv("http_proxy");
|
||||
// If http_proxy environment is defined in linux then proxy server is configured.
|
||||
if (!proxy_env.isEmpty()) {
|
||||
QNetworkProxy::setApplicationProxy(QNetworkProxy());
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
// Display the splash screen
|
||||
QSplashScreen * Runtime::displaySplash(QApplication *app)
|
||||
{
|
||||
QSplashScreen *splash = new QSplashScreen();
|
||||
splash->setPixmap(QPixmap(":/splash.png"));
|
||||
splash->setWindowFlags(splash->windowFlags() | Qt::WindowStaysOnTopHint);
|
||||
splash->show();
|
||||
app->processEvents(QEventLoop::AllEvents);
|
||||
|
||||
return splash;
|
||||
}
|
||||
|
||||
|
||||
// Get the port number we're going to use
|
||||
quint16 Runtime::getPort() const
|
||||
{
|
||||
quint16 port = 0L;
|
||||
QSettings settings;
|
||||
|
||||
if (settings.value("FixedPort", false).toBool())
|
||||
{
|
||||
// Use the fixed port number
|
||||
port = settings.value("PortNumber", 5050).toUInt();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Find an unused port number. Essentially, we're just reserving one
|
||||
// here that Flask will use when we start up the server.
|
||||
QTcpSocket socket;
|
||||
|
||||
#if QT_VERSION >= 0x050900
|
||||
socket.setProxy(QNetworkProxy::NoProxy);
|
||||
#endif
|
||||
|
||||
socket.bind(0, QTcpSocket::ShareAddress);
|
||||
port = socket.localPort();
|
||||
}
|
||||
|
||||
return port;
|
||||
}
|
||||
|
||||
|
||||
// Create a tray icon
|
||||
TrayIcon * Runtime::createTrayIcon(MenuActions *menuActions)
|
||||
{
|
||||
TrayIcon *trayIcon = Q_NULLPTR;
|
||||
|
||||
m_splash->showMessage(QString(QWidget::tr("Checking for system tray...")), Qt::AlignBottom | Qt::AlignCenter);
|
||||
Logger::GetLogger()->Log("Checking for system tray...");
|
||||
|
||||
// Start the tray service
|
||||
trayIcon = new TrayIcon();
|
||||
|
||||
// Set the MenuActions object to connect to slot
|
||||
if (trayIcon != Q_NULLPTR)
|
||||
trayIcon->setMenuActions(menuActions);
|
||||
|
||||
trayIcon->Init();
|
||||
|
||||
return trayIcon;
|
||||
}
|
||||
|
||||
|
||||
// Create a floating window
|
||||
FloatingWindow * Runtime::createFloatingWindow(MenuActions *menuActions)
|
||||
{
|
||||
FloatingWindow *floatingWindow = Q_NULLPTR;
|
||||
|
||||
m_splash->showMessage(QString(QWidget::tr("System tray not found, creating floating window...")), Qt::AlignBottom | Qt::AlignCenter);
|
||||
Logger::GetLogger()->Log("System tray not found, creating floating window...");
|
||||
floatingWindow = new FloatingWindow();
|
||||
if (floatingWindow == Q_NULLPTR)
|
||||
{
|
||||
QString error = QString(QWidget::tr("Unable to initialize either a tray icon or floating window."));
|
||||
QMessageBox::critical(Q_NULLPTR, QString(QWidget::tr("Fatal Error")), error);
|
||||
Logger::GetLogger()->Log(error);
|
||||
Logger::ReleaseLogger();
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// Set the MenuActions object to connect to slot
|
||||
floatingWindow->setMenuActions(menuActions);
|
||||
floatingWindow->Init();
|
||||
|
||||
return floatingWindow;
|
||||
}
|
||||
|
||||
|
||||
void Runtime::openConfigureWindow(const QString errorMsg)
|
||||
{
|
||||
m_splash->finish(Q_NULLPTR);
|
||||
|
||||
qDebug() << errorMsg;
|
||||
QMessageBox::critical(Q_NULLPTR, QString(QWidget::tr("Fatal Error")), errorMsg);
|
||||
Logger::GetLogger()->Log(errorMsg);
|
||||
|
||||
// Allow the user to tweak the configuration if needed
|
||||
m_configDone = false;
|
||||
QSettings settings;
|
||||
bool oldFixedPort = settings.value("FixedPort", false).toBool();
|
||||
|
||||
|
||||
ConfigWindow *dlg = new ConfigWindow();
|
||||
dlg->setAttribute(Qt::WA_DeleteOnClose);
|
||||
dlg->show();
|
||||
dlg->raise();
|
||||
dlg->activateWindow();
|
||||
QObject::connect(dlg, SIGNAL(closing(bool)), this, SLOT(onConfigDone(bool)));
|
||||
|
||||
// Wait for configuration to be completed
|
||||
while (!m_configDone)
|
||||
delay(100);
|
||||
|
||||
// Read the value of port again if user has changed.
|
||||
bool newFixedPort = settings.value("FixedPort", false).toBool();
|
||||
quint16 newPort = settings.value("PortNumber").toUInt();
|
||||
|
||||
// User hasn't changed the value of fixed port check box
|
||||
// only change the value of the port
|
||||
if (oldFixedPort == newFixedPort && newFixedPort && m_port != newPort)
|
||||
m_port = newPort;
|
||||
// User has selected the fixed port and it's old value is random port,
|
||||
// so port needs to be updated.
|
||||
else if (oldFixedPort != newFixedPort && newFixedPort)
|
||||
m_port = newPort;
|
||||
// User has deselect the fixed port and it's old value is fixed port,
|
||||
// so we will have to get the random port
|
||||
else if (oldFixedPort != newFixedPort && !newFixedPort)
|
||||
m_port = getPort();
|
||||
}
|
||||
|
||||
// Server startup loop
|
||||
Server * Runtime::startServerLoop(QString key)
|
||||
{
|
||||
bool done = false;
|
||||
Server *server;
|
||||
|
||||
// Get the port number to use
|
||||
m_port = getPort();
|
||||
|
||||
while (!done)
|
||||
{
|
||||
server = startServer(key);
|
||||
if (server == NULL)
|
||||
{
|
||||
Logger::ReleaseLogger();
|
||||
QApplication::quit();
|
||||
}
|
||||
|
||||
// Check for server startup errors
|
||||
if (server->isFinished() || server->getError().length() > 0)
|
||||
{
|
||||
QString error = QString(QWidget::tr("An error occurred initialising the pgAdmin 4 server:\n\n%1")).arg(server->getError());
|
||||
|
||||
delete server;
|
||||
|
||||
// Enable the View Log option for diagnostics
|
||||
if (m_floatingWindow)
|
||||
m_floatingWindow->enableViewLogOption();
|
||||
if (m_trayIcon)
|
||||
m_trayIcon->enableViewLogOption();
|
||||
|
||||
// Open the configuration window
|
||||
openConfigureWindow(error);
|
||||
|
||||
// Disable the View Log option again
|
||||
if (m_floatingWindow)
|
||||
m_floatingWindow->disableViewLogOption();
|
||||
if (m_trayIcon)
|
||||
m_trayIcon->disableViewLogOption();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Startup appears successful
|
||||
done = true;
|
||||
}
|
||||
}
|
||||
|
||||
return server;
|
||||
}
|
||||
|
||||
|
||||
// Slot called when re-configuration is done.
|
||||
void Runtime::onConfigDone(bool accepted)
|
||||
{
|
||||
if (accepted)
|
||||
m_configDone = true;
|
||||
else
|
||||
exit(0);
|
||||
}
|
||||
|
||||
|
||||
// Start the server
|
||||
Server * Runtime::startServer(QString key)
|
||||
{
|
||||
Server *server;
|
||||
|
||||
m_splash->showMessage(QString(QWidget::tr("Starting pgAdmin4 server...")), Qt::AlignBottom | Qt::AlignCenter);
|
||||
Logger::GetLogger()->Log("Starting pgAdmin4 server...");
|
||||
|
||||
QString msg = QString(QWidget::tr("Creating server object, port:%1, key:%2, logfile:%3")).arg(m_port).arg(key).arg(g_serverLogFile);
|
||||
Logger::GetLogger()->Log(msg);
|
||||
server = new Server(this, m_port, key, g_serverLogFile);
|
||||
|
||||
Logger::GetLogger()->Log("Initializing server...");
|
||||
if (!server->Init())
|
||||
{
|
||||
m_splash->finish(Q_NULLPTR);
|
||||
|
||||
qDebug() << server->getError();
|
||||
|
||||
QString error = QString(QWidget::tr("An error occurred initialising the pgAdmin 4 server:\n\n%1")).arg(server->getError());
|
||||
QMessageBox::critical(Q_NULLPTR, QString(QWidget::tr("Fatal Error")), error);
|
||||
|
||||
Logger::GetLogger()->Log(error);
|
||||
Logger::ReleaseLogger();
|
||||
|
||||
exit(1);
|
||||
}
|
||||
|
||||
Logger::GetLogger()->Log("Server initialized, starting server thread...");
|
||||
server->start();
|
||||
|
||||
// This is a hack to give the server a chance to start and potentially fail. As
|
||||
// the Python interpreter is a synchronous call, we can't check for proper startup
|
||||
// easily in a more robust way - we have to rely on a clean startup not returning.
|
||||
// It should always fail pretty quickly, and take longer to start if it succeeds, so
|
||||
// we don't really get a visible delay here.
|
||||
delay(1000);
|
||||
|
||||
return server;
|
||||
}
|
||||
|
||||
|
||||
// Check the server is running properly
|
||||
void Runtime::checkServer(QString url)
|
||||
{
|
||||
// Read the server connection timeout from the registry or set the default timeout.
|
||||
QSettings settings;
|
||||
int timeout = settings.value("ConnectionTimeout", 90).toInt();
|
||||
|
||||
// Now the server should be up, we'll attempt to connect and get a response.
|
||||
// We'll retry in a loop a few time before aborting if necessary.
|
||||
|
||||
QTime endTime = QTime::currentTime().addSecs(timeout);
|
||||
QTime midTime1 = QTime::currentTime().addSecs(timeout/3);
|
||||
QTime midTime2 = QTime::currentTime().addSecs(timeout*2/3);
|
||||
bool alive = false;
|
||||
bool enableOptions = false;
|
||||
|
||||
Logger::GetLogger()->Log("The server should be up. Attempting to connect and get a response.");
|
||||
while(QTime::currentTime() <= endTime)
|
||||
{
|
||||
alive = pingServer(QUrl(url));
|
||||
|
||||
if (alive)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if(QTime::currentTime() >= midTime1)
|
||||
{
|
||||
if (m_floatingWindow && !enableOptions)
|
||||
{
|
||||
m_floatingWindow->enableViewLogOption();
|
||||
m_floatingWindow->enableConfigOption();
|
||||
enableOptions = true;
|
||||
}
|
||||
|
||||
if (m_trayIcon && !enableOptions)
|
||||
{
|
||||
m_trayIcon->enableViewLogOption();
|
||||
m_trayIcon->enableConfigOption();
|
||||
enableOptions = true;
|
||||
}
|
||||
|
||||
if(QTime::currentTime() < midTime2) {
|
||||
m_splash->showMessage(QString(QWidget::tr("Taking longer than usual...")), Qt::AlignBottom | Qt::AlignCenter);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_splash->showMessage(QString(QWidget::tr("Almost there...")), Qt::AlignBottom | Qt::AlignCenter);
|
||||
}
|
||||
}
|
||||
|
||||
delay(200);
|
||||
}
|
||||
|
||||
// Attempt to connect one more time in case of a long network timeout while looping
|
||||
Logger::GetLogger()->Log("Attempt to connect one more time in case of a long network timeout while looping");
|
||||
if (!alive && !pingServer(QUrl(url)))
|
||||
{
|
||||
m_splash->finish(Q_NULLPTR);
|
||||
QString error(QWidget::tr("The pgAdmin 4 server could not be contacted."));
|
||||
QMessageBox::critical(Q_NULLPTR, QString(QWidget::tr("Fatal Error")), error);
|
||||
|
||||
Logger::ReleaseLogger();
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Create the address file
|
||||
void Runtime::createAddressFile(QString url) const
|
||||
{
|
||||
QFile addressFile(g_addressFile);
|
||||
if (addressFile.open(QIODevice::WriteOnly))
|
||||
{
|
||||
addressFile.setPermissions(QFile::ReadOwner|QFile::WriteOwner);
|
||||
QTextStream out(&addressFile);
|
||||
out << url << endl;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Open a browser tab
|
||||
void Runtime::openBrowserTab(QString url) const
|
||||
{
|
||||
QSettings settings;
|
||||
QString cmd = settings.value("BrowserCommand").toString();
|
||||
|
||||
if (!cmd.isEmpty())
|
||||
{
|
||||
cmd.replace("%URL%", url);
|
||||
QProcess::startDetached(cmd);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!QDesktopServices::openUrl(url))
|
||||
{
|
||||
QString error(QWidget::tr("Failed to open the system default web browser. Is one installed?."));
|
||||
QMessageBox::critical(Q_NULLPTR, QString(QWidget::tr("Fatal Error")), error);
|
||||
|
||||
Logger::GetLogger()->Log(error);
|
||||
Logger::ReleaseLogger();
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Make a request to the Python API server
|
||||
QString Runtime::serverRequest(QUrl url, QString path)
|
||||
{
|
||||
QNetworkAccessManager manager;
|
||||
QEventLoop loop;
|
||||
QNetworkReply *reply;
|
||||
QVariant redirectUrl;
|
||||
|
||||
|
||||
url.setPath(path);
|
||||
QString requestUrl = url.toString();
|
||||
|
||||
do
|
||||
{
|
||||
reply = manager.get(QNetworkRequest(url));
|
||||
QObject::connect(reply, SIGNAL(finished()), &loop, SLOT(quit()));
|
||||
loop.exec();
|
||||
|
||||
redirectUrl = reply->attribute(QNetworkRequest::RedirectionTargetAttribute);
|
||||
url = redirectUrl.toUrl();
|
||||
|
||||
if (!redirectUrl.isNull())
|
||||
delete reply;
|
||||
|
||||
} while (!redirectUrl.isNull());
|
||||
|
||||
if (reply->error() != QNetworkReply::NoError)
|
||||
{
|
||||
qDebug() << "Failed to connect to the server:" << reply->errorString() << "- request URL:" << requestUrl << ".";
|
||||
return QString();
|
||||
}
|
||||
|
||||
QString response = reply->readAll();
|
||||
qDebug() << "Server response:" << response << "- request URL:" << requestUrl << ".";
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
|
||||
// Ping the application server to see if it's alive
|
||||
bool Runtime::pingServer(QUrl url)
|
||||
{
|
||||
return serverRequest(url, "/misc/ping") == "PING";
|
||||
}
|
||||
|
||||
|
||||
// Shutdown the application server
|
||||
bool Runtime::shutdownServer(QUrl url)
|
||||
{
|
||||
return serverRequest(url, "/misc/shutdown") == "SHUTDOWN";
|
||||
}
|
||||
|
||||
|
||||
void Runtime::delay(int milliseconds) const
|
||||
{
|
||||
QTime endTime = QTime::currentTime().addMSecs(milliseconds);
|
||||
while(QTime::currentTime() < endTime)
|
||||
{
|
||||
QCoreApplication::processEvents(QEventLoop::AllEvents, 100);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,69 +0,0 @@
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// pgAdmin 4 - PostgreSQL Tools
|
||||
//
|
||||
// Copyright (C) 2013 - 2021, The pgAdmin Development Team
|
||||
// This software is released under the PostgreSQL Licence
|
||||
//
|
||||
// Runtime.h - Core of the runtime
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef RUNTIME_H
|
||||
#define RUNTIME_H
|
||||
|
||||
// Include the Python header here as it needs to appear before any QT
|
||||
// headers anywhere in the app.
|
||||
#ifdef __MINGW32__
|
||||
#include <cmath>
|
||||
#endif
|
||||
#include <Python.h>
|
||||
|
||||
#include "TrayIcon.h"
|
||||
#include "MenuActions.h"
|
||||
#include "FloatingWindow.h"
|
||||
|
||||
// QT headers
|
||||
#include <QtWidgets>
|
||||
|
||||
class Server;
|
||||
|
||||
class Runtime: public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
Runtime();
|
||||
|
||||
bool alreadyRunning();
|
||||
bool go(int argc, char *argv[]);
|
||||
void delay(int milliseconds) const;
|
||||
bool shutdownServer(QUrl url);
|
||||
|
||||
private:
|
||||
QSharedMemory *m_shmem;
|
||||
bool m_configDone;
|
||||
FloatingWindow *m_floatingWindow = Q_NULLPTR;
|
||||
TrayIcon *m_trayIcon = Q_NULLPTR;
|
||||
QSplashScreen *m_splash = Q_NULLPTR;
|
||||
quint16 m_port = 0;
|
||||
|
||||
void setupStyling(QApplication *app) const;
|
||||
void configureProxy() const;
|
||||
QSplashScreen *displaySplash(QApplication *app);
|
||||
quint16 getPort() const;
|
||||
TrayIcon *createTrayIcon(MenuActions *menuActions);
|
||||
FloatingWindow *createFloatingWindow(MenuActions *menuActions);
|
||||
Server *startServerLoop(QString key);
|
||||
Server *startServer(QString key);
|
||||
void checkServer(QString url);
|
||||
void createAddressFile(QString url) const;
|
||||
void openBrowserTab(QString url) const;
|
||||
QString serverRequest(QUrl url, QString path);
|
||||
bool pingServer(QUrl url);
|
||||
void openConfigureWindow(const QString errorMsg);
|
||||
|
||||
private slots:
|
||||
void onConfigDone(bool accepted);
|
||||
};
|
||||
|
||||
#endif // RUNTIME_H
|
||||
@@ -1,363 +0,0 @@
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// pgAdmin 4 - PostgreSQL Tools
|
||||
//
|
||||
// Copyright (C) 2013 - 2021, The pgAdmin Development Team
|
||||
// This software is released under the PostgreSQL Licence
|
||||
//
|
||||
// Server.cpp - Thread in which the web server will run.
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "pgAdmin4.h"
|
||||
|
||||
// Must be before QT
|
||||
#include <Python.h>
|
||||
|
||||
#include "Server.h"
|
||||
#include "Logger.h"
|
||||
|
||||
// QT headers
|
||||
#include <QCoreApplication>
|
||||
#include <QDebug>
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QMessageBox>
|
||||
#include <QSettings>
|
||||
|
||||
|
||||
static void add_to_path(QString &python_path, QString path, bool prepend=false)
|
||||
{
|
||||
if (!python_path.contains(path))
|
||||
{
|
||||
if (!prepend)
|
||||
{
|
||||
#if defined(Q_OS_WIN)
|
||||
if (!python_path.isEmpty() && !python_path.endsWith(";"))
|
||||
python_path.append(";");
|
||||
#else
|
||||
if (!python_path.isEmpty() && !python_path.endsWith(":"))
|
||||
python_path.append(":");
|
||||
#endif
|
||||
|
||||
python_path.append(path);
|
||||
}
|
||||
else
|
||||
{
|
||||
#if defined(Q_OS_WIN)
|
||||
if (!python_path.isEmpty() && !python_path.startsWith(";"))
|
||||
python_path.prepend(";");
|
||||
#else
|
||||
if (!python_path.isEmpty() && !python_path.startsWith(":"))
|
||||
python_path.prepend(":");
|
||||
#endif
|
||||
|
||||
python_path.prepend(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Server::Server(Runtime *runtime, quint16 port, QString key, QString logFileName):
|
||||
m_runtime(runtime),
|
||||
m_port(port),
|
||||
m_key(key),
|
||||
m_logFileName(logFileName)
|
||||
{
|
||||
// Initialise Python
|
||||
Py_NoSiteFlag=1;
|
||||
Py_NoUserSiteDirectory=1;
|
||||
Py_DontWriteBytecodeFlag=1;
|
||||
|
||||
// Python3 requires conversion of char * to wchar_t *, so...
|
||||
const char *appName = QString("pgAdmin 4").toUtf8();
|
||||
const size_t cSize = strlen(appName)+1;
|
||||
m_wcAppName = new wchar_t[cSize];
|
||||
mbstowcs (m_wcAppName, appName, cSize);
|
||||
Py_SetProgramName(m_wcAppName);
|
||||
|
||||
// Setup the search path
|
||||
QSettings settings;
|
||||
QString python_path = settings.value("PythonPath").toString();
|
||||
|
||||
// Get the application directory
|
||||
QString app_dir = QCoreApplication::applicationDirPath();
|
||||
QString path_env = qgetenv("PATH");
|
||||
QString pythonHome;
|
||||
QStringList path_list;
|
||||
int i;
|
||||
|
||||
#ifdef Q_OS_MAC
|
||||
// In the case we're running in a release appbundle, we need to ensure the
|
||||
// bundled virtual env is included in the Python path. We include it at the
|
||||
// end, so expert users can override the path, but we do not save it, because
|
||||
// if users move the app bundle, we'll end up with dead entries
|
||||
|
||||
// Build (and canonicalise) the virtual environment path
|
||||
QFileInfo venvBinPath(app_dir + "/../Resources/venv/bin");
|
||||
QFileInfo venvLibPath(app_dir + "/../Resources/venv/lib/python");
|
||||
QFileInfo venvDynLibPath(app_dir + "/../Resources/venv/lib/python/lib-dynload");
|
||||
QFileInfo venvSitePackagesPath(app_dir + "/../Resources/venv/lib/python/site-packages");
|
||||
QFileInfo venvPath(app_dir + "/../Resources/venv");
|
||||
|
||||
// Prepend the bin directory to the path
|
||||
add_to_path(path_env, venvBinPath.canonicalFilePath(), true);
|
||||
// Append the path, if it's not already there
|
||||
add_to_path(python_path, venvLibPath.canonicalFilePath());
|
||||
add_to_path(python_path, venvDynLibPath.canonicalFilePath());
|
||||
add_to_path(python_path, venvSitePackagesPath.canonicalFilePath());
|
||||
add_to_path(pythonHome, venvPath.canonicalFilePath());
|
||||
#elif defined(Q_OS_WIN)
|
||||
|
||||
// In the case we're running in a release application, we need to ensure the
|
||||
// bundled virtual env is included in the Python path. We include it at the
|
||||
// end, so expert users can override the path, but we do not save it.
|
||||
|
||||
// Build (and canonicalise) the virtual environment path
|
||||
QFileInfo venvBinPath(app_dir + "/../venv");
|
||||
QFileInfo venvLibPath(app_dir + "/../venv/Lib");
|
||||
QFileInfo venvDLLsPath(app_dir + "/../venv/DLLs");
|
||||
QFileInfo venvSitePackagesPath(app_dir + "/../venv/Lib/site-packages");
|
||||
QFileInfo venvPath(app_dir + "/../venv");
|
||||
|
||||
// Prepend the bin directory to the path
|
||||
add_to_path(path_env, venvBinPath.canonicalFilePath(), true);
|
||||
// Append paths, if they're not already there
|
||||
add_to_path(python_path, venvLibPath.canonicalFilePath());
|
||||
add_to_path(python_path, venvDLLsPath.canonicalFilePath());
|
||||
add_to_path(python_path, venvSitePackagesPath.canonicalFilePath());
|
||||
add_to_path(pythonHome, venvPath.canonicalFilePath());
|
||||
#else
|
||||
// Build (and canonicalise) the virtual environment path
|
||||
QFileInfo venvBinPath(app_dir + "/../venv/bin");
|
||||
QFileInfo venvLibPath(app_dir + "/../venv/lib/python");
|
||||
QFileInfo venvDynLibPath(app_dir + "/../venv/lib/python/lib-dynload");
|
||||
QFileInfo venvSitePackagesPath(app_dir + "/../venv/lib/python/site-packages");
|
||||
QFileInfo venvPath(app_dir + "/../venv");
|
||||
|
||||
// Prepend the bin directory to the path
|
||||
add_to_path(path_env, venvBinPath.canonicalFilePath(), true);
|
||||
// Append the path, if it's not already there
|
||||
add_to_path(python_path, venvLibPath.canonicalFilePath());
|
||||
add_to_path(python_path, venvDynLibPath.canonicalFilePath());
|
||||
add_to_path(python_path, venvSitePackagesPath.canonicalFilePath());
|
||||
add_to_path(pythonHome, venvPath.canonicalFilePath());
|
||||
#endif
|
||||
|
||||
qputenv("PATH", path_env.toUtf8().data());
|
||||
|
||||
if (python_path.length() > 0)
|
||||
{
|
||||
// Split the path setting into individual entries
|
||||
path_list = python_path.split(";", QString::SkipEmptyParts);
|
||||
python_path = QString();
|
||||
|
||||
// Add new additional path elements
|
||||
for (i = path_list.size() - 1; i >= 0 ; --i)
|
||||
{
|
||||
python_path.append(path_list.at(i));
|
||||
if (i > 0)
|
||||
{
|
||||
#if defined(Q_OS_WIN)
|
||||
python_path.append(";");
|
||||
#else
|
||||
python_path.append(":");
|
||||
#endif
|
||||
}
|
||||
}
|
||||
qputenv("PYTHONPATH", python_path.toUtf8().data());
|
||||
}
|
||||
|
||||
qDebug() << "Python path: " << python_path
|
||||
<< "\nPython Home: " << pythonHome;
|
||||
|
||||
Logger::GetLogger()->Log(QString("Python Path: %1").arg(python_path));
|
||||
Logger::GetLogger()->Log(QString("Python Home: %1").arg(pythonHome));
|
||||
|
||||
if (!pythonHome.isEmpty())
|
||||
{
|
||||
const char *python_home = pythonHome.toUtf8().data();
|
||||
const size_t home_size = strlen(python_home) + 1;
|
||||
m_wcPythonHome = new wchar_t[home_size];
|
||||
mbstowcs (m_wcPythonHome, python_home, home_size);
|
||||
|
||||
Py_SetPythonHome(m_wcPythonHome);
|
||||
}
|
||||
|
||||
Logger::GetLogger()->Log("Initializing Python...");
|
||||
Py_Initialize();
|
||||
Logger::GetLogger()->Log("Python initialized.");
|
||||
|
||||
// Get the current path
|
||||
PyObject* sysPath = PySys_GetObject(const_cast<char *>("path"));
|
||||
if (sysPath != Q_NULLPTR)
|
||||
{
|
||||
// Add new additional path elements
|
||||
Logger::GetLogger()->Log("Adding new additional path elements");
|
||||
for (i = path_list.size() - 1; i >= 0 ; --i)
|
||||
{
|
||||
PyList_Append(sysPath, PyUnicode_DecodeFSDefault(path_list.at(i).toUtf8().data()));
|
||||
}
|
||||
}
|
||||
else
|
||||
Logger::GetLogger()->Log("Unable to get the current path.");
|
||||
|
||||
// Redirect stderr
|
||||
Logger::GetLogger()->Log("Redirecting stderr...");
|
||||
PyObject *sys = PyImport_ImportModule("sys");
|
||||
if (sys != Q_NULLPTR)
|
||||
{
|
||||
PyObject *err = Q_NULLPTR;
|
||||
FILE *log = Q_NULLPTR;
|
||||
|
||||
#if defined(Q_OS_WIN)
|
||||
char *logFile = m_logFileName.toUtf8().data();
|
||||
size_t fileSize = strlen(logFile) + 1;
|
||||
wchar_t * wcLogFileName = new wchar_t[fileSize];
|
||||
mbstowcs (wcLogFileName, logFile, fileSize);
|
||||
|
||||
log = _wfopen(wcLogFileName, (wchar_t *)"w");
|
||||
#else
|
||||
log = fopen(m_logFileName.toUtf8().data(), const_cast<char *>("w"));
|
||||
#endif
|
||||
if (log != Q_NULLPTR)
|
||||
{
|
||||
int fd = fileno(log);
|
||||
err = PyFile_FromFd(fd, Q_NULLPTR, const_cast<char *>("w"), -1, Q_NULLPTR, Q_NULLPTR, Q_NULLPTR, 0);
|
||||
}
|
||||
else
|
||||
Logger::GetLogger()->Log(QString("Failed to open log file: %1").arg(m_logFileName));
|
||||
|
||||
#if defined(Q_OS_WIN)
|
||||
if (wcLogFileName != NULL)
|
||||
{
|
||||
delete wcLogFileName;
|
||||
wcLogFileName = NULL;
|
||||
}
|
||||
#endif
|
||||
QFile(m_logFileName).setPermissions(QFile::ReadOwner|QFile::WriteOwner);
|
||||
if (err != Q_NULLPTR)
|
||||
{
|
||||
PyObject_SetAttrString(sys, "stderr", err);
|
||||
Logger::GetLogger()->Log("stderr redirected successfully.");
|
||||
}
|
||||
else
|
||||
Logger::GetLogger()->Log(QString("Failed to get the file pointer of: %1 ").arg(m_logFileName));
|
||||
}
|
||||
else
|
||||
Logger::GetLogger()->Log("Failed to import 'sys' module.");
|
||||
}
|
||||
|
||||
Server::~Server()
|
||||
{
|
||||
if (m_wcAppName)
|
||||
delete m_wcAppName;
|
||||
|
||||
if (m_wcPythonHome)
|
||||
delete m_wcPythonHome;
|
||||
|
||||
// Shutdown Python
|
||||
Py_Finalize();
|
||||
}
|
||||
|
||||
bool Server::Init()
|
||||
{
|
||||
QSettings settings;
|
||||
|
||||
// Find the webapp
|
||||
QStringList paths;
|
||||
paths.append("../web/"); // Linux source tree
|
||||
paths.append("../../web/"); // Windows source tree
|
||||
paths.append("../../../../web/"); // Mac source tree (in a dev env)
|
||||
#ifdef Q_OS_MAC
|
||||
paths.append("../Resources/web/"); // Mac source tree (in a release app bundle)
|
||||
#endif
|
||||
paths.append(settings.value("ApplicationPath").toString()); // System configured value
|
||||
paths.append(""); // Should be last!
|
||||
|
||||
for (int i = 0; i < paths.size(); ++i)
|
||||
{
|
||||
QDir dir;
|
||||
|
||||
if (paths[i].startsWith('/'))
|
||||
dir.setPath(paths[i]);
|
||||
else
|
||||
dir.setPath(QCoreApplication::applicationDirPath() + "/" + paths[i]);
|
||||
|
||||
m_appfile = dir.canonicalPath() + "/pgAdmin4.py";
|
||||
|
||||
if (QFile::exists(m_appfile))
|
||||
{
|
||||
qDebug() << "Webapp path: " << m_appfile;
|
||||
Logger::GetLogger()->Log(QString("Webapp Path: %1").arg(m_appfile));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!QFile::exists(m_appfile))
|
||||
{
|
||||
Logger::GetLogger()->Log("Failed to locate pgAdmin4.py, terminating server thread.");
|
||||
setError(tr("Failed to locate pgAdmin4.py, terminating server thread."));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Server::run()
|
||||
{
|
||||
// Open the application code and run it.
|
||||
Logger::GetLogger()->Log("Open the application code and run it.");
|
||||
FILE *cp = fopen(m_appfile.toUtf8().data(), "r");
|
||||
if (!cp)
|
||||
{
|
||||
Logger::GetLogger()->Log(QString(tr("Failed to open the application file: %1, server thread exiting.")).arg(m_appfile));
|
||||
setError(QString(tr("Failed to open the application file: %1, server thread exiting.")).arg(m_appfile));
|
||||
return;
|
||||
}
|
||||
|
||||
// Set the port number and key, and force SERVER_MODE off.
|
||||
Logger::GetLogger()->Log("Set the port number, key and force SERVER_MODE off");
|
||||
PyRun_SimpleString(QString("PGADMIN_INT_PORT = %1").arg(m_port).toLatin1());
|
||||
PyRun_SimpleString(QString("PGADMIN_INT_KEY = '%1'").arg(m_key).toLatin1());
|
||||
PyRun_SimpleString(QString("SERVER_MODE = False").toLatin1());
|
||||
|
||||
// Run the app!
|
||||
QByteArray m_appfile_utf8 = m_appfile.toUtf8();
|
||||
|
||||
/*
|
||||
* Untrusted search path vulnerability in the PySys_SetArgv API function in Python 2.6 and earlier, and possibly later
|
||||
* versions, prepends an empty string to sys.path when the argv[0] argument does not contain a path separator,
|
||||
* which might allow local users to execute arbitrary code via a Trojan horse Python file in the current working directory.
|
||||
* Here we have to set arguments explicitly to python interpreter. Check more details in 'PySys_SetArgv' documentation.
|
||||
*/
|
||||
const char *appName = m_appfile_utf8.data();
|
||||
const size_t cSize = strlen(appName)+1;
|
||||
wchar_t* wcAppName = new wchar_t[cSize];
|
||||
mbstowcs (wcAppName, appName, cSize);
|
||||
wchar_t* n_argv[] = { wcAppName };
|
||||
PySys_SetArgv(1, n_argv);
|
||||
|
||||
Logger::GetLogger()->Log("PyRun_SimpleFile launching application server...");
|
||||
if (PyRun_SimpleFile(cp, m_appfile_utf8.data()) != 0)
|
||||
{
|
||||
Logger::GetLogger()->Log("Failed to launch the application server, server thread exiting.");
|
||||
setError(tr("Failed to launch the application server, server thread exiting."));
|
||||
}
|
||||
|
||||
fclose(cp);
|
||||
}
|
||||
|
||||
void Server::shutdown(QUrl url)
|
||||
{
|
||||
if (!m_runtime->shutdownServer(url))
|
||||
setError(tr("Failed to shut down application server thread."));
|
||||
|
||||
QThread::quit();
|
||||
QThread::wait();
|
||||
while(!this->isFinished())
|
||||
{
|
||||
Logger::GetLogger()->Log("Waiting for server to shut down.");
|
||||
m_runtime->delay(250);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// pgAdmin 4 - PostgreSQL Tools
|
||||
//
|
||||
// Copyright (C) 2013 - 2021, The pgAdmin Development Team
|
||||
// This software is released under the PostgreSQL Licence
|
||||
//
|
||||
// Server.h - Thread in which the web server will run.
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef SERVER_H
|
||||
#define SERVER_H
|
||||
|
||||
#include "Runtime.h"
|
||||
|
||||
#include <QThread>
|
||||
#include <QUrl>
|
||||
|
||||
class Server : public QThread
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
Server(Runtime *runtime, quint16 port, QString key, QString logFileName);
|
||||
~Server();
|
||||
|
||||
bool Init();
|
||||
QString getError() const { return m_error; }
|
||||
|
||||
public slots:
|
||||
void shutdown(QUrl url);
|
||||
|
||||
protected:
|
||||
void run();
|
||||
|
||||
private:
|
||||
void setError(QString error) { m_error = error; }
|
||||
|
||||
QString m_appfile;
|
||||
QString m_error;
|
||||
|
||||
Runtime *m_runtime;
|
||||
quint16 m_port;
|
||||
QString m_key;
|
||||
QString m_logFileName;
|
||||
|
||||
// Application name in UTF-8 for Python
|
||||
wchar_t *m_wcAppName = Q_NULLPTR;
|
||||
|
||||
// PythonHome for Python
|
||||
wchar_t *m_wcPythonHome = Q_NULLPTR;
|
||||
};
|
||||
|
||||
#endif // SERVER_H
|
||||
|
||||
@@ -1,143 +0,0 @@
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// pgAdmin 4 - PostgreSQL Tools
|
||||
//
|
||||
// Copyright (C) 2013 - 2021, The pgAdmin Development Team
|
||||
// This software is released under the PostgreSQL Licence
|
||||
//
|
||||
// TrayIcon.cpp - Manages the tray icon
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "pgAdmin4.h"
|
||||
#include "TrayIcon.h"
|
||||
|
||||
#include <QMenu>
|
||||
|
||||
|
||||
TrayIcon::TrayIcon()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
void TrayIcon::Init()
|
||||
{
|
||||
createTrayIcon();
|
||||
|
||||
if (m_trayIcon)
|
||||
m_trayIcon->show();
|
||||
}
|
||||
|
||||
|
||||
// Create the tray icon
|
||||
void TrayIcon::createTrayIcon()
|
||||
{
|
||||
createActions();
|
||||
|
||||
if (m_trayIconMenu)
|
||||
{
|
||||
delete m_trayIconMenu;
|
||||
m_trayIconMenu = Q_NULLPTR;
|
||||
}
|
||||
|
||||
m_trayIconMenu = new QMenu(this);
|
||||
m_trayIconMenu->addAction(m_newAction);
|
||||
m_trayIconMenu->addAction(m_copyUrlAction);
|
||||
m_trayIconMenu->addSeparator();
|
||||
m_trayIconMenu->addAction(m_configAction);
|
||||
m_trayIconMenu->addAction(m_logAction);
|
||||
m_trayIconMenu->addSeparator();
|
||||
m_trayIconMenu->addAction(m_quitAction);
|
||||
|
||||
if (!m_trayIcon)
|
||||
m_trayIcon = new QSystemTrayIcon(this);
|
||||
|
||||
m_trayIcon->setContextMenu(m_trayIconMenu);
|
||||
|
||||
// Setup the icon itself. For convenience, we'll also use it for the dialogue.
|
||||
#ifdef Q_OS_MAC
|
||||
QIcon icon(":pgAdmin4-mac.png");
|
||||
#else
|
||||
QIcon icon(":pgAdmin4.png");
|
||||
#endif
|
||||
|
||||
m_trayIcon->setIcon(icon);
|
||||
setWindowIcon(icon);
|
||||
}
|
||||
|
||||
|
||||
// Create the menu actions
|
||||
void TrayIcon::createActions()
|
||||
{
|
||||
m_newAction = new QAction(tr("&New pgAdmin 4 window..."), this);
|
||||
m_newAction->setEnabled(false);
|
||||
connect(m_newAction, SIGNAL(triggered()), m_menuActions, SLOT(onNew()));
|
||||
|
||||
m_copyUrlAction = new QAction(tr("&Copy server URL"), this);
|
||||
m_copyUrlAction->setEnabled(false);
|
||||
connect(m_copyUrlAction, SIGNAL(triggered()), m_menuActions, SLOT(onCopyUrl()));
|
||||
|
||||
m_configAction = new QAction(tr("&Configure..."), this);
|
||||
m_configAction->setEnabled(false);
|
||||
connect(m_configAction, SIGNAL(triggered()), m_menuActions, SLOT(onConfig()));
|
||||
|
||||
m_logAction = new QAction(tr("&View log..."), this);
|
||||
m_logAction->setEnabled(false);
|
||||
connect(m_logAction, SIGNAL(triggered()), m_menuActions, SLOT(onLog()));
|
||||
|
||||
m_quitAction = new QAction(tr("&Shut down server"), this);
|
||||
m_quitAction->setEnabled(false);
|
||||
connect(m_quitAction, SIGNAL(triggered()), m_menuActions, SLOT(onQuit()));
|
||||
}
|
||||
|
||||
|
||||
void TrayIcon::enablePostStartOptions()
|
||||
{
|
||||
if (m_newAction != Q_NULLPTR)
|
||||
m_newAction->setEnabled(true);
|
||||
|
||||
if (m_copyUrlAction != Q_NULLPTR)
|
||||
m_copyUrlAction->setEnabled(true);
|
||||
|
||||
if (m_configAction != Q_NULLPTR)
|
||||
m_configAction->setEnabled(true);
|
||||
|
||||
if (m_logAction != Q_NULLPTR)
|
||||
m_logAction->setEnabled(true);
|
||||
|
||||
if (m_quitAction != Q_NULLPTR)
|
||||
m_quitAction->setEnabled(true);
|
||||
}
|
||||
|
||||
// Enable the View Log option
|
||||
void TrayIcon::enableViewLogOption()
|
||||
{
|
||||
if (m_logAction != Q_NULLPTR)
|
||||
m_logAction->setEnabled(true);
|
||||
}
|
||||
|
||||
// Disable the View Log option
|
||||
void TrayIcon::disableViewLogOption()
|
||||
{
|
||||
if (m_logAction != Q_NULLPTR)
|
||||
m_logAction->setEnabled(false);
|
||||
}
|
||||
|
||||
// Enable the configure option
|
||||
void TrayIcon::enableConfigOption()
|
||||
{
|
||||
if (m_configAction != Q_NULLPTR)
|
||||
m_configAction->setEnabled(true);
|
||||
}
|
||||
|
||||
// Disable the configure option
|
||||
void TrayIcon::disableConfigOption()
|
||||
{
|
||||
if (m_configAction != Q_NULLPTR)
|
||||
m_configAction->setEnabled(false);
|
||||
}
|
||||
|
||||
void TrayIcon::setMenuActions(MenuActions * menuActions)
|
||||
{
|
||||
m_menuActions = menuActions;
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// pgAdmin 4 - PostgreSQL Tools
|
||||
//
|
||||
// Copyright (C) 2013 - 2021, The pgAdmin Development Team
|
||||
// This software is released under the PostgreSQL Licence
|
||||
//
|
||||
// TrayIcon.h - Manages the tray icon
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef TRAYICON_H
|
||||
#define TRAYICON_H
|
||||
|
||||
#include "MenuActions.h"
|
||||
|
||||
#include <QWidget>
|
||||
#include <QSystemTrayIcon>
|
||||
|
||||
class TrayIcon : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
TrayIcon();
|
||||
|
||||
void Init();
|
||||
void enablePostStartOptions();
|
||||
void enableViewLogOption();
|
||||
void disableViewLogOption();
|
||||
void enableConfigOption();
|
||||
void disableConfigOption();
|
||||
void setMenuActions(MenuActions * menuActions);
|
||||
|
||||
private:
|
||||
void createTrayIcon();
|
||||
void createActions();
|
||||
|
||||
QAction *m_newAction = Q_NULLPTR;
|
||||
QAction *m_copyUrlAction = Q_NULLPTR;
|
||||
QAction *m_configAction = Q_NULLPTR;
|
||||
QAction *m_logAction = Q_NULLPTR;
|
||||
QAction *m_quitAction = Q_NULLPTR;
|
||||
|
||||
QSystemTrayIcon *m_trayIcon = Q_NULLPTR;
|
||||
QMenu *m_trayIconMenu = Q_NULLPTR;
|
||||
|
||||
MenuActions *m_menuActions = Q_NULLPTR;
|
||||
};
|
||||
|
||||
#endif // TRAYICON_H
|
||||
|
Before Width: | Height: | Size: 97 KiB After Width: | Height: | Size: 97 KiB |
1
runtime/assets/welcome_logo.svg
Normal file
1
runtime/assets/welcome_logo.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 17 KiB |
4
runtime/dev_config.json.in
Normal file
4
runtime/dev_config.json.in
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"pythonPath": "C:/Python38/python.exe",
|
||||
"pgadminFile": "../web/pgAdmin4.py"
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user