Compare commits

..
38 Commits
Author SHA1 Message Date
Akshay Joshi b58f288eec Fixed fatal error while launching the pgAdmin4 3.5. Update the version of the Flask to 0.12.4 for release. Fixes #3730 2018-10-31 13:36:43 +05:30
Murtuza Zabuawala 140372a5cf Fix minor alert layout issue on IE 11. 2018-10-30 09:27:14 +00:00
Akshay Joshi d97431cb4f Bump version number for release. 2018-10-29 22:29:07 +05:30
Akshay Joshi 1e14b0d068 Update message catalogs 2018-10-29 22:21:40 +05:30
Dave Page 4ff0fcdd82 Update to latest Flask, to avoid a security issue. 2018-10-29 14:17:30 +00:00
Akshay Joshi 696fbfb8b9 Fix CREATE Script functionality for EDB-Wrapped functions. Fixes #3676 2018-10-29 11:54:11 +00:00
Dave Page be47390baf Fix the Docker build. 2018-10-29 11:01:47 +00:00
Akshay Joshi 0d5a6eeeff Ensure that utility existence check should work for schema and
other child objects while taking Backup/Restore. Fixes #3722
2018-10-25 18:55:21 +05:30
Akshay Joshi 6ddab02769 1) Fixed process watcher status message when user has stopped the process.
2) Saved the process state in sqlite database.
2018-10-25 17:03:34 +05:30
Aditya Toshniwal 091aa62084 Fixed background colour while replacing a file in File Manager dialog. 2018-10-24 17:53:06 +05:30
Akshay Joshi 2058cdf662 Fixed test cases for Utilities when default_binary_path is not provided in test_config file. 2018-10-22 13:22:35 +05:30
Akshay Joshi 6bc6bc7f60 Ensure that Utilities(Backup/Restore/Maintenence/Import-Export) should not be started
if binary path is wrong and also added 'Stop Process' button to cancel the process.
2018-10-22 12:35:53 +05:30
Akshay Joshi 370df47042 Fixed issue where session files are not deleted first time when pgAdmin4 server started,
it always wait for CHECK_SESSION_FILES_INTERVAL which is 24 hours by default.
2018-10-18 18:02:45 +05:30
Usman Muzaffar 9ae49cbf31 Test case fixes for server mode. 2018-10-18 09:39:09 +01:00
Harshal Dhumal 822d384746 Purge connections from the cache on logout. Fixes #3703 2018-10-17 12:42:41 +01:00
Harshal Dhumal ce31726af5 Fix connection garbage collector. Fixes #3700 2018-10-17 12:35:45 +01:00
Dave Page 494a8ac3fe Release note update 2018-10-17 12:30:01 +01:00
Akshay Joshi 50738db2b8 Fix syntax error when creating new pgAgent schedules with a start
date/time and exception. Fixes #3638

- Fixed syntax error.
- Update/Delete exceptions are not working, SQL is wrong.
- Wrong exception time is shown, it should shown in UTC as datatype of
  the column is timestamp with out timezone.
- Added test cases for Schedule and Steps of pgAgent Job.
2018-10-17 12:28:31 +01:00
Aditya Toshniwal 6fa6939f52 Additional feature test fixes:
1) Changes required for working with Bootstrap 4.
2) Change to fix the timeout exception when waiting for element (tested multiple times on multiple server, did not occur to me thereafter)
3) Removed reset layout after each test case. Instead, delete the layout entry from sqlite db file and do a plain refresh. This will save some time and will also remove dependency on reset layout menu.
4) Disables tree state saving when feature test run starts. Feature tests got confused with auto expanding tree.
2018-10-17 11:50:22 +01:00
Akshay Joshi ef0e53e8fc Fixed pep8 issue 2018-10-15 17:41:21 +05:30
Akshay Joshi fd2c2c4ff7 Fixed minor issue in SQL autocomplete. 2018-10-15 17:36:11 +05:30
Aditya Toshniwal d6c4911135 Fixed calender control issues with Bootstrap 4 2018-10-12 15:02:38 +05:30
Aditya Toshniwal 6991c06937 Additional Bootstrap 4 fixes:
- Calendar control
- Query Tool's Sort/Filter drop down
- Backform hidden controls
- Debugger toolbar
2018-10-11 13:23:59 +01:00
Usman Muzaffar 3f16a0c52c Log process exceptions using ASCII mode to avoid issues with string concatenation. 2018-10-11 13:13:08 +01:00
Aditya Toshniwal 4a6426d3e7 Doc updates after renaming SQL Editor section of the Preference panel. 2018-10-11 09:10:02 +01:00
Khushboo Vashi 235a75202d Fixed minor issue in saving tree state logic. 2018-10-11 11:22:24 +05:30
Dave Page 55cf0cf2f9 Fix message. 2018-10-10 13:30:55 +01:00
Aditya Toshniwal 0565f8dd78 Update to use Bootstrap 3 instead of Bootstrap 4. Fixes #3562 2018-10-10 12:43:26 +01:00
Dave Page bdf90e67a7 When an execution is cancelled in query tool, two different messages are encountered at different time. There is no exact pattern for them. So now, the test case will look for any one of following message in the output text box
i) canceling statement due to user request
ii) Execution cancelled!
2018-10-10 11:23:14 +01:00
Akshay Joshi 7144db7f5a Cleanup session files periodically. Fixes #3674 2018-10-09 11:34:13 +01:00
Murtuza Zabuawala c66840bc8e Fix some styling regressed in #2927. 2018-10-09 11:22:54 +01:00
Aditya Toshniwal 26ad6d4c5d Rename the 'SQL Editor' section of the Preferences to 'Query Tool' as it applies to the whole tool, not just the editor. Fixes #3660 2018-10-08 11:43:02 +01:00
Fred Escallier 87b64cf3c0 Update French translation. 2018-10-08 11:18:59 +01:00
Murtuza Zabuawala 0f1651686b Code refactoring:
- Move the duplicate code for closing the background process dialog into 'test_gui_helper.py',
- Simplify the logic by removing the duplicate steps to insert/update binary paths for each server type from 'test_utils.py'.
2018-10-08 11:09:30 +01:00
Khushboo Vashi 528ea88dec Save the treeview state periodically, and restore it automatically when reconnecting. Fixes #1253 2018-10-08 10:03:19 +01:00
Dave Page 44ef501283 Fix PEP-8 issue. 2018-10-03 10:12:00 +01:00
Usman Muzaffar eb27fa29b1 Test stabilisation, mostly for Windows. Fixes #3136 2018-10-03 10:03:35 +01:00
Identifier Anonymous a89ab88097 Updated Japanese translation. 2018-10-02 15:44:05 +01:00
220 changed files with 8779 additions and 5750 deletions

No files matched your search

+2
View File
@@ -113,6 +113,8 @@ When you’ve specified the details that will be incorporated into the pg_dump c
.. image:: images/backup_messages.png
:alt: Backup success notification popup
Use the **Stop Process** button to stop the Backup process.
If the backup is successful, a popup window will confirm success. Click *Click here for details* on the popup window to launch the *Process Watcher*. The *Process Watcher* logs all the activity associated with the backup and provides additional information for troubleshooting.
.. image:: images/backup_process_watcher.png
+2
View File
@@ -24,6 +24,8 @@ Click the *Backup* button to build and execute a command based on your selection
.. image:: images/backup_globals_messages.png
:alt: Backup globals success notification popup
Use the **Stop Process** button to stop the Backup process.
If the backup is successful, a popup window will confirm success. Click *Click here for details* on the popup window to launch the *Process Watcher*. The *Process Watcher* logs all the activity associated with the backup and provides additional information for troubleshooting.
.. image:: images/backup_globals_process_watcher.png
+2
View File
@@ -78,6 +78,8 @@ Click the *Backup* button to build and execute a command based on your selection
.. image:: images/backup_server_messages.png
:alt: Backup server success notification popup
Use the **Stop Process** button to stop the Backup process.
If the backup is successful, a popup window will confirm success. Click *Click here for details* on the popup window to launch the *Process Watcher*. The *Process Watcher* logs all the activity associated with the backup and provides additional information for troubleshooting.
.. image:: images/backup_server_process_watcher.png
Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 244 KiB

After

Width:  |  Height:  |  Size: 340 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 188 KiB

After

Width:  |  Height:  |  Size: 204 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 244 KiB

After

Width:  |  Height:  |  Size: 322 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 109 KiB

After

Width:  |  Height:  |  Size: 149 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 174 KiB

After

Width:  |  Height:  |  Size: 228 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 129 KiB

After

Width:  |  Height:  |  Size: 140 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

After

Width:  |  Height:  |  Size: 137 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 141 KiB

After

Width:  |  Height:  |  Size: 226 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

After

Width:  |  Height:  |  Size: 141 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 135 KiB

After

Width:  |  Height:  |  Size: 180 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 104 KiB

After

Width:  |  Height:  |  Size: 233 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

After

Width:  |  Height:  |  Size: 143 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 109 KiB

After

Width:  |  Height:  |  Size: 259 KiB

+2
View File
@@ -53,6 +53,8 @@ After completing the *Import/Export data* dialog, click the *OK* button to perfo
.. image:: images/import_export_complete.png
:alt: Import Export data completion notification
Use the **Stop Process** button to stop the Import/Export process.
Use the *Click here for details* link on the notification to open the *Process Watcher* and review detailed information about the execution of the command that performed the import or export:
.. image:: images/import_export_pw.png
+2
View File
@@ -34,6 +34,8 @@ pgAdmin will inform you when the background process completes:
.. image:: images/maintenance_complete.png
:alt: Maintenance completion notification
Use the **Stop Process** button to stop the Maintenance process.
Use the *Click here for details* link on the notification to open the *Process Watcher* and review detailed information about the execution of the command that performed the import or export:
.. image:: images/maintenance_pw.png
+6 -6
View File
@@ -127,9 +127,9 @@ Use the fields on the *Help* panel to specify the location of help files.
Please note: the default help paths include the *VERSION* placeholder; the $VERSION$ placeholder will be replaced by the current database version.
**The SQL Editor Node**
**The Query Tool Node**
Expand the *SQL Editor* node to access panels that allow you to specify your preferences for the SQL Editor tool.
Expand the *Query Tool* node to access panels that allow you to specify your preferences for the Query Editor tool.
.. image:: images/preferences_sql_auto_completion.png
:alt: Preferences dialog sqleditor auto completion option
@@ -150,13 +150,13 @@ Use the fields on the *CSV Output* panel to control the CSV output.
.. image:: images/preferences_sql_display.png
:alt: Preferences dialog sqleditor display options
Use the fields on the *Display* panel to specify your preferences for the SQL Editor display.
Use the fields on the *Display* panel to specify your preferences for the Query Tool display.
* When the *Connection status* switch is set to *True*, each new instance of the SQL Editor will display connection and transaction status.
* When the *Connection status* switch is set to *True*, each new instance of the Query Tool will display connection and transaction status.
* Use the *Connection status refresh rate* field to specify the number of seconds between connection/transaction status updates.
* When the *Open in new browser tab* switch is set to *True*, each new instance of the SQL Editor will open in a new browser tab.
* When the *Open in new browser tab* switch is set to *True*, each new instance of the Query Tool will open in a new browser tab.
* Use the *Query info notifier timeout* field to control the behaviour of the notifier that is displayed when query execution completes. A value of *-1* will disable the notifier, and a value of 0 will display it until clicked. If a positive value above zero is specified, the notifier will be displayed for the specified number of seconds. The default is *5*.
@@ -207,7 +207,7 @@ Use the fields on the *Results grid* panel to specify your formatting preference
* Use the *Result copy quote character* drop-down listbox to select the quote character for copied data.
* Use the *Result copy quoting* drop-down listbox to select which type of fields require quoting; select *All*, *None*, or *Strings*.
Use the fields on the *Keyboard shortcuts* panel to configure shortcuts for the sql editor window navigation:
Use the fields on the *Keyboard shortcuts* panel to configure shortcuts for the query tool window navigation:
.. image:: images/preferences_sql_keyboard_shortcuts.png
:alt: Preferences dialog sql keyboard shortcuts section
+1
View File
@@ -9,6 +9,7 @@ for it.
.. toctree::
release_notes_3_5
release_notes_3_4
release_notes_3_3
release_notes_3_2
+28
View File
@@ -0,0 +1,28 @@
***********
Version 3.5
***********
Release date: 2018-11-01
This release contains a number of features and fixes reported since the release of pgAdmin4 3.4
Features
********
| `Feature #1253 <https://redmine.postgresql.org/issues/1253>`_ - Save the treeview state periodically, and restore it automatically when reconnecting.
| `Feature #3562 <https://redmine.postgresql.org/issues/3562>`_ - Migrate from Bootstrap 3 to Bootstrap 4.
Bug fixes
*********
| `Bug #3232 <https://redmine.postgresql.org/issues/3232>`_ - Ensure that Utilities(Backup/Restore/Maintenence/Import-Export) should not be started if binary path is wrong and also added 'Stop Process' button to cancel the process.
| `Bug #3638 <https://redmine.postgresql.org/issues/3638>`_ - Fix syntax error when creating new pgAgent schedules with a start date/time and exception.
| `Bug #3674 <https://redmine.postgresql.org/issues/3674>`_ - Cleanup session files periodically.
| `Bug #3660 <https://redmine.postgresql.org/issues/3660>`_ - Rename the 'SQL Editor' section of the Preferences to 'Query Tool' as it applies to the whole tool, not just the editor.
| `Bug #3676 <https://redmine.postgresql.org/issues/3676>`_ - Fix CREATE Script functionality for EDB-Wrapped functions.
| `Bug #3700 <https://redmine.postgresql.org/issues/3700>`_ - Fix connection garbage collector.
| `Bug #3703 <https://redmine.postgresql.org/issues/3703>`_ - Purge connections from the cache on logout.
| `Bug #3722 <https://redmine.postgresql.org/issues/3722>`_ - Ensure that utility existence check should work for schema and other child objects while taking Backup/Restore.
| `Bug #3730 <https://redmine.postgresql.org/issues/3730>`_ - Fixed fatal error while launching the pgAdmin4 3.5. Update the version of the Flask to 0.12.4 for release.
+2
View File
@@ -84,6 +84,8 @@ When you’ve specified the details that will be incorporated into the pg_restor
.. image:: images/restore_messages.png
:alt: Restore dialog notifications
Use the **Stop Process** button to stop the Restore process.
Click *Click here for details* on the popup to launch the *Process Watcher*. The *Process Watcher* logs all the activity associated with the restore, and provides additional information for troubleshooting should the restore command encounter problems.
.. image:: images/restore_process_watcher.png
+2 -1
View File
@@ -45,7 +45,8 @@ ENV PYTHONPATH=/pgadmin4
# so that deps do not increase the size of resulting image by remaining in layers
COPY ./pgadmin4/requirements.txt /pgadmin4
RUN set -ex && \
apk add --no-cache --virtual build-deps build-base postgresql-dev libffi-dev && \
apk add --no-cache --virtual build-deps build-base postgresql-dev libffi-dev linux-headers && \
pip install --upgrade pip && \
pip install --no-cache-dir -r requirements.txt && \
apk del --no-cache build-deps
+2 -1
View File
@@ -6,7 +6,7 @@
blinker==1.4
extras==1.0.0
fixtures==3.0.0
Flask==0.12.2
Flask==0.12.4
Flask-Gravatar==0.5.0
Flask-Login==0.3.2
Flask-Mail==0.9.1
@@ -28,6 +28,7 @@ speaklater==1.3
sqlparse==0.2.4
WTForms==2.1
Flask-Paranoid==0.2.0
psutil==5.4.7
################################################################
# Modules specifically requires for Python2.7 or greater version
+2 -2
View File
@@ -8,9 +8,9 @@
<key>CFBundleName</key>
<string>pgAdmin 4</string>
<key>CFBundleShortVersionString</key>
<string>3.4.0</string>
<string>3.5.0</string>
<key>CFBundleVersion</key>
<string>3.4.0</string>
<string>3.5.0</string>
<key>LSMinimumSystemVersion</key>
<string>10.10</string>
<key>NSHumanReadableCopyright</key>
+1 -1
View File
@@ -1,4 +1,4 @@
VERSION = 3.4.0.0
VERSION = 3.5.0.0
QMAKE_TARGET_COMPANY = "The pgAdmin Development Team"
QMAKE_TARGET_PRODUCT = "pgAdmin 4"
QMAKE_TARGET_DESCRIPTION = "pgAdmin 4 Desktop Runtime"
+13 -2
View File
@@ -50,7 +50,7 @@ APP_ICON = 'pg-icon'
# Application version number components
APP_RELEASE = 3
APP_REVISION = 4
APP_REVISION = 5
# Application version suffix, e.g. 'beta1', 'dev'. Usually an empty string
# for GA releases.
@@ -59,7 +59,7 @@ APP_SUFFIX = ''
# Numeric application version for upgrade checks. Should be in the format:
# [X]XYYZZ, where X is the release version, Y is the revision, with a leading
# zero if needed, and Z represents the suffix, with a leading zero if needed
APP_VERSION_INT = 30400
APP_VERSION_INT = 30500
# DO NOT CHANGE!
# The application version string, constructed from the components
@@ -387,6 +387,17 @@ SESSION_SKIP_PATHS = [
'/misc/ping'
]
##########################################################################
# Session expiration support
##########################################################################
# SESSION_EXPIRATION_TIME is the interval in Days. Session will be
# expire after the specified number of *days*.
SESSION_EXPIRATION_TIME = 1
# CHECK_SESSION_FILES_INTERVAL is interval in Hours. Application will check
# the session files for cleanup after specified number of *hours*.
CHECK_SESSION_FILES_INTERVAL = 24
##########################################################################
# SSH Tunneling supports only for Python 2.7 and 3.4+
##########################################################################
+33
View File
@@ -0,0 +1,33 @@
##########################################################################
#
# pgAdmin 4 - PostgreSQL Tools
#
# Copyright (C) 2013 - 2018, The pgAdmin Development Team
# This software is released under the PostgreSQL Licence
#
##########################################################################
"""Added state of the utility process
Revision ID: b5b87fdfcb30
Revises: ece2e76bf60e
Create Date: 2018-10-24 12:37:59.487969
"""
from pgadmin.model import db
# revision identifiers, used by Alembic.
revision = 'b5b87fdfcb30'
down_revision = 'ece2e76bf60e'
branch_labels = None
depends_on = None
def upgrade():
db.engine.execute(
'ALTER TABLE process ADD COLUMN process_state INTEGER DEFAULT 0'
)
def downgrade():
pass
+34
View File
@@ -0,0 +1,34 @@
##########################################################################
#
# pgAdmin 4 - PostgreSQL Tools
#
# Copyright (C) 2013 - 2018, The pgAdmin Development Team
# This software is released under the PostgreSQL Licence
#
##########################################################################
""" Added utility pid to stop process
Revision ID: ece2e76bf60e
Revises: ca00ec32581b
Create Date: 2018-10-18 14:45:13.483068
"""
from pgadmin.model import db
# revision identifiers, used by Alembic.
revision = 'ece2e76bf60e'
down_revision = 'ca00ec32581b'
branch_labels = None
depends_on = None
def upgrade():
db.engine.execute(
'ALTER TABLE process ADD COLUMN utility_pid INTEGER'
)
def downgrade():
pass
+5 -2
View File
@@ -33,6 +33,7 @@
"karma-webpack": "~2.0.13",
"node-sass": "^4.5.3",
"optimize-css-assets-webpack-plugin": "^2.0.0",
"popper.js": "^1.14.4",
"raw-loader": "^0.5.1",
"sass-loader": "^7.1.0",
"sass-resources-loader": "^1.3.3",
@@ -56,7 +57,7 @@
"backgrid-select-all": "^0.3.5",
"backgrid-sizeable-columns": "^0.1.1",
"bignumber.js": "^6.0.0",
"bootstrap": "^3.3.7",
"bootstrap": "^4.1.3",
"bootstrap-datepicker": "^1.7.0",
"bootstrap-switch": "3.3.4",
"bowser": "1.6.1",
@@ -65,7 +66,6 @@
"css-loader": "0.14.0",
"cssnano": "^3.10.0",
"dropzone": "^5.1.1",
"eonasdan-bootstrap-datetimepicker": "^4.17.47",
"exports-loader": "~0.6.4",
"flotr2": "git+https://github.com/EnterpriseDB/Flotr2.git",
"font-awesome": "^4.7.0",
@@ -78,6 +78,7 @@
"jquery-ui": "^1.12.1",
"leaflet": "^1.3.3",
"moment": "^2.20.1",
"moment-timezone": "^0.5.21",
"mousetrap": "^1.6.1",
"prop-types": "^15.5.10",
"react": "^16.2.0",
@@ -91,6 +92,8 @@
"spectrum-colorpicker": "^1.8.0",
"sprintf-js": "^1.1.1",
"tablesorter": "^2.30.6",
"tempusdominus-bootstrap-4": "^5.1.2",
"tempusdominus-core": "^5.0.3",
"underscore": "^1.8.3",
"underscore.string": "^3.3.4",
"watchify": "~3.9.0",
+12
View File
@@ -32,6 +32,7 @@ from pgadmin.utils import PgAdminModule, driver
from pgadmin.utils.preferences import Preferences
from pgadmin.utils.session import create_session_interface, pga_unauthorised
from pgadmin.utils.versioned_template_loader import VersionedTemplateLoader
from datetime import timedelta
# If script is running under python3, it will not have the xrange function
# defined
@@ -354,6 +355,10 @@ def create_app(app_name=None):
# register custom unauthorised handler.
app.login_manager.unauthorized_handler(pga_unauthorised)
# Set the permanent session lifetime to the specified value in config file.
app.permanent_session_lifetime = timedelta(
days=config.SESSION_EXPIRATION_TIME)
app.session_interface = create_session_interface(
app, config.SESSION_SKIP_PATHS
)
@@ -545,6 +550,13 @@ def create_app(app_name=None):
def force_session_write(app, user):
session.force_write = True
@user_logged_out.connect_via(app)
def clear_current_user_connections(app, user):
from config import PG_DEFAULT_DRIVER
from pgadmin.utils.driver import get_driver
_driver = get_driver(PG_DEFAULT_DRIVER)
_driver.gc_own()
##########################################################################
# Load plugin modules
##########################################################################
+1 -1
View File
@@ -24,7 +24,7 @@
<div class="col-sm-9">{{ info.current_user }}</div>
</div>
<div class="row">
<div class="col-xs-12 text-right"><img
<div class="col-12 text-right"><img
src="{{ url_for('static', filename='img/logo-right-128.png') }}"
alt="{{ config.APP_NAME }} {{ _('logo') }}"
></div>
@@ -28,6 +28,16 @@ def register_browser_preferences(self):
True, category_label=gettext('Display')
)
self.preference.register(
'display', 'browser_tree_state_save_interval',
gettext("Browser tree state saving interval"), 'integer',
30, category_label=gettext('Display'),
help_str=gettext(
'Browser tree state saving interval in seconds.'
'Use -1 to disable the tree saving mechanism.'
)
)
self.table_row_count_threshold = self.preference.register(
'properties', 'table_row_count_threshold',
gettext("Count rows if estimated less than"), 'integer', 2000,
@@ -585,7 +585,8 @@ class ServerNode(PGChildNodeView):
server_icon_and_background(connected, manager, server),
True,
self.node_type,
connected=False,
connected=connected,
user=manager.user_info if connected else None,
server_type='pg' # default server type
)
)
@@ -197,12 +197,12 @@ define('pgadmin.node.fts_configuration', [
'<div class="subnode-header-form">',
' <div class="container-fluid">',
' <div class="row">',
' <div class="col-xs-3">',
' <div class="col-3">',
' <label class="control-label"><%-token_label%></label>',
' </div>',
' <div class="col-xs-6" header="token"></div>',
' <div class="col-xs-2">',
' <button class="btn-sm btn-default add fa fa-plus" <%=canAdd ? "" : "disabled=\'disabled\'"%> ></button>',
' <div class="col-6" header="token"></div>',
' <div class="col-2">',
' <button class="btn btn-sm-sq btn-default add fa fa-plus" <%=canAdd ? "" : "disabled=\'disabled\'"%> ></button>',
' </div>',
' </div>',
' </div>',
@@ -257,7 +257,7 @@ define('pgadmin.node.fts_configuration', [
var self = this,
titleTmpl = _.template('<div class=\'subnode-header\'></div>'),
$gridBody = $('<div></div>', {
class:'pgadmin-control-group backgrid form-group col-xs-12 object subnode',
class:'pgadmin-control-group backgrid form-group col-12 object subnode',
}).append(
titleTmpl({label: data.label})
);
@@ -29,9 +29,7 @@ CREATE{% if query_type is defined %}{{' OR REPLACE'}}{% endif %} FUNCTION {{ con
AS {% if data.lanname == 'c' %}
{{ data.probin|qtLiteral }}, {{ data.prosrc_c|qtLiteral }}
{% else %}
$BODY$
{{ data.prosrc }}
$BODY${% endif %};
$BODY${{ data.prosrc }}$BODY${% endif %};
{% if data.funcowner %}
ALTER FUNCTION {{ conn|qtIdent(data.pronamespace, data.name) }}({{data.func_args_without}})
@@ -36,9 +36,7 @@ CREATE{% if query_type is defined %}{{' OR REPLACE'}}{% endif %} FUNCTION {{ con
AS {% if data.lanname == 'c' %}
{{ data.probin|qtLiteral }}, {{ data.prosrc_c|qtLiteral }}
{% else %}
$BODY$
{{ data.prosrc }}
$BODY${% endif -%};
$BODY${{ data.prosrc }}$BODY${% endif -%};
{% if data.funcowner %}
ALTER FUNCTION {{ conn|qtIdent(data.pronamespace, data.name) }}({{data.func_args_without}})
@@ -33,9 +33,7 @@ CREATE{% if query_type is defined %}{{' OR REPLACE'}}{% endif %} FUNCTION {{ con
AS {% if data.lanname == 'c' %}
{{ data.probin|qtLiteral }}, {{ data.prosrc_c|qtLiteral }}
{% else %}
$BODY$
{{ data.prosrc }}
$BODY${% endif -%};
$BODY${{ data.prosrc }}$BODY${% endif -%};
{% if data.funcowner %}
ALTER FUNCTION {{ conn|qtIdent(data.pronamespace, data.name) }}({{data.func_args_without}})
@@ -33,9 +33,7 @@ CREATE{% if query_type is defined %}{{' OR REPLACE'}}{% endif %} FUNCTION {{ con
AS {% if data.lanname == 'c' %}
{{ data.probin|qtLiteral }}, {{ data.prosrc_c|qtLiteral }}
{% else %}
$BODY$
{{ data.prosrc }}
$BODY${% endif -%};
$BODY${{ data.prosrc }}$BODY${% endif -%};
{% if data.funcowner %}
ALTER FUNCTION {{ conn|qtIdent(data.pronamespace, data.name) }}({{data.func_args_without}})
@@ -36,9 +36,7 @@ CREATE{% if query_type is defined %}{{' OR REPLACE'}}{% endif %} FUNCTION {{ con
AS {% if data.lanname == 'c' %}
{{ data.probin|qtLiteral }}, {{ data.prosrc_c|qtLiteral }}
{% else %}
$BODY$
{{ data.prosrc }}
$BODY${% endif -%};
$BODY${{ data.prosrc }}$BODY${% endif -%};
{% if data.funcowner %}
ALTER FUNCTION {{ conn|qtIdent(data.pronamespace, data.name) }}({{data.func_args_without}})
@@ -29,9 +29,7 @@ CREATE{% if query_type is defined %}{{' OR REPLACE'}}{% endif %} FUNCTION {{ con
AS {% if data.lanname == 'c' %}
{{ data.probin|qtLiteral }}, {{ data.prosrc_c|qtLiteral }}
{% else %}
$BODY$
{{ data.prosrc }}
$BODY${% endif %};
$BODY${{ data.prosrc }}$BODY${% endif %};
{% if data.funcowner %}
ALTER FUNCTION {{ conn|qtIdent(data.pronamespace, data.name) }}({{data.func_args_without}})
@@ -33,9 +33,7 @@ CREATE{% if query_type is defined %}{{' OR REPLACE'}}{% endif %} FUNCTION {{ con
AS {% if data.lanname == 'c' %}
{{ data.probin|qtLiteral }}, {{ data.prosrc_c|qtLiteral }}
{% else %}
$BODY$
{{ data.prosrc }}
$BODY${% endif -%};
$BODY${{ data.prosrc }}$BODY${% endif -%};
{% if data.funcowner %}
ALTER FUNCTION {{ conn|qtIdent(data.pronamespace, data.name) }}({{data.func_args_without}})
@@ -31,9 +31,7 @@ CREATE{% if query_type is defined %}{{' OR REPLACE'}}{% endif %} FUNCTION {{ con
AS {% if data.lanname == 'c' %}
{{ data.probin|qtLiteral }}, {{ data.prosrc_c|qtLiteral }}
{% else %}
$BODY$
{{ data.prosrc }}
$BODY${% endif -%};
$BODY${{ data.prosrc }}$BODY${% endif -%};
{% if data.funcowner %}
ALTER FUNCTION {{ conn|qtIdent(data.pronamespace, data.name) }}({{data.func_args_without}})
@@ -34,9 +34,7 @@ CREATE{% if query_type is defined %}{{' OR REPLACE'}}{% endif %} FUNCTION {{ con
AS {% if data.lanname == 'c' %}
{{ data.probin|qtLiteral }}, {{ data.prosrc_c|qtLiteral }}
{% else %}
$BODY$
{{ data.prosrc }}
$BODY${% endif -%};
$BODY${{ data.prosrc }}$BODY${% endif -%};
{% if data.funcowner %}
ALTER FUNCTION {{ conn|qtIdent(data.pronamespace, data.name) }}({{data.func_args_without}})
@@ -29,9 +29,7 @@ CREATE{% if query_type is defined %}{{' OR REPLACE'}}{% endif %} FUNCTION {{ con
AS {% if data.lanname == 'c' %}
{{ data.probin|qtLiteral }}, {{ data.prosrc_c|qtLiteral }}
{% else %}
$BODY$
{{ data.prosrc }}
$BODY${% endif %};
$BODY${{ data.prosrc }}$BODY${% endif %};
{% if data.funcowner %}
ALTER FUNCTION {{ conn|qtIdent(data.pronamespace, data.name) }}({{data.func_args_without}})
@@ -23,9 +23,7 @@ CREATE FUNCTION {{ conn|qtIdent(data.pronamespace, data.name) }}({% if data.proa
AS {% if data.lanname == 'c' %}
{{ data.probin|qtLiteral }}, {{ data.prosrc_c|qtLiteral }}
{% else %}
$BODY$
{{ data.prosrc }}
$BODY${% endif -%};
$BODY${{ data.prosrc }}$BODY${% endif -%};
{% if data.funcowner %}
ALTER FUNCTION {{ conn|qtIdent(data.pronamespace, data.name) }}({{data.func_args}})
@@ -23,9 +23,7 @@ CREATE FUNCTION {{ conn|qtIdent(data.pronamespace, data.name) }}()
AS {% if data.lanname == 'c' %}
{{ data.probin|qtLiteral }}, {{ data.prosrc_c|qtLiteral }}
{% else %}
$BODY$
{{ data.prosrc }}
$BODY${% endif -%};
$BODY${{ data.prosrc }}$BODY${% endif -%};
{% if data.funcowner %}
ALTER FUNCTION {{ conn|qtIdent(data.pronamespace, data.name) }}({{data.func_args}})
@@ -23,9 +23,7 @@ CREATE FUNCTION {{ conn|qtIdent(data.pronamespace, data.name) }}({% if data.proa
AS {% if data.lanname == 'c' %}
{{ data.probin|qtLiteral }}, {{ data.prosrc_c|qtLiteral }}
{% else %}
$BODY$
{{ data.prosrc }}
$BODY${% endif -%};
$BODY${{ data.prosrc }}$BODY${% endif -%};
{% if data.funcowner %}
ALTER FUNCTION {{ conn|qtIdent(data.pronamespace, data.name) }}({{data.func_args}})
@@ -20,9 +20,7 @@ CREATE FUNCTION {{ conn|qtIdent(data.pronamespace, data.name) }}()
AS {% if data.lanname == 'c' %}
{{ data.probin|qtLiteral }}, {{ data.prosrc_c|qtLiteral }}
{% else %}
$BODY$
{{ data.prosrc }}
$BODY${% endif %};
$BODY${{ data.prosrc }}$BODY${% endif %};
{% if data.funcowner %}
ALTER FUNCTION {{ conn|qtIdent(data.pronamespace, data.name) }}({{data.func_args}})
@@ -23,9 +23,7 @@ CREATE FUNCTION {{ conn|qtIdent(data.pronamespace, data.name) }}()
AS {% if data.lanname == 'c' %}
{{ data.probin|qtLiteral }}, {{ data.prosrc_c|qtLiteral }}
{% else %}
$BODY$
{{ data.prosrc }}
$BODY${% endif -%};
$BODY${{ data.prosrc }}$BODY${% endif -%};
{% if data.funcowner %}
ALTER FUNCTION {{ conn|qtIdent(data.pronamespace, data.name) }}({{data.func_args}})
@@ -23,9 +23,7 @@ CREATE FUNCTION {{ conn|qtIdent(data.pronamespace, data.name) }}()
AS {% if data.lanname == 'c' %}
{{ data.probin|qtLiteral }}, {{ data.prosrc_c|qtLiteral }}
{% else %}
$BODY$
{{ data.prosrc }}
$BODY${% endif -%};
$BODY${{ data.prosrc }}$BODY${% endif -%};
{% if data.funcowner %}
ALTER FUNCTION {{ conn|qtIdent(data.pronamespace, data.name) }}({{data.func_args}})
@@ -20,9 +20,7 @@ CREATE FUNCTION {{ conn|qtIdent(data.pronamespace, data.name) }}()
AS {% if data.lanname == 'c' %}
{{ data.probin|qtLiteral }}, {{ data.prosrc_c|qtLiteral }}
{% else %}
$BODY$
{{ data.prosrc }}
$BODY${% endif %};
$BODY${{ data.prosrc }}$BODY${% endif %};
{% if data.funcowner %}
ALTER FUNCTION {{ conn|qtIdent(data.pronamespace, data.name) }}({{data.func_args}})
@@ -64,7 +64,7 @@ define('pgadmin.node.schema', [
'<div class="subnode-header">',
' <label class="control-label col-sm-4"><%-label%></label>',
'</div>'].join('\n')),
gridBody = $('<div class="pgadmin-control-group backgrid form-group col-xs-12 object subnode"></div>').append(
gridBody = $('<div class="pgadmin-control-group backgrid form-group col-12 object subnode"></div>').append(
gridHeader(attributes)
);
@@ -391,12 +391,12 @@ define('pgadmin.node.exclusion_constraint', [
'<div class="subnode-header-form">',
' <div class="container-fluid">',
' <div class="row">',
' <div class="col-xs-4">',
' <div class="col-4">',
' <label class="control-label"><%-column_label%></label>',
' </div>',
' <div class="col-xs-4" header="column"></div>',
' <div class="col-xs-4">',
' <button class="btn-sm btn-default add fa fa-plus" <%=canAdd ? "" : "disabled=\'disabled\'"%> ></button>',
' <div class="col-4" header="column"></div>',
' <div class="col-4">',
' <button class="btn btn-sm-sq btn-default add fa fa-plus" <%=canAdd ? "" : "disabled=\'disabled\'"%> ></button>',
' </div>',
' </div>',
' </div>',
@@ -443,7 +443,7 @@ define('pgadmin.node.exclusion_constraint', [
var self = this,
titleTmpl = _.template('<div class=\'subnode-header\'></div>'),
$gridBody =
$('<div class=\'pgadmin-control-group backgrid form-group col-xs-12 object subnode\'></div>').append(
$('<div class=\'pgadmin-control-group backgrid form-group col-12 object subnode\'></div>').append(
// Append titleTmpl only if create/edit mode
data.mode !== 'properties' ? titleTmpl({label: data.label}) : ''
);
@@ -309,7 +309,7 @@ define('pgadmin.node.foreign_key', [
' </div>',
' <div class="col-md-6" header="local_column"></div>',
' <div class="col-md-2">',
' <button class="btn-sm btn-default add fa fa-plus" <%=canAdd ? "" : "disabled=\'disabled\'"%> ></button>',
' <button class="btn btn-sm-sq btn-default add fa fa-plus" <%=canAdd ? "" : "disabled=\'disabled\'"%> ></button>',
' </div>',
' </div>',
' <div class="row">',
@@ -374,7 +374,7 @@ define('pgadmin.node.foreign_key', [
'<label class=\'control-label\'><%-label%></label>',
'</div>'].join('\n')),
$gridBody =
$('<div class=\'pgadmin-control-group backgrid form-group col-xs-12 object subnode\'></div>').append(
$('<div class=\'pgadmin-control-group backgrid form-group col-12 object subnode\'></div>').append(
// Append titleTmpl only if create/edit mode
data.mode !== 'properties' ? titleTmpl({label: data.label}) : ''
);
@@ -25,7 +25,7 @@ define('pgadmin.node.trigger', [
' <span class="<%=Backform.helpMessageClassName%>"><%=helpMessage%></span>',
'<% } %>',
].join('\n')),
className: 'pgadmin-control-group form-group col-xs-6',
className: 'pgadmin-control-group form-group col-6',
});
if (!pgBrowser.Nodes['coll-trigger']) {
@@ -148,9 +148,11 @@ define('pgadmin.node.pga_schedule', [
},{
id: 'jextime', type: 'text', placeholder: gettext('<any>'),
label: gettext('Time'), editable: true, cell: AnyDatetimeCell,
options: {format: 'HH:mm'}, displayFormat: 'HH:mm',
modelFormat: 'HH:mm:ss', displayInUTC: false, allowEmpty: true,
cellHeaderClasses:'width_percent_50', modalInUTC: false,
options: {format: 'HH:mm', buttons: {
showToday: false,
}}, displayFormat: 'HH:mm',
modelFormat: 'HH:mm:ss', displayInUTC: true, allowEmpty: true,
cellHeaderClasses:'width_percent_50', modalInUTC: true,
}],
validate: function() {
var self = this, exceptions = this.collection,
@@ -343,7 +345,7 @@ define('pgadmin.node.pga_schedule', [
);
this.$el.prepend(
'<div class="set-group pg-el-xs-12 ' +
'<div class="set-group c ' +
Backform.helpMessageClassName + '">' +
gettext('Schedules are specified using a <b>cron-style</b> format.<br/><ul><li>For each selected time or date element, the schedule will execute.<br/>e.g. To execute at 5 minutes past every hour, simply select ‘05’ in the Minutes list box.<br/></li><li>Values from more than one field may be specified in order to further control the schedule.<br/>e.g. To execute at 12:05 and 14:05 every Monday and Thursday, you would click minute 05, hours 12 and 14, and weekdays Monday and Thursday.</li><li>For additional flexibility, the Month Days check list includes an extra Last Day option. This matches the last day of the month, whether it happens to be the 28th, 29th, 30th or 31st.</li></ul>') +
'</div>'
@@ -354,7 +356,7 @@ define('pgadmin.node.pga_schedule', [
}),
schema:[{
id: 'jscweekdays', label: gettext('Week Days'), cell: 'select2',
group: gettext('Days'), control: 'select2',
group: gettext('Days'), control: 'select2', type: 'array',
select2: {
first_empty: false,
multiple: true,
@@ -370,7 +372,7 @@ define('pgadmin.node.pga_schedule', [
options: BooleanArrayOptions,
},{
id: 'jscmonthdays', label: gettext('Month Days'), cell: 'select2',
group: gettext('Days'), control: 'select2',
group: gettext('Days'), control: 'select2', type: 'array',
select2: {
first_empty: false,
multiple: true,
@@ -385,7 +387,7 @@ define('pgadmin.node.pga_schedule', [
selector: monthdays, options: BooleanArrayOptions,
},{
id: 'jscmonths', label: gettext('Months'), cell: 'select2',
group: gettext('Days'), control: 'select2',
group: gettext('Days'), control: 'select2', type: 'array',
select2: {
first_empty: false,
multiple: true,
@@ -404,7 +406,7 @@ define('pgadmin.node.pga_schedule', [
group: gettext('Repeat'), mode: ['create', 'edit'],
schema:[{
id: 'jschours', label: gettext('Hours'), cell: 'select2',
group: gettext('Times'), control: 'select2',
group: gettext('Times'), control: 'select2', type: 'array',
select2: {
first_empty: false,
multiple: true,
@@ -419,7 +421,7 @@ define('pgadmin.node.pga_schedule', [
selector: hours, options: BooleanArrayOptions,
},{
id: 'jscminutes', label: gettext('Minutes'), cell: 'select2',
group: gettext('Times'), control: 'select2',
group: gettext('Times'), control: 'select2', type: 'array',
select2: {
first_empty: false,
multiple: true,
@@ -7,18 +7,18 @@
INSERT INTO pgagent.pga_exception (
jexscid, jexdate, jextime
) VALUES (
{% if jscid %}{{ jscid|qtLiteral }}{% else %}scid{% endif %}, {% if data.jexdate %}to_date({{ data.jexdate|qtLiteral }}, 'MM/DD/YYYY')::date{% else %}NULL::date{% endif %}, {% if data.jextime %}{{ data.jextime|qtLiteral }}::time without time zone{% else %}NULL::time without time zone{% endif %}
{% if jscid %}{{ jscid|qtLiteral }}{% else %}scid{% endif %}, {% if data.jexdate %}to_date({{ data.jexdate|qtLiteral }}, 'YYYY-MM-DD')::date{% else %}NULL::date{% endif %}, {% if data.jextime %}{{ data.jextime|qtLiteral }}::time without time zone{% else %}NULL::time without time zone{% endif %}
);
{%- endmacro %}
{% macro UPDATE(jscid, data) -%}
-- Updating an existing schedule exception (id: {{ data.jexid|qtLiteral }}, schedule: {{ jscid|qtLiteral }})
UPDATE pgagent.pga_exception SET
{% if 'jexdate' in data %}jexdate={% if data.jexdate %}to_date({{ data.jexdate|qtLiteral }}, 'MM/DD/YYYY')::date{% else %}NULL::date{% endif %}{% endif %}{% if 'jextime' in data%}{% if 'jexdate' in data %}, {% endif %}jextime={% if data.jextime %}{{ data.jextime|qtLiteral }}::time without time zone{% else %}NULL::time without time zone{% endif %}{% endif %}
{% if 'jexdate' in data %}jexdate={% if data.jexdate %}to_date({{ data.jexdate|qtLiteral }}, 'YYYY-MM-DD')::date{% else %}NULL::date{% endif %}{% endif %}{% if 'jextime' in data%}{% if 'jexdate' in data %}, {% endif %}jextime={% if data.jextime %}{{ data.jextime|qtLiteral }}::time without time zone{% else %}NULL::time without time zone{% endif %}{% endif %}
WHERE jexid={{ data.jexid|qtLiteral }}::integer AND jscid={{ jscid|qtLiteral }}::integer;
WHERE jexid={{ data.jexid|qtLiteral }}::integer AND jexscid={{ jscid|qtLiteral }}::integer;
{%- endmacro %}
{% macro DELETE(jscid, data) -%}
-- Deleting a schedule exception (id: {{ data.jexid|qtLiteral }}, schedule: {{ jscid|qtLiteral }})
DELETE FROM pgagent.pga_exception WHERE jexid={{ data.jexid|qtLiteral }}::integer AND jscid={{ jscid|qtLiteral }}::integer;
DELETE FROM pgagent.pga_exception WHERE jexid={{ data.jexid|qtLiteral }}::integer AND jexscid={{ jscid|qtLiteral }}::integer;
{%- endmacro %}
@@ -23,7 +23,7 @@ INSERT INTO pgagent.pga_schedule(
{{ data.jscmonthdays|qtLiteral }}::boolean[],
-- Months
{{ data.jscmonths|qtLiteral }}::boolean[]
) RETURNING jscid {% if not jid %}INTO scid;{% endif %}{% if 'jscexceptions' in data %}
) RETURNING jscid INTO scid;{% if 'jscexceptions' in data %}
{% for exc in data.jscexceptions %}
{{ EXCEPTIONS.INSERT(None, exc) }}{% endfor %}{% endif %}
@@ -25,6 +25,6 @@
margin: 5px;
}
div[role=tabpanel] > .pgadmin-control-group.form-group.pg-el-xs-12.jscexceptions {
div[role=tabpanel] > .pgadmin-control-group.form-group.c.jscexceptions {
min-height: 400px;
}
@@ -1,2 +1,8 @@
{% import 'macros/pga_schedule.macros' as SCHEDULE %}
DO $$
DECLARE
scid integer;
BEGIN
{{ SCHEDULE.INSERT(jid, data) }}
END
$$;
@@ -0,0 +1,80 @@
##########################################################################
#
# pgAdmin 4 - PostgreSQL Tools
#
# Copyright (C) 2013 - 2018, The pgAdmin Development Team
# This software is released under the PostgreSQL Licence
#
##########################################################################
import simplejson as json
import uuid
from pgadmin.utils.route import BaseTestGenerator
from regression.python_test_utils import test_utils as utils
from . import utils as pgagent_utils
class PgAgentAddScheduleTestCase(BaseTestGenerator):
"""This class will test the add schedule in the pgAgent job API"""
scenarios = [
('Create schedule with exception in pgAgent job', dict(
url='/browser/pga_schedule/obj/'))
]
def setUp(self):
flag, msg = pgagent_utils.is_valid_server_to_run_pgagent(self)
if not flag:
self.skipTest(msg)
flag, msg = pgagent_utils.is_pgagent_installed_on_server(self)
if not flag:
self.skipTest(msg)
name = "test_job_get%s" % str(uuid.uuid4())[1:8]
self.job_id = pgagent_utils.create_pgagent_job(self, name)
def runTest(self):
# Check and Delete entry for pga_exception table for the above
# date and time as no primary key is defined for pga_exception table
# and there is a unique constraint for date and time. So when we run
# the test cases multiple time then it will fail with unique
# constraint error.
jexdate = '2050-01-01'
jextime = '12:00:00'
pgagent_utils.delete_pgagent_exception(self, jexdate, jextime)
self.pgagent_schedule_name = "test_sch_add%s" % str(uuid.uuid4())[1:8]
data = {
'jscjobid': self.job_id,
'jscenabled': True,
'jscdesc': '',
'jscname': self.pgagent_schedule_name,
'jscexceptions': [{'jexdate': jexdate,
'jextime': jextime}],
'jscstart': '2050-01-01 12:14:21 +05:30',
'jscend': '2050-03-01 12:14:21 +05:30',
'jscminutes': [False] * 60,
'jscweekdays': [True] * 7,
'jscmonthdays': [True] * 32,
'jschours': [False] * 24,
'jscmonths': [True] * 12
}
response = self.tester.post(
'{0}{1}/{2}/{3}/'.format(
self.url, str(utils.SERVER_GROUP), str(self.server_id),
str(self.job_id)
),
data=json.dumps(data),
content_type='html/json'
)
self.assertEquals(response.status_code, 200)
response_data = json.loads(response.data)
self.schedule_id = response_data['node']['_id']
is_present = pgagent_utils.verify_pgagent_schedule(self)
self.assertTrue(
is_present, "pgAgent schedule was not created successfully"
)
def tearDown(self):
"""Clean up code"""
pgagent_utils.delete_pgagent_job(self)
@@ -0,0 +1,69 @@
##########################################################################
#
# pgAdmin 4 - PostgreSQL Tools
#
# Copyright (C) 2013 - 2018, The pgAdmin Development Team
# This software is released under the PostgreSQL Licence
#
##########################################################################
import simplejson as json
import uuid
from pgadmin.utils.route import BaseTestGenerator
from regression.python_test_utils import test_utils as utils
from . import utils as pgagent_utils
class PgAgentAddStepTestCase(BaseTestGenerator):
"""This class will test the add step in the pgAgent job API"""
scenarios = [
('Create step for pgAgent job', dict(
url='/browser/pga_jobstep/obj/'))
]
def setUp(self):
flag, msg = pgagent_utils.is_valid_server_to_run_pgagent(self)
if not flag:
self.skipTest(msg)
flag, msg = pgagent_utils.is_pgagent_installed_on_server(self)
if not flag:
self.skipTest(msg)
name = "test_job_get%s" % str(uuid.uuid4())[1:8]
self.job_id = pgagent_utils.create_pgagent_job(self, name)
def runTest(self):
self.pgagent_step_name = "test_step_add%s" % str(uuid.uuid4())[1:8]
data = {
'jstjobid': self.job_id,
'jstname': self.pgagent_step_name,
'jstdesc': '',
'jstenabled': True,
'jstkind': True,
'jstconntype': True,
'jstcode': 'SELECT 1;',
'jstconnstr': None,
'jstdbname': 'postgres',
'jstonerror': 'f',
'jstnextrun': '',
}
response = self.tester.post(
'{0}{1}/{2}/{3}/'.format(
self.url, str(utils.SERVER_GROUP), str(self.server_id),
str(self.job_id)
),
data=json.dumps(data),
content_type='html/json'
)
self.assertEquals(response.status_code, 200)
response_data = json.loads(response.data)
self.step_id = response_data['node']['_id']
is_present = pgagent_utils.verify_pgagent_step(self)
self.assertTrue(
is_present, "pgAgent step was not created successfully"
)
def tearDown(self):
"""Clean up code"""
pgagent_utils.delete_pgagent_job(self)
@@ -0,0 +1,52 @@
##########################################################################
#
# pgAdmin 4 - PostgreSQL Tools
#
# Copyright (C) 2013 - 2018, The pgAdmin Development Team
# This software is released under the PostgreSQL Licence
#
##########################################################################
import uuid
from pgadmin.utils.route import BaseTestGenerator
from regression.python_test_utils import test_utils as utils
from . import utils as pgagent_utils
class PgAgentDeleteScheduleTestCase(BaseTestGenerator):
"""This class will test the delete pgAgent job schedule API"""
scenarios = [
('Delete pgAgent Schedule', dict(url='/browser/pga_schedule/obj/'))
]
def setUp(self):
flag, msg = pgagent_utils.is_valid_server_to_run_pgagent(self)
if not flag:
self.skipTest(msg)
flag, msg = pgagent_utils.is_pgagent_installed_on_server(self)
if not flag:
self.skipTest(msg)
name = "test_job_delete%s" % str(uuid.uuid4())[1:8]
self.job_id = pgagent_utils.create_pgagent_job(self, name)
sch_name = "test_schedule_delete%s" % str(uuid.uuid4())[1:8]
self.schedule_id = pgagent_utils.create_pgagent_schedule(
self, sch_name, self.job_id)
def runTest(self):
"""This function will deletes pgAgent job schedule"""
response = self.tester.delete(
'{0}{1}/{2}/{3}/{4}'.format(
self.url, str(utils.SERVER_GROUP), str(self.server_id),
str(self.job_id), str(self.schedule_id)
),
content_type='html/json'
)
self.assertEquals(response.status_code, 200)
is_present = pgagent_utils.verify_pgagent_schedule(self)
self.assertFalse(
is_present, "pgAgent schedule was not deleted successfully"
)
def tearDown(self):
"""Clean up code"""
pgagent_utils.delete_pgagent_job(self)
@@ -0,0 +1,52 @@
##########################################################################
#
# pgAdmin 4 - PostgreSQL Tools
#
# Copyright (C) 2013 - 2018, The pgAdmin Development Team
# This software is released under the PostgreSQL Licence
#
##########################################################################
import uuid
from pgadmin.utils.route import BaseTestGenerator
from regression.python_test_utils import test_utils as utils
from . import utils as pgagent_utils
class PgAgentDeleteStepTestCase(BaseTestGenerator):
"""This class will test the delete pgAgent job step API"""
scenarios = [
('Delete pgAgent Step', dict(url='/browser/pga_jobstep/obj/'))
]
def setUp(self):
flag, msg = pgagent_utils.is_valid_server_to_run_pgagent(self)
if not flag:
self.skipTest(msg)
flag, msg = pgagent_utils.is_pgagent_installed_on_server(self)
if not flag:
self.skipTest(msg)
name = "test_job_delete%s" % str(uuid.uuid4())[1:8]
self.job_id = pgagent_utils.create_pgagent_job(self, name)
step_name = "test_step_delete%s" % str(uuid.uuid4())[1:8]
self.step_id = pgagent_utils.create_pgagent_step(
self, step_name, self.job_id)
def runTest(self):
"""This function will deletes pgAgent job step"""
response = self.tester.delete(
'{0}{1}/{2}/{3}/{4}'.format(
self.url, str(utils.SERVER_GROUP), str(self.server_id),
str(self.job_id), str(self.step_id)
),
content_type='html/json'
)
self.assertEquals(response.status_code, 200)
is_present = pgagent_utils.verify_pgagent_step(self)
self.assertFalse(
is_present, "pgAgent step was not deleted successfully"
)
def tearDown(self):
"""Clean up code"""
pgagent_utils.delete_pgagent_job(self)
@@ -0,0 +1,131 @@
##########################################################################
#
# pgAdmin 4 - PostgreSQL Tools
#
# Copyright (C) 2013 - 2018, The pgAdmin Development Team
# This software is released under the PostgreSQL Licence
#
##########################################################################
import simplejson as json
import uuid
from pgadmin.utils.route import BaseTestGenerator
from regression.python_test_utils import test_utils as utils
from . import utils as pgagent_utils
class PgAgentPutScheduleTestCase(BaseTestGenerator):
"""This class will test the update pgAgent schedule API"""
scenarios = [
('Update schedule with start and end time', dict(
url='/browser/pga_schedule/obj/',
data={
'jscdesc': 'Test Schedule',
'jscstart': '2050-01-01 12:00:00 +05:30',
'jscend': '2050-01-20 12:00:00 +05:30',
})),
('Update schedule with repeat', dict(
url='/browser/pga_schedule/obj/',
data={
'jscmonthdays': '[true,false,true,false,true,false,false,'
'false,false,false,false,false,false,false,'
'false,false,false,false,false,false,false,'
'false,false,false,false,false,false,false,'
'false,false,false,false]',
'jscweekdays': '[true,false,false,true,false,false,false]',
'jscmonths': '[true,false,false,true,false,false,false,false,'
'false,false,false,false]',
'jschours': '[false,false,false,false,true,false,false,false,'
'false,false,false,false,false,false,false,false,'
'false,false,false,false,false,false,false,false]'
})),
('Update schedule add exception', dict(
url='/browser/pga_schedule/obj/',
data={
'jscexceptions': {
'added': [{'jexdate': '2050-01-01',
'jextime': '12:00:00'}]
}},
delete_existing_exception=True)),
('Update schedule change exception date and time', dict(
url='/browser/pga_schedule/obj/',
data={
'jscexceptions': {
'changed': [{'jexdate': '2050-01-31',
'jextime': '20:00:00'}]
}},
create_exception=True)),
('Update schedule delete exception', dict(
url='/browser/pga_schedule/obj/',
data={
'jscexceptions': {
'deleted': [{'jexdate': '2050-01-01',
'jextime': '12:00:00'}]
}},
create_exception=True,
is_delete=True)),
]
def setUp(self):
flag, msg = pgagent_utils.is_valid_server_to_run_pgagent(self)
if not flag:
self.skipTest(msg)
flag, msg = pgagent_utils.is_pgagent_installed_on_server(self)
if not flag:
self.skipTest(msg)
name = "test_job_update%s" % str(uuid.uuid4())[1:8]
self.job_id = pgagent_utils.create_pgagent_job(self, name)
sch_name = "test_schedule_update%s" % str(uuid.uuid4())[1:8]
self.schedule_id = pgagent_utils.create_pgagent_schedule(
self, sch_name, self.job_id)
def runTest(self):
"""This function will update pgAgent schedule"""
# Check and Delete entry for pga_exception table for the specified
# date and time as no primary key is defined for pga_exception table
# and there is a unique constraint for date and time. So when we run
# the test cases multiple time then it will fail with unique
# constraint error.
if hasattr(self, 'delete_existing_exception'):
pgagent_utils.delete_pgagent_exception(
self, self.data['jscexceptions']['added'][0]['jexdate'],
self.data['jscexceptions']['added'][0]['jextime'])
# Create exception for update and delete
if hasattr(self, 'create_exception'):
date = None
time = None
if hasattr(self, 'is_delete'):
date = self.data['jscexceptions']['deleted'][0]['jexdate']
time = self.data['jscexceptions']['deleted'][0]['jextime']
else:
date = self.data['jscexceptions']['changed'][0]['jexdate']
time = self.data['jscexceptions']['changed'][0]['jextime']
self.excp_id = pgagent_utils.create_pgagent_exception(
self, self.schedule_id, date, time)
# Add created exception id in data
if hasattr(self, 'is_delete'):
self.data['jscexceptions']['deleted'][0]['jexid'] = \
self.excp_id
else:
self.data['jscexceptions']['changed'][0]['jexid'] = \
self.excp_id
self.data['jscid'] = str(self.schedule_id)
response = self.tester.put(
'{0}{1}/{2}/{3}/{4}'.format(
self.url, str(utils.SERVER_GROUP), str(self.server_id),
str(self.job_id), str(self.schedule_id)
),
data=json.dumps(self.data),
content_type='html/json'
)
self.assertEquals(response.status_code, 200)
def tearDown(self):
"""Clean up code"""
pgagent_utils.delete_pgagent_schedule(self)
pgagent_utils.delete_pgagent_job(self)
@@ -0,0 +1,67 @@
##########################################################################
#
# pgAdmin 4 - PostgreSQL Tools
#
# Copyright (C) 2013 - 2018, The pgAdmin Development Team
# This software is released under the PostgreSQL Licence
#
##########################################################################
import simplejson as json
import uuid
from pgadmin.utils.route import BaseTestGenerator
from regression.python_test_utils import test_utils as utils
from . import utils as pgagent_utils
class PgAgentPutStepTestCase(BaseTestGenerator):
"""This class will test the update pgAgent steps API"""
scenarios = [
('Update step with kind, description, code and error', dict(
url='/browser/pga_jobstep/obj/',
data={
'jstdesc': 'Test Steps',
'jstkind': False,
'jstcode': 'SELECT 12345',
'jstonerror': 'i'
})),
('Update step with connection type and string', dict(
url='/browser/pga_jobstep/obj/',
data={
'jstconntype': False,
'jstconnstr':
'host=localhost port=5432 dbname=mydb connect_timeout=10'
}))
]
def setUp(self):
flag, msg = pgagent_utils.is_valid_server_to_run_pgagent(self)
if not flag:
self.skipTest(msg)
flag, msg = pgagent_utils.is_pgagent_installed_on_server(self)
if not flag:
self.skipTest(msg)
name = "test_job_update%s" % str(uuid.uuid4())[1:8]
self.job_id = pgagent_utils.create_pgagent_job(self, name)
step_name = "test_step_update%s" % str(uuid.uuid4())[1:8]
self.step_id = pgagent_utils.create_pgagent_step(
self, step_name, self.job_id)
def runTest(self):
"""This function will update pgAgent steps"""
self.data['jstid'] = str(self.step_id)
response = self.tester.put(
'{0}{1}/{2}/{3}/{4}'.format(
self.url, str(utils.SERVER_GROUP), str(self.server_id),
str(self.job_id), str(self.step_id)
),
data=json.dumps(self.data),
content_type='html/json'
)
self.assertEquals(response.status_code, 200)
def tearDown(self):
"""Clean up code"""
pgagent_utils.delete_pgagent_step(self)
pgagent_utils.delete_pgagent_job(self)
@@ -151,7 +151,7 @@ def delete_pgagent_job(self):
def verify_pgagent_job(self):
"""
This function deletes the pgAgent job.
This function verifies the pgAgent job.
"""
try:
connection = utils.get_db_connection(
@@ -173,3 +173,236 @@ def verify_pgagent_job(self):
return count is not None and int(count) != 0
except Exception:
traceback.print_exc(file=sys.stderr)
def create_pgagent_schedule(self, sch_name, jobid):
"""
This function create the pgAgent schedule.
"""
try:
connection = utils.get_db_connection(
self.server['db'],
self.server['username'],
self.server['db_password'],
self.server['host'],
self.server['port'],
self.server['sslmode']
)
old_isolation_level = connection.isolation_level
connection.set_isolation_level(0)
pg_cursor = connection.cursor()
query = """
INSERT INTO pgagent.pga_schedule(
jscname, jscjobid, jscenabled, jscdesc, jscstart, jscend
) VALUES (
'{0}'::text, {1}::int, true, '',
'2050-01-01 12:14:21 +05:30'::timestamp with time zone,
'2050-01-30 12:14:21 +05:30'::timestamp with time zone
) RETURNING jscid;
""".format(sch_name, jobid)
pg_cursor.execute(query)
sch_id = pg_cursor.fetchone()
connection.set_isolation_level(old_isolation_level)
connection.commit()
connection.close()
return sch_id[0]
except Exception:
traceback.print_exc(file=sys.stderr)
def delete_pgagent_schedule(self):
"""
This function deletes the pgAgent schedule.
"""
try:
connection = utils.get_db_connection(
self.server['db'],
self.server['username'],
self.server['db_password'],
self.server['host'],
self.server['port'],
self.server['sslmode']
)
old_isolation_level = connection.isolation_level
connection.set_isolation_level(0)
pg_cursor = connection.cursor()
pg_cursor.execute(
"DELETE FROM pgagent.pga_schedule "
"WHERE jscid = '%s'::integer;" % self.schedule_id
)
connection.set_isolation_level(old_isolation_level)
connection.commit()
connection.close()
except Exception:
traceback.print_exc(file=sys.stderr)
def verify_pgagent_schedule(self):
"""
This function verifies the pgAgent schedule.
"""
try:
connection = utils.get_db_connection(
self.server['db'],
self.server['username'],
self.server['db_password'],
self.server['host'],
self.server['port'],
self.server['sslmode']
)
pg_cursor = connection.cursor()
pg_cursor.execute(
"SELECT COUNT(*) FROM pgagent.pga_schedule "
"WHERE jscid = '%s'::integer;" % self.schedule_id
)
result = pg_cursor.fetchone()
count = result[0]
connection.close()
return count is not None and int(count) != 0
except Exception:
traceback.print_exc(file=sys.stderr)
def delete_pgagent_exception(self, date, time):
"""
This function deletes the pgAgent exception.
"""
try:
connection = utils.get_db_connection(
self.server['db'],
self.server['username'],
self.server['db_password'],
self.server['host'],
self.server['port'],
self.server['sslmode']
)
pg_cursor = connection.cursor()
query = "DELETE FROM pgagent.pga_exception " \
"WHERE jexdate = to_date('{0}', 'YYYY-MM-DD') AND " \
"jextime = '{1}'::time without time zone;".format(date, time)
pg_cursor.execute(query)
connection.close()
except Exception:
traceback.print_exc(file=sys.stderr)
def create_pgagent_exception(self, schid, date, time):
"""
This function create the pgAgent exception.
"""
try:
# Delete existing exception if already exists
delete_pgagent_exception(self, date, time)
connection = utils.get_db_connection(
self.server['db'],
self.server['username'],
self.server['db_password'],
self.server['host'],
self.server['port'],
self.server['sslmode']
)
old_isolation_level = connection.isolation_level
connection.set_isolation_level(0)
pg_cursor = connection.cursor()
query = """
INSERT INTO pgagent.pga_exception(jexscid, jexdate, jextime
) VALUES ({0},
to_date('{1}', 'YYYY-MM-DD'), '{2}'::time without time zone
) RETURNING jexid;
""".format(schid, date, time)
pg_cursor.execute(query)
excep_id = pg_cursor.fetchone()
connection.set_isolation_level(old_isolation_level)
connection.commit()
connection.close()
return excep_id[0]
except Exception:
traceback.print_exc(file=sys.stderr)
def create_pgagent_step(self, step_name, jobid):
"""
This function create the pgAgent step.
"""
try:
connection = utils.get_db_connection(
self.server['db'],
self.server['username'],
self.server['db_password'],
self.server['host'],
self.server['port'],
self.server['sslmode']
)
old_isolation_level = connection.isolation_level
connection.set_isolation_level(0)
pg_cursor = connection.cursor()
query = """
INSERT INTO pgagent.pga_jobstep(
jstname, jstjobid, jstenabled, jstkind,
jstcode, jstdbname
) VALUES (
'{0}'::text, {1}::int, true, 's', 'SELECT 1', 'postgres'
) RETURNING jstid;
""".format(step_name, jobid)
pg_cursor.execute(query)
step_id = pg_cursor.fetchone()
connection.set_isolation_level(old_isolation_level)
connection.commit()
connection.close()
return step_id[0]
except Exception:
traceback.print_exc(file=sys.stderr)
def delete_pgagent_step(self):
"""
This function deletes the pgAgent step.
"""
try:
connection = utils.get_db_connection(
self.server['db'],
self.server['username'],
self.server['db_password'],
self.server['host'],
self.server['port'],
self.server['sslmode']
)
old_isolation_level = connection.isolation_level
connection.set_isolation_level(0)
pg_cursor = connection.cursor()
pg_cursor.execute(
"DELETE FROM pgagent.pga_jobstep "
"WHERE jstid = '%s'::integer;" % self.step_id
)
connection.set_isolation_level(old_isolation_level)
connection.commit()
connection.close()
except Exception:
traceback.print_exc(file=sys.stderr)
def verify_pgagent_step(self):
"""
This function verifies the pgAgent step .
"""
try:
connection = utils.get_db_connection(
self.server['db'],
self.server['username'],
self.server['db_password'],
self.server['host'],
self.server['port'],
self.server['sslmode']
)
pg_cursor = connection.cursor()
pg_cursor.execute(
"SELECT COUNT(*) FROM pgagent.pga_jobstep "
"WHERE jstid = '%s'::integer;" % self.step_id
)
result = pg_cursor.fetchone()
count = result[0]
connection.close()
return count is not None and int(count) != 0
except Exception:
traceback.print_exc(file=sys.stderr)
@@ -66,8 +66,8 @@ define('pgadmin.node.role', [
var RoleCustomSwitchControl = Backform.SwitchControl.extend({
template: _.template([
'<label class="control-label pg-el-sm-9 pg-el-xs-12"><%=label%></label>',
'<div class="pgadmin-controls pg-el-sm-3 pg-el-xs-12">',
'<label class="control-label"><%=label%></label>',
'<div class="pgadmin-controls">',
' <div class="checkbox" style="float:right">',
' <label>',
' <input type="checkbox" class="<%=extraClasses.join(\' \')%>" name="<%=name%>" <%=value ? "checked=\'checked\'" : ""%> <%=disabled ? "disabled" : ""%> <%=required ? "required" : ""%> />',
@@ -82,7 +82,7 @@ define('pgadmin.node.role', [
var RoleMembersControl = Backform.Control.extend({
defaults: _.defaults(
{extraClasses: ['col-xs-12 col-sm-12 col-md-12']},
{extraClasses: ['col-12 col-sm-12 col-md-12']},
Backform.NodeListByNameControl.prototype.defaults
),
initialize: function() {
@@ -0,0 +1,8 @@
.icon-server-connecting {
background-image: url('~top/static/img/load-node.gif') !important;
background-repeat: no-repeat;
background-size: 20px !important;
align-content: center;
vertical-align: middle;
height: 1.3em;
}
@@ -328,10 +328,10 @@ define([
titleTmpl = _.template([
'<div class=\'subnode-header\'>',
'<label class=\'control-label\'><%-label%></label>',
'<button class=\'btn-sm btn-default add fa fa-plus\' title=\'' + _('Add new row') + '\' <%=canAdd ? \'\' : \'disabled="disabled"\'%>></button>',
'<button class=\'btn btn-sm-sq btn-default add fa fa-plus\' title=\'' + _('Add new row') + '\' <%=canAdd ? \'\' : \'disabled="disabled"\'%>></button>',
'</div>'].join('\n')),
$gridBody =
$('<div class=\'pgadmin-control-group backgrid form-group col-xs-12 object subnode\'></div>').append(
$('<div class=\'pgadmin-control-group backgrid form-group col-12 object subnode\'></div>').append(
titleTmpl(data)
);
@@ -16,15 +16,6 @@
height: 1.3em;
}
.icon-server-connecting {
background-image: url('{{ url_for('static', filename='js/generated/img/load-node.gif')}}') !important;
background-repeat: no-repeat;
background-size: 20px !important;
align-content: center;
vertical-align: middle;
height: 1.3em;
}
.change_password {
padding-left: 7px;
}
+15 -22
View File
@@ -9,7 +9,7 @@ define('pgadmin.browser', [
'pgadmin.browser.error', 'pgadmin.browser.frame',
'pgadmin.browser.node', 'pgadmin.browser.collection',
'sources/codemirror/addon/fold/pgadmin-sqlfoldcode',
'pgadmin.browser.keyboard',
'pgadmin.browser.keyboard', 'sources/tree/pgadmin_tree_save_state',
], function(
tree,
gettext, url_for, require, $, _, S, Bootstrap, pgAdmin, Alertify,
@@ -132,7 +132,7 @@ define('pgadmin.browser', [
isCloseable: false,
isPrivate: true,
elContainer: true,
content: '<div class="obj_properties"><div class="alert alert-info pg-panel-message">' + select_object_msg + '</div></div>',
content: '<div class="obj_properties container-fluid"><div class="alert alert-info pg-panel-message">' + select_object_msg + '</div></div>',
events: panelEvents,
onCreate: function(myPanel, $container) {
$container.addClass('pg-no-overflow');
@@ -146,7 +146,7 @@ define('pgadmin.browser', [
width: 500,
isCloseable: false,
isPrivate: true,
content: '<div><div class="alert alert-info pg-panel-message pg-panel-statistics-message">' + select_object_msg + '</div><div class="pg-panel-statistics-container hidden"></div></div>',
content: '<div><div class="alert alert-info pg-panel-message pg-panel-statistics-message">' + select_object_msg + '</div><div class="pg-panel-statistics-container d-none"></div></div>',
events: panelEvents,
}),
// Reversed engineered SQL for the object
@@ -167,7 +167,7 @@ define('pgadmin.browser', [
width: 500,
isCloseable: false,
isPrivate: true,
content: '<div><div class="alert alert-info pg-panel-message pg-panel-depends-message">' + select_object_msg + '</div><div class="pg-panel-depends-container hidden"></div></div>',
content: '<div><div class="alert alert-info pg-panel-message pg-panel-depends-message">' + select_object_msg + '</div><div class="pg-panel-depends-container d-none"></div></div>',
events: panelEvents,
}),
// Dependents of the object
@@ -178,7 +178,7 @@ define('pgadmin.browser', [
width: 500,
isCloseable: false,
isPrivate: true,
content: '<div><div class="alert alert-info pg-panel-message pg-panel-depends-message">' + select_object_msg + '</div><div class="pg-panel-depends-container hidden"></div></div>',
content: '<div><div class="alert alert-info pg-panel-message pg-panel-depends-message">' + select_object_msg + '</div><div class="pg-panel-depends-container d-none"></div></div>',
events: panelEvents,
}),
},
@@ -279,7 +279,7 @@ define('pgadmin.browser', [
// menu navigation bar
navbar = $('#navbar-menu > ul').first(),
// Drop down menu for objects
$obj_mnu = navbar.find('li#mnu_obj > ul.dropdown-menu').first(),
$obj_mnu = navbar.find('li#mnu_obj .dropdown-menu').first(),
// data for current selected object
d = obj.tree.itemData(item),
update_menuitem = function(m) {
@@ -320,7 +320,7 @@ define('pgadmin.browser', [
// Create a dummy 'no object seleted' menu
var create_submenu = pgAdmin.Browser.MenuGroup(
obj.menu_categories['create'], [{
$el: $('<li class="menu-item disabled"><a href="#">' + gettext('No object selected') + '</a></li>'),
$el: $('<li><a class="dropdown-item text-white disabled" href="#">' + gettext('No object selected') + '</a></li>'),
priority: 1,
category: 'create',
update: function() {},
@@ -513,9 +513,11 @@ define('pgadmin.browser', [
.done(function() {})
.fail(function() {});
}, 300000);
obj.Events.on('pgadmin:browser:tree:add', obj.onAddTreeNode, obj);
obj.Events.on('pgadmin:browser:tree:update', obj.onUpdateTreeNode, obj);
obj.Events.on('pgadmin:browser:tree:refresh', obj.onRefreshTreeNode, obj);
},
add_menu_category: function(
@@ -609,11 +611,11 @@ define('pgadmin.browser', [
if (pgAdmin.Browser.MenuCreator(
$dropdown, obj.menus[o.menu], obj.menu_categories
)) {
$mnu.removeClass('hide');
$mnu.removeClass('d-none');
}
});
navbar.children('#mnu_obj').removeClass('hide');
navbar.children('#mnu_obj').removeClass('d-none');
obj.enable_disable_menus();
},
// General function to handle callbacks for object or dialog help.
@@ -1942,19 +1944,10 @@ define('pgadmin.browser', [
pgAdmin.Browser.editor_shortcut_keys.Tab = 'insertSoftTab';
}
window.onbeforeunload = function() {
var e = window.event,
msg = S(gettext('Are you sure you wish to close the %s browser?')).sprintf(pgBrowser.utils.app_name).value();
// For IE and Firefox prior to version 4
if (e) {
e.returnValue = msg;
}
// For Safari
return msg;
};
$(window).on('beforeunload', function() {
if (pgBrowser.get_preference('browser', 'browser_tree_state_save_interval').value !== -1)
pgAdmin.Browser.browserTreeState.save_state();
});
return pgAdmin.Browser;
});
+1 -1
View File
@@ -59,7 +59,7 @@ define([
j = panel.$container.find('.obj_properties').first(),
view = j.data('obj-view'),
content = $('<div></div>')
.addClass('pg-prop-content col-xs-12'),
.addClass('pg-prop-content col-12'),
node = pgBrowser.Nodes[that.node],
// This will be the URL, used for object manipulation.
urlBase = this.generate_url(item, 'properties', data),
+1 -1
View File
@@ -13,7 +13,7 @@ function(gettext, _, alertify, pgAdmin) {
' + gettext('Error message') + '</a>\
</h4>\
</div>\
<div id="collapseOne" class="panel-collapse collapse in" role="tabpanel" aria-labelledby="headingOne">\
<div id="collapseOne" class="panel-collapse collapse show" role="tabpanel" aria-labelledby="headingOne">\
<div class="panel-body" style="overflow: auto;">' + unescape(message) + '</div>\
</div>\
</div>';
+108 -35
View File
@@ -4,6 +4,7 @@ import $ from 'jquery';
import Mousetrap from 'mousetrap';
import * as commonUtils from '../../../static/js/utils';
import dialogTabNavigator from '../../../static/js/dialog_tab_navigator';
import {default as keyboardFunc} from 'sources/keyboard_shortcuts';
const pgBrowser = pgAdmin.Browser = pgAdmin.Browser || {};
@@ -37,9 +38,7 @@ _.extend(pgBrowser.keyboardNavigation, {
this.keyboardShortcut.help_shortcut],
}, // Main menu
'bindRightPanel': {'shortcuts': [this.keyboardShortcut.tabbed_panel_backward, this.keyboardShortcut.tabbed_panel_forward]}, // Main window panels
'bindMainMenuLeft': {'shortcuts': 'left', 'bindElem': '.pg-navbar'}, // Main menu
'bindMainMenuRight': {'shortcuts': 'right', 'bindElem': '.pg-navbar'}, // Main menu
'bindMainMenuUpDown': {'shortcuts': ['up', 'down']}, // Main menu
'bindSubMenuClose': {'shortcuts': ['esc','enter']},
'bindLeftTree': {'shortcuts': this.keyboardShortcut.left_tree_shortcut}, // Main menu,
'bindSubMenuQueryTool': {'shortcuts': this.keyboardShortcut.sub_menu_query_tool}, // Sub menu - Open Query Tool,
'bindSubMenuViewData': {'shortcuts': this.keyboardShortcut.sub_menu_view_data}, // Sub menu - Open View Data,
@@ -58,9 +57,37 @@ _.extend(pgBrowser.keyboardNavigation, {
_.each(self.shortcutMethods, (keyCombo, callback) => {
self._bindWithMousetrap(keyCombo.shortcuts, self[callback], keyCombo.bindElem);
});
/* Dropdown submenu was not working properly for up/down arrow keys.
* So up/down/right/left events for dropdown were removed from Mousetrap and were
* handled manually.
*/
const LEFT_KEY = 37,
UP_KEY = 38,
RIGHT_KEY = 39,
DOWN_KEY = 40;
$('.pg-navbar').on('keydown', (event)=> {
switch(event.keyCode) {
case LEFT_KEY:
self.bindMainMenuLeft(event);
break;
case UP_KEY:
self.bindMainMenuUpDown(event, 'up');
break;
case RIGHT_KEY:
self.bindMainMenuRight(event);
break;
case DOWN_KEY:
self.bindMainMenuUpDown(event, 'down');
break;
}
});
},
_bindWithMousetrap: function(shortcuts, callback, bindElem) {
const self = this;
Mousetrap.unbind(shortcuts);
if (bindElem) {
const elem = document.querySelector(bindElem);
Mousetrap(elem).bind(shortcuts, function() {
@@ -72,6 +99,15 @@ _.extend(pgBrowser.keyboardNavigation, {
});
}
},
unbindShortcuts: function() {
// Reset previous events on each instance
const self = this;
_.each(self.mousetrapInstances, (obj) => {
obj['instance'].reset();
});
// Clear already processed events
self.mousetrapInstances = [];
},
bindMainMenu: function(event, combo) {
const shortcut_obj = this.keyboardShortcut;
if (combo === shortcut_obj.file_shortcut) $('#mnu_file a.dropdown-toggle').dropdown('toggle');
@@ -106,48 +142,85 @@ _.extend(pgBrowser.keyboardNavigation, {
}, 1000);
},
bindMainMenuLeft: function(event) {
let prevMenu;
if ($(event.target).hasClass('menu-link')) { // Menu items
prevMenu = $(event.target).parent().parent().parent().prev('.dropdown');
}
else if ($(event.target).parent().hasClass('dropdown-submenu')) { // Sub menu
$(event.target).parent().toggleClass('open');
return;
}
else { //Menu headers
prevMenu = $(event.target).parent().prev('.dropdown');
}
if ($(event.target).hasClass('nav-link')) { // Menu items
let currNavMenu = $(event.target).closest('.nav-item');
keyboardFunc._stopEventPropagation(event);
if (prevMenu.hasClass('hide')) prevMenu = prevMenu.prev('.dropdown'); // Skip hidden menus
currNavMenu = currNavMenu.prev('.nav-item');
// Skip hidden menus
while(currNavMenu.hasClass('d-none')) {
currNavMenu = currNavMenu.prev('.nav-item');
}
prevMenu.find('a:first').dropdown('toggle');
currNavMenu.find('.dropdown-toggle').first().dropdown('toggle');
} else if($(event.target).closest('.dropdown-menu').length > 0) {
let currLi = $(event.target).closest('li');
keyboardFunc._stopEventPropagation(event);
/*close submenu*/
let currMenu = currLi.closest('.dropdown-menu');
if(currMenu.closest('.dropdown-submenu').length > 0) {
currMenu.removeClass('show');
currLi = currMenu.closest('.dropdown-submenu');
currLi.find('.dropdown-item').trigger('focus');
}
}
},
bindMainMenuRight: function(event) {
let nextMenu;
if ($(event.target).hasClass('menu-link')) { // Menu items
nextMenu = $(event.target).parent().parent().parent().next('.dropdown');
}
else if ($(event.target).parent().hasClass('dropdown-submenu')) { // Sub menu
$(event.target).parent().toggleClass('open');
return;
}
else { //Menu headers
nextMenu = $(event.target).parent().next('.dropdown');
}
if ($(event.target).hasClass('nav-link')) { // Menu items
let currNavMenu = $(event.target).closest('.nav-item');
keyboardFunc._stopEventPropagation(event);
if (nextMenu.hasClass('hide')) nextMenu = nextMenu.next('.dropdown'); // Skip hidden menus
currNavMenu = currNavMenu.next('.nav-item');
// Skip hidden menus
while(currNavMenu.hasClass('d-none')) {
currNavMenu = currNavMenu.next('.nav-item');
}
nextMenu.find('a:first').dropdown('toggle');
currNavMenu.find('.dropdown-toggle').first().dropdown('toggle');
} else if($(event.target).closest('.dropdown-menu').length > 0) {
let currLi = $(event.target).closest('li');
keyboardFunc._stopEventPropagation(event);
/*open submenu if any*/
if(currLi.hasClass('dropdown-submenu')){
currLi.find('.dropdown-menu').addClass('show');
currLi = currLi.find('.dropdown-menu .dropdown-item').first().trigger('focus');
}
}
},
bindMainMenuUpDown: function(event, combo) {
// Handle Sub-menus
if (combo === 'up' && $(event.target).parent().prev().prev('.dropdown-submenu').length > 0) {
$(event.target).parent().prev().prev('.dropdown-submenu').find('a:first').trigger('focus');
} else {
if ($(event.target).parent().hasClass('dropdown-submenu')) {
$(event.target).parent().parent().parent().find('a:first').dropdown('toggle');
$(event.target).parent().parent().children().eq(2).find('a:first').trigger('focus');
if($(event.target).closest('.dropdown-menu').length > 0) {
keyboardFunc._stopEventPropagation(event);
let currLi = $(event.target).closest('li');
/*close all the submenus on movement*/
$(event.target).closest('.dropdown-menu').find('.show').removeClass('show');
if(combo === 'up') {
currLi = currLi.prev();
}
else if(combo === 'down'){
currLi = currLi.next();
}
/*do not focus on divider and disabled */
while(currLi.hasClass('dropdown-divider')
|| currLi.find('.dropdown-item').first().hasClass('disabled')) {
if(combo === 'up') {
currLi = currLi.prev();
}
else if(combo === 'down'){
currLi = currLi.next();
}
}
currLi.find('.dropdown-item').trigger('focus');
}
},
bindSubMenuClose: function() {
if($(event.target).hasClass('dropdown-item')
&& $(event.target).closest('.dropdown-submenu').length > 0) {
$(event.target).closest('.dropdown').find('.dropdown-submenu .dropdown-menu').removeClass('show');
}
},
bindLeftTree: function() {
+16 -59
View File
@@ -55,7 +55,7 @@ define([
module: this.module || pgAdmin.Browser,
cb: this.callback,
data: this.data,
}).addClass('menu-link');
}).addClass('dropdown-item');
this.is_disabled = this.disabled(node, item);
if (this.icon) {
@@ -64,53 +64,14 @@ define([
}));
}
url.addClass((this.is_disabled ? ' disabled' : ''));
var textSpan = $('<span data-test="menu-item-text"></span>').text(' ' + this.label);
url.append(textSpan);
this.$el = $('<li/>')
.addClass('menu-item' + (this.is_disabled ? ' disabled' : ''))
.append(url);
this.applyStyle();
this.$el = $('<li/>').append(url);
},
applyDisabledStyle: function() {
var span = this.$el.find('span');
var icon = this.$el.find('i');
span.addClass('text-gray');
span.removeClass('text-fg-inverse');
icon.addClass('text-gray');
icon.removeClass('text-fg-inverse');
},
applyEnabledStyle: function() {
var element = this.$el;
var span = this.$el.find('span');
span.addClass('text-fg-inverse');
span.removeClass('text-gray');
element.find('i').addClass('text-fg-inverse');
element.find('i').removeClass('text-gray');
span.on('mouseover',() => {
element.addClass('bg-gray-dark');
});
span.on('mouseout',() => {
element.removeClass('bg-gray-dark');
});
},
applyStyle: function() {
if (this.is_disabled) {
this.applyDisabledStyle();
} else {
this.applyEnabledStyle();
}
},
/*
* Updates the enable/disable state of the menu-item based on the current
* selection using the disabled function. This also creates a object
@@ -118,17 +79,13 @@ define([
*/
update: function(node, item) {
if (this.$el && !this.$el.hasClass('disabled')) {
this.$el.addClass('disabled');
if (this.$el && !this.$el.find('.dropdown-item').hasClass('disabled')) {
this.$el.find('.dropdown-item').addClass('disabled');
}
this.is_disabled = this.disabled(node, item);
if (this.$el && !this.is_disabled) {
this.$el.removeClass('disabled');
}
if (this.$el) {
this.applyStyle();
this.$el.find('.dropdown-item').removeClass('disabled');
}
this.context = {
@@ -208,16 +165,16 @@ define([
pgAdmin.Browser.MenuGroup = function(opts, items, prev, ctx) {
var template = _.template([
'<% if (above) { %><hr><% } %>',
'<li class="menu-item dropdown dropdown-submenu">',
' <a href="#" class="dropdown-toggle" data-toggle="dropdown">',
'<% if (above) { %><li class="dropdown-divider"></li><% } %>',
'<li class="dropdown-submenu">',
' <a href="#" class="dropdown-item">',
' <% if (icon) { %><i class="<%= icon %>"></i><% } %>',
' <span><%= label %></span>',
' <span class="text-white"><%= label %></span>',
' </a>',
' <ul class="dropdown-menu navbar-inverse">',
' <ul class="dropdown-menu bg-dark">',
' </ul>',
'</li>',
'<% if (below) { %><hr><% } %>',
'<% if (below) { %><li class="dropdown-divider"></li><% } %>',
].join('\n')),
data = {
'label': opts.label,
@@ -393,7 +350,7 @@ define([
Menu.DEFAULTS = {};
Menu.prototype.toggle = function(ev) {
var $parent = this.$element.closest('.menu-item');
var $parent = this.$element.closest('.dropdown-item');
if ($parent.hasClass('disabled')) {
ev.preventDefault();
return false;
@@ -446,8 +403,8 @@ define([
$(document)
.on('click.pg.menu.data-api', '[data-toggle^="pg-menu"]', function(ev) {
var $menu = $(ev.target);
if (!$menu.hasClass('menu-link'))
$menu = $menu.closest('.menu-link');
if (!$menu.hasClass('dropdown-item'))
$menu = $menu.closest('.dropdown-item');
Plugin.call($menu, 'toggle', ev);
})
.on(
+17 -6
View File
@@ -1,11 +1,11 @@
define('pgadmin.browser.node', [
'sources/tree/pgadmin_tree_node',
'sources/tree/pgadmin_tree_node', 'sources/url_for',
'sources/gettext', 'jquery', 'underscore', 'underscore.string', 'sources/pgadmin',
'pgadmin.browser.menu', 'backbone', 'pgadmin.alertifyjs', 'pgadmin.browser.datamodel',
'backform', 'sources/browser/generate_url', 'sources/utils', 'pgadmin.browser.utils',
'pgadmin.backform',
], function(
pgadminTreeNode,
pgadminTreeNode, url_for,
gettext, $, _, S, pgAdmin, Menu, Backbone, Alertify, pgBrowser, Backform, generateUrl, commonUtils
) {
@@ -395,7 +395,7 @@ define('pgadmin.browser.node', [
success: function() {
// Clear timeout and remove message
clearTimeout(timer);
$msgDiv.addClass('hidden');
$msgDiv.addClass('d-none');
// We got the latest attributes of the object. Render the view
// now.
@@ -479,7 +479,7 @@ define('pgadmin.browser.node', [
isCloseable: true,
isPrivate: true,
elContainer: true,
content: '<div class="obj_properties"><div class="alert alert-info pg-panel-message">' + gettext('Please wait while we fetch information about the node from the server...') + '</div></div>',
content: '<div class="obj_properties container-fluid"><div class="alert alert-info pg-panel-message">' + gettext('Please wait while we fetch information about the node from the server...') + '</div></div>',
onCreate: function(myPanel, $container) {
$container.addClass('pg-no-overflow');
},
@@ -849,7 +849,6 @@ define('pgadmin.browser.node', [
}
}
},
added: function(item, data, browser) {
var b = browser || pgBrowser,
t = b.tree,
@@ -876,6 +875,8 @@ define('pgadmin.browser.node', [
);
}
pgBrowser.Events.trigger('pgadmin:browser:tree:expand-from-previous-tree-state',
item);
pgBrowser.Node.callbacks.change_server_background(item, data);
},
// Callback called - when a node is selected in browser tree.
@@ -905,6 +906,8 @@ define('pgadmin.browser.node', [
}
}
pgBrowser.Events.trigger('pgadmin:browser:tree:update-tree-state',
item);
return true;
},
removed: function(item) {
@@ -958,6 +961,14 @@ define('pgadmin.browser.node', [
},
});
},
opened: function(item) {
pgBrowser.Events.trigger('pgadmin:browser:tree:update-tree-state',
item);
},
closed: function(item) {
pgBrowser.Events.trigger('pgadmin:browser:tree:remove-from-tree-state',
item);
},
},
/**********************************************************************
* A hook (not a callback) to show object properties in given HTML
@@ -972,7 +983,7 @@ define('pgadmin.browser.node', [
j = panel.$container.find('.obj_properties').first(),
view = j.data('obj-view'),
content = $('<div tabindex="1"></div>')
.addClass('pg-prop-content col-xs-12');
.addClass('pg-prop-content col-12');
// Handle key press events for Cancel, save and help button
var handleKeyDown = function(event, context) {
+1 -1
View File
@@ -38,7 +38,7 @@ define([
].join(''));
let $btnContainer = $(
'<div style="">'
'<div class="select2-select-all-adapter-container">'
).append($selectAll).append($unselectAll);
if (!this.$element.prop('multiple')) {
@@ -94,6 +94,9 @@ _.extend(pgBrowser, {
modifyAnimation.modifyAlertifyAnimation(self);
}
// Initialize Tree saving/reloading
pgBrowser.browserTreeState.init();
/* Once the cache is loaded after changing the preferences,
* notify the modules of the change
*/
+1 -1
View File
@@ -101,7 +101,7 @@ define([
' <div class="wizard-progress-bar"><% if (show_progress_bar) { %>' +
' <p class="alert alert-info col-sm-12"><%= show_progress_bar %></p><% } %>' +
' </div>' +
' <div class="wizard-right-panel_content col-xs-12">' +
' <div class="wizard-right-panel_content col-12">' +
' </div>' +
' </div>' +
' </div>' +
@@ -45,8 +45,8 @@ samp,
}
.pg-toolbar-btn:hover {
border: 1px solid $color-gray-light !important;
background-color: $color-gray-lighter !important;
border: none;
background-color: $color-bg !important;
}
.pg-toolbar-btn {
@@ -55,5 +55,5 @@ samp,
background-color: $color-bg !important;
font-size: 14px !important;
color: $color-gray-darker !important;
border: 1px solid $color-gray-light !important;
border: none;
}
@@ -34,7 +34,7 @@ require.onResourceLoad = function (context, map, depMaps) {
if (loadingStatusEl) {
if (!context) {
// we will call onResourceLoad(false) by ourselves when requirejs
// is not loading anything hide the indicator and exit
// is not loading anything d-none the indicator and exit
setTimeout(function() {
if (panel != null) {
try{
@@ -80,10 +80,9 @@ window.onload = function(e){
setTimeout(function() {
var gravatarImg = {{ IMG.PREPARE_HTML()|safe }}
//$('#navbar-menu .navbar-right > li > a').html(gravatarImg);
var navbarRight = document.getElementById("navbar-menu").getElementsByClassName("navbar-right")[0];
if (navbarRight) {
var list = navbarRight.getElementsByTagName("LI")[0];
list.getElementsByTagName("a")[0].innerHTML = gravatarImg;
var navbarUser = document.getElementById("navbar-user");
if (navbarUser) {
navbarUser.innerHTML = gravatarImg;
}
}, 1000);
};
@@ -93,85 +92,77 @@ window.onload = function(e){
{% block body %}
<div id="pg-spinner">
<div class="pg-sp-content">
<div class="row"><div class="col-xs-12 pg-sp-icon fa fa-spinner fa-pulse"></div></div>
<div class="row"><div class="col-xs-12 pg-sp-text">{{ _('Loading {0} v{1}...').format(config.APP_NAME, config.APP_VERSION) }}</div></div>
<div class="row"><div class="col-12 pg-sp-icon fa fa-spinner fa-pulse"></div></div>
<div class="row"><div class="col-12 pg-sp-text">{{ _('Loading {0} v{1}...').format(config.APP_NAME, config.APP_VERSION) }}</div></div>
</div>
</div>
<nav class="navbar-inverse navbar-fixed-top pg-navbar">
<div class="container-fluid">
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar-menu">
<span class="sr-only">{{ _('Toggle navigation') }}</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand pgadmin_header_logo" onClick="return false;" href="{{ '#' }}"
title="{{ config.APP_NAME }} {{ _('logo') }}">
<i class="app-icon {{ config.APP_ICON }}"></i>
<span>&nbsp;{{ config.APP_NAME }}</span>
</a>
</div>
<div class="collapse navbar-collapse" id="navbar-menu" role="navigation">
<ul class="nav navbar-nav">
<li id="mnu_file" class="dropdown hide">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false">{{
_('File') }} <span class="caret"></span></a>
<ul class="dropdown-menu navbar-inverse" role="menu"></ul>
</li>
<li id="mnu_edit" class="dropdown hide">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false">{{
_('Edit') }} <span class="caret"></span></a>
<ul class="dropdown-menu navbar-inverse" role="menu"></ul>
</li>
<li id="mnu_obj" class="dropdown ">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false">{{
_('Object') }} <span class="caret"></span></a>
<ul class="dropdown-menu navbar-inverse" role="menu"></ul>
</li>
<li id="mnu_management" class="dropdown hide">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false">{{
_('Management') }} <span class="caret"></span></a>
<ul class="dropdown-menu navbar-inverse" role="menu"></ul>
</li>
<li id="mnu_tools" class="dropdown hide">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false">{{
_('Tools') }} <span class="caret"></span></a>
<ul class="dropdown-menu navbar-inverse" role="menu"></ul>
</li>
<li id="mnu_help" class="dropdown hide">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false">{{
_('Help') }} <span class="caret"></span></a>
<ul class="dropdown-menu navbar-inverse" role="menu"></ul>
</li>
</ul>
{% if config.SERVER_MODE %}
<ul class="nav navbar-nav navbar-right">
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false"></a>
<ul class="dropdown-menu navbar-inverse">
<li>
<a href="#" onclick="pgAdmin.Browser.UserManagement.change_password(
'{{ url_for('browser.change_password') }}'
)">
{{ _('Change Password') }}
</a>
</li>
<li class="divider"></li>
{% if is_admin %}
<li><a href="#" onclick="pgAdmin.Browser.UserManagement.show_users()">{{ _('Users') }}</a></li>
<li class="divider"></li>
{% endif %}
<li><a href="{{ url_for('security.logout') }}?next={{url_for('browser.index')}}">{{ _('Logout') }}</a></li>
</ul>
</li>
</ul>
{% endif %}
</div>
<nav class="navbar fixed-top navbar-expand-lg navbar-dark bg-dark pg-navbar">
<a class="navbar-brand pgadmin_header_logo" onClick="return false;" href="{{ '#' }}"
title="{{ config.APP_NAME }} {{ _('logo') }}">
<i class="app-icon {{ config.APP_ICON }}"></i>
<span>&nbsp;{{ config.APP_NAME }}</span>
</a>
<button type="button" class="navbar-toggler collapsed" data-toggle="collapse" data-target="#navbar-menu" aria-controls="navbar-menu">
<span class="sr-only">{{ _('Toggle navigation') }}</span>
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbar-menu" role="navigation">
<ul class="navbar-nav mr-auto">
<li id="mnu_file" class="nav-item active dropdown d-none">
<a href="#" class="nav-link dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false">{{
_('File') }} <span class="caret"></span></a>
<ul class="dropdown-menu" role="menu"></ul>
</li>
<li id="mnu_edit" class="nav-item active dropdown d-none">
<a href="#" class="nav-link dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false">{{
_('Edit') }} <span class="caret"></span></a>
<ul class="dropdown-menu" role="menu"></ul>
</li>
<li id="mnu_obj" class="nav-item active dropdown ">
<a href="#" class="nav-link dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false">{{
_('Object') }} <span class="caret"></span></a>
<ul class="dropdown-menu" role="menu"></ul>
</li>
<li id="mnu_management" class="nav-item active dropdown d-none">
<a href="#" class="nav-link dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false">{{
_('Management') }} <span class="caret"></span></a>
<ul class="dropdown-menu" role="menu"></ul>
</li>
<li id="mnu_tools" class="nav-item active dropdown d-none">
<a href="#" class="nav-link dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false">{{
_('Tools') }} <span class="caret"></span></a>
<ul class="dropdown-menu" role="menu"></ul>
</li>
<li id="mnu_help" class="nav-item active dropdown d-none">
<a href="#" class="nav-link dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false">{{
_('Help') }} <span class="caret"></span></a>
<ul class="dropdown-menu" role="menu"></ul>
</li>
</ul>
{% if config.SERVER_MODE %}
<ul class="navbar-nav mx-2">
<li class="nav-item active dropdown">
<a href="#" class="nav-link dropdown-toggle" data-toggle="dropdown"
role="button" aria-expanded="false" id="navbar-user"></a>
<ul class="dropdown-menu dropdown-menu-right" role="menu">
<li>
<a class="dropdown-item" href="#" onclick="pgAdmin.Browser.UserManagement.change_password(
'{{ url_for('browser.change_password') }}'
)">
{{ _('Change Password') }}
</a>
</li>
<li class="dropdown-divider"></li>
{% if is_admin %}
<li><a class="dropdown-item" href="#" onclick="pgAdmin.Browser.UserManagement.show_users()">{{ _('Users') }}</a></li>
<li class="dropdown-divider"></li>
{% endif %}
<li><a class="dropdown-item" href="{{ url_for('security.logout') }}?next={{url_for('browser.index')}}">{{ _('Logout') }}</a></li>
</ul>
</li>
</ul>
{% endif %}
</div>
</nav>
@@ -94,13 +94,13 @@ class ChangePasswordTestCase(BaseTestGenerator):
if 'valid_password' in dir(self):
response = self.tester.post(
'/user_management/user/',
data=dict(
data=json.dumps(dict(
email=self.username,
newPassword=self.password,
confirmPassword=self.password,
active=1,
active=True,
role="2"
),
)),
follow_redirects=True
)
user_id = json.loads(response.data.decode('utf-8'))['id']
@@ -43,6 +43,10 @@ class TestLoginUserImage(BaseTestGenerator):
"Logout first if already logged in"
utils.logout_tester_account(cls.tester)
# No need to call baseclass setup function
def setUp(self):
pass
def runTest(self):
# Login and check type of image in response
response = self.tester.post(
@@ -15,6 +15,7 @@ from pgadmin import create_app
class InternalVersionTestCase(BaseTestGenerator):
"""
This test case verifies application creation with new versions parameters
"""
scenarios = [
('TestCase with INTERNAL_VERSION_PARAM none', dict(
@@ -47,12 +48,14 @@ class InternalVersionTestCase(BaseTestGenerator):
self.config_bak = config
def runTest(self):
# assign config file parameters new values
config.APP_VERSION_INT = self.app_version
config.INTERNAL_VERSION_PARAM = self.version_param
config.INTERNAL_VERSION_EXTN = self.version_extn
config.APP_VERSION_PARAM = self.version_param
config.APP_VERSION_EXTN = self.version_extn
version_string = "?{0}={1}".format(config.INTERNAL_VERSION_PARAM,
config.APP_VERSION_INT)
version_string = "?{0}={1}".format(self.version_param,
self.app_version)
# create application
app = create_app()
with app.app_context(), app.test_request_context():
url = url_for(self.endpoint, filename=self.filename)
+4
View File
@@ -86,6 +86,10 @@ class LoginTestCase(BaseTestGenerator):
"""
utils.logout_tester_account(cls.tester)
# No need to call base class setup function
def setUp(self):
pass
def runTest(self):
"""This function checks login functionality."""
response = self.tester.post(
+4
View File
@@ -28,6 +28,10 @@ class LogoutTest(BaseTestGenerator):
def setUpClass(cls):
pass
# No need to call base class setup function
def setUp(self):
pass
def runTest(self):
"""This function checks the logout functionality."""
@@ -42,6 +42,10 @@ class ResetPasswordTestCase(BaseTestGenerator):
def setUpClass(cls):
logout_tester_account(cls.tester)
# No need to call baseclass setup function
def setUp(self):
pass
def runTest(self):
"""This function checks reset password functionality."""
@@ -57,6 +57,10 @@ class VersionInRangeTestCase(BaseTestGenerator):
def setUpClass(cls):
pass
# No need to call base class setup function
def setUp(self):
pass
def runTest(self):
"""This function will check version in range functionality."""
if self.scenario == 1:
@@ -1,5 +1,6 @@
.dashboard-container {
padding: 15px;
padding-top: 15px;
padding-bottom: 15px;
}
.dashboard-row {
@@ -27,27 +27,13 @@
padding-bottom: 40px;
}
.dashboard-tab-container .subnode-dialog {
background-color: $color-bg;
border: 0px solid $color-gray-lighter;
}
.dashboard-tab-container .sub-node-form {
padding: 0px 0px 0px 1px;
background-color: $color-gray-lighter;
}
.dashboard-tab-container .subnode-body {
background-color: $color-gray-lighter;
}
.dashboard-tab-container .sub-node-form > .nav-tabs {
background-color: $color-gray-lighter !important;
}
.dashboard-tab-container .subnode-dialog .nav > li > a {
font-weight: bold;
border-color: $color-gray-lighter;
}
.dashboard-tab-container .subnode-dialog .nav-tabs > li.active > a,
@@ -63,7 +49,3 @@
background-color: $color-gray-lighter !important;
border-bottom-color: $color-gray-lighter;
}
.dashboard-tab-container .subnode-dialog .tab-pane {
border: 1px solid $color-gray-lighter;
}
Loaded 100 of 220 files, more files were not shown because too many files have changed in this diff. Show more