Dashboards v1
parent
07e0de5399
commit
0628ee0425
|
|
@ -96,7 +96,7 @@ class ServerGroupView(NodeView):
|
|||
|
||||
for sg in ServerGroup.query.filter_by(
|
||||
user_id=current_user.id
|
||||
).order_by(name):
|
||||
).order_by('name'):
|
||||
res.append({
|
||||
'id': sg.id,
|
||||
'name': sg.name
|
||||
|
|
@ -255,7 +255,7 @@ class ServerGroupView(NodeView):
|
|||
|
||||
for group in groups:
|
||||
nodes.append(
|
||||
self.generate_browser_node(
|
||||
self.blueprint.generate_browser_node(
|
||||
"%d" % (group.id), None,
|
||||
group.name,
|
||||
"icon-%s" % self.node_type,
|
||||
|
|
|
|||
|
|
@ -333,8 +333,9 @@ class ServerNode(PGChildNodeView):
|
|||
from pgadmin.utils.driver import get_driver
|
||||
manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(sid)
|
||||
conn = manager.connection()
|
||||
connected = conn.connected()
|
||||
|
||||
if conn.connected():
|
||||
if connected:
|
||||
for arg in (
|
||||
'host', 'port', 'db', 'username', 'sslmode', 'role'
|
||||
):
|
||||
|
|
@ -373,11 +374,16 @@ class ServerNode(PGChildNodeView):
|
|||
|
||||
return make_json_response(
|
||||
success=1,
|
||||
data={
|
||||
'id': server.id,
|
||||
'gid': server.servergroup_id,
|
||||
'icon': 'icon-server-not-connected'
|
||||
}
|
||||
node=self.blueprint.generate_browser_node(
|
||||
"%d" % (server.id), server.servergroup_id,
|
||||
server.name,
|
||||
"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):
|
||||
|
|
@ -490,7 +496,7 @@ class ServerNode(PGChildNodeView):
|
|||
try:
|
||||
server = Server(
|
||||
user_id=current_user.id,
|
||||
servergroup_id=gid,
|
||||
servergroup_id=data[u'gid'] if u'gid' in data else gid,
|
||||
name=data[u'name'],
|
||||
host=data[u'host'],
|
||||
port=data[u'port'],
|
||||
|
|
@ -505,7 +511,7 @@ class ServerNode(PGChildNodeView):
|
|||
|
||||
return jsonify(
|
||||
node=self.blueprint.generate_browser_node(
|
||||
"%d" % (server.id), gid,
|
||||
"%d" % (server.id), server.servergroup_id,
|
||||
server.name,
|
||||
"icon-server-not-connected",
|
||||
True,
|
||||
|
|
|
|||
|
|
@ -148,6 +148,7 @@ function($, _, S, pgAdmin, pgBrowser, alertify) {
|
|||
},
|
||||
model: pgAdmin.Browser.Node.Model.extend({
|
||||
defaults: {
|
||||
gid: undefined,
|
||||
id: undefined,
|
||||
name: null,
|
||||
sslmode: 'prefer',
|
||||
|
|
@ -157,8 +158,21 @@ function($, _, S, pgAdmin, pgBrowser, alertify) {
|
|||
username: '{{ username }}',
|
||||
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: [{
|
||||
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',
|
||||
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
|
||||
* these're about to be loaded.
|
||||
*
|
||||
* This will make sure that - we do check for the script for
|
||||
* loading only once.
|
||||
* This will make sure that we check for the script to be
|
||||
* loaded only once.
|
||||
*
|
||||
*/
|
||||
delete obj.scripts[d._type];
|
||||
|
|
@ -405,7 +405,7 @@ function(require, $, _, S, Bootstrap, pgAdmin, alertify, CodeMirror) {
|
|||
if (!s.loaded) {
|
||||
require([s.name], function(m) {
|
||||
s.loaded = true;
|
||||
// Call the initialize (if present)
|
||||
// Call the initializer (if present)
|
||||
if (m && m.init && typeof m.init == 'function') {
|
||||
try {
|
||||
m.init();
|
||||
|
|
|
|||
|
|
@ -10,14 +10,20 @@
|
|||
"""A blueprint module implementing the dashboard frame."""
|
||||
MODULE_NAME = 'dashboard'
|
||||
|
||||
from functools import wraps
|
||||
|
||||
from config import PG_DEFAULT_DRIVER
|
||||
from flask import render_template, url_for, Response
|
||||
from flask.ext.babel import gettext
|
||||
from flask.ext.security import login_required
|
||||
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.driver import get_driver
|
||||
from pgadmin.utils.menu import Panel
|
||||
from pgadmin.utils.preferences import Preferences
|
||||
|
||||
server_info = {}
|
||||
|
||||
|
||||
class DashboardModule(PgAdminModule):
|
||||
|
|
@ -34,6 +40,16 @@ class DashboardModule(PgAdminModule):
|
|||
'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):
|
||||
return [
|
||||
Panel(
|
||||
|
|
@ -41,11 +57,60 @@ class DashboardModule(PgAdminModule):
|
|||
priority=1,
|
||||
title=gettext('Dashboard'),
|
||||
icon='fa fa-tachometer',
|
||||
content=url_for('dashboard.index'),
|
||||
content='',
|
||||
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__)
|
||||
|
||||
|
|
@ -58,21 +123,28 @@ def check_precondition(f):
|
|||
"""
|
||||
|
||||
@wraps(f)
|
||||
def wrap(**kwargs):
|
||||
def wrap(*args, **kwargs):
|
||||
# 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']
|
||||
)
|
||||
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 not conn.connected():
|
||||
if not server_info['conn'].connected():
|
||||
return precondition_required(
|
||||
gettext(
|
||||
"Connection to the server has been lost!"
|
||||
)
|
||||
gettext("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
|
||||
|
||||
|
|
@ -91,10 +163,193 @@ def script():
|
|||
@blueprint.route('/<int:sid>/<int:did>')
|
||||
@login_required
|
||||
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
|
||||
if sid is None and did is None:
|
||||
return render_template('/dashboard/welcome_dashboard.html')
|
||||
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:
|
||||
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 />
|
||||
<b>Database ID: </b>{{ did }}
|
||||
<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('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'],
|
||||
function($, pgAdmin, _) {
|
||||
define([
|
||||
'require', 'jquery', 'pgadmin', 'underscore', 'backbone', 'flotr2', 'wcdocker',
|
||||
'pgadmin.browser', 'bootstrap'
|
||||
],
|
||||
function(r, $, pgAdmin, _, Backbone) {
|
||||
|
||||
var wcDocker = window.wcDocker,
|
||||
pgBrowser = pgAdmin.Browser;
|
||||
var wcDocker = window.wcDocker,
|
||||
pgBrowser = pgAdmin.Browser;
|
||||
|
||||
/* Return back, this has been called more than once */
|
||||
if (pgAdmin.Dashboard)
|
||||
return;
|
||||
/* Return back, this has been called more than once */
|
||||
if (pgAdmin.Dashboard)
|
||||
return;
|
||||
|
||||
pgAdmin.Dashboard = {
|
||||
init: function() {
|
||||
if (this.initialized)
|
||||
return;
|
||||
pgAdmin.Dashboard = {
|
||||
init: function() {
|
||||
if (this.initialized)
|
||||
return;
|
||||
|
||||
this.initialized = true;
|
||||
this.initialized = true;
|
||||
|
||||
// Bind the Dashboard object with the 'object_selected' function
|
||||
var selected = this.object_selected.bind(this);
|
||||
// Bind the Dashboard object with the 'object_selected' function
|
||||
var selected = this.object_selected.bind(this);
|
||||
|
||||
// Listen for selection of any of object
|
||||
pgBrowser.Events.on('pgadmin-browser:tree:selected', selected);
|
||||
},
|
||||
// Listen for selection of any of object
|
||||
pgBrowser.Events.on('pgadmin-browser:tree:selected', selected);
|
||||
|
||||
object_selected: function(item, itemData, node) {
|
||||
var treeHierarchy = node.getTreeNodeHierarchy(item)
|
||||
if (itemData && itemData._type)
|
||||
{
|
||||
switch(itemData._type) {
|
||||
case ('server-group'):
|
||||
url = '{{ url_for('dashboard.index') }}';
|
||||
break;
|
||||
// Load the default welcome dashboard
|
||||
url = '{{ url_for('dashboard.index') }}';
|
||||
|
||||
case ('server'):
|
||||
case ('coll-database'):
|
||||
case ('coll-role'):
|
||||
case ('role'):
|
||||
case ('coll-tablespace'):
|
||||
case ('tablespace'):
|
||||
url = '{{ url_for('dashboard.index') }}'
|
||||
+ treeHierarchy.server._id;
|
||||
break;
|
||||
var dashboardPanel = pgBrowser.panels['dashboard'].panel;
|
||||
if (dashboardPanel) {
|
||||
var div = dashboardPanel.layout().scene().find('.pg-panel-content');
|
||||
|
||||
default:
|
||||
url = '{{ url_for('dashboard.index') }}'
|
||||
+ treeHierarchy.server._id
|
||||
+ '/' + treeHierarchy.database._id;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (div) {
|
||||
$.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>'
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
var dashboardPanel = pgBrowser.frames['dashboard'].panel;
|
||||
if (dashboardPanel) {
|
||||
var frame = $(dashboardPanel).data('embeddedFrame');
|
||||
// Cache the current IDs for next time
|
||||
$(dashboardPanel).data('sid', -1)
|
||||
$(dashboardPanel).data('did', -1)
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
if (frame) {
|
||||
// Avoid unnecessary reloads
|
||||
if (_.isUndefined(treeHierarchy.server) || _.isUndefined(treeHierarchy.server._id))
|
||||
sid = -1
|
||||
else
|
||||
sid = treeHierarchy.server._id
|
||||
// Handle treeview clicks
|
||||
object_selected: function(item, itemData, node) {
|
||||
var treeHierarchy = node.getTreeNodeHierarchy(item)
|
||||
if (itemData && itemData._type)
|
||||
{
|
||||
switch(itemData._type) {
|
||||
case ('server-group'):
|
||||
url = '{{ url_for('dashboard.index') }}';
|
||||
break;
|
||||
|
||||
if (_.isUndefined(treeHierarchy.database) || _.isUndefined(treeHierarchy.database._id))
|
||||
did = -1
|
||||
else
|
||||
did = treeHierarchy.database._id
|
||||
case ('server'):
|
||||
case ('coll-database'):
|
||||
case ('coll-role'):
|
||||
case ('role'):
|
||||
case ('coll-tablespace'):
|
||||
case ('tablespace'):
|
||||
url = '{{ url_for('dashboard.index') }}'
|
||||
+ treeHierarchy.server._id;
|
||||
break;
|
||||
|
||||
if (sid != $(dashboardPanel).data('sid') ||
|
||||
did != $(dashboardPanel).data('did')) {
|
||||
frame.openURL(url);
|
||||
default:
|
||||
url = '{{ url_for('dashboard.index') }}'
|
||||
+ treeHierarchy.server._id
|
||||
+ '/' + treeHierarchy.database._id;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Cache the current IDs for next time
|
||||
$(dashboardPanel).data('sid', sid)
|
||||
$(dashboardPanel).data('did', did)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
var dashboardPanel = pgBrowser.panels['dashboard'].panel;
|
||||
if (dashboardPanel) {
|
||||
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 */
|
||||
.pg-panel-message {
|
||||
margin-top: 25px !important;
|
||||
width: 600px;
|
||||
width: 80%;
|
||||
margin: 0 auto;
|
||||
position: relative;
|
||||
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": {
|
||||
"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 %},
|
||||
'{{ 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 %}
|
||||
|
|
@ -116,7 +123,9 @@
|
|||
"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')) }}",
|
||||
"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 %}
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ class Panel(object):
|
|||
|
||||
def __init__(self, name, title, content='', width=500, height=600, isIframe=True,
|
||||
showTitle=True, isCloseable=True, isPrivate=False, priority=None,
|
||||
icon=None, data=None):
|
||||
icon=None, data=None, events=None):
|
||||
self.name = name
|
||||
self.title = title
|
||||
self.content = content
|
||||
|
|
@ -31,7 +31,8 @@ class Panel(object):
|
|||
self.isCloseable = isCloseable
|
||||
self.isPrivate = isPrivate
|
||||
self.icon = icon
|
||||
self.data = None
|
||||
self.data = data
|
||||
self.events = events
|
||||
if priority is None:
|
||||
global PRIORITY
|
||||
PRIORITY += 100
|
||||
|
|
|
|||
Loading…
Reference in New Issue