Dashboards v1
parent
07e0de5399
commit
0628ee0425
|
|
@ -96,7 +96,7 @@ class ServerGroupView(NodeView):
|
||||||
|
|
||||||
for sg in ServerGroup.query.filter_by(
|
for sg in ServerGroup.query.filter_by(
|
||||||
user_id=current_user.id
|
user_id=current_user.id
|
||||||
).order_by(name):
|
).order_by('name'):
|
||||||
res.append({
|
res.append({
|
||||||
'id': sg.id,
|
'id': sg.id,
|
||||||
'name': sg.name
|
'name': sg.name
|
||||||
|
|
@ -255,7 +255,7 @@ class ServerGroupView(NodeView):
|
||||||
|
|
||||||
for group in groups:
|
for group in groups:
|
||||||
nodes.append(
|
nodes.append(
|
||||||
self.generate_browser_node(
|
self.blueprint.generate_browser_node(
|
||||||
"%d" % (group.id), None,
|
"%d" % (group.id), None,
|
||||||
group.name,
|
group.name,
|
||||||
"icon-%s" % self.node_type,
|
"icon-%s" % self.node_type,
|
||||||
|
|
|
||||||
|
|
@ -333,8 +333,9 @@ class ServerNode(PGChildNodeView):
|
||||||
from pgadmin.utils.driver import get_driver
|
from pgadmin.utils.driver import get_driver
|
||||||
manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(sid)
|
manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(sid)
|
||||||
conn = manager.connection()
|
conn = manager.connection()
|
||||||
|
connected = conn.connected()
|
||||||
|
|
||||||
if conn.connected():
|
if connected:
|
||||||
for arg in (
|
for arg in (
|
||||||
'host', 'port', 'db', 'username', 'sslmode', 'role'
|
'host', 'port', 'db', 'username', 'sslmode', 'role'
|
||||||
):
|
):
|
||||||
|
|
@ -373,11 +374,16 @@ class ServerNode(PGChildNodeView):
|
||||||
|
|
||||||
return make_json_response(
|
return make_json_response(
|
||||||
success=1,
|
success=1,
|
||||||
data={
|
node=self.blueprint.generate_browser_node(
|
||||||
'id': server.id,
|
"%d" % (server.id), server.servergroup_id,
|
||||||
'gid': server.servergroup_id,
|
server.name,
|
||||||
'icon': 'icon-server-not-connected'
|
"icon-server-not-connected" if not connected else
|
||||||
}
|
"icon-{0}".format(manager.server_type),
|
||||||
|
True,
|
||||||
|
self.node_type,
|
||||||
|
connected=False,
|
||||||
|
server_type='pg' # default server type
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
def list(self, gid):
|
def list(self, gid):
|
||||||
|
|
@ -490,7 +496,7 @@ class ServerNode(PGChildNodeView):
|
||||||
try:
|
try:
|
||||||
server = Server(
|
server = Server(
|
||||||
user_id=current_user.id,
|
user_id=current_user.id,
|
||||||
servergroup_id=gid,
|
servergroup_id=data[u'gid'] if u'gid' in data else gid,
|
||||||
name=data[u'name'],
|
name=data[u'name'],
|
||||||
host=data[u'host'],
|
host=data[u'host'],
|
||||||
port=data[u'port'],
|
port=data[u'port'],
|
||||||
|
|
@ -505,7 +511,7 @@ class ServerNode(PGChildNodeView):
|
||||||
|
|
||||||
return jsonify(
|
return jsonify(
|
||||||
node=self.blueprint.generate_browser_node(
|
node=self.blueprint.generate_browser_node(
|
||||||
"%d" % (server.id), gid,
|
"%d" % (server.id), server.servergroup_id,
|
||||||
server.name,
|
server.name,
|
||||||
"icon-server-not-connected",
|
"icon-server-not-connected",
|
||||||
True,
|
True,
|
||||||
|
|
|
||||||
|
|
@ -148,6 +148,7 @@ function($, _, S, pgAdmin, pgBrowser, alertify) {
|
||||||
},
|
},
|
||||||
model: pgAdmin.Browser.Node.Model.extend({
|
model: pgAdmin.Browser.Node.Model.extend({
|
||||||
defaults: {
|
defaults: {
|
||||||
|
gid: undefined,
|
||||||
id: undefined,
|
id: undefined,
|
||||||
name: null,
|
name: null,
|
||||||
sslmode: 'prefer',
|
sslmode: 'prefer',
|
||||||
|
|
@ -157,8 +158,21 @@ function($, _, S, pgAdmin, pgBrowser, alertify) {
|
||||||
username: '{{ username }}',
|
username: '{{ username }}',
|
||||||
role: null
|
role: null
|
||||||
},
|
},
|
||||||
|
// Default values!
|
||||||
|
initialize: function(attrs, args) {
|
||||||
|
var isNew = (_.size(attrs) === 0);
|
||||||
|
|
||||||
|
if (isNew) {
|
||||||
|
this.set({'gid': args.node_info['server-group']._id});
|
||||||
|
}
|
||||||
|
pgAdmin.Browser.Node.Model.prototype.initialize.apply(this, arguments);
|
||||||
|
},
|
||||||
schema: [{
|
schema: [{
|
||||||
id: 'id', label: '{{ _('ID') }}', type: 'int', mode: ['properties']
|
id: 'id', label: '{{ _('ID') }}', type: 'int', mode: ['properties']
|
||||||
|
},{
|
||||||
|
id: 'gid', label: '{{ _('Server Group') }}', type: 'int',
|
||||||
|
control: 'node-list-by-id', node: 'server-group',
|
||||||
|
mode: ['create', 'edit'], select2: {allowClear: false}
|
||||||
},{
|
},{
|
||||||
id: 'name', label:'{{ _('Name') }}', type: 'text',
|
id: 'name', label:'{{ _('Name') }}', type: 'text',
|
||||||
mode: ['properties', 'edit', 'create']
|
mode: ['properties', 'edit', 'create']
|
||||||
|
|
|
||||||
|
|
@ -394,8 +394,8 @@ function(require, $, _, S, Bootstrap, pgAdmin, alertify, CodeMirror) {
|
||||||
* We can remove it from the Browser.scripts object as
|
* We can remove it from the Browser.scripts object as
|
||||||
* these're about to be loaded.
|
* these're about to be loaded.
|
||||||
*
|
*
|
||||||
* This will make sure that - we do check for the script for
|
* This will make sure that we check for the script to be
|
||||||
* loading only once.
|
* loaded only once.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
delete obj.scripts[d._type];
|
delete obj.scripts[d._type];
|
||||||
|
|
@ -405,7 +405,7 @@ function(require, $, _, S, Bootstrap, pgAdmin, alertify, CodeMirror) {
|
||||||
if (!s.loaded) {
|
if (!s.loaded) {
|
||||||
require([s.name], function(m) {
|
require([s.name], function(m) {
|
||||||
s.loaded = true;
|
s.loaded = true;
|
||||||
// Call the initialize (if present)
|
// Call the initializer (if present)
|
||||||
if (m && m.init && typeof m.init == 'function') {
|
if (m && m.init && typeof m.init == 'function') {
|
||||||
try {
|
try {
|
||||||
m.init();
|
m.init();
|
||||||
|
|
|
||||||
|
|
@ -10,14 +10,20 @@
|
||||||
"""A blueprint module implementing the dashboard frame."""
|
"""A blueprint module implementing the dashboard frame."""
|
||||||
MODULE_NAME = 'dashboard'
|
MODULE_NAME = 'dashboard'
|
||||||
|
|
||||||
|
from functools import wraps
|
||||||
|
|
||||||
from config import PG_DEFAULT_DRIVER
|
from config import PG_DEFAULT_DRIVER
|
||||||
from flask import render_template, url_for, Response
|
from flask import render_template, url_for, Response
|
||||||
from flask.ext.babel import gettext
|
from flask.ext.babel import gettext
|
||||||
from flask.ext.security import login_required
|
from flask.ext.security import login_required
|
||||||
from pgadmin.utils import PgAdminModule
|
from pgadmin.utils import PgAdminModule
|
||||||
|
from pgadmin.utils.ajax import make_response as ajax_response, internal_server_error
|
||||||
from pgadmin.utils.ajax import precondition_required
|
from pgadmin.utils.ajax import precondition_required
|
||||||
from pgadmin.utils.driver import get_driver
|
from pgadmin.utils.driver import get_driver
|
||||||
from pgadmin.utils.menu import Panel
|
from pgadmin.utils.menu import Panel
|
||||||
|
from pgadmin.utils.preferences import Preferences
|
||||||
|
|
||||||
|
server_info = {}
|
||||||
|
|
||||||
|
|
||||||
class DashboardModule(PgAdminModule):
|
class DashboardModule(PgAdminModule):
|
||||||
|
|
@ -34,6 +40,16 @@ class DashboardModule(PgAdminModule):
|
||||||
'when': None
|
'when': None
|
||||||
}]
|
}]
|
||||||
|
|
||||||
|
def get_own_stylesheets(self):
|
||||||
|
"""
|
||||||
|
Returns:
|
||||||
|
list: the stylesheets used by this module.
|
||||||
|
"""
|
||||||
|
stylesheets = [
|
||||||
|
url_for('dashboard.static', filename='css/dashboard.css')
|
||||||
|
]
|
||||||
|
return stylesheets
|
||||||
|
|
||||||
def get_panels(self):
|
def get_panels(self):
|
||||||
return [
|
return [
|
||||||
Panel(
|
Panel(
|
||||||
|
|
@ -41,11 +57,60 @@ class DashboardModule(PgAdminModule):
|
||||||
priority=1,
|
priority=1,
|
||||||
title=gettext('Dashboard'),
|
title=gettext('Dashboard'),
|
||||||
icon='fa fa-tachometer',
|
icon='fa fa-tachometer',
|
||||||
content=url_for('dashboard.index'),
|
content='',
|
||||||
isCloseable=False,
|
isCloseable=False,
|
||||||
isPrivate=True)
|
isPrivate=True,
|
||||||
|
isIframe=False)
|
||||||
]
|
]
|
||||||
|
|
||||||
|
def register_preferences(self):
|
||||||
|
"""
|
||||||
|
register_preferences
|
||||||
|
Register preferences for this module.
|
||||||
|
"""
|
||||||
|
# Register options for the PG and PPAS help paths
|
||||||
|
self.dashboard_preference = Preferences('dashboards', gettext('Dashboards'))
|
||||||
|
|
||||||
|
self.session_stats_refresh = self.dashboard_preference.register(
|
||||||
|
'dashboards', 'session_stats_refresh',
|
||||||
|
gettext("Session statistics refresh rate"), 'integer',
|
||||||
|
1, min_val=1, max_val=999999,
|
||||||
|
category_label=gettext('Graphs'),
|
||||||
|
help_str=gettext('The number of seconds between graph samples.')
|
||||||
|
)
|
||||||
|
|
||||||
|
self.session_stats_refresh = self.dashboard_preference.register(
|
||||||
|
'dashboards', 'tps_stats_refresh',
|
||||||
|
gettext("Transaction throughput refresh rate"), 'integer',
|
||||||
|
1, min_val=1, max_val=999999,
|
||||||
|
category_label=gettext('Graphs'),
|
||||||
|
help_str=gettext('The number of seconds between graph samples.')
|
||||||
|
)
|
||||||
|
|
||||||
|
self.session_stats_refresh = self.dashboard_preference.register(
|
||||||
|
'dashboards', 'ti_stats_refresh',
|
||||||
|
gettext("Tuples in refresh rate"), 'integer',
|
||||||
|
1, min_val=1, max_val=999999,
|
||||||
|
category_label=gettext('Graphs'),
|
||||||
|
help_str=gettext('The number of seconds between graph samples.')
|
||||||
|
)
|
||||||
|
|
||||||
|
self.session_stats_refresh = self.dashboard_preference.register(
|
||||||
|
'dashboards', 'to_stats_refresh',
|
||||||
|
gettext("Tuples out refresh rate"), 'integer',
|
||||||
|
1, min_val=1, max_val=999999,
|
||||||
|
category_label=gettext('Graphs'),
|
||||||
|
help_str=gettext('The number of seconds between graph samples.')
|
||||||
|
)
|
||||||
|
|
||||||
|
self.session_stats_refresh = self.dashboard_preference.register(
|
||||||
|
'dashboards', 'bio_stats_refresh',
|
||||||
|
gettext("Block I/O statistics refresh rate"), 'integer',
|
||||||
|
1, min_val=1, max_val=999999,
|
||||||
|
category_label=gettext('Graphs'),
|
||||||
|
help_str=gettext('The number of seconds between graph samples.')
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
blueprint = DashboardModule(MODULE_NAME, __name__)
|
blueprint = DashboardModule(MODULE_NAME, __name__)
|
||||||
|
|
||||||
|
|
@ -58,21 +123,28 @@ def check_precondition(f):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@wraps(f)
|
@wraps(f)
|
||||||
def wrap(**kwargs):
|
def wrap(*args, **kwargs):
|
||||||
# Here args[0] will hold self & kwargs will hold gid,sid,did
|
# Here args[0] will hold self & kwargs will hold gid,sid,did
|
||||||
manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(
|
|
||||||
|
server_info.clear()
|
||||||
|
server_info['manager'] = get_driver(
|
||||||
|
PG_DEFAULT_DRIVER).connection_manager(
|
||||||
kwargs['sid']
|
kwargs['sid']
|
||||||
)
|
)
|
||||||
conn = manager.connection(did=kwargs['did'] if 'did' in kwargs and kwargs['did'] != 0 else None)
|
server_info['conn'] = server_info['manager'].connection()
|
||||||
|
|
||||||
# If DB not connected then return error to browser
|
# If DB not connected then return error to browser
|
||||||
if not conn.connected():
|
if not server_info['conn'].connected():
|
||||||
return precondition_required(
|
return precondition_required(
|
||||||
gettext(
|
gettext("Connection to the server has been lost!")
|
||||||
"Connection to the server has been lost!"
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
return f(obj, **kwargs)
|
# Set template path for sql scripts
|
||||||
|
server_info['server_type'] = server_info['manager'].server_type
|
||||||
|
server_info['version'] = server_info['manager'].version
|
||||||
|
server_info['template_path'] = 'dashboard/sql/9.1_plus'
|
||||||
|
|
||||||
|
return f(*args, **kwargs)
|
||||||
|
|
||||||
return wrap
|
return wrap
|
||||||
|
|
||||||
|
|
@ -91,10 +163,193 @@ def script():
|
||||||
@blueprint.route('/<int:sid>/<int:did>')
|
@blueprint.route('/<int:sid>/<int:did>')
|
||||||
@login_required
|
@login_required
|
||||||
def index(sid=None, did=None):
|
def index(sid=None, did=None):
|
||||||
|
"""
|
||||||
|
Renders the welcome, server or database dashboard
|
||||||
|
Args:
|
||||||
|
sid: Server ID
|
||||||
|
did: Database ID
|
||||||
|
|
||||||
|
Returns: Welcome/Server/database dashboard
|
||||||
|
|
||||||
|
"""
|
||||||
|
rates = {}
|
||||||
|
|
||||||
|
prefs = Preferences.module('dashboards')
|
||||||
|
|
||||||
|
session_stats_refresh_pref = prefs.preference('session_stats_refresh')
|
||||||
|
rates['session_stats_refresh'] = session_stats_refresh_pref.get()
|
||||||
|
tps_stats_refresh_pref = prefs.preference('tps_stats_refresh')
|
||||||
|
rates['tps_stats_refresh'] = tps_stats_refresh_pref.get()
|
||||||
|
ti_stats_refresh_pref = prefs.preference('ti_stats_refresh')
|
||||||
|
rates['ti_stats_refresh'] = ti_stats_refresh_pref.get()
|
||||||
|
to_stats_refresh_pref = prefs.preference('to_stats_refresh')
|
||||||
|
rates['to_stats_refresh'] = to_stats_refresh_pref.get()
|
||||||
|
bio_stats_refresh_pref = prefs.preference('bio_stats_refresh')
|
||||||
|
rates['bio_stats_refresh'] = bio_stats_refresh_pref.get()
|
||||||
|
|
||||||
# Show the appropriate dashboard based on the identifiers passed to us
|
# Show the appropriate dashboard based on the identifiers passed to us
|
||||||
if sid is None and did is None:
|
if sid is None and did is None:
|
||||||
return render_template('/dashboard/welcome_dashboard.html')
|
return render_template('/dashboard/welcome_dashboard.html')
|
||||||
if did is None:
|
if did is None:
|
||||||
return render_template('/dashboard/server_dashboard.html', sid=sid)
|
return render_template('/dashboard/server_dashboard.html', sid=sid, rates=rates)
|
||||||
else:
|
else:
|
||||||
return render_template('/dashboard/database_dashboard.html', sid=sid, did=did)
|
return render_template('/dashboard/database_dashboard.html', sid=sid, did=did, rates=rates)
|
||||||
|
|
||||||
|
|
||||||
|
def get_data(sid, did, template):
|
||||||
|
"""
|
||||||
|
Generic function to get server stats based on an SQL template
|
||||||
|
Args:
|
||||||
|
sid: The server ID
|
||||||
|
did: The database ID
|
||||||
|
template: The SQL template name
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
# Allow no server ID to be specified (so we can generate a route in JS)
|
||||||
|
# but throw an error if it's actually called.
|
||||||
|
if not sid:
|
||||||
|
return internal_server_error(errormsg='Server ID not specified.')
|
||||||
|
|
||||||
|
# Get the db connection
|
||||||
|
manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(sid)
|
||||||
|
conn = manager.connection()
|
||||||
|
|
||||||
|
sql = render_template(
|
||||||
|
"/".join([server_info['template_path'], template]), did=did
|
||||||
|
)
|
||||||
|
status, res = conn.execute_dict(sql)
|
||||||
|
|
||||||
|
if not status:
|
||||||
|
return internal_server_error(errormsg=res)
|
||||||
|
|
||||||
|
return ajax_response(
|
||||||
|
response=res['rows'],
|
||||||
|
status=200
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@blueprint.route('/session_stats/')
|
||||||
|
@blueprint.route('/session_stats/<int:sid>')
|
||||||
|
@blueprint.route('/session_stats/<int:sid>/<int:did>')
|
||||||
|
@login_required
|
||||||
|
@check_precondition
|
||||||
|
def session_stats(sid=None, did=None):
|
||||||
|
"""
|
||||||
|
This function returns server session statistics
|
||||||
|
:param sid: server id
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
return get_data(sid, did, 'session_stats.sql')
|
||||||
|
|
||||||
|
|
||||||
|
@blueprint.route('/tps_stats/')
|
||||||
|
@blueprint.route('/tps_stats/<int:sid>')
|
||||||
|
@blueprint.route('/tps_stats/<int:sid>/<int:did>')
|
||||||
|
@login_required
|
||||||
|
@check_precondition
|
||||||
|
def tps_stats(sid=None, did=None):
|
||||||
|
"""
|
||||||
|
This function returns server TPS throughput
|
||||||
|
:param sid: server id
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
return get_data(sid, did, 'tps_stats.sql')
|
||||||
|
|
||||||
|
|
||||||
|
@blueprint.route('/ti_stats/')
|
||||||
|
@blueprint.route('/ti_stats/<int:sid>')
|
||||||
|
@blueprint.route('/ti_stats/<int:sid>/<int:did>')
|
||||||
|
@login_required
|
||||||
|
@check_precondition
|
||||||
|
def ti_stats(sid=None, did=None):
|
||||||
|
"""
|
||||||
|
This function returns server tuple input statistics
|
||||||
|
:param sid: server id
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
return get_data(sid, did, 'ti_stats.sql')
|
||||||
|
|
||||||
|
|
||||||
|
@blueprint.route('/to_stats/')
|
||||||
|
@blueprint.route('/to_stats/<int:sid>')
|
||||||
|
@blueprint.route('/to_stats/<int:sid>/<int:did>')
|
||||||
|
@login_required
|
||||||
|
@check_precondition
|
||||||
|
def to_stats(sid=None, did=None):
|
||||||
|
"""
|
||||||
|
This function returns server tuple output statistics
|
||||||
|
:param sid: server id
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
return get_data(sid, did, 'to_stats.sql')
|
||||||
|
|
||||||
|
|
||||||
|
@blueprint.route('/bio_stats/')
|
||||||
|
@blueprint.route('/bio_stats/<int:sid>')
|
||||||
|
@blueprint.route('/bio_stats/<int:sid>/<int:did>')
|
||||||
|
@login_required
|
||||||
|
@check_precondition
|
||||||
|
def bio_stats(sid=None, did=None):
|
||||||
|
"""
|
||||||
|
This function returns server block IO statistics
|
||||||
|
:param sid: server id
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
return get_data(sid, did, 'bio_stats.sql')
|
||||||
|
|
||||||
|
|
||||||
|
@blueprint.route('/activity/')
|
||||||
|
@blueprint.route('/activity/<int:sid>')
|
||||||
|
@blueprint.route('/activity/<int:sid>/<int:did>')
|
||||||
|
@login_required
|
||||||
|
@check_precondition
|
||||||
|
def activity(sid=None, did=None):
|
||||||
|
"""
|
||||||
|
This function returns server activity information
|
||||||
|
:param sid: server id
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
return get_data(sid, did, 'activity.sql')
|
||||||
|
|
||||||
|
|
||||||
|
@blueprint.route('/locks/')
|
||||||
|
@blueprint.route('/locks/<int:sid>')
|
||||||
|
@blueprint.route('/locks/<int:sid>/<int:did>')
|
||||||
|
@login_required
|
||||||
|
@check_precondition
|
||||||
|
def locks(sid=None, did=None):
|
||||||
|
"""
|
||||||
|
This function returns server lock information
|
||||||
|
:param sid: server id
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
return get_data(sid, did, 'locks.sql')
|
||||||
|
|
||||||
|
|
||||||
|
@blueprint.route('/prepared/')
|
||||||
|
@blueprint.route('/prepared/<int:sid>')
|
||||||
|
@blueprint.route('/prepared/<int:sid>/<int:did>')
|
||||||
|
@login_required
|
||||||
|
@check_precondition
|
||||||
|
def prepared(sid=None, did=None):
|
||||||
|
"""
|
||||||
|
This function returns prepared XACT information
|
||||||
|
:param sid: server id
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
return get_data(sid, did, 'prepared.sql')
|
||||||
|
|
||||||
|
|
||||||
|
@blueprint.route('/config/')
|
||||||
|
@blueprint.route('/config/<int:sid>')
|
||||||
|
@login_required
|
||||||
|
@check_precondition
|
||||||
|
def config(sid=None):
|
||||||
|
"""
|
||||||
|
This function returns server config information
|
||||||
|
:param sid: server id
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
return get_data(sid, None, 'config.sql')
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,55 @@
|
||||||
|
.dashboard-container {
|
||||||
|
padding: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard-header-spacer {
|
||||||
|
padding-top: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard-link {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard-icon {
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard-tab-container {
|
||||||
|
border-left: 1px solid #E2E2E2;
|
||||||
|
border-right: 1px solid #E2E2E2;
|
||||||
|
border-bottom: 1px solid #E2E2E2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard-tab-panel > li > a {
|
||||||
|
padding: 0px 15px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard-tab {
|
||||||
|
border: 0px;
|
||||||
|
border-radius: 4px 4px 0px 0px;
|
||||||
|
margin-right: 1px;
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 25px;
|
||||||
|
margin-top: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard-tab.active {
|
||||||
|
margin-top: 0px;
|
||||||
|
line-height: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.graph-container {
|
||||||
|
margin-top: 10px;
|
||||||
|
height: 150px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.graph-error {
|
||||||
|
background-color: #E2E2E2;
|
||||||
|
padding-top: 20px
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid-error {
|
||||||
|
background-color: #E2E2E2;
|
||||||
|
padding-top: 20px;
|
||||||
|
padding-bottom: 40px;
|
||||||
|
}
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 22 KiB |
|
|
@ -1,4 +1,89 @@
|
||||||
<h1>Server Dashboard</h1>
|
<div class="dashboard-container">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-xs-6">
|
||||||
|
<div class="obj_properties">
|
||||||
|
<legend class="badge">{{ gettext('Database sessions') }}</legend>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-6">
|
||||||
|
<div class="obj_properties">
|
||||||
|
<legend class="badge">{{ gettext('Transactions per second') }}</legend>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-xs-6">
|
||||||
|
<div id="graph-sessions" class="graph-container"></div>
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-6">
|
||||||
|
<div id="graph-tps" class="graph-container"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<b>Server ID: </b>{{ sid }}<br />
|
<div class="row dashboard-header-spacer">
|
||||||
<b>Database ID: </b>{{ did }}
|
<div class="col-xs-4">
|
||||||
|
<div class="obj_properties">
|
||||||
|
<legend class="badge">{{ gettext('Tuples in') }}</legend>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-4">
|
||||||
|
<div class="obj_properties">
|
||||||
|
<legend class="badge">{{ gettext('Tuples out') }}</legend>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-4">
|
||||||
|
<div class="obj_properties">
|
||||||
|
<legend class="badge">{{ gettext('Block I/O') }}</legend>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-xs-4">
|
||||||
|
<div id="graph-ti" class="graph-container"></div>
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-4">
|
||||||
|
<div id="graph-to" class="graph-container"></div>
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-4">
|
||||||
|
<div id="graph-bio" class="graph-container"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row dashboard-header-spacer">
|
||||||
|
<div class="col-xs-12">
|
||||||
|
<div class="obj_properties">
|
||||||
|
<legend class="badge">{{ gettext('Database activity') }}</legend>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-xs-12">
|
||||||
|
<div class="dashboard-tab-container">
|
||||||
|
<!-- Nav tabs -->
|
||||||
|
<ul class="nav nav-tabs dashboard-tab-panel" role="tablist">
|
||||||
|
<li role="presentation" class="active dashboard-tab"><a href="#tab_panel_database_activity" aria-controls="tab_database_activity" role="tab" data-toggle="tab">Sessions</a></li>
|
||||||
|
<li role="presentation" class="dashboard-tab"><a href="#tab_panel_database_locks" aria-controls="tab_database_locks" role="tab" data-toggle="tab">Locks</a></li>
|
||||||
|
<li role="presentation" class="dashboard-tab"><a href="#tab_panel_database_prepared" aria-controls="tab_database_prepared" role="tab" data-toggle="tab">Prepared Transactions</a></li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<!-- Tab panes -->
|
||||||
|
<div class="tab-content">
|
||||||
|
<div role="tabpanel" class="tab-pane active" id="tab_panel_database_activity">
|
||||||
|
<div id="database_activity" class="grid-container"></div>
|
||||||
|
</div>
|
||||||
|
<div role="tabpanel" class="tab-pane" id="tab_panel_database_locks">
|
||||||
|
<div id="database_locks" class="grid-container"></div>
|
||||||
|
</div>
|
||||||
|
<div role="tabpanel" class="tab-pane" id="tab_panel_database_prepared">
|
||||||
|
<div id="database_prepared" class="grid-container"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
pgAdmin.Dashboard.init_database_dashboard({{ sid }}, {{ did }}, {{ rates.session_stats_refresh }}, {{ rates.tps_stats_refresh }}, {{ rates.ti_stats_refresh }}, {{ rates.to_stats_refresh }}, {{ rates.bio_stats_refresh }} );
|
||||||
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -1,84 +1,783 @@
|
||||||
define(['jquery', 'pgadmin', 'underscore', 'wcdocker', 'pgadmin.browser', 'bootstrap'],
|
define([
|
||||||
function($, pgAdmin, _) {
|
'require', 'jquery', 'pgadmin', 'underscore', 'backbone', 'flotr2', 'wcdocker',
|
||||||
|
'pgadmin.browser', 'bootstrap'
|
||||||
|
],
|
||||||
|
function(r, $, pgAdmin, _, Backbone) {
|
||||||
|
|
||||||
var wcDocker = window.wcDocker,
|
var wcDocker = window.wcDocker,
|
||||||
pgBrowser = pgAdmin.Browser;
|
pgBrowser = pgAdmin.Browser;
|
||||||
|
|
||||||
/* Return back, this has been called more than once */
|
/* Return back, this has been called more than once */
|
||||||
if (pgAdmin.Dashboard)
|
if (pgAdmin.Dashboard)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
pgAdmin.Dashboard = {
|
pgAdmin.Dashboard = {
|
||||||
init: function() {
|
init: function() {
|
||||||
if (this.initialized)
|
if (this.initialized)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
this.initialized = true;
|
this.initialized = true;
|
||||||
|
|
||||||
// Bind the Dashboard object with the 'object_selected' function
|
// Bind the Dashboard object with the 'object_selected' function
|
||||||
var selected = this.object_selected.bind(this);
|
var selected = this.object_selected.bind(this);
|
||||||
|
|
||||||
// Listen for selection of any of object
|
// Listen for selection of any of object
|
||||||
pgBrowser.Events.on('pgadmin-browser:tree:selected', selected);
|
pgBrowser.Events.on('pgadmin-browser:tree:selected', selected);
|
||||||
},
|
|
||||||
|
|
||||||
object_selected: function(item, itemData, node) {
|
// Load the default welcome dashboard
|
||||||
var treeHierarchy = node.getTreeNodeHierarchy(item)
|
url = '{{ url_for('dashboard.index') }}';
|
||||||
if (itemData && itemData._type)
|
|
||||||
{
|
|
||||||
switch(itemData._type) {
|
|
||||||
case ('server-group'):
|
|
||||||
url = '{{ url_for('dashboard.index') }}';
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ('server'):
|
var dashboardPanel = pgBrowser.panels['dashboard'].panel;
|
||||||
case ('coll-database'):
|
if (dashboardPanel) {
|
||||||
case ('coll-role'):
|
var div = dashboardPanel.layout().scene().find('.pg-panel-content');
|
||||||
case ('role'):
|
|
||||||
case ('coll-tablespace'):
|
|
||||||
case ('tablespace'):
|
|
||||||
url = '{{ url_for('dashboard.index') }}'
|
|
||||||
+ treeHierarchy.server._id;
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
if (div) {
|
||||||
url = '{{ url_for('dashboard.index') }}'
|
$.ajax({
|
||||||
+ treeHierarchy.server._id
|
url: url,
|
||||||
+ '/' + treeHierarchy.database._id;
|
type: "GET",
|
||||||
break;
|
dataType: "html",
|
||||||
}
|
success: function (data) {
|
||||||
}
|
$(div).html(data);
|
||||||
|
},
|
||||||
|
error: function (xhr, status) {
|
||||||
|
$(div).html(
|
||||||
|
'<div class="alert alert-danger pg-panel-message" role="alert">{{ gettext('An error occurred whilst loading the dashboard.') }}</div>'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
var dashboardPanel = pgBrowser.frames['dashboard'].panel;
|
// Cache the current IDs for next time
|
||||||
if (dashboardPanel) {
|
$(dashboardPanel).data('sid', -1)
|
||||||
var frame = $(dashboardPanel).data('embeddedFrame');
|
$(dashboardPanel).data('did', -1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
if (frame) {
|
// Handle treeview clicks
|
||||||
// Avoid unnecessary reloads
|
object_selected: function(item, itemData, node) {
|
||||||
if (_.isUndefined(treeHierarchy.server) || _.isUndefined(treeHierarchy.server._id))
|
var treeHierarchy = node.getTreeNodeHierarchy(item)
|
||||||
sid = -1
|
if (itemData && itemData._type)
|
||||||
else
|
{
|
||||||
sid = treeHierarchy.server._id
|
switch(itemData._type) {
|
||||||
|
case ('server-group'):
|
||||||
|
url = '{{ url_for('dashboard.index') }}';
|
||||||
|
break;
|
||||||
|
|
||||||
if (_.isUndefined(treeHierarchy.database) || _.isUndefined(treeHierarchy.database._id))
|
case ('server'):
|
||||||
did = -1
|
case ('coll-database'):
|
||||||
else
|
case ('coll-role'):
|
||||||
did = treeHierarchy.database._id
|
case ('role'):
|
||||||
|
case ('coll-tablespace'):
|
||||||
|
case ('tablespace'):
|
||||||
|
url = '{{ url_for('dashboard.index') }}'
|
||||||
|
+ treeHierarchy.server._id;
|
||||||
|
break;
|
||||||
|
|
||||||
if (sid != $(dashboardPanel).data('sid') ||
|
default:
|
||||||
did != $(dashboardPanel).data('did')) {
|
url = '{{ url_for('dashboard.index') }}'
|
||||||
frame.openURL(url);
|
+ treeHierarchy.server._id
|
||||||
|
+ '/' + treeHierarchy.database._id;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Cache the current IDs for next time
|
var dashboardPanel = pgBrowser.panels['dashboard'].panel;
|
||||||
$(dashboardPanel).data('sid', sid)
|
if (dashboardPanel) {
|
||||||
$(dashboardPanel).data('did', did)
|
var div = dashboardPanel.layout().scene().find('.pg-panel-content');
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return pgAdmin.Dashboard;
|
if (div) {
|
||||||
|
// Avoid unnecessary reloads
|
||||||
|
if (_.isUndefined(treeHierarchy.server) || _.isUndefined(treeHierarchy.server._id))
|
||||||
|
sid = -1
|
||||||
|
else
|
||||||
|
sid = treeHierarchy.server._id
|
||||||
|
|
||||||
|
if (_.isUndefined(treeHierarchy.database) || _.isUndefined(treeHierarchy.database._id))
|
||||||
|
did = -1
|
||||||
|
else
|
||||||
|
did = treeHierarchy.database._id
|
||||||
|
|
||||||
|
if (sid != $(dashboardPanel).data('sid') ||
|
||||||
|
did != $(dashboardPanel).data('did')) {
|
||||||
|
|
||||||
|
// Clear out everything so any existing timers die off
|
||||||
|
$(div).empty();
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: url,
|
||||||
|
type: "GET",
|
||||||
|
dataType: "html",
|
||||||
|
success: function (data) {
|
||||||
|
$(div).html(data);
|
||||||
|
},
|
||||||
|
error: function (xhr, status) {
|
||||||
|
$(div).html(
|
||||||
|
'<div class="alert alert-danger pg-panel-message" role="alert">{{ gettext('An error occurred whilst loading the dashboard.') }}</div>'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Cache the current IDs for next time
|
||||||
|
$(dashboardPanel).data('sid', sid)
|
||||||
|
$(dashboardPanel).data('did', did)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Render a chart
|
||||||
|
render_chart: function(container, data, dataset, sid, did, url, options, counter, refresh) {
|
||||||
|
|
||||||
|
// Data format:
|
||||||
|
// [
|
||||||
|
// { data: [[0, y0], [1, y1]...], label: 'Label 1', [options] },
|
||||||
|
// { data: [[0, y0], [1, y1]...], label: 'Label 2', [options] },
|
||||||
|
// { data: [[0, y0], [1, y1]...], label: 'Label 3', [options] }
|
||||||
|
// ]
|
||||||
|
|
||||||
|
y = 0;
|
||||||
|
if (dataset.length == 0) {
|
||||||
|
if (counter == true)
|
||||||
|
{
|
||||||
|
// Have we stashed initial values?
|
||||||
|
if (_.isUndefined($(container).data('counter_previous_vals'))) {
|
||||||
|
$(container).data('counter_previous_vals', data[0])
|
||||||
|
} else {
|
||||||
|
// Create the initial data structure
|
||||||
|
for (var x in data[0]) {
|
||||||
|
dataset.push({ 'data': [[0, data[0][x] - $(container).data('counter_previous_vals')[x]]], 'label': x });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Create the initial data structure
|
||||||
|
for (var x in data[0]) {
|
||||||
|
dataset.push({ 'data': [[0, data[0][x]]], 'label': x });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (var x in data[0]) {
|
||||||
|
// Push new values onto the existing data structure
|
||||||
|
// If this is a counter stat, we need to subtract the previous value
|
||||||
|
if (counter == false) {
|
||||||
|
dataset[y]['data'].unshift([0, data[0][x]]);
|
||||||
|
} else {
|
||||||
|
// Store the current value, minus the previous one we stashed.
|
||||||
|
// It's possible the tab has been reloaded, in which case out previous values are gone
|
||||||
|
if (_.isUndefined($(container).data('counter_previous_vals')))
|
||||||
|
return
|
||||||
|
|
||||||
|
dataset[y]['data'].unshift([0, data[0][x] - $(container).data('counter_previous_vals')[x]]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset the time index to get a proper scrolling display
|
||||||
|
for (z = 0; z < dataset[y]['data'].length; z++) {
|
||||||
|
dataset[y]['data'][z][0] = z;
|
||||||
|
}
|
||||||
|
|
||||||
|
y++;
|
||||||
|
}
|
||||||
|
$(container).data('counter_previous_vals', data[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove uneeded elements
|
||||||
|
for (x = 0; x < dataset.length; x++) {
|
||||||
|
// Remove old data points
|
||||||
|
if (dataset[x]['data'].length > 101) {
|
||||||
|
dataset[x]['data'].pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw Graph, if the container still exists and has a size
|
||||||
|
var dashboardPanel = pgBrowser.panels['dashboard'].panel;
|
||||||
|
var div = dashboardPanel.layout().scene().find('.pg-panel-content');
|
||||||
|
if ($(div).find(container).length) { // Exists?
|
||||||
|
if (container.clientHeight > 0 && container.clientWidth > 0) { // Not hidden?
|
||||||
|
Flotr.draw(container, dataset, options);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Animate
|
||||||
|
var setTimeoutFunc = function () {
|
||||||
|
path = url + sid;
|
||||||
|
if (did != -1) {
|
||||||
|
path += '/' + did;
|
||||||
|
}
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: path,
|
||||||
|
type: "GET",
|
||||||
|
dataType: "html",
|
||||||
|
success: function (resp) {
|
||||||
|
$(container).removeClass('graph-error')
|
||||||
|
data = JSON.parse(resp);
|
||||||
|
pgAdmin.Dashboard.render_chart(container, data, dataset, sid, did, url, options, counter, refresh);
|
||||||
|
},
|
||||||
|
error: function (xhr, status, msg) {
|
||||||
|
// If we get a 428, it means the server isn't connected
|
||||||
|
if (xhr.status == 428) {
|
||||||
|
msg = '{{ gettext('Please connect to the selected server to view the graph.') }}';
|
||||||
|
cls = 'info';
|
||||||
|
} else {
|
||||||
|
msg = '{{ gettext('An error occurred whilst rendering the graph.') }}';
|
||||||
|
cls = 'danger';
|
||||||
|
}
|
||||||
|
|
||||||
|
$(container).addClass('graph-error');
|
||||||
|
$(container).html(
|
||||||
|
'<div class="alert alert-' + cls + ' pg-panel-message" role="alert">' + msg + '</div>'
|
||||||
|
);
|
||||||
|
|
||||||
|
// Try again...
|
||||||
|
if (container.clientHeight > 0 && container.clientWidth > 0) {
|
||||||
|
setTimeout(setTimeoutFunc, refresh * 1000);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
setTimeout(setTimeoutFunc, refresh * 1000);
|
||||||
|
},
|
||||||
|
|
||||||
|
// Handler function to support the "Add Server" link
|
||||||
|
add_new_server: function() {
|
||||||
|
if (pgBrowser && pgBrowser.tree) {
|
||||||
|
var i = pgBrowser.tree.first(null, false),
|
||||||
|
serverModule = r('pgadmin.node.server');
|
||||||
|
|
||||||
|
if (serverModule) {
|
||||||
|
serverModule.callbacks.show_obj_properties.apply(
|
||||||
|
serverModule, [{action: 'create'}, i]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Render a grid
|
||||||
|
render_grid: function(container, sid, did, url, columns) {
|
||||||
|
var Datum = Backbone.Model.extend({});
|
||||||
|
|
||||||
|
path = url + sid;
|
||||||
|
if (did != -1) {
|
||||||
|
path += '/' + did;
|
||||||
|
}
|
||||||
|
|
||||||
|
var Data = Backbone.Collection.extend({
|
||||||
|
model: Datum,
|
||||||
|
url: path,
|
||||||
|
mode: "client"
|
||||||
|
});
|
||||||
|
|
||||||
|
var data = new Data();
|
||||||
|
|
||||||
|
// Set up the grid
|
||||||
|
var grid = new Backgrid.Grid({
|
||||||
|
columns: columns,
|
||||||
|
collection: data,
|
||||||
|
className: "backgrid table-bordered table-striped"
|
||||||
|
});
|
||||||
|
|
||||||
|
// Render the grid
|
||||||
|
$(container).append(grid.render().el)
|
||||||
|
|
||||||
|
// Initialize a client-side filter to filter on the client
|
||||||
|
// mode pageable collection's cache.
|
||||||
|
var filter = new Backgrid.Extension.ClientSideFilter({
|
||||||
|
collection: data
|
||||||
|
});
|
||||||
|
|
||||||
|
// Render the filter
|
||||||
|
$(container).before(filter.render().el);
|
||||||
|
|
||||||
|
// Add some space to the filter and move it to the right
|
||||||
|
$(filter.el).css({float: "right", margin: "5px", "margin-right": "0px"});
|
||||||
|
|
||||||
|
// Stash objects for future use
|
||||||
|
$(container).data('data', data);
|
||||||
|
$(container).data('grid', grid);
|
||||||
|
$(container).data('filter', filter);
|
||||||
|
},
|
||||||
|
|
||||||
|
// Render the data in a grid
|
||||||
|
render_grid_data: function(container) {
|
||||||
|
data = $(container).data('data');
|
||||||
|
grid = $(container).data('grid');
|
||||||
|
filter = $(container).data('filter');
|
||||||
|
|
||||||
|
data.fetch({
|
||||||
|
reset: true,
|
||||||
|
success: function() {
|
||||||
|
// If we're showing an error, remove it, and replace the grid & filter
|
||||||
|
if ($(container).hasClass('grid-error')) {
|
||||||
|
$(container).removeClass('grid-error');
|
||||||
|
$(container).html(grid.render().el)
|
||||||
|
$(filter.el).show();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Re-apply search criteria
|
||||||
|
filter.search();
|
||||||
|
},
|
||||||
|
error: function(model, xhr, options) {
|
||||||
|
// If we get a 428, it means the server isn't connected
|
||||||
|
if (xhr.status == 428) {
|
||||||
|
msg = '{{ gettext('Please connect to the selected server to view the table.') }}';
|
||||||
|
cls = 'info';
|
||||||
|
} else {
|
||||||
|
msg = '{{ gettext('An error occurred whilst rendering the table.') }}';
|
||||||
|
cls = 'danger';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stash the old content, and replace with the error, if not already present
|
||||||
|
if (!$(container).hasClass('grid-error')) {
|
||||||
|
$(filter.el).hide();
|
||||||
|
$(container).addClass('grid-error');
|
||||||
|
$(container).html(
|
||||||
|
'<div class="alert alert-' + cls + ' pg-panel-message" role="alert">' + msg + '</div>'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try again
|
||||||
|
setTimeout(function() {
|
||||||
|
pgAdmin.Dashboard.render_grid_data(container, data);
|
||||||
|
}, 5000)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// Rock n' roll on the server dashboard
|
||||||
|
init_server_dashboard: function(sid, session_stats_refresh, tps_stats_refresh, ti_stats_refresh, to_stats_refresh, bio_stats_refresh) {
|
||||||
|
var div_sessions = document.getElementById('graph-sessions');
|
||||||
|
var div_tps = document.getElementById('graph-tps');
|
||||||
|
var div_ti = document.getElementById('graph-ti');
|
||||||
|
var div_to = document.getElementById('graph-to');
|
||||||
|
var div_bio = document.getElementById('graph-bio');
|
||||||
|
var div_server_activity = document.getElementById('server_activity');
|
||||||
|
var div_server_locks = document.getElementById('server_locks');
|
||||||
|
var div_server_prepared = document.getElementById('server_prepared');
|
||||||
|
var div_server_config = document.getElementById('server_config');
|
||||||
|
var dataset_sessions = [];
|
||||||
|
var data_sessions = [];
|
||||||
|
var dataset_tps = [];
|
||||||
|
var data_tps = [];
|
||||||
|
var dataset_ti = [];
|
||||||
|
var data_ti = [];
|
||||||
|
var dataset_to = [];
|
||||||
|
var data_to = [];
|
||||||
|
var dataset_bio = [];
|
||||||
|
var data_bio = [];
|
||||||
|
|
||||||
|
// Fake DB ID
|
||||||
|
did = -1;
|
||||||
|
|
||||||
|
var options_line = {
|
||||||
|
parseFloat: false,
|
||||||
|
xaxis: {
|
||||||
|
min: 100,
|
||||||
|
max: 0,
|
||||||
|
autoscale: 0
|
||||||
|
},
|
||||||
|
yaxis : {
|
||||||
|
autoscale: 1
|
||||||
|
},
|
||||||
|
legend : {
|
||||||
|
position : 'nw',
|
||||||
|
backgroundColor : '#D2E8FF'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var server_activity_columns = [{
|
||||||
|
name: "pid",
|
||||||
|
label: "{{ _('Process ID') }}",
|
||||||
|
editable: false,
|
||||||
|
cell: "string"
|
||||||
|
}, {
|
||||||
|
name: "datname",
|
||||||
|
label: "{{ _('Database') }}",
|
||||||
|
editable: false,
|
||||||
|
cell: "string"
|
||||||
|
}, {
|
||||||
|
name: "usename",
|
||||||
|
label: "{{ _('User') }}",
|
||||||
|
editable: false,
|
||||||
|
cell: "string"
|
||||||
|
}, {
|
||||||
|
name: "application_name",
|
||||||
|
label: "{{ _('Application name') }}",
|
||||||
|
editable: false,
|
||||||
|
cell: "string"
|
||||||
|
}, {
|
||||||
|
name: "client_addr",
|
||||||
|
label: "{{ _('Client address') }}",
|
||||||
|
editable: false,
|
||||||
|
cell: "string"
|
||||||
|
}, {
|
||||||
|
name: "backend_start",
|
||||||
|
label: "{{ _('Backend start') }}",
|
||||||
|
editable: false,
|
||||||
|
cell: "string"
|
||||||
|
}, {
|
||||||
|
name: "state",
|
||||||
|
label: "{{ _('State') }}",
|
||||||
|
editable: false,
|
||||||
|
cell: "string"
|
||||||
|
}];
|
||||||
|
|
||||||
|
var server_locks_columns = [{
|
||||||
|
name: "pid",
|
||||||
|
label: "{{ _('Process ID') }}",
|
||||||
|
editable: false,
|
||||||
|
cell: "string"
|
||||||
|
}, {
|
||||||
|
name: "datname",
|
||||||
|
label: "{{ _('Database') }}",
|
||||||
|
editable: false,
|
||||||
|
cell: "string"
|
||||||
|
}, {
|
||||||
|
name: "locktype",
|
||||||
|
label: "{{ _('Lock type') }}",
|
||||||
|
editable: false,
|
||||||
|
cell: "string"
|
||||||
|
}, {
|
||||||
|
name: "relation",
|
||||||
|
label: "{{ _('Target relation') }}",
|
||||||
|
editable: false,
|
||||||
|
cell: "string"
|
||||||
|
}, {
|
||||||
|
name: "page",
|
||||||
|
label: "{{ _('Page') }}",
|
||||||
|
editable: false,
|
||||||
|
cell: "string"
|
||||||
|
}, {
|
||||||
|
name: "tuple",
|
||||||
|
label: "{{ _('Tuple') }}",
|
||||||
|
editable: false,
|
||||||
|
cell: "string"
|
||||||
|
}, {
|
||||||
|
name: "virtualxid",
|
||||||
|
label: "{{ _('Virtual XID (target)') }}",
|
||||||
|
editable: false,
|
||||||
|
cell: "string"
|
||||||
|
}, {
|
||||||
|
name: "transactionid",
|
||||||
|
label: "{{ _('XID (target)') }}",
|
||||||
|
editable: false,
|
||||||
|
cell: "string"
|
||||||
|
},{
|
||||||
|
name: "classid",
|
||||||
|
label: "{{ _('Class') }}",
|
||||||
|
editable: false,
|
||||||
|
cell: "string"
|
||||||
|
},{
|
||||||
|
name: "objid",
|
||||||
|
label: "{{ _('Object ID') }}",
|
||||||
|
editable: false,
|
||||||
|
cell: "string"
|
||||||
|
},{
|
||||||
|
name: "virtualtransaction",
|
||||||
|
label: "{{ _('Virtual XID (owner)') }}",
|
||||||
|
editable: false,
|
||||||
|
cell: "string"
|
||||||
|
},{
|
||||||
|
name: "mode",
|
||||||
|
label: "{{ _('Mode') }}",
|
||||||
|
editable: false,
|
||||||
|
cell: "string"
|
||||||
|
},{
|
||||||
|
name: "granted",
|
||||||
|
label: "{{ _('Granted?') }}",
|
||||||
|
editable: false,
|
||||||
|
cell: "string"
|
||||||
|
},{
|
||||||
|
name: "fastpath",
|
||||||
|
label: "{{ _('Fast path?') }}",
|
||||||
|
editable: false,
|
||||||
|
cell: "string"
|
||||||
|
}];
|
||||||
|
|
||||||
|
var server_prepared_columns = [{
|
||||||
|
name: "git",
|
||||||
|
label: "{{ _('Name') }}",
|
||||||
|
editable: false,
|
||||||
|
cell: "string"
|
||||||
|
}, {
|
||||||
|
name: "database",
|
||||||
|
label: "{{ _('Database') }}",
|
||||||
|
editable: false,
|
||||||
|
cell: "string"
|
||||||
|
}, {
|
||||||
|
name: "Owner",
|
||||||
|
label: "{{ _('Owner') }}",
|
||||||
|
editable: false,
|
||||||
|
cell: "string"
|
||||||
|
}, {
|
||||||
|
name: "transaction",
|
||||||
|
label: "{{ _('XID') }}",
|
||||||
|
editable: false,
|
||||||
|
cell: "string"
|
||||||
|
}, {
|
||||||
|
name: "prepared",
|
||||||
|
label: "{{ _('Prepared at') }}",
|
||||||
|
editable: false,
|
||||||
|
cell: "string"
|
||||||
|
}];
|
||||||
|
|
||||||
|
var server_config_columns = [{
|
||||||
|
name: "name",
|
||||||
|
label: "{{ _('Name') }}",
|
||||||
|
editable: false,
|
||||||
|
cell: "string"
|
||||||
|
}, {
|
||||||
|
name: "category",
|
||||||
|
label: "{{ _('Category') }}",
|
||||||
|
editable: false,
|
||||||
|
cell: "string"
|
||||||
|
}, {
|
||||||
|
name: "setting",
|
||||||
|
label: "{{ _('Setting') }}",
|
||||||
|
editable: false,
|
||||||
|
cell: "string"
|
||||||
|
}, {
|
||||||
|
name: "unit",
|
||||||
|
label: "{{ _('Unit') }}",
|
||||||
|
editable: false,
|
||||||
|
cell: "string"
|
||||||
|
}, {
|
||||||
|
name: "short_desc",
|
||||||
|
label: "{{ _('Description') }}",
|
||||||
|
editable: false,
|
||||||
|
cell: "string"
|
||||||
|
}];
|
||||||
|
|
||||||
|
// Render the graphs
|
||||||
|
pgAdmin.Dashboard.render_chart(div_sessions, data_sessions, dataset_sessions, sid, did, '{{ url_for('dashboard.session_stats') }}', options_line, false, session_stats_refresh);
|
||||||
|
pgAdmin.Dashboard.render_chart(div_tps, data_tps, dataset_tps, sid, did, '{{ url_for('dashboard.tps_stats') }}', options_line, true, tps_stats_refresh);
|
||||||
|
pgAdmin.Dashboard.render_chart(div_ti, data_ti, dataset_ti, sid, did, '{{ url_for('dashboard.ti_stats') }}', options_line, true, ti_stats_refresh);
|
||||||
|
pgAdmin.Dashboard.render_chart(div_to, data_to, dataset_to, sid, did, '{{ url_for('dashboard.to_stats') }}', options_line, true, to_stats_refresh);
|
||||||
|
pgAdmin.Dashboard.render_chart(div_bio, data_bio, dataset_bio, sid, did, '{{ url_for('dashboard.bio_stats') }}', options_line, true, bio_stats_refresh);
|
||||||
|
|
||||||
|
// Render the tabs, but only get data for the activity tab for now
|
||||||
|
pgAdmin.Dashboard.render_grid(div_server_activity, sid, did, '{{ url_for('dashboard.activity') }}', server_activity_columns);
|
||||||
|
pgAdmin.Dashboard.render_grid(div_server_locks, sid, did, '{{ url_for('dashboard.locks') }}', server_locks_columns);
|
||||||
|
pgAdmin.Dashboard.render_grid(div_server_prepared, sid, did, '{{ url_for('dashboard.prepared') }}', server_prepared_columns);
|
||||||
|
pgAdmin.Dashboard.render_grid(div_server_config, sid, did, '{{ url_for('dashboard.config') }}', server_config_columns);
|
||||||
|
|
||||||
|
pgAdmin.Dashboard.render_grid_data(div_server_activity);
|
||||||
|
|
||||||
|
// (Re)render the appropriate tab
|
||||||
|
$('a[data-toggle="tab"]').on('shown.bs.tab', function (e) {
|
||||||
|
switch ($(e.target).attr('aria-controls')) {
|
||||||
|
case "tab_server_activity":
|
||||||
|
pgAdmin.Dashboard.render_grid_data(div_server_activity);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "tab_server_locks":
|
||||||
|
pgAdmin.Dashboard.render_grid_data(div_server_locks);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "tab_server_prepared":
|
||||||
|
pgAdmin.Dashboard.render_grid_data(div_server_prepared);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "tab_server_config":
|
||||||
|
pgAdmin.Dashboard.render_grid_data(div_server_config);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
// Rock n' roll on the database dashboard
|
||||||
|
init_database_dashboard: function(sid, did, session_stats_refresh, tps_stats_refresh, ti_stats_refresh, to_stats_refresh, bio_stats_refresh) {
|
||||||
|
var div_sessions = document.getElementById('graph-sessions');
|
||||||
|
var div_tps = document.getElementById('graph-tps');
|
||||||
|
var div_ti = document.getElementById('graph-ti');
|
||||||
|
var div_to = document.getElementById('graph-to');
|
||||||
|
var div_bio = document.getElementById('graph-bio');
|
||||||
|
var div_database_activity = document.getElementById('database_activity');
|
||||||
|
var div_database_locks = document.getElementById('database_locks');
|
||||||
|
var div_database_prepared = document.getElementById('database_prepared');
|
||||||
|
var dataset_sessions = [];
|
||||||
|
var data_sessions = [];
|
||||||
|
var dataset_tps = [];
|
||||||
|
var data_tps = [];
|
||||||
|
var dataset_ti = [];
|
||||||
|
var data_ti = [];
|
||||||
|
var dataset_to = [];
|
||||||
|
var data_to = [];
|
||||||
|
var dataset_bio = [];
|
||||||
|
var data_bio = [];
|
||||||
|
|
||||||
|
var options_line = {
|
||||||
|
parseFloat: false,
|
||||||
|
xaxis: {
|
||||||
|
min: 100,
|
||||||
|
max: 0,
|
||||||
|
autoscale: 0
|
||||||
|
},
|
||||||
|
yaxis : {
|
||||||
|
autoscale: 1
|
||||||
|
},
|
||||||
|
legend : {
|
||||||
|
position : 'nw',
|
||||||
|
backgroundColor : '#D2E8FF'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var database_activity_columns = [{
|
||||||
|
name: "pid",
|
||||||
|
label: "{{ _('Process ID') }}",
|
||||||
|
editable: false,
|
||||||
|
cell: "string"
|
||||||
|
}, {
|
||||||
|
name: "usename",
|
||||||
|
label: "{{ _('User') }}",
|
||||||
|
editable: false,
|
||||||
|
cell: "string"
|
||||||
|
}, {
|
||||||
|
name: "application_name",
|
||||||
|
label: "{{ _('Application name') }}",
|
||||||
|
editable: false,
|
||||||
|
cell: "string"
|
||||||
|
}, {
|
||||||
|
name: "client_addr",
|
||||||
|
label: "{{ _('Client address') }}",
|
||||||
|
editable: false,
|
||||||
|
cell: "string"
|
||||||
|
}, {
|
||||||
|
name: "backend_start",
|
||||||
|
label: "{{ _('Backend start') }}",
|
||||||
|
editable: false,
|
||||||
|
cell: "string"
|
||||||
|
}, {
|
||||||
|
name: "state",
|
||||||
|
label: "{{ _('State') }}",
|
||||||
|
editable: false,
|
||||||
|
cell: "string"
|
||||||
|
}];
|
||||||
|
|
||||||
|
var database_locks_columns = [{
|
||||||
|
name: "pid",
|
||||||
|
label: "{{ _('Process ID') }}",
|
||||||
|
editable: false,
|
||||||
|
cell: "string"
|
||||||
|
}, {
|
||||||
|
name: "locktype",
|
||||||
|
label: "{{ _('Lock type') }}",
|
||||||
|
editable: false,
|
||||||
|
cell: "string"
|
||||||
|
}, {
|
||||||
|
name: "relation",
|
||||||
|
label: "{{ _('Target relation') }}",
|
||||||
|
editable: false,
|
||||||
|
cell: "string"
|
||||||
|
}, {
|
||||||
|
name: "page",
|
||||||
|
label: "{{ _('Page') }}",
|
||||||
|
editable: false,
|
||||||
|
cell: "string"
|
||||||
|
}, {
|
||||||
|
name: "tuple",
|
||||||
|
label: "{{ _('Tuple') }}",
|
||||||
|
editable: false,
|
||||||
|
cell: "string"
|
||||||
|
}, {
|
||||||
|
name: "virtualxid",
|
||||||
|
label: "{{ _('Virtual XID (target)') }}",
|
||||||
|
editable: false,
|
||||||
|
cell: "string"
|
||||||
|
}, {
|
||||||
|
name: "transactionid",
|
||||||
|
label: "{{ _('XID (target)') }}",
|
||||||
|
editable: false,
|
||||||
|
cell: "string"
|
||||||
|
},{
|
||||||
|
name: "classid",
|
||||||
|
label: "{{ _('Class') }}",
|
||||||
|
editable: false,
|
||||||
|
cell: "string"
|
||||||
|
},{
|
||||||
|
name: "objid",
|
||||||
|
label: "{{ _('Object ID') }}",
|
||||||
|
editable: false,
|
||||||
|
cell: "string"
|
||||||
|
},{
|
||||||
|
name: "virtualtransaction",
|
||||||
|
label: "{{ _('Virtual XID (owner)') }}",
|
||||||
|
editable: false,
|
||||||
|
cell: "string"
|
||||||
|
},{
|
||||||
|
name: "mode",
|
||||||
|
label: "{{ _('Mode') }}",
|
||||||
|
editable: false,
|
||||||
|
cell: "string"
|
||||||
|
},{
|
||||||
|
name: "granted",
|
||||||
|
label: "{{ _('Granted?') }}",
|
||||||
|
editable: false,
|
||||||
|
cell: "string"
|
||||||
|
},{
|
||||||
|
name: "fastpath",
|
||||||
|
label: "{{ _('Fast path?') }}",
|
||||||
|
editable: false,
|
||||||
|
cell: "string"
|
||||||
|
}];
|
||||||
|
|
||||||
|
var database_prepared_columns = [{
|
||||||
|
name: "git",
|
||||||
|
label: "{{ _('Name') }}",
|
||||||
|
editable: false,
|
||||||
|
cell: "string"
|
||||||
|
}, {
|
||||||
|
name: "Owner",
|
||||||
|
label: "{{ _('Owner') }}",
|
||||||
|
editable: false,
|
||||||
|
cell: "string"
|
||||||
|
}, {
|
||||||
|
name: "transaction",
|
||||||
|
label: "{{ _('XID') }}",
|
||||||
|
editable: false,
|
||||||
|
cell: "string"
|
||||||
|
}, {
|
||||||
|
name: "prepared",
|
||||||
|
label: "{{ _('Prepared at') }}",
|
||||||
|
editable: false,
|
||||||
|
cell: "string"
|
||||||
|
}];
|
||||||
|
|
||||||
|
// Render the graphs
|
||||||
|
pgAdmin.Dashboard.render_chart(div_sessions, data_sessions, dataset_sessions, sid, did, '{{ url_for('dashboard.session_stats') }}', options_line, false, session_stats_refresh);
|
||||||
|
pgAdmin.Dashboard.render_chart(div_tps, data_tps, dataset_tps, sid, did, '{{ url_for('dashboard.tps_stats') }}', options_line, true, tps_stats_refresh);
|
||||||
|
pgAdmin.Dashboard.render_chart(div_ti, data_ti, dataset_ti, sid, did, '{{ url_for('dashboard.ti_stats') }}', options_line, true, ti_stats_refresh);
|
||||||
|
pgAdmin.Dashboard.render_chart(div_to, data_to, dataset_to, sid, did, '{{ url_for('dashboard.to_stats') }}', options_line, true, to_stats_refresh);
|
||||||
|
pgAdmin.Dashboard.render_chart(div_bio, data_bio, dataset_bio, sid, did, '{{ url_for('dashboard.bio_stats') }}', options_line, true, bio_stats_refresh);
|
||||||
|
|
||||||
|
// Render the tabs, but only get data for the activity tab for now
|
||||||
|
pgAdmin.Dashboard.render_grid(div_database_activity, sid, did, '{{ url_for('dashboard.activity') }}', database_activity_columns);
|
||||||
|
pgAdmin.Dashboard.render_grid(div_database_locks, sid, did, '{{ url_for('dashboard.locks') }}', database_locks_columns);
|
||||||
|
pgAdmin.Dashboard.render_grid(div_database_prepared, sid, did, '{{ url_for('dashboard.prepared') }}', database_prepared_columns);
|
||||||
|
|
||||||
|
pgAdmin.Dashboard.render_grid_data(div_database_activity);
|
||||||
|
|
||||||
|
// (Re)render the appropriate tab
|
||||||
|
$('a[data-toggle="tab"]').on('shown.bs.tab', function (e) {
|
||||||
|
switch ($(e.target).attr('aria-controls')) {
|
||||||
|
case "tab_database_activity":
|
||||||
|
pgAdmin.Dashboard.render_grid_data(div_database_activity);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "tab_database_locks":
|
||||||
|
pgAdmin.Dashboard.render_grid_data(div_database_locks);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "tab_database_prepared":
|
||||||
|
pgAdmin.Dashboard.render_grid_data(div_database_prepared);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return pgAdmin.Dashboard;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,93 @@
|
||||||
<h1>Server Dashboard</h1>
|
<div class="dashboard-container">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-xs-6">
|
||||||
|
<div class="obj_properties">
|
||||||
|
<legend class="badge">{{ gettext('Server sessions') }}</legend>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-6">
|
||||||
|
<div class="obj_properties">
|
||||||
|
<legend class="badge">{{ gettext('Transactions per second') }}</legend>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-xs-6">
|
||||||
|
<div id="graph-sessions" class="graph-container"></div>
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-6">
|
||||||
|
<div id="graph-tps" class="graph-container"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<b>Server ID: </b>{{ sid }}<br />
|
<div class="row dashboard-header-spacer">
|
||||||
|
<div class="col-xs-4">
|
||||||
|
<div class="obj_properties">
|
||||||
|
<legend class="badge">{{ gettext('Tuples in') }}</legend>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-4">
|
||||||
|
<div class="obj_properties">
|
||||||
|
<legend class="badge">{{ gettext('Tuples out') }}</legend>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-4">
|
||||||
|
<div class="obj_properties">
|
||||||
|
<legend class="badge">{{ gettext('Block I/O') }}</legend>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-xs-4">
|
||||||
|
<div id="graph-ti" class="graph-container"></div>
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-4">
|
||||||
|
<div id="graph-to" class="graph-container"></div>
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-4">
|
||||||
|
<div id="graph-bio" class="graph-container"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row dashboard-header-spacer">
|
||||||
|
<div class="col-xs-12">
|
||||||
|
<div class="obj_properties">
|
||||||
|
<legend class="badge">{{ gettext('Server activity') }}</legend>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-xs-12">
|
||||||
|
<div class="dashboard-tab-container">
|
||||||
|
<!-- Nav tabs -->
|
||||||
|
<ul class="nav nav-tabs dashboard-tab-panel" role="tablist">
|
||||||
|
<li role="presentation" class="active dashboard-tab"><a href="#tab_panel_server_activity" aria-controls="tab_server_activity" role="tab" data-toggle="tab">Sessions</a></li>
|
||||||
|
<li role="presentation" class="dashboard-tab"><a href="#tab_panel_server_locks" aria-controls="tab_server_locks" role="tab" data-toggle="tab">Locks</a></li>
|
||||||
|
<li role="presentation" class="dashboard-tab"><a href="#tab_panel_server_prepared" aria-controls="tab_server_prepared" role="tab" data-toggle="tab">Prepared Transactions</a></li>
|
||||||
|
<li role="presentation" class="dashboard-tab"><a href="#tab_panel_server_config" aria-controls="tab_server_config" role="tab" data-toggle="tab">Configuration</a></li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<!-- Tab panes -->
|
||||||
|
<div class="tab-content">
|
||||||
|
<div role="tabpanel" class="tab-pane active" id="tab_panel_server_activity">
|
||||||
|
<div id="server_activity" class="grid-container"></div>
|
||||||
|
</div>
|
||||||
|
<div role="tabpanel" class="tab-pane" id="tab_panel_server_locks">
|
||||||
|
<div id="server_locks" class="grid-container"></div>
|
||||||
|
</div>
|
||||||
|
<div role="tabpanel" class="tab-pane" id="tab_panel_server_prepared">
|
||||||
|
<div id="server_prepared" class="grid-container"></div>
|
||||||
|
</div>
|
||||||
|
<div role="tabpanel" class="tab-pane" id="tab_panel_server_config">
|
||||||
|
<div id="server_config" class="grid-container"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
pgAdmin.Dashboard.init_server_dashboard({{ sid }}, {{ rates.session_stats_refresh }}, {{ rates.tps_stats_refresh }}, {{ rates.ti_stats_refresh }}, {{ rates.to_stats_refresh }}, {{ rates.bio_stats_refresh }} );
|
||||||
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
SELECT
|
||||||
|
pid,
|
||||||
|
datname,
|
||||||
|
usename,
|
||||||
|
application_name,
|
||||||
|
client_addr,
|
||||||
|
backend_start,
|
||||||
|
state
|
||||||
|
FROM
|
||||||
|
pg_stat_activity
|
||||||
|
{% if did %}WHERE
|
||||||
|
datname = (SELECT datname FROM pg_database WHERE oid = {{ did }}){% endif %}
|
||||||
|
ORDER BY pid
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
SELECT
|
||||||
|
(SELECT sum(blks_read) FROM pg_stat_database{% if did %} WHERE datname = (SELECT datname FROM pg_database WHERE oid = {{ did }}){% endif %}) AS "{{ _('Reads') }}",
|
||||||
|
(SELECT sum(blks_hit) FROM pg_stat_database{% if did %} WHERE datname = (SELECT datname FROM pg_database WHERE oid = {{ did }}){% endif %}) AS "{{ _('Hits') }}"
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
SELECT
|
||||||
|
name,
|
||||||
|
category,
|
||||||
|
setting,
|
||||||
|
unit,
|
||||||
|
short_desc
|
||||||
|
FROM
|
||||||
|
pg_settings
|
||||||
|
ORDER BY
|
||||||
|
category
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
SELECT
|
||||||
|
pid,
|
||||||
|
locktype,
|
||||||
|
datname,
|
||||||
|
relation::regclass,
|
||||||
|
page,
|
||||||
|
tuple,
|
||||||
|
virtualxid
|
||||||
|
transactionid,
|
||||||
|
classid::regclass,
|
||||||
|
objid,
|
||||||
|
objsubid,
|
||||||
|
virtualtransaction,
|
||||||
|
mode,
|
||||||
|
granted,
|
||||||
|
fastpath
|
||||||
|
FROM
|
||||||
|
pg_locks l
|
||||||
|
LEFT OUTER JOIN pg_database d ON (l.database = d.oid)
|
||||||
|
{% if did %}WHERE
|
||||||
|
datname = (SELECT datname FROM pg_database WHERE oid = {{ did }}){% endif %}
|
||||||
|
ORDER BY
|
||||||
|
pid, locktype
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
SELECT
|
||||||
|
gid,
|
||||||
|
database,
|
||||||
|
owner,
|
||||||
|
transaction,
|
||||||
|
prepared
|
||||||
|
FROM
|
||||||
|
pg_prepared_xacts
|
||||||
|
{% if did %}WHERE
|
||||||
|
database = (SELECT datname FROM pg_database WHERE oid = {{ did }}){% endif %}
|
||||||
|
ORDER BY
|
||||||
|
gid, database, owner
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
SELECT
|
||||||
|
(SELECT count(*) FROM pg_stat_activity{% if did %} WHERE datname = (SELECT datname FROM pg_database WHERE oid = {{ did }}){% endif %}) AS "{{ _('Total') }}",
|
||||||
|
(SELECT count(*) FROM pg_stat_activity WHERE state = 'active'{% if did %} AND datname = (SELECT datname FROM pg_database WHERE oid = {{ did }}){% endif %}) AS "{{ _('Active') }}",
|
||||||
|
(SELECT count(*) FROM pg_stat_activity WHERE state = 'idle'{% if did %} AND datname = (SELECT datname FROM pg_database WHERE oid = {{ did }}){% endif %}) AS "{{ _('Idle') }}"
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
SELECT
|
||||||
|
(SELECT sum(tup_inserted) FROM pg_stat_database{% if did %} WHERE datname = (SELECT datname FROM pg_database WHERE oid = {{ did }}){% endif %}) AS "{{ _('Inserts') }}",
|
||||||
|
(SELECT sum(tup_updated) FROM pg_stat_database{% if did %} WHERE datname = (SELECT datname FROM pg_database WHERE oid = {{ did }}){% endif %}) AS "{{ _('Updates') }}",
|
||||||
|
(SELECT sum(tup_deleted) FROM pg_stat_database{% if did %} WHERE datname = (SELECT datname FROM pg_database WHERE oid = {{ did }}){% endif %}) AS "{{ _('Deletes') }}"
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
SELECT
|
||||||
|
(SELECT sum(tup_fetched) FROM pg_stat_database{% if did %} WHERE datname = (SELECT datname FROM pg_database WHERE oid = {{ did }}){% endif %}) AS "{{ _('Fetched') }}",
|
||||||
|
(SELECT sum(tup_returned) FROM pg_stat_database{% if did %} WHERE datname = (SELECT datname FROM pg_database WHERE oid = {{ did }}){% endif %}) AS "{{ _('Returned') }}"
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
SELECT
|
||||||
|
(SELECT sum(xact_commit) + sum(xact_rollback) FROM pg_stat_database{% if did %} WHERE datname = (SELECT datname FROM pg_database WHERE oid = {{ did }}){% endif %}) AS "{{ _('Transactions') }}",
|
||||||
|
(SELECT sum(xact_commit) FROM pg_stat_database{% if did %} WHERE datname = (SELECT datname FROM pg_database WHERE oid = {{ did }}){% endif %}) AS "{{ _('Commits') }}",
|
||||||
|
(SELECT sum(xact_rollback) FROM pg_stat_database{% if did %} WHERE datname = (SELECT datname FROM pg_database WHERE oid = {{ did }}){% endif %}) AS "{{ _('Rollbacks') }}"
|
||||||
|
|
@ -1 +1,77 @@
|
||||||
<h1>Welcome Dashboard</h1>
|
<div class="dashboard-container">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-xs-12">
|
||||||
|
<div class="obj_properties">
|
||||||
|
<legend class="badge">Welcome</legend>
|
||||||
|
</div>
|
||||||
|
<div class="well well-lg">
|
||||||
|
<img src="{{ url_for('dashboard.static', filename='img/welcome_logo.png') }}" alt="pgAdmin 4">
|
||||||
|
<h4>Feature rich | Maximises PostgreSQL | Open Source </h4>
|
||||||
|
<p>
|
||||||
|
pgAdmin is an open source administration and management tool for the PostgreSQL
|
||||||
|
database. The tools include a graphical administration interface, an SQL query
|
||||||
|
tool, a procedural code debugger and much more. The tool is designed to answer
|
||||||
|
the needs of developers, DBAs and system administrators alike.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-xs-12">
|
||||||
|
<div class="obj_properties">
|
||||||
|
<legend class="badge">Quick Links</legend>
|
||||||
|
</div>
|
||||||
|
<div class="well well-lg">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-xs-6 dashboard-link">
|
||||||
|
<a onclick="pgAdmin.Dashboard.add_new_server()">
|
||||||
|
<span class="fa fa-4x dashboard-icon fa-server" aria-hidden="true"></span><br />
|
||||||
|
Add New Server
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-6 dashboard-link">
|
||||||
|
<a onclick="pgAdmin.Preferences.show()">
|
||||||
|
<span id="mnu_preferences" class="fa fa-4x dashboard-icon fa-cogs" aria-hidden="true"></span><br />
|
||||||
|
Configure pgAdmin
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-xs-12">
|
||||||
|
<div class="obj_properties">
|
||||||
|
<legend class="badge">Getting Started</legend>
|
||||||
|
</div>
|
||||||
|
<div class="well well-lg">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-xs-3 dashboard-link">
|
||||||
|
<a href="http://www.postgresql.org/docs" target="_new">
|
||||||
|
<span class="fa fa-4x dashboard-icon icon-postgres" aria-hidden="true"></span><br />
|
||||||
|
PostgreSQL Documentation
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-3 dashboard-link">
|
||||||
|
<a href="http://www.pgadmin.org" target="_new">
|
||||||
|
<span class="fa fa-4x dashboard-icon fa-globe" aria-hidden="true"></span><br />
|
||||||
|
pgAdmin Website
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-3 dashboard-link">
|
||||||
|
<a href="http://planet.postgresql.org" target="_new">
|
||||||
|
<span class="fa fa-4x dashboard-icon fa-book" aria-hidden="true"></span><br />
|
||||||
|
Planet PostgreSQL
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-3 dashboard-link">
|
||||||
|
<a href="http://www.postgresql.org/community" target="_new">
|
||||||
|
<span class="fa fa-4x dashboard-icon fa-users" aria-hidden="true"></span><br />
|
||||||
|
Community Support
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -809,7 +809,7 @@ td.edit-cell.editable.sortable.renderable.editor {
|
||||||
/* Message panel shown on browser tabs */
|
/* Message panel shown on browser tabs */
|
||||||
.pg-panel-message {
|
.pg-panel-message {
|
||||||
margin-top: 25px !important;
|
margin-top: 25px !important;
|
||||||
width: 600px;
|
width: 80%;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
position: relative;
|
position: relative;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1,503 @@
|
||||||
|
/*!
|
||||||
|
* bean.js - copyright Jacob Thornton 2011
|
||||||
|
* https://github.com/fat/bean
|
||||||
|
* MIT License
|
||||||
|
* special thanks to:
|
||||||
|
* dean edwards: http://dean.edwards.name/
|
||||||
|
* dperini: https://github.com/dperini/nwevents
|
||||||
|
* the entire mootools team: github.com/mootools/mootools-core
|
||||||
|
*/
|
||||||
|
/*global module:true, define:true*/
|
||||||
|
!function (name, context, definition) {
|
||||||
|
if (typeof module !== 'undefined') module.exports = definition(name, context);
|
||||||
|
else if (typeof define === 'function' && typeof define.amd === 'object') define(definition);
|
||||||
|
else context[name] = definition(name, context);
|
||||||
|
}('bean', this, function (name, context) {
|
||||||
|
var win = window
|
||||||
|
, old = context[name]
|
||||||
|
, overOut = /over|out/
|
||||||
|
, namespaceRegex = /[^\.]*(?=\..*)\.|.*/
|
||||||
|
, nameRegex = /\..*/
|
||||||
|
, addEvent = 'addEventListener'
|
||||||
|
, attachEvent = 'attachEvent'
|
||||||
|
, removeEvent = 'removeEventListener'
|
||||||
|
, detachEvent = 'detachEvent'
|
||||||
|
, doc = document || {}
|
||||||
|
, root = doc.documentElement || {}
|
||||||
|
, W3C_MODEL = root[addEvent]
|
||||||
|
, eventSupport = W3C_MODEL ? addEvent : attachEvent
|
||||||
|
, slice = Array.prototype.slice
|
||||||
|
, mouseTypeRegex = /click|mouse|menu|drag|drop/i
|
||||||
|
, touchTypeRegex = /^touch|^gesture/i
|
||||||
|
, ONE = { one: 1 } // singleton for quick matching making add() do one()
|
||||||
|
|
||||||
|
, nativeEvents = (function (hash, events, i) {
|
||||||
|
for (i = 0; i < events.length; i++)
|
||||||
|
hash[events[i]] = 1
|
||||||
|
return hash
|
||||||
|
})({}, (
|
||||||
|
'click dblclick mouseup mousedown contextmenu ' + // mouse buttons
|
||||||
|
'mousewheel DOMMouseScroll ' + // mouse wheel
|
||||||
|
'mouseover mouseout mousemove selectstart selectend ' + // mouse movement
|
||||||
|
'keydown keypress keyup ' + // keyboard
|
||||||
|
'orientationchange ' + // mobile
|
||||||
|
'focus blur change reset select submit ' + // form elements
|
||||||
|
'load unload beforeunload resize move DOMContentLoaded readystatechange ' + // window
|
||||||
|
'error abort scroll ' + // misc
|
||||||
|
(W3C_MODEL ? // element.fireEvent('onXYZ'... is not forgiving if we try to fire an event
|
||||||
|
// that doesn't actually exist, so make sure we only do these on newer browsers
|
||||||
|
'show ' + // mouse buttons
|
||||||
|
'input invalid ' + // form elements
|
||||||
|
'touchstart touchmove touchend touchcancel ' + // touch
|
||||||
|
'gesturestart gesturechange gestureend ' + // gesture
|
||||||
|
'message readystatechange pageshow pagehide popstate ' + // window
|
||||||
|
'hashchange offline online ' + // window
|
||||||
|
'afterprint beforeprint ' + // printing
|
||||||
|
'dragstart dragenter dragover dragleave drag drop dragend ' + // dnd
|
||||||
|
'loadstart progress suspend emptied stalled loadmetadata ' + // media
|
||||||
|
'loadeddata canplay canplaythrough playing waiting seeking ' + // media
|
||||||
|
'seeked ended durationchange timeupdate play pause ratechange ' + // media
|
||||||
|
'volumechange cuechange ' + // media
|
||||||
|
'checking noupdate downloading cached updateready obsolete ' + // appcache
|
||||||
|
'' : '')
|
||||||
|
).split(' ')
|
||||||
|
)
|
||||||
|
|
||||||
|
, customEvents = (function () {
|
||||||
|
function isDescendant(parent, node) {
|
||||||
|
while ((node = node.parentNode) !== null) {
|
||||||
|
if (node === parent) return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
function check(event) {
|
||||||
|
var related = event.relatedTarget
|
||||||
|
if (!related) return related === null
|
||||||
|
return (related !== this && related.prefix !== 'xul' && !/document/.test(this.toString()) && !isDescendant(this, related))
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
mouseenter: { base: 'mouseover', condition: check }
|
||||||
|
, mouseleave: { base: 'mouseout', condition: check }
|
||||||
|
, mousewheel: { base: /Firefox/.test(navigator.userAgent) ? 'DOMMouseScroll' : 'mousewheel' }
|
||||||
|
}
|
||||||
|
})()
|
||||||
|
|
||||||
|
, fixEvent = (function () {
|
||||||
|
var commonProps = 'altKey attrChange attrName bubbles cancelable ctrlKey currentTarget detail eventPhase getModifierState isTrusted metaKey relatedNode relatedTarget shiftKey srcElement target timeStamp type view which'.split(' ')
|
||||||
|
, mouseProps = commonProps.concat('button buttons clientX clientY dataTransfer fromElement offsetX offsetY pageX pageY screenX screenY toElement'.split(' '))
|
||||||
|
, keyProps = commonProps.concat('char charCode key keyCode'.split(' '))
|
||||||
|
, touchProps = commonProps.concat('touches targetTouches changedTouches scale rotation'.split(' '))
|
||||||
|
, preventDefault = 'preventDefault'
|
||||||
|
, createPreventDefault = function (event) {
|
||||||
|
return function () {
|
||||||
|
if (event[preventDefault])
|
||||||
|
event[preventDefault]()
|
||||||
|
else
|
||||||
|
event.returnValue = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
, stopPropagation = 'stopPropagation'
|
||||||
|
, createStopPropagation = function (event) {
|
||||||
|
return function () {
|
||||||
|
if (event[stopPropagation])
|
||||||
|
event[stopPropagation]()
|
||||||
|
else
|
||||||
|
event.cancelBubble = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
, createStop = function (synEvent) {
|
||||||
|
return function () {
|
||||||
|
synEvent[preventDefault]()
|
||||||
|
synEvent[stopPropagation]()
|
||||||
|
synEvent.stopped = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
, copyProps = function (event, result, props) {
|
||||||
|
var i, p
|
||||||
|
for (i = props.length; i--;) {
|
||||||
|
p = props[i]
|
||||||
|
if (!(p in result) && p in event) result[p] = event[p]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return function (event, isNative) {
|
||||||
|
var result = { originalEvent: event, isNative: isNative }
|
||||||
|
if (!event)
|
||||||
|
return result
|
||||||
|
|
||||||
|
var props
|
||||||
|
, type = event.type
|
||||||
|
, target = event.target || event.srcElement
|
||||||
|
|
||||||
|
result[preventDefault] = createPreventDefault(event)
|
||||||
|
result[stopPropagation] = createStopPropagation(event)
|
||||||
|
result.stop = createStop(result)
|
||||||
|
result.target = target && target.nodeType === 3 ? target.parentNode : target
|
||||||
|
|
||||||
|
if (isNative) { // we only need basic augmentation on custom events, the rest is too expensive
|
||||||
|
if (type.indexOf('key') !== -1) {
|
||||||
|
props = keyProps
|
||||||
|
result.keyCode = event.which || event.keyCode
|
||||||
|
} else if (mouseTypeRegex.test(type)) {
|
||||||
|
props = mouseProps
|
||||||
|
result.rightClick = event.which === 3 || event.button === 2
|
||||||
|
result.pos = { x: 0, y: 0 }
|
||||||
|
if (event.pageX || event.pageY) {
|
||||||
|
result.clientX = event.pageX
|
||||||
|
result.clientY = event.pageY
|
||||||
|
} else if (event.clientX || event.clientY) {
|
||||||
|
result.clientX = event.clientX + doc.body.scrollLeft + root.scrollLeft
|
||||||
|
result.clientY = event.clientY + doc.body.scrollTop + root.scrollTop
|
||||||
|
}
|
||||||
|
if (overOut.test(type))
|
||||||
|
result.relatedTarget = event.relatedTarget || event[(type === 'mouseover' ? 'from' : 'to') + 'Element']
|
||||||
|
} else if (touchTypeRegex.test(type)) {
|
||||||
|
props = touchProps
|
||||||
|
}
|
||||||
|
copyProps(event, result, props || commonProps)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
})()
|
||||||
|
|
||||||
|
// if we're in old IE we can't do onpropertychange on doc or win so we use doc.documentElement for both
|
||||||
|
, targetElement = function (element, isNative) {
|
||||||
|
return !W3C_MODEL && !isNative && (element === doc || element === win) ? root : element
|
||||||
|
}
|
||||||
|
|
||||||
|
// we use one of these per listener, of any type
|
||||||
|
, RegEntry = (function () {
|
||||||
|
function entry(element, type, handler, original, namespaces) {
|
||||||
|
this.element = element
|
||||||
|
this.type = type
|
||||||
|
this.handler = handler
|
||||||
|
this.original = original
|
||||||
|
this.namespaces = namespaces
|
||||||
|
this.custom = customEvents[type]
|
||||||
|
this.isNative = nativeEvents[type] && element[eventSupport]
|
||||||
|
this.eventType = W3C_MODEL || this.isNative ? type : 'propertychange'
|
||||||
|
this.customType = !W3C_MODEL && !this.isNative && type
|
||||||
|
this.target = targetElement(element, this.isNative)
|
||||||
|
this.eventSupport = this.target[eventSupport]
|
||||||
|
}
|
||||||
|
|
||||||
|
entry.prototype = {
|
||||||
|
// given a list of namespaces, is our entry in any of them?
|
||||||
|
inNamespaces: function (checkNamespaces) {
|
||||||
|
var i, j
|
||||||
|
if (!checkNamespaces)
|
||||||
|
return true
|
||||||
|
if (!this.namespaces)
|
||||||
|
return false
|
||||||
|
for (i = checkNamespaces.length; i--;) {
|
||||||
|
for (j = this.namespaces.length; j--;) {
|
||||||
|
if (checkNamespaces[i] === this.namespaces[j])
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// match by element, original fn (opt), handler fn (opt)
|
||||||
|
, matches: function (checkElement, checkOriginal, checkHandler) {
|
||||||
|
return this.element === checkElement &&
|
||||||
|
(!checkOriginal || this.original === checkOriginal) &&
|
||||||
|
(!checkHandler || this.handler === checkHandler)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return entry
|
||||||
|
})()
|
||||||
|
|
||||||
|
, registry = (function () {
|
||||||
|
// our map stores arrays by event type, just because it's better than storing
|
||||||
|
// everything in a single array. uses '$' as a prefix for the keys for safety
|
||||||
|
var map = {}
|
||||||
|
|
||||||
|
// generic functional search of our registry for matching listeners,
|
||||||
|
// `fn` returns false to break out of the loop
|
||||||
|
, forAll = function (element, type, original, handler, fn) {
|
||||||
|
if (!type || type === '*') {
|
||||||
|
// search the whole registry
|
||||||
|
for (var t in map) {
|
||||||
|
if (t.charAt(0) === '$')
|
||||||
|
forAll(element, t.substr(1), original, handler, fn)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
var i = 0, l, list = map['$' + type], all = element === '*'
|
||||||
|
if (!list)
|
||||||
|
return
|
||||||
|
for (l = list.length; i < l; i++) {
|
||||||
|
if (all || list[i].matches(element, original, handler))
|
||||||
|
if (!fn(list[i], list, i, type))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
, has = function (element, type, original) {
|
||||||
|
// we're not using forAll here simply because it's a bit slower and this
|
||||||
|
// needs to be fast
|
||||||
|
var i, list = map['$' + type]
|
||||||
|
if (list) {
|
||||||
|
for (i = list.length; i--;) {
|
||||||
|
if (list[i].matches(element, original, null))
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
, get = function (element, type, original) {
|
||||||
|
var entries = []
|
||||||
|
forAll(element, type, original, null, function (entry) { return entries.push(entry) })
|
||||||
|
return entries
|
||||||
|
}
|
||||||
|
|
||||||
|
, put = function (entry) {
|
||||||
|
(map['$' + entry.type] || (map['$' + entry.type] = [])).push(entry)
|
||||||
|
return entry
|
||||||
|
}
|
||||||
|
|
||||||
|
, del = function (entry) {
|
||||||
|
forAll(entry.element, entry.type, null, entry.handler, function (entry, list, i) {
|
||||||
|
list.splice(i, 1)
|
||||||
|
if (list.length === 0)
|
||||||
|
delete map['$' + entry.type]
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// dump all entries, used for onunload
|
||||||
|
, entries = function () {
|
||||||
|
var t, entries = []
|
||||||
|
for (t in map) {
|
||||||
|
if (t.charAt(0) === '$')
|
||||||
|
entries = entries.concat(map[t])
|
||||||
|
}
|
||||||
|
return entries
|
||||||
|
}
|
||||||
|
|
||||||
|
return { has: has, get: get, put: put, del: del, entries: entries }
|
||||||
|
})()
|
||||||
|
|
||||||
|
// add and remove listeners to DOM elements
|
||||||
|
, listener = W3C_MODEL ? function (element, type, fn, add) {
|
||||||
|
element[add ? addEvent : removeEvent](type, fn, false)
|
||||||
|
} : function (element, type, fn, add, custom) {
|
||||||
|
if (custom && add && element['_on' + custom] === null)
|
||||||
|
element['_on' + custom] = 0
|
||||||
|
element[add ? attachEvent : detachEvent]('on' + type, fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
, nativeHandler = function (element, fn, args) {
|
||||||
|
return function (event) {
|
||||||
|
event = fixEvent(event || ((this.ownerDocument || this.document || this).parentWindow || win).event, true)
|
||||||
|
return fn.apply(element, [event].concat(args))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
, customHandler = function (element, fn, type, condition, args, isNative) {
|
||||||
|
return function (event) {
|
||||||
|
if (condition ? condition.apply(this, arguments) : W3C_MODEL ? true : event && event.propertyName === '_on' + type || !event) {
|
||||||
|
if (event)
|
||||||
|
event = fixEvent(event || ((this.ownerDocument || this.document || this).parentWindow || win).event, isNative)
|
||||||
|
fn.apply(element, event && (!args || args.length === 0) ? arguments : slice.call(arguments, event ? 0 : 1).concat(args))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
, once = function (rm, element, type, fn, originalFn) {
|
||||||
|
// wrap the handler in a handler that does a remove as well
|
||||||
|
return function () {
|
||||||
|
rm(element, type, originalFn)
|
||||||
|
fn.apply(this, arguments)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
, removeListener = function (element, orgType, handler, namespaces) {
|
||||||
|
var i, l, entry
|
||||||
|
, type = (orgType && orgType.replace(nameRegex, ''))
|
||||||
|
, handlers = registry.get(element, type, handler)
|
||||||
|
|
||||||
|
for (i = 0, l = handlers.length; i < l; i++) {
|
||||||
|
if (handlers[i].inNamespaces(namespaces)) {
|
||||||
|
if ((entry = handlers[i]).eventSupport)
|
||||||
|
listener(entry.target, entry.eventType, entry.handler, false, entry.type)
|
||||||
|
// TODO: this is problematic, we have a registry.get() and registry.del() that
|
||||||
|
// both do registry searches so we waste cycles doing this. Needs to be rolled into
|
||||||
|
// a single registry.forAll(fn) that removes while finding, but the catch is that
|
||||||
|
// we'll be splicing the arrays that we're iterating over. Needs extra tests to
|
||||||
|
// make sure we don't screw it up. @rvagg
|
||||||
|
registry.del(entry)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
, addListener = function (element, orgType, fn, originalFn, args) {
|
||||||
|
var entry
|
||||||
|
, type = orgType.replace(nameRegex, '')
|
||||||
|
, namespaces = orgType.replace(namespaceRegex, '').split('.')
|
||||||
|
|
||||||
|
if (registry.has(element, type, fn))
|
||||||
|
return element // no dupe
|
||||||
|
if (type === 'unload')
|
||||||
|
fn = once(removeListener, element, type, fn, originalFn) // self clean-up
|
||||||
|
if (customEvents[type]) {
|
||||||
|
if (customEvents[type].condition)
|
||||||
|
fn = customHandler(element, fn, type, customEvents[type].condition, true)
|
||||||
|
type = customEvents[type].base || type
|
||||||
|
}
|
||||||
|
entry = registry.put(new RegEntry(element, type, fn, originalFn, namespaces[0] && namespaces))
|
||||||
|
entry.handler = entry.isNative ?
|
||||||
|
nativeHandler(element, entry.handler, args) :
|
||||||
|
customHandler(element, entry.handler, type, false, args, false)
|
||||||
|
if (entry.eventSupport)
|
||||||
|
listener(entry.target, entry.eventType, entry.handler, true, entry.customType)
|
||||||
|
}
|
||||||
|
|
||||||
|
, del = function (selector, fn, $) {
|
||||||
|
return function (e) {
|
||||||
|
var target, i, array = typeof selector === 'string' ? $(selector, this) : selector
|
||||||
|
for (target = e.target; target && target !== this; target = target.parentNode) {
|
||||||
|
for (i = array.length; i--;) {
|
||||||
|
if (array[i] === target) {
|
||||||
|
return fn.apply(target, arguments)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
, remove = function (element, typeSpec, fn) {
|
||||||
|
var k, m, type, namespaces, i
|
||||||
|
, rm = removeListener
|
||||||
|
, isString = typeSpec && typeof typeSpec === 'string'
|
||||||
|
|
||||||
|
if (isString && typeSpec.indexOf(' ') > 0) {
|
||||||
|
// remove(el, 't1 t2 t3', fn) or remove(el, 't1 t2 t3')
|
||||||
|
typeSpec = typeSpec.split(' ')
|
||||||
|
for (i = typeSpec.length; i--;)
|
||||||
|
remove(element, typeSpec[i], fn)
|
||||||
|
return element
|
||||||
|
}
|
||||||
|
type = isString && typeSpec.replace(nameRegex, '')
|
||||||
|
if (type && customEvents[type])
|
||||||
|
type = customEvents[type].type
|
||||||
|
if (!typeSpec || isString) {
|
||||||
|
// remove(el) or remove(el, t1.ns) or remove(el, .ns) or remove(el, .ns1.ns2.ns3)
|
||||||
|
if (namespaces = isString && typeSpec.replace(namespaceRegex, ''))
|
||||||
|
namespaces = namespaces.split('.')
|
||||||
|
rm(element, type, fn, namespaces)
|
||||||
|
} else if (typeof typeSpec === 'function') {
|
||||||
|
// remove(el, fn)
|
||||||
|
rm(element, null, typeSpec)
|
||||||
|
} else {
|
||||||
|
// remove(el, { t1: fn1, t2, fn2 })
|
||||||
|
for (k in typeSpec) {
|
||||||
|
if (typeSpec.hasOwnProperty(k))
|
||||||
|
remove(element, k, typeSpec[k])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return element
|
||||||
|
}
|
||||||
|
|
||||||
|
, add = function (element, events, fn, delfn, $) {
|
||||||
|
var type, types, i, args
|
||||||
|
, originalFn = fn
|
||||||
|
, isDel = fn && typeof fn === 'string'
|
||||||
|
|
||||||
|
if (events && !fn && typeof events === 'object') {
|
||||||
|
for (type in events) {
|
||||||
|
if (events.hasOwnProperty(type))
|
||||||
|
add.apply(this, [ element, type, events[type] ])
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
args = arguments.length > 3 ? slice.call(arguments, 3) : []
|
||||||
|
types = (isDel ? fn : events).split(' ')
|
||||||
|
isDel && (fn = del(events, (originalFn = delfn), $)) && (args = slice.call(args, 1))
|
||||||
|
// special case for one()
|
||||||
|
this === ONE && (fn = once(remove, element, events, fn, originalFn))
|
||||||
|
for (i = types.length; i--;) addListener(element, types[i], fn, originalFn, args)
|
||||||
|
}
|
||||||
|
return element
|
||||||
|
}
|
||||||
|
|
||||||
|
, one = function () {
|
||||||
|
return add.apply(ONE, arguments)
|
||||||
|
}
|
||||||
|
|
||||||
|
, fireListener = W3C_MODEL ? function (isNative, type, element) {
|
||||||
|
var evt = doc.createEvent(isNative ? 'HTMLEvents' : 'UIEvents')
|
||||||
|
evt[isNative ? 'initEvent' : 'initUIEvent'](type, true, true, win, 1)
|
||||||
|
element.dispatchEvent(evt)
|
||||||
|
} : function (isNative, type, element) {
|
||||||
|
element = targetElement(element, isNative)
|
||||||
|
// if not-native then we're using onpropertychange so we just increment a custom property
|
||||||
|
isNative ? element.fireEvent('on' + type, doc.createEventObject()) : element['_on' + type]++
|
||||||
|
}
|
||||||
|
|
||||||
|
, fire = function (element, type, args) {
|
||||||
|
var i, j, l, names, handlers
|
||||||
|
, types = type.split(' ')
|
||||||
|
|
||||||
|
for (i = types.length; i--;) {
|
||||||
|
type = types[i].replace(nameRegex, '')
|
||||||
|
if (names = types[i].replace(namespaceRegex, ''))
|
||||||
|
names = names.split('.')
|
||||||
|
if (!names && !args && element[eventSupport]) {
|
||||||
|
fireListener(nativeEvents[type], type, element)
|
||||||
|
} else {
|
||||||
|
// non-native event, either because of a namespace, arguments or a non DOM element
|
||||||
|
// iterate over all listeners and manually 'fire'
|
||||||
|
handlers = registry.get(element, type)
|
||||||
|
args = [false].concat(args)
|
||||||
|
for (j = 0, l = handlers.length; j < l; j++) {
|
||||||
|
if (handlers[j].inNamespaces(names))
|
||||||
|
handlers[j].handler.apply(element, args)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return element
|
||||||
|
}
|
||||||
|
|
||||||
|
, clone = function (element, from, type) {
|
||||||
|
var i = 0
|
||||||
|
, handlers = registry.get(from, type)
|
||||||
|
, l = handlers.length
|
||||||
|
|
||||||
|
for (;i < l; i++)
|
||||||
|
handlers[i].original && add(element, handlers[i].type, handlers[i].original)
|
||||||
|
return element
|
||||||
|
}
|
||||||
|
|
||||||
|
, bean = {
|
||||||
|
add: add
|
||||||
|
, one: one
|
||||||
|
, remove: remove
|
||||||
|
, clone: clone
|
||||||
|
, fire: fire
|
||||||
|
, noConflict: function () {
|
||||||
|
context[name] = old
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (win[attachEvent]) {
|
||||||
|
// for IE, clean up on unload to avoid leaks
|
||||||
|
var cleanup = function () {
|
||||||
|
var i, entries = registry.entries()
|
||||||
|
for (i in entries) {
|
||||||
|
if (entries[i].type && entries[i].type !== 'unload')
|
||||||
|
remove(entries[i].element, entries[i].type)
|
||||||
|
}
|
||||||
|
win[detachEvent]('onunload', cleanup)
|
||||||
|
win.CollectGarbage && win.CollectGarbage()
|
||||||
|
}
|
||||||
|
win[attachEvent]('onunload', cleanup)
|
||||||
|
}
|
||||||
|
|
||||||
|
return bean
|
||||||
|
});
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -89,6 +89,13 @@
|
||||||
},
|
},
|
||||||
"pgadmin.backform": {
|
"pgadmin.backform": {
|
||||||
"deps": ['backform', "pgadmin.backgrid", "select2"],
|
"deps": ['backform', "pgadmin.backgrid", "select2"],
|
||||||
|
},
|
||||||
|
"flotr2": {
|
||||||
|
deps: ['bean'],
|
||||||
|
exports: function(bean) {
|
||||||
|
Flotr.bean = bean;
|
||||||
|
return this.Flotr;
|
||||||
|
}
|
||||||
}{% for script in current_app.javascripts %}{% if 'deps' in script or 'exports' in script %},
|
}{% for script in current_app.javascripts %}{% if 'deps' in script or 'exports' in script %},
|
||||||
'{{ script.name }}': {
|
'{{ script.name }}': {
|
||||||
{% if 'deps' in script %}"deps": [ {% set comma = False %}{% for dep in script['deps'] %} {% if comma %},{% else %}{% set comma = True %}{% endif %} '{{ dep }}'{% endfor %}],{% endif %}
|
{% if 'deps' in script %}"deps": [ {% set comma = False %}{% for dep in script['deps'] %} {% if comma %},{% else %}{% set comma = True %}{% endif %} '{{ dep }}'{% endfor %}],{% endif %}
|
||||||
|
|
@ -116,7 +123,9 @@
|
||||||
"backgrid.filter": "{{ url_for('static', filename='js/backgrid/' + ('backgrid-filter' if config.DEBUG else 'backgrid-filter.min')) }}",
|
"backgrid.filter": "{{ url_for('static', filename='js/backgrid/' + ('backgrid-filter' if config.DEBUG else 'backgrid-filter.min')) }}",
|
||||||
"backbone.undo": "{{ url_for('static', filename='js/' + ('backbone.undo' if config.DEBUG else 'backbone.undo.min')) }}",
|
"backbone.undo": "{{ url_for('static', filename='js/' + ('backbone.undo' if config.DEBUG else 'backbone.undo.min')) }}",
|
||||||
"pgadmin.backgrid": "{{ url_for('static', filename='js/backgrid/backgrid.pgadmin') }}",
|
"pgadmin.backgrid": "{{ url_for('static', filename='js/backgrid/backgrid.pgadmin') }}",
|
||||||
'pgadmin.backform': "{{ url_for('static', filename='js/backform.pgadmin') }}"{% for script in current_app.javascripts %},
|
'pgadmin.backform': "{{ url_for('static', filename='js/backform.pgadmin') }}",
|
||||||
|
bean :"{{ url_for('static', filename='js/flotr2/' + ('bean' if config.DEBUG else 'bean-min')) }}",
|
||||||
|
flotr2 :"{{ url_for('static', filename='js/flotr2/flotr2.amd') }}"{% for script in current_app.javascripts %},
|
||||||
'{{ script.name }}': "{{ script.path }}"{% endfor %}
|
'{{ script.name }}': "{{ script.path }}"{% endfor %}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ class Panel(object):
|
||||||
|
|
||||||
def __init__(self, name, title, content='', width=500, height=600, isIframe=True,
|
def __init__(self, name, title, content='', width=500, height=600, isIframe=True,
|
||||||
showTitle=True, isCloseable=True, isPrivate=False, priority=None,
|
showTitle=True, isCloseable=True, isPrivate=False, priority=None,
|
||||||
icon=None, data=None):
|
icon=None, data=None, events=None):
|
||||||
self.name = name
|
self.name = name
|
||||||
self.title = title
|
self.title = title
|
||||||
self.content = content
|
self.content = content
|
||||||
|
|
@ -31,7 +31,8 @@ class Panel(object):
|
||||||
self.isCloseable = isCloseable
|
self.isCloseable = isCloseable
|
||||||
self.isPrivate = isPrivate
|
self.isPrivate = isPrivate
|
||||||
self.icon = icon
|
self.icon = icon
|
||||||
self.data = None
|
self.data = data
|
||||||
|
self.events = events
|
||||||
if priority is None:
|
if priority is None:
|
||||||
global PRIORITY
|
global PRIORITY
|
||||||
PRIORITY += 100
|
PRIORITY += 100
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue