pgAgent - add modules for jobs, steps and schedules. Fixes #1341
|
@ -0,0 +1,527 @@
|
|||
##########################################################################
|
||||
#
|
||||
# pgAdmin 4 - PostgreSQL Tools
|
||||
#
|
||||
# Copyright (C) 2013 - 2016, The pgAdmin Development Team
|
||||
# This software is released under the PostgreSQL Licence
|
||||
#
|
||||
##########################################################################
|
||||
|
||||
"""Implements the pgAgent Jobs Node"""
|
||||
from functools import wraps
|
||||
import json
|
||||
|
||||
from flask import render_template, make_response, request, jsonify
|
||||
from flask_babel import gettext as _
|
||||
|
||||
from config import PG_DEFAULT_DRIVER
|
||||
|
||||
from pgadmin.browser.collection import CollectionNodeModule
|
||||
from pgadmin.browser.utils import PGChildNodeView
|
||||
from pgadmin.browser.server_groups import servers
|
||||
from pgadmin.utils.ajax import make_json_response, internal_server_error, \
|
||||
make_response as ajax_response, gone, success_return
|
||||
from pgadmin.utils.driver import get_driver
|
||||
|
||||
|
||||
class JobModule(CollectionNodeModule):
|
||||
NODE_TYPE = 'pga_job'
|
||||
COLLECTION_LABEL = _("pgAgent Jobs")
|
||||
|
||||
def get_nodes(self, gid, sid):
|
||||
"""
|
||||
Generate the collection node
|
||||
"""
|
||||
if self.show_node:
|
||||
yield self.generate_browser_collection_node(sid)
|
||||
|
||||
@property
|
||||
def script_load(self):
|
||||
"""
|
||||
Load the module script for server, when any of the server-group node is
|
||||
initialized.
|
||||
"""
|
||||
return servers.ServerModule.NODE_TYPE
|
||||
|
||||
def BackendSupported(self, manager, **kwargs):
|
||||
if hasattr(self, 'show_node'):
|
||||
if not self.show_node:
|
||||
return False
|
||||
|
||||
conn = manager.connection()
|
||||
|
||||
status, res = conn.execute_scalar("""
|
||||
SELECT
|
||||
has_table_privilege('pgagent.pga_job', 'INSERT, SELECT, UPDATE') has_priviledge
|
||||
WHERE EXISTS(
|
||||
SELECT has_schema_privilege('pgagent', 'USAGE')
|
||||
WHERE EXISTS(
|
||||
SELECT cl.oid FROM pg_class cl
|
||||
LEFT JOIN pg_namespace ns ON ns.oid=relnamespace
|
||||
WHERE relname='pga_job' AND nspname='pgagent'
|
||||
)
|
||||
)
|
||||
""")
|
||||
if status and res:
|
||||
status, res = conn.execute_dict("""
|
||||
SELECT EXISTS(
|
||||
SELECT 1 FROM information_schema.columns
|
||||
WHERE
|
||||
table_schema='pgagent' AND table_name='pga_jobstep' AND
|
||||
column_name='jstconnstr'
|
||||
) has_connstr""")
|
||||
|
||||
manager.db_info['pgAgent'] = res['rows'][0]
|
||||
return True
|
||||
return False
|
||||
|
||||
@property
|
||||
def csssnippets(self):
|
||||
"""
|
||||
Returns a snippet of css to include in the page
|
||||
"""
|
||||
snippets = [
|
||||
render_template(
|
||||
"browser/css/collection.css",
|
||||
node_type=self.node_type,
|
||||
_=_
|
||||
),
|
||||
render_template(
|
||||
"pga_job/css/pga_job.css",
|
||||
node_type=self.node_type,
|
||||
_=_
|
||||
)
|
||||
]
|
||||
|
||||
for submodule in self.submodules:
|
||||
snippets.extend(submodule.csssnippets)
|
||||
|
||||
return snippets
|
||||
|
||||
|
||||
blueprint = JobModule(__name__)
|
||||
|
||||
|
||||
class JobView(PGChildNodeView):
|
||||
node_type = blueprint.node_type
|
||||
|
||||
parent_ids = [
|
||||
{'type': 'int', 'id': 'gid'},
|
||||
{'type': 'int', 'id': 'sid'}
|
||||
]
|
||||
ids = [
|
||||
{'type': 'int', 'id': 'jid'}
|
||||
]
|
||||
|
||||
operations = dict({
|
||||
'obj': [
|
||||
{'get': 'properties', 'delete': 'delete', 'put': 'update'},
|
||||
{'get': 'properties', 'post': 'create'}
|
||||
],
|
||||
'nodes': [{'get': 'nodes'}, {'get': 'nodes'}],
|
||||
'sql': [{'get': 'sql'}],
|
||||
'msql': [{'get': 'msql'}, {'get': 'msql'}],
|
||||
'run_now': [{'post': 'run_now'}],
|
||||
'classes': [{}, {'get': 'job_classes'}],
|
||||
'children': [{'get': 'children'}],
|
||||
'stats': [{'get': 'statistics'}],
|
||||
'module.js': [{}, {}, {'get': 'module_js'}]
|
||||
})
|
||||
|
||||
def check_precondition(f):
|
||||
"""
|
||||
This function will behave as a decorator which will checks
|
||||
database connection before running view, it will also attaches
|
||||
manager,conn & template_path properties to self
|
||||
"""
|
||||
|
||||
@wraps(f)
|
||||
def wrap(self, *args, **kwargs):
|
||||
|
||||
self.manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(kwargs['sid'])
|
||||
self.conn = self.manager.connection()
|
||||
|
||||
# Set the template path for the sql scripts.
|
||||
self.template_path = 'pga_job/sql/pre3.4'
|
||||
|
||||
if not ('pgAgent' in self.manager.db_info):
|
||||
status, res = self.conn.execute_dict("""
|
||||
SELECT EXISTS(
|
||||
SELECT 1 FROM information_schema.columns
|
||||
WHERE
|
||||
table_schema='pgagent' AND table_name='pga_jobstep' AND
|
||||
column_name='jstconnstr'
|
||||
) has_connstr""")
|
||||
|
||||
self.manager.db_info['pgAgent'] = res['rows'][0]
|
||||
|
||||
return f(self, *args, **kwargs)
|
||||
return wrap
|
||||
|
||||
@check_precondition
|
||||
def nodes(self, gid, sid, jid=None):
|
||||
SQL = render_template(
|
||||
"/".join([self.template_path, 'nodes.sql']),
|
||||
jid=jid, conn=self.conn
|
||||
)
|
||||
status, rset = self.conn.execute_dict(SQL)
|
||||
|
||||
if not status:
|
||||
return internal_server_error(errormsg=rset)
|
||||
|
||||
if jid is not None:
|
||||
if len(rset['rows']) != 1:
|
||||
return gone(
|
||||
errormsg=_(
|
||||
"Could not find the pgAgent job on the server."
|
||||
))
|
||||
return make_json_response(
|
||||
data=self.blueprint.generate_browser_node(
|
||||
rset['rows'][0]['jobid'],
|
||||
sid,
|
||||
rset['rows'][0]['jobname'],
|
||||
"icon-pga_job" if rset['rows'][0]['jobenabled'] else
|
||||
"icon-pga_job-disabled"
|
||||
),
|
||||
status=200
|
||||
)
|
||||
|
||||
res = []
|
||||
for row in rset['rows']:
|
||||
res.append(
|
||||
self.blueprint.generate_browser_node(
|
||||
row['jobid'],
|
||||
sid,
|
||||
row['jobname'],
|
||||
"icon-pga_job" if row['jobenabled'] else
|
||||
"icon-pga_job-disabled"
|
||||
)
|
||||
)
|
||||
|
||||
return make_json_response(
|
||||
data=res,
|
||||
status=200
|
||||
)
|
||||
|
||||
@check_precondition
|
||||
def properties(self, gid, sid, jid=None):
|
||||
SQL = render_template(
|
||||
"/".join([self.template_path, 'properties.sql']),
|
||||
jid=jid, conn=self.conn
|
||||
)
|
||||
status, rset = self.conn.execute_dict(SQL)
|
||||
|
||||
if not status:
|
||||
return internal_server_error(errormsg=rset)
|
||||
|
||||
if jid is not None:
|
||||
if len(rset['rows']) != 1:
|
||||
return gone(
|
||||
errormsg=_(
|
||||
"Could not find the pgAgent job on the server."
|
||||
)
|
||||
)
|
||||
res = rset['rows'][0]
|
||||
status, rset = self.conn.execute_dict(
|
||||
render_template(
|
||||
"/".join([self.template_path, 'steps.sql']),
|
||||
jid=jid, conn=self.conn,
|
||||
has_connstr=self.manager.db_info['pgAgent']['has_connstr']
|
||||
)
|
||||
)
|
||||
if not status:
|
||||
return internal_server_error(errormsg=rset)
|
||||
res['jsteps'] = rset['rows']
|
||||
status, rset = self.conn.execute_dict(
|
||||
render_template(
|
||||
"/".join([self.template_path, 'schedules.sql']),
|
||||
jid=jid, conn=self.conn
|
||||
)
|
||||
)
|
||||
if not status:
|
||||
return internal_server_error(errormsg=rset)
|
||||
res['jschedules'] = rset['rows']
|
||||
else:
|
||||
res = rset['rows']
|
||||
|
||||
return ajax_response(
|
||||
response=res,
|
||||
status=200
|
||||
)
|
||||
|
||||
def module_js(self):
|
||||
"""
|
||||
This property defines (if javascript) exists for this node.
|
||||
Override this property for your own logic.
|
||||
"""
|
||||
return make_response(
|
||||
render_template(
|
||||
"pga_job/js/pga_job.js",
|
||||
_=_
|
||||
),
|
||||
200, {'Content-Type': 'application/x-javascript'}
|
||||
)
|
||||
|
||||
@check_precondition
|
||||
def create(self, gid, sid):
|
||||
"""Create the pgAgent job."""
|
||||
required_args = [
|
||||
u'jobname'
|
||||
]
|
||||
|
||||
data = request.form if request.form else json.loads(
|
||||
request.data.decode('utf-8')
|
||||
)
|
||||
|
||||
for arg in required_args:
|
||||
if arg not in data:
|
||||
return make_json_response(
|
||||
status=410,
|
||||
success=0,
|
||||
errormsg=_(
|
||||
"Could not find the required parameter (%s)." % arg
|
||||
)
|
||||
)
|
||||
|
||||
status, res = self.conn.execute_void('BEGIN')
|
||||
if not status:
|
||||
return internal_server_error(errormsg=res)
|
||||
|
||||
status, res = self.conn.execute_scalar(
|
||||
render_template(
|
||||
"/".join([self.template_path, 'create.sql']),
|
||||
data=data, conn=self.conn, fetch_id=True,
|
||||
has_connstr=self.manager.db_info['pgAgent']['has_connstr']
|
||||
)
|
||||
)
|
||||
|
||||
if not status:
|
||||
self.conn.execute_void('END')
|
||||
return internal_server_error(errormsg=res)
|
||||
|
||||
# We need oid of newly created database
|
||||
status, res = self.conn.execute_dict(
|
||||
render_template(
|
||||
"/".join([self.template_path, 'nodes.sql']),
|
||||
jid=res, conn=self.conn
|
||||
)
|
||||
)
|
||||
|
||||
self.conn.execute_void('END')
|
||||
if not status:
|
||||
return internal_server_error(errormsg=res)
|
||||
|
||||
row = res['rows'][0]
|
||||
|
||||
return jsonify(
|
||||
node=self.blueprint.generate_browser_node(
|
||||
row['jobid'],
|
||||
sid,
|
||||
row['jobname'],
|
||||
icon="icon-pga_job"
|
||||
)
|
||||
)
|
||||
|
||||
@check_precondition
|
||||
def update(self, gid, sid, jid):
|
||||
"""Update the pgAgent Job."""
|
||||
|
||||
data = request.form if request.form else json.loads(
|
||||
request.data.decode('utf-8')
|
||||
)
|
||||
|
||||
status, res = self.conn.execute_void(
|
||||
render_template(
|
||||
"/".join([self.template_path, 'update.sql']),
|
||||
data=data, conn=self.conn, jid=jid,
|
||||
has_connstr=self.manager.db_info['pgAgent']['has_connstr']
|
||||
)
|
||||
)
|
||||
|
||||
if not status:
|
||||
return internal_server_error(errormsg=res)
|
||||
|
||||
# We need oid of newly created database
|
||||
status, res = self.conn.execute_dict(
|
||||
render_template(
|
||||
"/".join([self.template_path, 'nodes.sql']),
|
||||
jid=res, conn=self.conn
|
||||
)
|
||||
)
|
||||
|
||||
if not status:
|
||||
return internal_server_error(errormsg=res)
|
||||
|
||||
row = res['rows'][0]
|
||||
|
||||
return jsonify(
|
||||
node=self.blueprint.generate_browser_node(
|
||||
jid,
|
||||
sid,
|
||||
row['jobname'],
|
||||
icon="icon-pga_job" if row['jobenabled'] else
|
||||
"icon-pga_job-disabled"
|
||||
)
|
||||
)
|
||||
|
||||
@check_precondition
|
||||
def delete(self, gid, sid, jid):
|
||||
"""Delete the pgAgent Job."""
|
||||
|
||||
status, res = self.conn.execute_void(
|
||||
render_template(
|
||||
"/".join([self.template_path, 'delete.sql']),
|
||||
jid=jid, conn=self.conn
|
||||
)
|
||||
)
|
||||
if not status:
|
||||
return internal_server_error(errormsg=res)
|
||||
|
||||
return make_json_response(success=1)
|
||||
|
||||
@check_precondition
|
||||
def msql(self, gid, sid, jid=None):
|
||||
"""
|
||||
This function to return modified SQL.
|
||||
"""
|
||||
data = {}
|
||||
for k, v in request.args.items():
|
||||
try:
|
||||
data[k] = json.loads(
|
||||
v.decode('utf-8') if hasattr(v, 'decode') else v
|
||||
)
|
||||
except ValueError:
|
||||
data[k] = v
|
||||
|
||||
return make_json_response(
|
||||
data=render_template(
|
||||
"/".join([
|
||||
self.template_path,
|
||||
'create.sql' if jid is None else 'update.sql'
|
||||
]),
|
||||
jid=jid, data=data, conn=self.conn, fetch_id=False,
|
||||
has_connstr=self.manager.db_info['pgAgent']['has_connstr']
|
||||
),
|
||||
status=200
|
||||
)
|
||||
|
||||
@check_precondition
|
||||
def statistics(self, gid, sid, jid):
|
||||
"""
|
||||
statistics
|
||||
Returns the statistics for a particular database if jid is specified,
|
||||
otherwise it will return statistics for all the databases in that
|
||||
server.
|
||||
"""
|
||||
status, res = self.conn.execute_dict(
|
||||
render_template(
|
||||
"/".join([self.template_path, 'stats.sql']),
|
||||
jid=jid, conn=self.conn
|
||||
)
|
||||
)
|
||||
|
||||
if not status:
|
||||
return internal_server_error(errormsg=res)
|
||||
|
||||
return make_json_response(
|
||||
data=res,
|
||||
status=200
|
||||
)
|
||||
|
||||
@check_precondition
|
||||
def sql(self, gid, sid, jid):
|
||||
"""
|
||||
This function will generate sql for sql panel
|
||||
"""
|
||||
SQL = render_template(
|
||||
"/".join([self.template_path, 'properties.sql']),
|
||||
jid=jid, conn=self.conn, last_system_oid=0
|
||||
)
|
||||
status, res = self.conn.execute_dict(SQL)
|
||||
if not status:
|
||||
return internal_server_error(errormsg=res)
|
||||
|
||||
row = res['rows'][0]
|
||||
|
||||
status, res= self.conn.execute_dict(
|
||||
render_template(
|
||||
"/".join([self.template_path, 'steps.sql']),
|
||||
jid=jid, conn=self.conn,
|
||||
has_connstr=self.manager.db_info['pgAgent']['has_connstr']
|
||||
)
|
||||
)
|
||||
if not status:
|
||||
return internal_server_error(errormsg=res)
|
||||
|
||||
row['jsteps'] = res['rows']
|
||||
|
||||
status, res = self.conn.execute_dict(
|
||||
render_template(
|
||||
"/".join([self.template_path, 'schedules.sql']),
|
||||
jid=jid, conn=self.conn
|
||||
)
|
||||
)
|
||||
if not status:
|
||||
return internal_server_error(errormsg=res)
|
||||
|
||||
row['jschedules'] = res['rows']
|
||||
for schedule in row['jschedules']:
|
||||
schedule['jscexceptions'] = []
|
||||
if schedule['jexid']:
|
||||
idx = 0
|
||||
for exc in schedule['jexid']:
|
||||
schedule['jscexceptions'].append({
|
||||
'jexid': exc,
|
||||
'jexdate': schedule['jexdate'][idx],
|
||||
'jextime': schedule['jextime'][idx]
|
||||
})
|
||||
idx+=1
|
||||
del schedule['jexid']
|
||||
del schedule['jexdate']
|
||||
del schedule['jextime']
|
||||
|
||||
return ajax_response(
|
||||
response=render_template(
|
||||
"/".join([self.template_path, 'create.sql']),
|
||||
jid=jid, data=row, conn=self.conn, fetch_id=False,
|
||||
has_connstr=self.manager.db_info['pgAgent']['has_connstr']
|
||||
)
|
||||
)
|
||||
|
||||
@check_precondition
|
||||
def run_now(self, gid, sid, jid):
|
||||
"""
|
||||
This function will set the next run to now, to inform the pgAgent to
|
||||
run the job now.
|
||||
"""
|
||||
status, res = self.conn.execute_void(
|
||||
render_template(
|
||||
"/".join([self.template_path, 'run_now.sql']),
|
||||
jid=jid, conn=self.conn
|
||||
)
|
||||
)
|
||||
if not status:
|
||||
return internal_server_error(errormsg=res)
|
||||
|
||||
return success_return(
|
||||
message=_("Updated the next-runtime to now!")
|
||||
)
|
||||
|
||||
@check_precondition
|
||||
def job_classes(self, gid, sid):
|
||||
"""
|
||||
This function will return the set of job classes.
|
||||
"""
|
||||
status, res = self.conn.execute_dict(
|
||||
render_template("/".join([self.template_path, 'job_classes.sql']))
|
||||
)
|
||||
|
||||
if not status:
|
||||
return internal_server_error(errormsg=res)
|
||||
|
||||
return make_json_response(
|
||||
data=res['rows'],
|
||||
status=200
|
||||
)
|
||||
|
||||
JobView.register_node_view(blueprint)
|
|
@ -0,0 +1,441 @@
|
|||
##########################################################################
|
||||
#
|
||||
# pgAdmin 4 - PostgreSQL Tools
|
||||
#
|
||||
# Copyright (C) 2013 - 2016, The pgAdmin Development Team
|
||||
# This software is released under the PostgreSQL Licence
|
||||
#
|
||||
##########################################################################
|
||||
|
||||
"""Implements pgAgent Job Schedule Node"""
|
||||
|
||||
import json
|
||||
from functools import wraps
|
||||
|
||||
from flask import render_template, make_response, request
|
||||
from flask_babel import gettext
|
||||
from pgadmin.browser.collection import CollectionNodeModule
|
||||
from pgadmin.browser.utils import PGChildNodeView
|
||||
from pgadmin.utils.ajax import make_json_response, gone, \
|
||||
make_response as ajax_response, internal_server_error
|
||||
from pgadmin.utils.driver import get_driver
|
||||
|
||||
from config import PG_DEFAULT_DRIVER
|
||||
|
||||
|
||||
class JobScheduleModule(CollectionNodeModule):
|
||||
"""
|
||||
class JobScheduleModule(CollectionNodeModule)
|
||||
|
||||
A module class for JobSchedule node derived from CollectionNodeModule.
|
||||
|
||||
Methods:
|
||||
-------
|
||||
* get_nodes(gid, sid, jid)
|
||||
- Method is used to generate the browser collection node.
|
||||
|
||||
* node_inode()
|
||||
- Method is overridden from its base class to make the node as leaf node.
|
||||
"""
|
||||
|
||||
NODE_TYPE = 'pga_schedule'
|
||||
COLLECTION_LABEL = gettext("Schedules")
|
||||
|
||||
def get_nodes(self, gid, sid, jid):
|
||||
"""
|
||||
Method is used to generate the browser collection node
|
||||
|
||||
Args:
|
||||
gid: Server Group ID
|
||||
sid: Server ID
|
||||
jid: Database Id
|
||||
"""
|
||||
yield self.generate_browser_collection_node(jid)
|
||||
|
||||
@property
|
||||
def node_inode(self):
|
||||
"""
|
||||
Override this property to make the node a leaf node.
|
||||
|
||||
Returns: False as this is the leaf node
|
||||
"""
|
||||
return False
|
||||
|
||||
@property
|
||||
def script_load(self):
|
||||
"""
|
||||
Load the module script for language, when any of the database nodes are initialized.
|
||||
|
||||
Returns: node type of the server module.
|
||||
"""
|
||||
return 'pga_job'
|
||||
|
||||
|
||||
blueprint = JobScheduleModule(__name__)
|
||||
|
||||
|
||||
class JobScheduleView(PGChildNodeView):
|
||||
"""
|
||||
class JobScheduleView(PGChildNodeView)
|
||||
|
||||
A view class for JobSchedule node derived from PGChildNodeView. This class is
|
||||
responsible for all the stuff related to view like updating language
|
||||
node, showing properties, showing sql in sql pane.
|
||||
|
||||
Methods:
|
||||
-------
|
||||
* __init__(**kwargs)
|
||||
- Method is used to initialize the JobScheduleView and it's base view.
|
||||
|
||||
* module_js()
|
||||
- This property defines (if javascript) exists for this node.
|
||||
Override this property for your own logic
|
||||
|
||||
* check_precondition()
|
||||
- This function will behave as a decorator which will checks
|
||||
database connection before running view, it will also attaches
|
||||
manager,conn & template_path properties to self
|
||||
|
||||
* list()
|
||||
- This function is used to list all the language nodes within that collection.
|
||||
|
||||
* nodes()
|
||||
- This function will used to create all the child node within that collection.
|
||||
Here it will create all the language node.
|
||||
|
||||
* properties(gid, sid, jid, jscid)
|
||||
- This function will show the properties of the selected language node
|
||||
|
||||
* update(gid, sid, jid, jscid)
|
||||
- This function will update the data for the selected language node
|
||||
|
||||
* msql(gid, sid, jid, jscid)
|
||||
- This function is used to return modified SQL for the selected language node
|
||||
"""
|
||||
|
||||
node_type = blueprint.node_type
|
||||
|
||||
parent_ids = [
|
||||
{'type': 'int', 'id': 'gid'},
|
||||
{'type': 'int', 'id': 'sid'},
|
||||
{'type': 'int', 'id': 'jid'}
|
||||
]
|
||||
ids = [
|
||||
{'type': 'int', 'id': 'jscid'}
|
||||
]
|
||||
|
||||
operations = dict({
|
||||
'obj': [
|
||||
{'get': 'properties', 'put': 'update'},
|
||||
{'get': 'list', 'post': 'create'}
|
||||
],
|
||||
'nodes': [{'get': 'nodes'}, {'get': 'nodes'}],
|
||||
'msql': [{'get': 'msql'}, {'get': 'msql'}],
|
||||
'module.js': [{}, {}, {'get': 'module_js'}]
|
||||
})
|
||||
|
||||
def _init_(self, **kwargs):
|
||||
"""
|
||||
Method is used to initialize the JobScheduleView and its base view.
|
||||
Initialize all the variables create/used dynamically like conn, template_path.
|
||||
|
||||
Args:
|
||||
**kwargs:
|
||||
"""
|
||||
self.conn = None
|
||||
self.template_path = None
|
||||
self.manager = None
|
||||
|
||||
super(JobScheduleView, self).__init__(**kwargs)
|
||||
|
||||
def module_js(self):
|
||||
"""
|
||||
This property defines whether javascript exists for this node.
|
||||
"""
|
||||
return make_response(
|
||||
render_template(
|
||||
"pga_schedule/js/pga_schedule.js",
|
||||
_=gettext
|
||||
),
|
||||
200, {'Content-Type': 'application/x-javascript'}
|
||||
)
|
||||
|
||||
def check_precondition(f):
|
||||
"""
|
||||
This function will behave as a decorator which will check the
|
||||
database connection before running the view. It also attaches
|
||||
manager, conn & template_path properties to self
|
||||
"""
|
||||
|
||||
@wraps(f)
|
||||
def wrap(*args, **kwargs):
|
||||
# Here args[0] will hold self & kwargs will hold gid,sid,jid
|
||||
self = args[0]
|
||||
self.driver = get_driver(PG_DEFAULT_DRIVER)
|
||||
self.manager = self.driver.connection_manager(kwargs['sid'])
|
||||
self.conn = self.manager.connection()
|
||||
|
||||
self.template_path = 'pga_schedule/sql/pre3.4'
|
||||
|
||||
return f(*args, **kwargs)
|
||||
|
||||
return wrap
|
||||
|
||||
@check_precondition
|
||||
def list(self, gid, sid, jid):
|
||||
"""
|
||||
This function is used to list all the language nodes within that collection.
|
||||
|
||||
Args:
|
||||
gid: Server Group ID
|
||||
sid: Server ID
|
||||
jid: Job ID
|
||||
"""
|
||||
sql = render_template(
|
||||
"/".join([self.template_path, 'properties.sql']),
|
||||
jid=jid
|
||||
)
|
||||
status, res = self.conn.execute_dict(sql)
|
||||
|
||||
if not status:
|
||||
return internal_server_error(errormsg=res)
|
||||
|
||||
return ajax_response(
|
||||
response=res['rows'],
|
||||
status=200
|
||||
)
|
||||
|
||||
@check_precondition
|
||||
def nodes(self, gid, sid, jid, jscid=None):
|
||||
"""
|
||||
This function is used to create all the child nodes within the collection.
|
||||
Here it will create all the language nodes.
|
||||
|
||||
Args:
|
||||
gid: Server Group ID
|
||||
sid: Server ID
|
||||
jid: Job ID
|
||||
"""
|
||||
res = []
|
||||
sql = render_template(
|
||||
"/".join([self.template_path, 'nodes.sql']),
|
||||
jscid = jscid,
|
||||
jid = jid
|
||||
)
|
||||
|
||||
status, result = self.conn.execute_2darray(sql)
|
||||
|
||||
if not status:
|
||||
return internal_server_error(errormsg=result)
|
||||
|
||||
if jscid is not None:
|
||||
if len(result['rows']) == 0:
|
||||
return gone(errormsg="Couldn't find the specified job step.")
|
||||
|
||||
row = result['rows'][0]
|
||||
return make_json_response(
|
||||
self.blueprint.generate_browser_node(
|
||||
row['jscid'],
|
||||
row['jscjobid'],
|
||||
row['jscname'],
|
||||
icon="icon-pga_schedule",
|
||||
enabled=row['jscenabled']
|
||||
)
|
||||
)
|
||||
|
||||
for row in result['rows']:
|
||||
res.append(
|
||||
self.blueprint.generate_browser_node(
|
||||
row['jscid'],
|
||||
row['jscjobid'],
|
||||
row['jscname'],
|
||||
icon="icon-pga_schedule",
|
||||
enabled=row['jscenabled']
|
||||
)
|
||||
)
|
||||
|
||||
return make_json_response(
|
||||
data=res,
|
||||
status=200
|
||||
)
|
||||
|
||||
@check_precondition
|
||||
def properties(self, gid, sid, jid, jscid):
|
||||
"""
|
||||
This function will show the properties of the selected language node.
|
||||
|
||||
Args:
|
||||
gid: Server Group ID
|
||||
sid: Server ID
|
||||
jid: Job ID
|
||||
jscid: JobSchedule ID
|
||||
"""
|
||||
sql = render_template(
|
||||
"/".join([self.template_path, 'properties.sql']),
|
||||
jscid=jscid, jid=jid
|
||||
)
|
||||
status, res = self.conn.execute_dict(sql)
|
||||
|
||||
if not status:
|
||||
return internal_server_error(errormsg=res)
|
||||
|
||||
if len(res['rows']) == 0:
|
||||
return gone(errormsg="Couldn't find the specified job step.")
|
||||
|
||||
return ajax_response(
|
||||
response=res['rows'][0],
|
||||
status=200
|
||||
)
|
||||
|
||||
@check_precondition
|
||||
def create(self, gid, sid, jid):
|
||||
"""
|
||||
This function will update the data for the selected language node.
|
||||
|
||||
Args:
|
||||
gid: Server Group ID
|
||||
sid: Server ID
|
||||
jid: Job ID
|
||||
"""
|
||||
data = {}
|
||||
for k, v in request.args.items():
|
||||
try:
|
||||
data[k] = json.loads(
|
||||
v.decode('utf-8') if hasattr(v, 'decode') else v
|
||||
)
|
||||
except ValueError:
|
||||
data[k] = v
|
||||
|
||||
sql = render_template(
|
||||
"/".join([self.template_path, 'create.sql']),
|
||||
jid=jid,
|
||||
data=data,
|
||||
fetch_id=False
|
||||
)
|
||||
|
||||
status, res = self.conn.execute_void('BEGIN')
|
||||
if not status:
|
||||
return internal_server_error(errormsg=res)
|
||||
|
||||
status, res = self.conn.execute_scalar(sql)
|
||||
|
||||
if not status:
|
||||
if self.conn.connected():
|
||||
self.conn.execute_void('END')
|
||||
return internal_server_error(errormsg=res)
|
||||
|
||||
self.conn.execute_void('END')
|
||||
sql = render_template(
|
||||
"/".join([self.template_path, 'nodes.sql']),
|
||||
jscid = res,
|
||||
jid = jid
|
||||
)
|
||||
status, res = self.conn.execute_2darray(sql)
|
||||
|
||||
if not status:
|
||||
return internal_server_error(errormsg=res)
|
||||
|
||||
row = res['rows'][0]
|
||||
return make_json_response(
|
||||
data=self.blueprint.generate_browser_node(
|
||||
row['jscid'],
|
||||
row['jscjobid'],
|
||||
row['jscname'],
|
||||
icon="icon-pga_schedule"
|
||||
)
|
||||
)
|
||||
|
||||
@check_precondition
|
||||
def update(self, gid, sid, jid, jscid):
|
||||
"""
|
||||
This function will update the data for the selected language node.
|
||||
|
||||
Args:
|
||||
gid: Server Group ID
|
||||
sid: Server ID
|
||||
jid: Job ID
|
||||
jscid: JobSchedule ID
|
||||
"""
|
||||
data = {}
|
||||
for k, v in request.args.items():
|
||||
try:
|
||||
data[k] = json.loads(
|
||||
v.decode('utf-8') if hasattr(v, 'decode') else v
|
||||
)
|
||||
except ValueError:
|
||||
data[k] = v
|
||||
|
||||
sql = render_template(
|
||||
"/".join([self.template_path, 'update.sql']),
|
||||
jid=jid,
|
||||
data=data
|
||||
)
|
||||
|
||||
status, res = self.conn.execute_void(sql)
|
||||
|
||||
if not status:
|
||||
return internal_server_error(errormsg=res)
|
||||
|
||||
sql = render_template(
|
||||
"/".join([self.template_path, 'nodes.sql']),
|
||||
jscid = jscid,
|
||||
jid = jid
|
||||
)
|
||||
status, res = self.conn.execute_2darray(sql)
|
||||
|
||||
if not status:
|
||||
return internal_server_error(errormsg=res)
|
||||
|
||||
row = res['rows'][0]
|
||||
return make_json_response(
|
||||
self.blueprint.generate_browser_node(
|
||||
row['jscid'],
|
||||
row['jscjobid'],
|
||||
row['jscname'],
|
||||
icon="icon-pga_schedule"
|
||||
)
|
||||
)
|
||||
|
||||
@check_precondition
|
||||
def msql(self, gid, sid, jid, jscid=None):
|
||||
"""
|
||||
This function is used to return modified SQL for the selected language node.
|
||||
|
||||
Args:
|
||||
gid: Server Group ID
|
||||
sid: Server ID
|
||||
jid: Job ID
|
||||
jscid: Job Schedule ID (optional)
|
||||
"""
|
||||
data = {}
|
||||
sql = ''
|
||||
for k, v in request.args.items():
|
||||
try:
|
||||
data[k] = json.loads(
|
||||
v.decode('utf-8') if hasattr(v, 'decode') else v
|
||||
)
|
||||
except ValueError:
|
||||
data[k] = v
|
||||
|
||||
if jscid is None:
|
||||
sql = render_template(
|
||||
"/".join([self.template_path, 'create.sql']),
|
||||
jid=jid,
|
||||
data=data,
|
||||
fetch_id=False
|
||||
)
|
||||
else:
|
||||
sql = render_template(
|
||||
"/".join([self.template_path, 'update.sql']),
|
||||
jid=jid,
|
||||
jscid=jscid,
|
||||
data=data
|
||||
)
|
||||
|
||||
return make_json_response(
|
||||
data=sql,
|
||||
status=200
|
||||
)
|
||||
|
||||
|
||||
JobScheduleView.register_node_view(blueprint)
|
After Width: | Height: | Size: 650 B |
After Width: | Height: | Size: 683 B |
|
@ -0,0 +1,7 @@
|
|||
.icon-pga_schedule {
|
||||
background-image: url('{{ url_for('NODE-pga_schedule.static', filename='img/pga_schedule.png') }}') !important;
|
||||
background-repeat: no-repeat;
|
||||
align-content: center;
|
||||
vertical-align: middle;
|
||||
height: 1.3em;
|
||||
}
|
|
@ -0,0 +1,475 @@
|
|||
define([
|
||||
'jquery', 'underscore', 'underscore.string', 'pgadmin', 'moment',
|
||||
'pgadmin.browser', 'alertify', 'backform', 'pgadmin.backform'
|
||||
],
|
||||
function($, _, S, pgAdmin, moment, pgBrowser, Alertify, Backform) {
|
||||
|
||||
if (!pgBrowser.Nodes['coll-pga_schedule']) {
|
||||
pgBrowser.Nodes['coll-pga_schedule'] =
|
||||
pgBrowser.Collection.extend({
|
||||
node: 'pga_schedule',
|
||||
label: '{{ _('Schedules') }}',
|
||||
type: 'coll-pga_schedule',
|
||||
columns: ['jscid', 'jscname', 'jscenabled'],
|
||||
hasStatistics: false
|
||||
});
|
||||
}
|
||||
|
||||
if (!pgBrowser.Nodes['pga_schedule']) {
|
||||
|
||||
var weekdays = [
|
||||
'{{ _("Sunday") }}', '{{ _("Monday") }}', '{{ _("Tuesday") }}',
|
||||
'{{ _("Wednesday") }}', '{{ _("Thursday") }}', '{{ _("Friday") }}',
|
||||
'{{ _("Saturday") }}'
|
||||
],
|
||||
monthdays = [
|
||||
'{{ _("1st") }}', '{{ _("2nd") }}', '{{ _("3rd") }}',
|
||||
'{{ _("4th") }}', '{{ _("5th") }}', '{{ _("6th") }}',
|
||||
'{{ _("7th") }}', '{{ _("8th") }}', '{{ _("9th") }}',
|
||||
'{{ _("10th") }}', '{{ _("11th") }}', '{{ _("12th") }}',
|
||||
'{{ _("13th") }}', '{{ _("14th") }}', '{{ _("15th") }}',
|
||||
'{{ _("16th") }}', '{{ _("17th") }}', '{{ _("18th") }}',
|
||||
'{{ _("19th") }}', '{{ _("20th") }}', '{{ _("21st") }}',
|
||||
'{{ _("22nd") }}', '{{ _("23rd") }}', '{{ _("24th") }}',
|
||||
'{{ _("25th") }}', '{{ _("26th") }}', '{{ _("27th") }}',
|
||||
'{{ _("28th") }}', '{{ _("29th") }}', '{{ _("30th") }}',
|
||||
'{{ _("31st") }}', '{{ _("Last day") }}'
|
||||
],
|
||||
months = [
|
||||
'{{ _("January") }}', '{{ _("February") }}', '{{ _("March") }}',
|
||||
'{{ _("April") }}', '{{ _("May") }}', '{{ _("June") }}',
|
||||
'{{ _("July") }}', '{{ _("August") }}', '{{ _("September") }}',
|
||||
'{{ _("October") }}', '{{ _("November") }}', '{{ _("December") }}'
|
||||
],
|
||||
hours = [
|
||||
'{{ _("00") }}', '{{ _("01") }}', '{{ _("02") }}', '{{ _("03") }}',
|
||||
'{{ _("04") }}', '{{ _("05") }}', '{{ _("06") }}', '{{ _("07") }}',
|
||||
'{{ _("08") }}', '{{ _("09") }}', '{{ _("10") }}', '{{ _("11") }}',
|
||||
'{{ _("12") }}', '{{ _("13") }}', '{{ _("14") }}', '{{ _("15") }}',
|
||||
'{{ _("16") }}', '{{ _("17") }}', '{{ _("18") }}', '{{ _("19") }}',
|
||||
'{{ _("20") }}', '{{ _("21") }}', '{{ _("22") }}', '{{ _("23") }}'
|
||||
],
|
||||
minutes = [
|
||||
'{{ _("00") }}', '{{ _("01") }}', '{{ _("02") }}', '{{ _("03") }}',
|
||||
'{{ _("04") }}', '{{ _("05") }}', '{{ _("06") }}', '{{ _("07") }}',
|
||||
'{{ _("08") }}', '{{ _("09") }}', '{{ _("10") }}', '{{ _("11") }}',
|
||||
'{{ _("12") }}', '{{ _("13") }}', '{{ _("14") }}', '{{ _("15") }}',
|
||||
'{{ _("16") }}', '{{ _("17") }}', '{{ _("18") }}', '{{ _("19") }}',
|
||||
'{{ _("20") }}', '{{ _("21") }}', '{{ _("22") }}', '{{ _("23") }}',
|
||||
'{{ _("24") }}', '{{ _("25") }}', '{{ _("26") }}', '{{ _("27") }}',
|
||||
'{{ _("28") }}', '{{ _("29") }}', '{{ _("30") }}', '{{ _("31") }}',
|
||||
'{{ _("32") }}', '{{ _("33") }}', '{{ _("34") }}', '{{ _("35") }}',
|
||||
'{{ _("36") }}', '{{ _("37") }}', '{{ _("38") }}', '{{ _("39") }}',
|
||||
'{{ _("40") }}', '{{ _("41") }}', '{{ _("42") }}', '{{ _("43") }}',
|
||||
'{{ _("44") }}', '{{ _("45") }}', '{{ _("46") }}', '{{ _("47") }}',
|
||||
'{{ _("48") }}', '{{ _("49") }}', '{{ _("50") }}', '{{ _("51") }}',
|
||||
'{{ _("52") }}', '{{ _("53") }}', '{{ _("54") }}', '{{ _("55") }}',
|
||||
'{{ _("56") }}', '{{ _("57") }}', '{{ _("58") }}', '{{ _("59") }}'
|
||||
],
|
||||
AnyDatetimeCell = Backgrid.Extension.MomentCell.extend({
|
||||
editor: Backgrid.Extension.DatetimePickerEditor,
|
||||
render: function() {
|
||||
this.$el.empty();
|
||||
var model = this.model;
|
||||
this.$el.text(this.formatter.fromRaw(model.get(this.column.get("name")), model) || '{{ _('<Any>') }}');
|
||||
this.delegateEvents();
|
||||
|
||||
return this;
|
||||
}
|
||||
}),
|
||||
BooleanArrayFormatter = function(selector, indexes) {
|
||||
var self = this;
|
||||
|
||||
self.selector = selector;
|
||||
self.indexes = indexes;
|
||||
|
||||
this.fromRaw = function(rawData) {
|
||||
if (!_.isArray(rawData)) {
|
||||
return rawData;
|
||||
}
|
||||
|
||||
var res = [], idx = 0, resIdx = [];
|
||||
|
||||
for (; idx < rawData.length; idx++) {
|
||||
if (!rawData[idx])
|
||||
continue;
|
||||
res.push(self.selector[idx]);
|
||||
resIdx.push(idx + 1);
|
||||
}
|
||||
|
||||
return self.indexes ? resIdx : res.join(', ');
|
||||
}
|
||||
this.toRaw = function(d) {
|
||||
if (!self.indexes)
|
||||
return d;
|
||||
var res = [], i = 0, l = self.selector.length;
|
||||
|
||||
for (; i < l; i++) {
|
||||
res.push(_.indexOf(d, String(i + 1)) != -1);
|
||||
}
|
||||
console.log(res);
|
||||
return res;
|
||||
}
|
||||
|
||||
return self;
|
||||
},
|
||||
BooleanArrayOptions = function(ctrl) {
|
||||
var selector = ctrl.field.get('selector'),
|
||||
val = ctrl.model.get(ctrl.field.get('name')),
|
||||
res = [];
|
||||
|
||||
if (selector) {
|
||||
res = _.map(
|
||||
selector, function(v, i) {
|
||||
return {label: v, value: i + 1, selected: val[i]};
|
||||
}
|
||||
);
|
||||
}
|
||||
return res;
|
||||
},
|
||||
ExceptionModel = pgBrowser.Node.Model.extend({
|
||||
defaults: {
|
||||
jexid: undefined,
|
||||
jexdate: null,
|
||||
jextime: null
|
||||
},
|
||||
idAttribute: 'jexid',
|
||||
schema: [{
|
||||
id: 'jexdate', type: 'text', label: '{{ _('Date') }}',
|
||||
editable: true, placeholder: '{{ _('<any>') }}',
|
||||
cell: AnyDatetimeCell, options: {format: 'YYYY-MM-DD'},
|
||||
displayFormat: 'YYYY-MM-DD', modelFormat: 'YYYY-MM-DD',
|
||||
cellHeaderClasses:'width_percent_50', allowEmpty: true
|
||||
},{
|
||||
id: 'jextime', type: 'text', placeholder: '{{ _('<any>') }}',
|
||||
label: '{{ _('Time') }}', editable: true, cell: AnyDatetimeCell,
|
||||
options: {format: 'HH:mm'}, displayFormat: 'HH:mm',
|
||||
modelFormat: 'HH:mm:ss', displayInUTC: false, allowEmpty: true,
|
||||
cellHeaderClasses:'width_percent_50', modalInUTC: false
|
||||
}],
|
||||
validate: function() {
|
||||
var self = this, exceptions = this.collection,
|
||||
dates = {}, errMsg, hasExceptionErr = false,
|
||||
d = (this.get('jexdate') || '<any>'),
|
||||
t = this.get('jextime') || '<any>',
|
||||
id = this.get('jexid') || this.cid;
|
||||
|
||||
self.errorModel.unset('jscdate');
|
||||
if (d == t && d == '<any>') {
|
||||
errMsg = '{{ _('Please specify date/time.') }}';
|
||||
self.errorModel.set('jscdate', errMsg);
|
||||
return errMsg ;
|
||||
}
|
||||
|
||||
exceptions.each(function(ex) {
|
||||
if (hasExceptionErr || id == (ex.get('jexid') || ex.cid))
|
||||
return;
|
||||
|
||||
if (
|
||||
d == (ex.get('jexdate') || '<any>') &&
|
||||
t == (ex.get('jextime') || '<any>')
|
||||
) {
|
||||
errMsg = '{{ _('Please specify unique set of exceptions.') }}';
|
||||
if (ex.errorModel.get('jscdate') != errMsg)
|
||||
self.errorModel.set('jscdate', errMsg);
|
||||
hasExceptionErr = true;
|
||||
}
|
||||
});
|
||||
|
||||
return errMsg;
|
||||
}
|
||||
});
|
||||
|
||||
pgBrowser.Nodes['pga_schedule'] = pgBrowser.Node.extend({
|
||||
parent_type: 'pga_job',
|
||||
type: 'pga_schedule',
|
||||
hasSQL: false,
|
||||
hasDepends: false,
|
||||
hasStatistics: false,
|
||||
canDrop: function(node) {
|
||||
return true;
|
||||
},
|
||||
label: '{{ _('Schedule') }}',
|
||||
node_image: 'icon-pga_schedule',
|
||||
Init: function() {
|
||||
/* Avoid mulitple registration of menus */
|
||||
if (this.initialized)
|
||||
return;
|
||||
|
||||
this.initialized = true;
|
||||
|
||||
pgBrowser.add_menus([{
|
||||
name: 'create_pga_schedule_on_job', node: 'pga_job', module: this,
|
||||
applies: ['object', 'context'], callback: 'show_obj_properties',
|
||||
category: 'create', priority: 4, label: '{{ _('Schedule...') }}',
|
||||
icon: 'wcTabIcon icon-pga_schedule', data: {action: 'create'}
|
||||
},{
|
||||
name: 'create_pga_schedule_on_coll', node: 'coll-pga_schedule', module: this,
|
||||
applies: ['object', 'context'], callback: 'show_obj_properties',
|
||||
category: 'create', priority: 4, label: '{{ _('Schedule...') }}',
|
||||
icon: 'wcTabIcon icon-pga_schedule', data: {action: 'create'}
|
||||
},{
|
||||
name: 'create_pga_schedule', node: 'pga_schedule', module: this,
|
||||
applies: ['object', 'context'], callback: 'show_obj_properties',
|
||||
category: 'create', priority: 4, label: '{{ _('Schedule...') }}',
|
||||
icon: 'wcTabIcon icon-pga_schedule', data: {action: 'create'}
|
||||
}]);
|
||||
},
|
||||
model: pgBrowser.Node.Model.extend({
|
||||
defaults: {
|
||||
jscid: null,
|
||||
jscjobid: null,
|
||||
jscname: '',
|
||||
jscdesc: '',
|
||||
jscenabled: true,
|
||||
jscstart: null,
|
||||
jscend: null,
|
||||
jscweekdays: _.map(weekdays, function() { return false; }),
|
||||
jscmonthdays: _.map(monthdays, function() { return false; }),
|
||||
jscmonths: _.map(months, function() { return false; }),
|
||||
jschours: _.map(hours, function() { return false; }),
|
||||
jscminutes: _.map(minutes, function() { return false; }),
|
||||
jscexceptions: []
|
||||
},
|
||||
idAttribute: 'jscid',
|
||||
parse: function(d) {
|
||||
d.jscexceptions = [];
|
||||
if (d.jexid && d.jexid.length) {
|
||||
var idx = 0;
|
||||
for (; idx < d.jexid.length; idx++) {
|
||||
d.jscexceptions.push({
|
||||
'jexid': d.jexid[idx],
|
||||
'jexdate': d.jexdate[idx],
|
||||
'jextime': d.jextime[idx]
|
||||
});
|
||||
}
|
||||
}
|
||||
delete d.jexid;
|
||||
delete d.jexdate;
|
||||
delete d.jextime;
|
||||
|
||||
return pgBrowser.Node.Model.prototype.parse.apply(this, arguments);
|
||||
},
|
||||
schema: [{
|
||||
id: 'jscid', label: '{{ _('ID') }}', type: 'integer',
|
||||
cellHeaderClasses: 'width_percent_5', mode: ['properties']
|
||||
},{
|
||||
id: 'jscname', label: '{{ _('Name') }}', type: 'text',
|
||||
cellHeaderClasses: 'width_percent_45',
|
||||
disabled: function() { return false; }
|
||||
},{
|
||||
id: 'jscenabled', label: '{{ _('Enabled') }}', type: 'switch',
|
||||
disabled: function() { return false; },
|
||||
cellHeaderClasses: 'width_percent_5'
|
||||
},{
|
||||
id: 'jscstart', label: '{{ _('Start') }}', type: 'text',
|
||||
control: 'datetimepicker', cell: 'moment',
|
||||
disabled: function() { return false; },
|
||||
displayFormat: 'YYYY-MM-DD HH:mm:SS Z',
|
||||
modelFormat: 'YYYY-MM-DD HH:mm:SS Z', options: {
|
||||
format: 'YYYY-MM-DD HH:mm:SS Z',
|
||||
}, cellHeaderClasses: 'width_percent_25'
|
||||
},{
|
||||
id: 'jscend', label: '{{ _('End') }}', type: 'text',
|
||||
control: 'datetimepicker', cell: 'moment',
|
||||
disabled: function() { return false; }, displayInUTC: false,
|
||||
displayFormat: 'YYYY-MM-DD HH:mm:SS Z', options: {
|
||||
format: 'YYYY-MM-DD HH:mm:SS Z', useCurrent: false
|
||||
}, cellHeaderClasses: 'width_percent_25',
|
||||
modelFormat: 'YYYY-MM-DD HH:mm:SS Z'
|
||||
},{
|
||||
id: 'jscweekdays', label:'{{ _('Week Days') }}', type: 'text',
|
||||
control: Backform.Control.extend({
|
||||
formatter: new BooleanArrayFormatter(weekdays, false)
|
||||
}), mode: ['properties']
|
||||
},{
|
||||
id: 'jscmonthdays', label:'{{ _('Month Days') }}', type: 'text',
|
||||
control: Backform.Control.extend({
|
||||
formatter: new BooleanArrayFormatter(monthdays, false)
|
||||
}), mode: ['properties']
|
||||
},{
|
||||
id: 'jscmonths', label:'{{ _('Months') }}', type: 'text',
|
||||
control: Backform.Control.extend({
|
||||
formatter: new BooleanArrayFormatter(months, false)
|
||||
}), mode: ['properties']
|
||||
},{
|
||||
id: 'jschours', label:'{{ _('Hours') }}', type: 'text',
|
||||
control: Backform.Control.extend({
|
||||
formatter: new BooleanArrayFormatter(hours, false)
|
||||
}), mode: ['properties']
|
||||
},{
|
||||
id: 'jscminutes', label:'{{ _('Minutes') }}', type: 'text',
|
||||
control: Backform.Control.extend({
|
||||
formatter: new BooleanArrayFormatter(minutes, false)
|
||||
}), mode: ['properties']
|
||||
},{
|
||||
id: 'jscexceptions', label:'{{ _('Exceptions') }}', type: 'text',
|
||||
control: Backform.Control.extend({
|
||||
formatter: new function() {
|
||||
this.fromRaw = function(rawData) {
|
||||
var res = '', idx = 0, d;
|
||||
|
||||
if (!rawData) {
|
||||
return res;
|
||||
}
|
||||
|
||||
for (; idx < rawData.length; idx++) {
|
||||
d = rawData[idx];
|
||||
if (idx)
|
||||
res += ', ';
|
||||
res += '[' + String((d.jexdate || '') + ' ' + (d.jextime || '')).replace(/^\s+|\s+$/g, '') + ']';
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
this.toRaw = function(data) { return data; }
|
||||
|
||||
return this;
|
||||
}
|
||||
}), mode: ['properties']
|
||||
},{
|
||||
type: 'nested', label: '{{ _('Days') }}', group: '{{ _('Repeat') }}',
|
||||
mode: ['create', 'edit'],
|
||||
control: Backform.FieldsetControl.extend({
|
||||
render: function() {
|
||||
var res = Backform.FieldsetControl.prototype.render.apply(
|
||||
this, arguments
|
||||
);
|
||||
|
||||
this.$el.prepend(
|
||||
'<div class="' + Backform.helpMessageClassName + ' set-group pg-el-xs-12">{{ _("Schedules are specified using a <b>cron-style</b> format.<br/><ul><li>For each selected time or date element, the schedule will execute.<br/>e.g. To execute at 5 minutes past every hour, simply select ‘05’ in the Minutes list box.<br/></li><li>Values from more than one field may be specified in order to further control the schedule.<br/>e.g. To execute at 12:05 and 14:05 every Monday and Thursday, you would click minute 05, hours 12 and 14, and weekdays Monday and Thursday.</li><li>For additional flexibility, the Month Days check list includes an extra Last Day option. This matches the last day of the month, whether it happens to be the 28th, 29th, 30th or 31st.</li></ul>") }}</div>'
|
||||
);
|
||||
|
||||
return res;
|
||||
}
|
||||
}),
|
||||
schema:[{
|
||||
id: 'jscweekdays', label:'{{ _('Week Days') }}', cell: 'select2',
|
||||
group: '{{ _('Days') }}', control: 'select2', type: 'array',
|
||||
select2: {
|
||||
first_empty: false,
|
||||
multiple: true,
|
||||
allowClear: true,
|
||||
placeholder: '{{ _("Select the weekdays...") }}',
|
||||
width: 'style',
|
||||
dropdownAdapter: $.fn.select2.amd.require(
|
||||
'select2/selectAllAdapter'
|
||||
)
|
||||
},
|
||||
selector: weekdays,
|
||||
formatter: new BooleanArrayFormatter(weekdays, true),
|
||||
options: BooleanArrayOptions
|
||||
},{
|
||||
id: 'jscmonthdays', label:'{{ _('Month Days') }}', cell: 'select2',
|
||||
group: '{{ _('Days') }}', control: 'select2', type: 'array',
|
||||
select2: {
|
||||
first_empty: false,
|
||||
multiple: true,
|
||||
allowClear: true,
|
||||
placeholder: '{{ _("Select the month days...") }}',
|
||||
width: 'style',
|
||||
dropdownAdapter: $.fn.select2.amd.require(
|
||||
'select2/selectAllAdapter'
|
||||
)
|
||||
},
|
||||
formatter: new BooleanArrayFormatter(monthdays, true),
|
||||
selector: monthdays, options: BooleanArrayOptions
|
||||
},{
|
||||
id: 'jscmonths', label:'{{ _('Months') }}', cell: 'select2',
|
||||
group: '{{ _('Days') }}', control: 'select2', type: 'array',
|
||||
select2: {
|
||||
first_empty: false,
|
||||
multiple: true,
|
||||
allowClear: true,
|
||||
placeholder: '{{ _("Select the months...") }}',
|
||||
width: 'style',
|
||||
dropdownAdapter: $.fn.select2.amd.require(
|
||||
'select2/selectAllAdapter'
|
||||
)
|
||||
},
|
||||
formatter: new BooleanArrayFormatter(months, true),
|
||||
selector: months, options: BooleanArrayOptions
|
||||
}]
|
||||
},{
|
||||
type: 'nested', control: 'fieldset', label: '{{ _('Times') }}',
|
||||
group: '{{ _('Repeat') }}', mode: ['create', 'edit'],
|
||||
schema:[{
|
||||
id: 'jschours', label:'{{ _('Hours') }}', cell: 'select2',
|
||||
group: '{{ _('Times') }}', control: 'select2', type: 'array',
|
||||
select2: {
|
||||
first_empty: false,
|
||||
multiple: true,
|
||||
allowClear: true,
|
||||
placeholder: '{{ _("Select the hours...") }}',
|
||||
width: 'style',
|
||||
dropdownAdapter: $.fn.select2.amd.require(
|
||||
'select2/selectAllAdapter'
|
||||
)
|
||||
},
|
||||
formatter: new BooleanArrayFormatter(hours, true),
|
||||
selector: hours, options: BooleanArrayOptions
|
||||
},{
|
||||
id: 'jscminutes', label:'{{ _('Minutes') }}', cell: 'select2',
|
||||
group: '{{ _('Times') }}', control: 'select2', type: 'array',
|
||||
select2: {
|
||||
first_empty: false,
|
||||
multiple: true,
|
||||
allowClear: true,
|
||||
placeholder: '{{ _("Select the minutes...") }}',
|
||||
width: 'style',
|
||||
dropdownAdapter: $.fn.select2.amd.require(
|
||||
'select2/selectAllAdapter'
|
||||
)
|
||||
},
|
||||
formatter: new BooleanArrayFormatter(minutes, true),
|
||||
selector: minutes, options: BooleanArrayOptions
|
||||
}]
|
||||
},{
|
||||
id: 'jscexceptions', type: 'collection', mode: ['edit', 'create'],
|
||||
label: "", canEdit: false, model: ExceptionModel, canAdd: true,
|
||||
group: '{{ _('Exceptions') }}', canDelete: true,
|
||||
cols: ['jexdate', 'jextime'], control: 'sub-node-collection'
|
||||
},{
|
||||
id: 'jscdesc', label: '{{ _('Comment') }}', type: 'multiline'
|
||||
}],
|
||||
validate: function(keys) {
|
||||
var val = this.get('jscname'),
|
||||
errMsg = null;
|
||||
|
||||
if (_.isUndefined(val) || _.isNull(val) ||
|
||||
String(val).replace(/^\s+|\s+$/g, '') == '') {
|
||||
var msg = '{{ _('Name cannot be empty.') }}';
|
||||
this.errorModel.set('jscname', msg);
|
||||
errMsg = msg;
|
||||
} else {
|
||||
this.errorModel.unset('jscname');
|
||||
}
|
||||
|
||||
val = this.get('jscstart');
|
||||
if (_.isUndefined(val) || _.isNull(val) ||
|
||||
String(val).replace(/^\s+|\s+$/g, '') == '') {
|
||||
var msg = '{{ _('Please enter the start time.') }}';
|
||||
this.errorModel.set('jscstart', msg);
|
||||
errMsg = errMsg || msg;
|
||||
} else {
|
||||
this.errorModel.unset('jscstart');
|
||||
}
|
||||
|
||||
val = this.get('jscend');
|
||||
if (_.isUndefined(val) || _.isNull(val) ||
|
||||
String(val).replace(/^\s+|\s+$/g, '') == '') {
|
||||
var msg = '{{ _('Please enter the end time.') }}';
|
||||
this.errorModel.set('jscend', msg);
|
||||
errMsg = errMsg || msg;
|
||||
} else {
|
||||
this.errorModel.unset('jscend');
|
||||
}
|
||||
|
||||
return errMsg;
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
return pgBrowser.Nodes['pga_schedule'];
|
||||
});
|
After Width: | Height: | Size: 521 B |
After Width: | Height: | Size: 601 B |
After Width: | Height: | Size: 678 B |
|
@ -0,0 +1,536 @@
|
|||
##########################################################################
|
||||
#
|
||||
# pgAdmin 4 - PostgreSQL Tools
|
||||
#
|
||||
# Copyright (C) 2013 - 2016, The pgAdmin Development Team
|
||||
# This software is released under the PostgreSQL Licence
|
||||
#
|
||||
##########################################################################
|
||||
|
||||
"""Implements pgAgent Job Step Node"""
|
||||
|
||||
import json
|
||||
from functools import wraps
|
||||
|
||||
from flask import render_template, make_response, request
|
||||
from flask_babel import gettext
|
||||
from pgadmin.browser.collection import CollectionNodeModule
|
||||
from pgadmin.browser.utils import PGChildNodeView
|
||||
from pgadmin.utils.ajax import make_json_response, gone, \
|
||||
make_response as ajax_response, internal_server_error
|
||||
from pgadmin.utils.driver import get_driver
|
||||
|
||||
from config import PG_DEFAULT_DRIVER
|
||||
|
||||
|
||||
class JobStepModule(CollectionNodeModule):
|
||||
"""
|
||||
class JobStepModule(CollectionNodeModule)
|
||||
|
||||
A module class for JobStep node derived from CollectionNodeModule.
|
||||
|
||||
Methods:
|
||||
-------
|
||||
* get_nodes(gid, sid, jid)
|
||||
- Method is used to generate the browser collection node.
|
||||
|
||||
* node_inode()
|
||||
- Method is overridden from its base class to make the node as leaf node.
|
||||
"""
|
||||
|
||||
NODE_TYPE = 'pga_jobstep'
|
||||
COLLECTION_LABEL = gettext("Steps")
|
||||
|
||||
def get_nodes(self, gid, sid, jid):
|
||||
"""
|
||||
Method is used to generate the browser collection node
|
||||
|
||||
Args:
|
||||
gid: Server Group ID
|
||||
sid: Server ID
|
||||
jid: Database Id
|
||||
"""
|
||||
yield self.generate_browser_collection_node(jid)
|
||||
|
||||
@property
|
||||
def node_inode(self):
|
||||
"""
|
||||
Override this property to make the node a leaf node.
|
||||
|
||||
Returns: False as this is the leaf node
|
||||
"""
|
||||
return False
|
||||
|
||||
@property
|
||||
def script_load(self):
|
||||
"""
|
||||
Load the module script for language, when any of the database nodes are initialized.
|
||||
|
||||
Returns: node type of the server module.
|
||||
"""
|
||||
return 'pga_job'
|
||||
|
||||
|
||||
blueprint = JobStepModule(__name__)
|
||||
|
||||
|
||||
class JobStepView(PGChildNodeView):
|
||||
"""
|
||||
class JobStepView(PGChildNodeView)
|
||||
|
||||
A view class for JobStep node derived from PGChildNodeView. This class is
|
||||
responsible for all the stuff related to view like updating language
|
||||
node, showing properties, showing sql in sql pane.
|
||||
|
||||
Methods:
|
||||
-------
|
||||
* __init__(**kwargs)
|
||||
- Method is used to initialize the JobStepView and it's base view.
|
||||
|
||||
* module_js()
|
||||
- This property defines (if javascript) exists for this node.
|
||||
Override this property for your own logic
|
||||
|
||||
* check_precondition()
|
||||
- This function will behave as a decorator which will checks
|
||||
database connection before running view, it will also attaches
|
||||
manager,conn & template_path properties to self
|
||||
|
||||
* list()
|
||||
- This function is used to list all the language nodes within that collection.
|
||||
|
||||
* nodes()
|
||||
- This function will used to create all the child node within that collection.
|
||||
Here it will create all the language node.
|
||||
|
||||
* properties(gid, sid, jid, jstid)
|
||||
- This function will show the properties of the selected language node
|
||||
|
||||
* update(gid, sid, jid, jstid)
|
||||
- This function will update the data for the selected language node
|
||||
|
||||
* msql(gid, sid, jid, jstid)
|
||||
- This function is used to return modified SQL for the selected language node
|
||||
"""
|
||||
|
||||
node_type = blueprint.node_type
|
||||
|
||||
parent_ids = [
|
||||
{'type': 'int', 'id': 'gid'},
|
||||
{'type': 'int', 'id': 'sid'},
|
||||
{'type': 'int', 'id': 'jid'}
|
||||
]
|
||||
ids = [
|
||||
{'type': 'int', 'id': 'jstid'}
|
||||
]
|
||||
|
||||
operations = dict({
|
||||
'obj': [
|
||||
{'get': 'properties', 'put': 'update'},
|
||||
{'get': 'list', 'post': 'create'}
|
||||
],
|
||||
'nodes': [{'get': 'nodes'}, {'get': 'nodes'}],
|
||||
'msql': [{'get': 'msql'}, {'get': 'msql'}],
|
||||
'stats': [{'get': 'statistics'}],
|
||||
'module.js': [{}, {}, {'get': 'module_js'}]
|
||||
})
|
||||
|
||||
def _init_(self, **kwargs):
|
||||
"""
|
||||
Method is used to initialize the JobStepView and its base view.
|
||||
Initialize all the variables create/used dynamically like conn, template_path.
|
||||
|
||||
Args:
|
||||
**kwargs:
|
||||
"""
|
||||
self.conn = None
|
||||
self.template_path = None
|
||||
self.manager = None
|
||||
|
||||
super(JobStepView, self).__init__(**kwargs)
|
||||
|
||||
def module_js(self):
|
||||
"""
|
||||
This property defines whether javascript exists for this node.
|
||||
"""
|
||||
return make_response(
|
||||
render_template(
|
||||
"pga_jobstep/js/pga_jobstep.js",
|
||||
_=gettext
|
||||
),
|
||||
200, {'Content-Type': 'application/x-javascript'}
|
||||
)
|
||||
|
||||
def check_precondition(f):
|
||||
"""
|
||||
This function will behave as a decorator which will check the
|
||||
database connection before running the view. It also attaches
|
||||
manager, conn & template_path properties to self
|
||||
"""
|
||||
|
||||
@wraps(f)
|
||||
def wrap(*args, **kwargs):
|
||||
# Here args[0] will hold self & kwargs will hold gid,sid,jid
|
||||
self = args[0]
|
||||
self.driver = get_driver(PG_DEFAULT_DRIVER)
|
||||
self.manager = self.driver.connection_manager(kwargs['sid'])
|
||||
self.conn = self.manager.connection()
|
||||
|
||||
self.template_path = 'pga_jobstep/sql/pre3.4'
|
||||
|
||||
if not ('pgAgent' in self.manager.db_info):
|
||||
status, res = self.conn.execute_dict("""
|
||||
SELECT EXISTS(
|
||||
SELECT 1 FROM information_schema.columns
|
||||
WHERE
|
||||
table_schema='pgagent' AND table_name='pga_jobstep' AND
|
||||
column_name='jstconnstr'
|
||||
) has_connstr""")
|
||||
|
||||
self.manager.db_info['pgAgent'] = res['rows'][0]
|
||||
|
||||
return f(*args, **kwargs)
|
||||
|
||||
return wrap
|
||||
|
||||
@check_precondition
|
||||
def list(self, gid, sid, jid):
|
||||
"""
|
||||
This function is used to list all the language nodes within that collection.
|
||||
|
||||
Args:
|
||||
gid: Server Group ID
|
||||
sid: Server ID
|
||||
jid: Job ID
|
||||
"""
|
||||
sql = render_template(
|
||||
"/".join([self.template_path, 'properties.sql']),
|
||||
jid=jid,
|
||||
has_connstr=self.manager.db_info['pgAgent']['has_connstr']
|
||||
)
|
||||
status, res = self.conn.execute_dict(sql)
|
||||
|
||||
if not status:
|
||||
return internal_server_error(errormsg=res)
|
||||
|
||||
return ajax_response(
|
||||
response=res['rows'],
|
||||
status=200
|
||||
)
|
||||
|
||||
@check_precondition
|
||||
def nodes(self, gid, sid, jid, jstid=None):
|
||||
"""
|
||||
This function is used to create all the child nodes within the collection.
|
||||
Here it will create all the language nodes.
|
||||
|
||||
Args:
|
||||
gid: Server Group ID
|
||||
sid: Server ID
|
||||
jid: Job ID
|
||||
"""
|
||||
res = []
|
||||
sql = render_template(
|
||||
"/".join([self.template_path, 'nodes.sql']),
|
||||
jstid = jstid,
|
||||
jid = jid
|
||||
)
|
||||
|
||||
status, result = self.conn.execute_2darray(sql)
|
||||
|
||||
if not status:
|
||||
return internal_server_error(errormsg=result)
|
||||
|
||||
if jstid is not None:
|
||||
if len(result['rows']) == 0:
|
||||
return gone(errormsg="Couldn't find the specified job step.")
|
||||
|
||||
row = result['rows'][0]
|
||||
return make_json_response(
|
||||
self.blueprint.generate_browser_node(
|
||||
row['jstid'],
|
||||
row['jstjobid'],
|
||||
row['jstname'],
|
||||
icon="icon-pga_jobstep",
|
||||
enabled=row['jstenabled'],
|
||||
kind=row['jstkind']
|
||||
)
|
||||
)
|
||||
|
||||
for row in result['rows']:
|
||||
res.append(
|
||||
self.blueprint.generate_browser_node(
|
||||
row['jstid'],
|
||||
row['jstjobid'],
|
||||
row['jstname'],
|
||||
icon="icon-pga_jobstep",
|
||||
enabled=row['jstenabled'],
|
||||
kind=row['jstkind']
|
||||
)
|
||||
)
|
||||
|
||||
return make_json_response(
|
||||
data=res,
|
||||
status=200
|
||||
)
|
||||
|
||||
@check_precondition
|
||||
def properties(self, gid, sid, jid, jstid):
|
||||
"""
|
||||
This function will show the properties of the selected language node.
|
||||
|
||||
Args:
|
||||
gid: Server Group ID
|
||||
sid: Server ID
|
||||
jid: Job ID
|
||||
jstid: JobStep ID
|
||||
"""
|
||||
sql = render_template(
|
||||
"/".join([self.template_path, 'properties.sql']),
|
||||
jstid=jstid,
|
||||
jid=jid,
|
||||
has_connstr=self.manager.db_info['pgAgent']['has_connstr']
|
||||
)
|
||||
status, res = self.conn.execute_dict(sql)
|
||||
|
||||
if not status:
|
||||
return internal_server_error(errormsg=res)
|
||||
|
||||
if len(res['rows']) == 0:
|
||||
return gone(errormsg="Couldn't find the specified job step.")
|
||||
|
||||
return ajax_response(
|
||||
response=res['rows'][0],
|
||||
status=200
|
||||
)
|
||||
|
||||
@check_precondition
|
||||
def create(self, gid, sid, jid):
|
||||
"""
|
||||
This function will update the data for the selected language node.
|
||||
|
||||
Args:
|
||||
gid: Server Group ID
|
||||
sid: Server ID
|
||||
jid: Job ID
|
||||
"""
|
||||
data = {}
|
||||
for k, v in request.args.items():
|
||||
try:
|
||||
data[k] = json.loads(
|
||||
v.decode('utf-8') if hasattr(v, 'decode') else v
|
||||
)
|
||||
except ValueError:
|
||||
data[k] = v
|
||||
|
||||
sql = render_template(
|
||||
"/".join([self.template_path, 'create.sql']),
|
||||
jid=jid,
|
||||
data=data,
|
||||
has_connstr=self.manager.db_info['pgAgent']['has_connstr']
|
||||
)
|
||||
|
||||
status, res = self.conn.execute_scalar(sql)
|
||||
|
||||
if not status:
|
||||
return internal_server_error(errormsg=res)
|
||||
|
||||
sql = render_template(
|
||||
"/".join([self.template_path, 'nodes.sql']),
|
||||
jstid = res,
|
||||
jid = jid
|
||||
)
|
||||
status, res = self.conn.execute_2darray(sql)
|
||||
|
||||
if not status:
|
||||
return internal_server_error(errormsg=res)
|
||||
|
||||
row = res['rows'][0]
|
||||
return make_json_response(
|
||||
data=self.blueprint.generate_browser_node(
|
||||
row['jstid'],
|
||||
row['jstjobid'],
|
||||
row['jstname'],
|
||||
icon="icon-pga_jobstep"
|
||||
)
|
||||
)
|
||||
|
||||
@check_precondition
|
||||
def update(self, gid, sid, jid, jstid):
|
||||
"""
|
||||
This function will update the data for the selected language node.
|
||||
|
||||
Args:
|
||||
gid: Server Group ID
|
||||
sid: Server ID
|
||||
jid: Job ID
|
||||
jstid: JobStep ID
|
||||
"""
|
||||
data = request.form if request.form else json.loads(
|
||||
request.data.decode('utf-8')
|
||||
)
|
||||
|
||||
if (
|
||||
self.manager.db_info['pgAgent']['has_connstr'] and
|
||||
'jstconntype' not in data and
|
||||
('jstdbname' in data or 'jstconnstr' in data)
|
||||
):
|
||||
sql = render_template(
|
||||
"/".join([self.template_path, 'properties.sql']),
|
||||
jstid=jstid,
|
||||
jid=jid,
|
||||
has_connstr=self.manager.db_info['pgAgent']['has_connstr']
|
||||
)
|
||||
status, res = self.conn.execute_dict(sql)
|
||||
|
||||
if not status:
|
||||
return internal_server_error(errormsg=res)
|
||||
|
||||
if len(res['rows']) == 0:
|
||||
return gone(
|
||||
errormsg=gettext(
|
||||
"Couldn't find the specified job step."
|
||||
)
|
||||
)
|
||||
row = res['rows'][0]
|
||||
data['jstconntype'] = row['jstconntype']
|
||||
|
||||
if row['jstconntype']:
|
||||
if not ('jstdbname' in data):
|
||||
data['jstdbname'] = row['jstdbname']
|
||||
else:
|
||||
if not ('jstconnstr' in data):
|
||||
data['jstconnstr'] = row['jstconnstr']
|
||||
|
||||
sql = render_template(
|
||||
"/".join([self.template_path, 'update.sql']),
|
||||
jid=jid,
|
||||
jstid=jstid,
|
||||
data=data,
|
||||
has_connstr=self.manager.db_info['pgAgent']['has_connstr']
|
||||
)
|
||||
|
||||
status, res = self.conn.execute_void(sql)
|
||||
|
||||
if not status:
|
||||
return internal_server_error(errormsg=res)
|
||||
|
||||
sql = render_template(
|
||||
"/".join([self.template_path, 'nodes.sql']),
|
||||
jstid = jstid,
|
||||
jid = jid
|
||||
)
|
||||
status, res = self.conn.execute_2darray(sql)
|
||||
|
||||
if not status:
|
||||
return internal_server_error(errormsg=res)
|
||||
|
||||
row = res['rows'][0]
|
||||
return make_json_response(
|
||||
self.blueprint.generate_browser_node(
|
||||
row['jstid'],
|
||||
row['jstjobid'],
|
||||
row['jstname'],
|
||||
icon="icon-pga_jobstep"
|
||||
)
|
||||
)
|
||||
|
||||
@check_precondition
|
||||
def msql(self, gid, sid, jid, jstid=None):
|
||||
"""
|
||||
This function is used to return modified SQL for the selected language node.
|
||||
|
||||
Args:
|
||||
gid: Server Group ID
|
||||
sid: Server ID
|
||||
jid: Job ID
|
||||
jstid: Job Step ID
|
||||
"""
|
||||
data = {}
|
||||
sql = ''
|
||||
for k, v in request.args.items():
|
||||
try:
|
||||
data[k] = json.loads(
|
||||
v.decode('utf-8') if hasattr(v, 'decode') else v
|
||||
)
|
||||
except ValueError:
|
||||
data[k] = v
|
||||
|
||||
if jstid is None:
|
||||
sql = render_template(
|
||||
"/".join([self.template_path, 'create.sql']),
|
||||
jid=jid,
|
||||
data=data,
|
||||
has_connstr=self.manager.db_info['pgAgent']['has_connstr']
|
||||
)
|
||||
else:
|
||||
if (
|
||||
self.manager.db_info['pgAgent']['has_connstr'] and
|
||||
'jstconntype' not in data and
|
||||
('jstdbname' in data or 'jstconnstr' in data)
|
||||
):
|
||||
sql = render_template(
|
||||
"/".join([self.template_path, 'properties.sql']),
|
||||
jstid=jstid,
|
||||
jid=jid,
|
||||
has_connstr=self.manager.db_info['pgAgent']['has_connstr']
|
||||
)
|
||||
status, res = self.conn.execute_dict(sql)
|
||||
|
||||
if not status:
|
||||
return internal_server_error(errormsg=res)
|
||||
|
||||
if len(res['rows']) == 0:
|
||||
return gone(
|
||||
errormsg=gettext(
|
||||
"Couldn't find the specified job step."
|
||||
)
|
||||
)
|
||||
row = res['rows'][0]
|
||||
data['jstconntype'] = row['jstconntype']
|
||||
|
||||
if row['jstconntype']:
|
||||
if not ('jstdbname' in data):
|
||||
data['jstdbname'] = row['jstdbname']
|
||||
else:
|
||||
if not ('jstconnstr' in data):
|
||||
data['jstconnstr'] = row['jstconnstr']
|
||||
|
||||
sql = render_template(
|
||||
"/".join([self.template_path, 'update.sql']),
|
||||
jid=jid,
|
||||
jstid=jstid,
|
||||
data=data,
|
||||
has_connstr=self.manager.db_info['pgAgent']['has_connstr']
|
||||
)
|
||||
|
||||
return make_json_response(
|
||||
data=sql,
|
||||
status=200
|
||||
)
|
||||
|
||||
@check_precondition
|
||||
def statistics(self, gid, sid, jid, jstid):
|
||||
"""
|
||||
statistics
|
||||
Returns the statistics for a particular database if jid is specified,
|
||||
otherwise it will return statistics for all the databases in that
|
||||
server.
|
||||
"""
|
||||
status, res = self.conn.execute_dict(
|
||||
render_template(
|
||||
"/".join([self.template_path, 'stats.sql']),
|
||||
jid=jid, jstid=jstid, conn=self.conn
|
||||
)
|
||||
)
|
||||
|
||||
if not status:
|
||||
return internal_server_error(errormsg=res)
|
||||
|
||||
return make_json_response(
|
||||
data=res,
|
||||
status=200
|
||||
)
|
||||
|
||||
|
||||
JobStepView.register_node_view(blueprint)
|
After Width: | Height: | Size: 505 B |
After Width: | Height: | Size: 430 B |
|
@ -0,0 +1,7 @@
|
|||
.icon-pga_jobstep {
|
||||
background-image: url('{{ url_for('NODE-pga_jobstep.static', filename='img/pga_jobstep.png') }}') !important;
|
||||
background-repeat: no-repeat;
|
||||
align-content: center;
|
||||
vertical-align: middle;
|
||||
height: 1.3em;
|
||||
}
|
|
@ -0,0 +1,275 @@
|
|||
define([
|
||||
'jquery', 'underscore', 'underscore.string', 'pgadmin',
|
||||
'pgadmin.browser', 'alertify', 'backform', 'pgadmin.backform'
|
||||
],
|
||||
function($, _, S, pgAdmin, pgBrowser, Alertify, Backform) {
|
||||
|
||||
if (!pgBrowser.Nodes['coll-pga_jobstep']) {
|
||||
pgBrowser.Nodes['coll-pga_jobstep'] =
|
||||
pgBrowser.Collection.extend({
|
||||
node: 'pga_jobstep',
|
||||
label: '{{ _('Steps') }}',
|
||||
type: 'coll-pga_jobstep',
|
||||
columns: [
|
||||
'jstid', 'jstname', 'jstenabled', 'jstkind', 'jstconntype',
|
||||
'jstonerror'
|
||||
],
|
||||
hasStatistics: false
|
||||
});
|
||||
}
|
||||
|
||||
if (!pgBrowser.Nodes['pga_jobstep']) {
|
||||
pgBrowser.Nodes['pga_jobstep'] = pgBrowser.Node.extend({
|
||||
parent_type: 'pga_job',
|
||||
type: 'pga_jobstep',
|
||||
hasSQL: true,
|
||||
hasDepends: false,
|
||||
hasStatistics: true,
|
||||
hasCollectiveStatistics: true,
|
||||
width: '70%',
|
||||
height: '80%',
|
||||
canDrop: function(node) {
|
||||
return true;
|
||||
},
|
||||
label: '{{ _('Steps') }}',
|
||||
node_image: function() {
|
||||
console.log(arguments);
|
||||
return 'icon-pga_jobstep';
|
||||
},
|
||||
Init: function() {
|
||||
/* Avoid mulitple registration of menus */
|
||||
if (this.initialized)
|
||||
return;
|
||||
|
||||
this.initialized = true;
|
||||
|
||||
pgBrowser.add_menus([{
|
||||
name: 'create_pga_jobstep_on_job', node: 'pga_job', module: this,
|
||||
applies: ['object', 'context'], callback: 'show_obj_properties',
|
||||
category: 'create', priority: 4, label: '{{ _('Job Step...') }}',
|
||||
data: {'action': 'create'}, icon: 'wcTabIcon icon-pga_jobstep'
|
||||
},{
|
||||
name: 'create_pga_jobstep_on_coll', node: 'coll-pga_jobstep', module: this,
|
||||
applies: ['object', 'context'], callback: 'show_obj_properties',
|
||||
category: 'create', priority: 4, label: '{{ _('Job Step...') }}',
|
||||
data: {'action': 'create'}, icon: 'wcTabIcon icon-pga_jobstep'
|
||||
},{
|
||||
name: 'create_pga_jobstep', node: 'pga_jobstep', module: this,
|
||||
applies: ['object', 'context'], callback: 'show_obj_properties',
|
||||
category: 'create', priority: 4, label: '{{ _('Job Step...') }}',
|
||||
data: {'action': 'create'}, icon: 'wcTabIcon icon-pga_jobstep'
|
||||
}]);
|
||||
},
|
||||
model: pgBrowser.Node.Model.extend({
|
||||
defaults: {
|
||||
jstid: null,
|
||||
jstjobid: null,
|
||||
jstname: '',
|
||||
jstdesc: '',
|
||||
jstenabled: true,
|
||||
jstkind: true,
|
||||
jstconntype: true,
|
||||
jstcode: '',
|
||||
jstconnstr: null,
|
||||
jstdbname: null,
|
||||
jstonerror: 'f',
|
||||
jstnextrun: ''
|
||||
},
|
||||
initialize: function() {
|
||||
pgBrowser.Node.Model.prototype.initialize.apply(this, arguments);
|
||||
if (this.isNew() && this.get('jstconntype')) {
|
||||
var args = arguments && arguments.length > 1 && arguments[1];
|
||||
|
||||
if (args) {
|
||||
this.set(
|
||||
'jstdbname',
|
||||
(args['node_info'] || args.collection.top['node_info'])['server']['db']
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
idAttribute: 'jstid',
|
||||
schema: [{
|
||||
id: 'jstid', label: '{{ _('ID') }}', type: 'integer',
|
||||
cellHeaderClasses: 'width_percent_5', mode: ['properties']
|
||||
},{
|
||||
id: 'jstname', label: '{{ _('Name') }}', type: 'text',
|
||||
disabled: function(m) { return false; },
|
||||
cellHeaderClasses: 'width_percent_60'
|
||||
},{
|
||||
id: 'jstenabled', label: '{{ _('Enabled') }}', type: 'switch',
|
||||
disabled: function(m) { return false; }
|
||||
},{
|
||||
id: 'jstkind', label: '{{ _('Kind') }}', type: 'switch',
|
||||
options: {
|
||||
'onText': '{{ _('SQL') }}', 'offText': '{{ _('Batch') }}',
|
||||
'onColor': 'primary', 'offColor': 'primary'
|
||||
}, control: Backform.SwitchControl,
|
||||
disabled: function(m) { return false; }
|
||||
},{
|
||||
id: 'jstconntype', label: '{{ _('Connection type') }}',
|
||||
type: 'switch', deps: ['jstkind'], mode: ['properties'],
|
||||
disabled: function(m) { return !m.get('jstkind'); },
|
||||
options: {
|
||||
'onText': '{{ _('Local') }}', 'offText': '{{ _('Remote') }}',
|
||||
'onColor': 'primary', 'offColor': 'primary'
|
||||
}
|
||||
},{
|
||||
id: 'jstconntype', label: '{{ _('Connection type') }}',
|
||||
type: 'switch', deps: ['jstkind'], mode: ['create', 'edit'],
|
||||
disabled: function(m) { return !m.get('jstkind'); },
|
||||
options: {
|
||||
'onText': '{{ _('Local') }}', 'offText': '{{ _('Remote') }}',
|
||||
'onColor': 'primary', 'offColor': 'primary'
|
||||
}, helpMessage: '{{ _('Select <b>Local</b> if the job step will execute on the local database server, or <b>Remote</b> to specify a remote database server.') }}'
|
||||
},{
|
||||
id: 'jstdbname', label: '{{ _('Database') }}', type: 'text',
|
||||
mode: ['properties'], disabled: function(m) { return false; }
|
||||
},{
|
||||
id: 'jstconnstr', type: 'text', mode: ['properties'],
|
||||
label: '{{ _('Connection string') }}'
|
||||
},{
|
||||
id: 'jstdbname', label: '{{ _('Database') }}', type: 'text',
|
||||
control: 'node-list-by-name', node: 'database',
|
||||
cache_node: 'database', select2: {allowClear: true, placeholder: ''},
|
||||
disabled: function(m) {
|
||||
return !m.get('jstkind') || !m.get('jstconntype');
|
||||
}, deps: ['jstkind', 'jstconntype'], mode: ['create', 'edit'],
|
||||
helpMessage: '{{ _('Please select the database on which the job step will run.') }}'
|
||||
},{
|
||||
id: 'jstconnstr', label: '{{ _('Connection string') }}', type: 'text',
|
||||
deps: ['jstkind', 'jstconntype'], disabled: function(m) {
|
||||
return !m.get('jstkind') || m.get('jstconntype');
|
||||
}, helpMessage: S(
|
||||
'{{ _("Please specify the connection string for the remote database server. Each parameter setting is in the form keyword = value. Spaces around the equal sign are optional. To write an empty value, or a value containing spaces, surround it with single quotes, e.g., keyword = \\'a value\\'. Single quotes and backslashes within the value must be escaped with a backslash, i.e., \\\' and \\\\.<br>For more information, please see the documentation on %s") }}'
|
||||
).sprintf(
|
||||
'<a href="https://www.postgresql.org/docs/current/static/libpq-connect.html#LIBPQ-CONNSTRING" target="_blank">libpq connection strings</a>'
|
||||
).value(), mode: ['create', 'edit']
|
||||
},{
|
||||
id: 'jstonerror', label: '{{ _('On error') }}', cell: 'select2',
|
||||
control: 'select2', options: [
|
||||
{'label': '{{ _("Fail") }}', 'value': "f"},
|
||||
{'label': '{{ _("Success") }}', 'value': "s"},
|
||||
{'label': '{{ _("Ignore") }}', 'value': "i"}
|
||||
], select2: {allowClear: false}, disabled: function(m) {
|
||||
return false;
|
||||
}
|
||||
},{
|
||||
id: 'jstdesc', label: '{{ _('Comment') }}', type: 'multiline'
|
||||
},{
|
||||
id: 'jstcode', label: '', cell: 'string', deps: ['jstkind'],
|
||||
type: 'text', control: 'sql-field', group: '{{ _('Code') }}',
|
||||
control: Backform.SqlFieldControl.extend({
|
||||
render: function() {
|
||||
if (this.model.get('jstkind')) {
|
||||
this.field.set('label', '{{ _('SQL query') }}');
|
||||
} else {
|
||||
this.field.set('label', '{{ _('Script') }}');
|
||||
}
|
||||
return Backform.SqlFieldControl.prototype.render.apply(
|
||||
this, arguments
|
||||
);
|
||||
}
|
||||
})
|
||||
}],
|
||||
validate: function(keys) {
|
||||
var val = this.get('jstname'),
|
||||
errMsg = null;
|
||||
|
||||
if (
|
||||
_.isUndefined(val) || _.isNull(val) ||
|
||||
String(val).replace(/^\s+|\s+$/g, '') == ''
|
||||
) {
|
||||
errMsg = '{{ _('Name cannot be empty.') }}';
|
||||
this.errorModel.set('jstname', errMsg);
|
||||
} else {
|
||||
this.errorModel.unset('jstname');
|
||||
}
|
||||
if (this.get('jstkind')) {
|
||||
if (this.get('jstconntype')) {
|
||||
this.errorModel.unset('jstconnstr');
|
||||
val = this.get('jstdbname');
|
||||
if (
|
||||
_.isUndefined(val) || _.isNull(val) ||
|
||||
String(val).replace(/^\s+|\s+$/g, '') == ''
|
||||
) {
|
||||
var msg = '{{ _('Please select a database.') }}';
|
||||
errMsg = errMsg || msg;
|
||||
this.errorModel.set('jstdbname', msg);
|
||||
} else {
|
||||
this.errorModel.unset('jstdbname');
|
||||
}
|
||||
} else {
|
||||
this.errorModel.unset('jstdbname');
|
||||
var msg,
|
||||
r = /\s*\b(\w+)\s*=\s*('([^'\\]*(?:\\.[^'\\]*)*)'|\w*)/g;
|
||||
val = this.get('jstconnstr');
|
||||
if (
|
||||
_.isUndefined(val) || _.isNull(val) ||
|
||||
String(val).replace(/^\s+|\s+$/g, '') == ''
|
||||
) {
|
||||
msg = '{{ _("Please enter a connection string.") }}';
|
||||
} else if (String(val).replace(r, '') != '') {
|
||||
msg = '{{ _("Please enter a valid connection string.") }}';
|
||||
} else {
|
||||
var m,
|
||||
params = {
|
||||
'host': true, 'hostaddr': true, 'port': true,
|
||||
'dbname': true, 'user': true, 'password': true,
|
||||
'connect_timeout': true, 'client_encoding': true,
|
||||
'application_name': true, 'options': true,
|
||||
'fallback_application_name': true, 'sslmode': true,
|
||||
'sslcert': true, 'sslkey': true, 'sslrootcert': true,
|
||||
'sslcrl': true, 'keepalives': true, 'service': true,
|
||||
'keepalives_idle': true, 'keepalives_interval': true,
|
||||
'keepalives_count': true, 'sslcompression': true,
|
||||
'requirepeer': true, 'krbsrvname': true, 'gsslib': true,
|
||||
};
|
||||
|
||||
while((m = r.exec(val))) {
|
||||
if (params[m[1]]) {
|
||||
if (m[2])
|
||||
continue;
|
||||
msg = '{{ _("Please enter a valid connection string.") }}';
|
||||
break;
|
||||
}
|
||||
|
||||
msg = S(
|
||||
'{{ _("Invalid parameter in the connection string - %s.") }}'
|
||||
).sprintf(m[1]).value();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (msg) {
|
||||
errMsg = errMsg || msg;
|
||||
this.errorModel.set('jstconnstr', msg);
|
||||
} else {
|
||||
this.errorModel.unset('jstconnstr');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.errorModel.unset('jstconnstr');
|
||||
this.errorModel.unset('jstdbname');
|
||||
}
|
||||
|
||||
val = this.get('jstcode');
|
||||
if (
|
||||
_.isUndefined(val) || _.isNull(val) ||
|
||||
String(val).replace(/^\s+|\s+$/g, '') == ''
|
||||
) {
|
||||
var msg = '{{ _('Please specify code to execute.') }}';
|
||||
errMsg = errMsg || msg;
|
||||
this.errorModel.set('jstcode', msg);
|
||||
} else {
|
||||
this.errorModel.unset('jstcode');
|
||||
}
|
||||
|
||||
return errMsg;
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
return pgBrowser.Nodes['pga_job'];
|
||||
});
|
|
@ -0,0 +1,24 @@
|
|||
{##################################################}
|
||||
{# This will be specific macro for pga_exception. #}
|
||||
{##################################################}
|
||||
{% macro INSERT(jscid, data) -%}
|
||||
-- Inserting a schedule exception {% if jscid %}(schedule: {{ jscid|qtLiteral}}){% endif %}
|
||||
|
||||
INSERT INTO pgagent.pga_exception (
|
||||
jexscid, jexdate, jextime
|
||||
) VALUES (
|
||||
{% if jscid %}{{ jscid|qtLiteral }}{% else %}scid{% endif %}, {% if data.jexdate %}to_date({{ data.jexdate|qtLiteral }}, 'MM/DD/YYYY')::date{% else %}NULL::date{% endif %}, {% if data.jextime %}{{ data.jextime|qtLiteral }}::time without time zone{% else %}NULL::time without time zone{% endif %}
|
||||
|
||||
);
|
||||
{%- endmacro %}
|
||||
{% macro UPDATE(jscid, data) -%}
|
||||
-- Updating an existing schedule exception (id: {{ data.jexid|qtLiteral }}, schedule: {{ jscid|qtLiteral }})
|
||||
UPDATE pgagent.pga_exception SET
|
||||
{% if 'jexdate' in data %}jexdate={% if data.jexdate %}to_date({{ data.jexdate|qtLiteral }}, 'MM/DD/YYYY')::date{% else %}NULL::date{% endif %}{% endif %}{% if 'jextime' in data%}{% if 'jexdate' in data %}, {% endif %}jextime={% if data.jextime %}{{ data.jextime|qtLiteral }}::time without time zone{% else %}NULL::time without time zone{% endif %}{% endif %}
|
||||
|
||||
WHERE jexid={{ data.jexid|qtLiteral }}::integer AND jscid={{ jscid|qtLiteral }}::integer;
|
||||
{%- endmacro %}
|
||||
{% macro DELETE(jscid, data) -%}
|
||||
-- Deleting a schedule exception (id: {{ data.jexid|qtLiteral }}, schedule: {{ jscid|qtLiteral }})
|
||||
DELETE FROM pgagent.pga_exception WHERE jexid={{ data.jexid|qtLiteral }}::integer AND jscid={{ jscid|qtLiteral }}::integer;
|
||||
{%- endmacro %}
|
|
@ -0,0 +1,51 @@
|
|||
{################################################}
|
||||
{# This will be specific macro for pga_jobstep. #}
|
||||
{################################################}
|
||||
{% macro INSERT(has_connstr, jid, data) -%}
|
||||
-- Inserting a step (jobid: {{ jid|qtLiteral }})
|
||||
INSERT INTO pgagent.pga_jobstep (
|
||||
jstjobid, jstname, jstenabled, jstkind,
|
||||
{% if has_connstr %}jstconnstr, {% endif %}jstdbname, jstonerror,
|
||||
jstcode, jstdesc
|
||||
) VALUES (
|
||||
{% if jid %}{{ jid|qtLiteral }}{% else %}jid{% endif %}, {{ data.jstname|qtLiteral }}::text, {% if data.jstenabled %}true{% else %}false {% endif %}, {% if data.jstkind %}'s'{% else %}'b'{% endif %}::character(1),
|
||||
{% if has_connstr %}{% if data.jstconntype %}''{% else %}{{ data.jstconnstr|qtLiteral }}{% endif %}::text, {% if not data.jstconntype %}''::name{% else %}{{ data.jstdbname|qtLiteral }}{% endif %}::name{% else %}{{ data.jstdbname|qtLiteral }}::name{% endif %}, {{ data.jstonerror|qtLiteral }}::character(1),
|
||||
{{ data.jstcode|qtLiteral }}::text, {{ data.jstdesc|qtLiteral }}::text
|
||||
) {% if jid %}RETURNING jstid{% endif %};
|
||||
{%- endmacro %}
|
||||
{% macro UPDATE(has_connstr, jid, jstid, data) -%}
|
||||
-- Updating the existing step (id: {{ jstid|qtLiteral }} jobid: {{ jid|qtLiteral }})
|
||||
UPDATE pgagent.pga_jobstep
|
||||
SET
|
||||
{% if has_connstr %}{% if 'jstconntype' in data %}{% if data.jstconntype %}jstdbname={{ data.jstdbname|qtLiteral }}, jstconnstr=''{% else %}jstdbname='', jstconnstr={{ data.jstconnstr|qtLiteral }}{% endif %}{% if 'jstname' in data or 'jstenabled' in data or 'jstdesc' in data or 'jstonerror' in data or 'jstcode' in data %},{% endif %}{% endif %}{% else %}{% if 'jstdbname' in data %}jstdbname={{ data.jstdbname|qtLiteral }}::name{% endif %}{% if 'jstname' in data or 'jstenabled' in data or 'jstdesc' in data or 'jstonerror' in data or 'jstcode' in data %},{% endif %}{% endif %}{% if 'jstname' in data %}
|
||||
|
||||
jstname={{ data.jstname|qtLiteral }}::text{% if 'jstenabled' in data or 'jstdesc' in data or 'jstonerror' in data or 'jstcode' in data %},{% endif %}{% endif %}{% if 'jstenabled' in data %}
|
||||
|
||||
jstenabled={% if data.jstenabled %}true{% else %}false{% endif %}{% if 'jstdesc' in data or 'jstonerror' in data or 'jstcode' in data %},{% endif %}{% endif %}{% if 'jstdesc' in data %}
|
||||
|
||||
jstdesc={{ data.jstdesc|qtLiteral }}{% if 'jstonerror' in data or 'jstcode' in data %},{% endif %}{% endif %}{% if 'jstonerror' in data %}
|
||||
|
||||
jstonerror={{ data.jstonerror|qtLiteral }}{% if 'jstcode' in data %},{% endif %}{% endif %}{% if 'jstcode' in data %}
|
||||
|
||||
jstcode={{ data.jstcode|qtLiteral }}{% endif %}
|
||||
|
||||
WHERE jstid={{ jstid|qtLiteral }}::integer AND jstjobid={{ jid|qtLiteral }}::integer;
|
||||
{%- endmacro %}
|
||||
{% macro DELETE(jid, jstid) -%}
|
||||
-- Deleting a step (id: {{ jstid|qtLiteral }}, jobid: {{ jid|qtLiteral }})
|
||||
DELETE FROM pgagent.pga_jobstep WHERE jstid={{ jstid|qtLiteral }}::integer AND jstjobid={{ jid|qtLiteral }}::integer;
|
||||
{%- endmacro %}
|
||||
{% macro PROPERTIES(has_connstr, jid, jstid) -%}
|
||||
SELECT
|
||||
jstid, jstjobid, jstname, jstdesc, jstenabled, jstkind = 's'::bpchar as jstkind,
|
||||
jstcode, CASE WHEN jstdbname != '' THEN true ELSE false END AS jstconntype,
|
||||
{% if has_connstr %}jstconnstr, {% endif %} jstdbname, jstonerror, jscnextrun
|
||||
FROM
|
||||
pgagent.pga_jobstep
|
||||
WHERE
|
||||
{% if jstid %}
|
||||
jstid = {{ jstid|qtLiteral }}::integer AND
|
||||
{% endif %}
|
||||
jstjobid = {{ jid|qtLiteral }}::integer
|
||||
ORDER BY jstname;
|
||||
{%- endmacro %}
|
|
@ -0,0 +1,107 @@
|
|||
{#################################################}
|
||||
{# This will be specific macro for pga_schedule. #}
|
||||
{#################################################}
|
||||
{% import 'macros/pga_exception.macros' as EXCEPTIONS %}
|
||||
{% macro INSERT(jid, data) -%}
|
||||
-- Inserting a schedule{% if jid %} (jobid: {{ jid|qtLiteral }}){% endif %}
|
||||
|
||||
INSERT INTO pgagent.pga_schedule(
|
||||
jscjobid, jscname, jscdesc, jscenabled,
|
||||
jscstart, {% if data.jscend %}jscend,{% endif %}
|
||||
jscminutes, jschours, jscweekdays, jscmonthdays, jscmonths
|
||||
) VALUES (
|
||||
{% if jid %}{{ jid|qtLiteral }}{% else %}jid{% endif %}, {{ data.jscname|qtLiteral }}::text, {{ data.jscdesc|qtLiteral }}::text, {% if data.jscenabled %}true{% else %}false{% endif %},
|
||||
{{ data.jscstart|qtLiteral }}::timestamp with time zone, {% if data.jscend %}{{ data.jscend|qtLiteral }}::timestamp with time zone,{% endif %}
|
||||
|
||||
-- Minutes
|
||||
{{ data.jscminutes|qtLiteral }}::boolean[],
|
||||
-- Hours
|
||||
{{ data.jschours|qtLiteral }}::boolean[],
|
||||
-- Week days
|
||||
{{ data.jscweekdays|qtLiteral }}::boolean[],
|
||||
-- Month days
|
||||
{{ data.jscmonthdays|qtLiteral }}::boolean[],
|
||||
-- Months
|
||||
{{ data.jscmonths|qtLiteral }}::boolean[]
|
||||
) RETURNING jscid INTO scid;{% if 'jscexceptions' in data %}
|
||||
{% for exc in data.jscexceptions %}
|
||||
|
||||
{{ EXCEPTIONS.INSERT(None, exc) }}{% endfor %}{% endif %}
|
||||
{%- endmacro %}
|
||||
{% macro UPDATE(jid, jscid, data) -%}
|
||||
{% if 'jscname' in data or 'jscenabled' in data or 'jscdesc' in data or 'jscstart' in data or 'jscend' in data or 'jscend' in data or 'jscmonths' in data or 'jscminutes' in data or 'jscmonthdays' in data or 'jschours' in data or 'jscweekdays' in data %}
|
||||
-- Updating the schedule (id: {{ jscid|qtLiteral }}, jobid: {{ jid|qtLiteral }})
|
||||
UPDATE pgagent.pga_schedule
|
||||
SET{% if 'jscname' in data %}
|
||||
|
||||
jscname={{ data.jscname|qtLiteral }}::text{% if 'jscdesc' in data or 'jscstart' in data or 'jscend' in data or 'jscend' in data or 'jscmonths' in data or 'jscminutes' in data or 'jscmonthdays' in data or 'jschours' in data or 'jscweekdays' in data or 'jscenabled' in data %},{% endif %}{% endif %}{% if 'jscenabled' in data %}
|
||||
|
||||
jscenabled={% if data.jscenabled %}true{% else %}false{% endif %}{% if 'jscdesc' in data or 'jscstart' in data or 'jscend' in data or 'jscend' in data or 'jscmonths' in data or 'jscminutes' in data or 'jscmonthdays' in data or 'jschours' in data or 'jscweekdays' in data %},{% endif %}{% endif %}{% if 'jscdesc' in data %}
|
||||
|
||||
jscdesc={% if data.jscdesc %}{{ data.jscdesc|qtLiteral }}::text{% else %}''::text{% endif %}{% if 'jscstart' in data or 'jscend' in data or 'jscend' in data or 'jscmonths' in data or 'jscminutes' in data or 'jscmonthdays' in data or 'jschours' in data or 'jscweekdays' in data %},{% endif %}{% endif %}{% if 'jscstart' in data %}
|
||||
|
||||
jscstart={{ data.jscstart|qtLiteral }}::text{% if 'jscend' in data or 'jscend' in data or 'jscmonths' in data or 'jscminutes' in data or 'jscmonthdays' in data or 'jschours' in data or 'jscweekdays' in data %},{% endif %}{% endif %}{% if 'jscend' in data %}
|
||||
|
||||
jscend={% if data.jscend %}{{ data.jscend|qtLiteral }}::timestamptz{% else %}NULL::timestamptz{% endif %}{% if 'jscend' in data or 'jscmonths' in data or 'jscminutes' in data or 'jscmonthdays' in data or 'jschours' in data or 'jscweekdays' in data %},{% endif %}{% endif %}{% if 'jscmonths' in data %}
|
||||
|
||||
jscmonths={{ data.jscmonths|qtLiteral }}::boolean[]{% if 'jscminutes' in data or 'jscmonthdays' in data or 'jschours' in data or 'jscweekdays' in data %},{% endif %}{% endif %}{% if 'jscminutes' in data %}
|
||||
|
||||
jscminutes={{ data.jscminutes|qtLiteral }}::boolean[]{% if 'jscmonthdays' in data or 'jschours' in data or 'jscweekdays' in data %},{% endif %}{% endif %}{% if 'jscmonthdays' in data %}
|
||||
|
||||
jscmonthdays={{ data.jscmonthdays|qtLiteral }}::boolean[]{% if 'jschours' in data or 'jscweekdays' in data %},{% endif %}{% endif %}{% if 'jschours' in data %}
|
||||
|
||||
jschours={{ data.jschours|qtLiteral }}::boolean[]{% if 'jscweekdays' in data %},{% endif %}{% endif %}{% if 'jscweekdays' in data %}
|
||||
|
||||
jscweekdays={{ data.jscweekdays|qtLiteral }}::boolean[]{% endif %}
|
||||
|
||||
WHERE jscid={{ jscid|qtLiteral }}::integer AND jscjobid={{ jid|qtLiteral }}::integer;{% endif %}{% if 'jscexceptions' in data %}
|
||||
{% if 'added' in data.jscexceptions and data.jscexceptions.added|length > 0 %}
|
||||
|
||||
{% for exc in data.jscexceptions.added %}
|
||||
{{ EXCEPTIONS.INSERT(jscid, exc) }}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% if 'deleted' in data.jscexceptions and data.jscexceptions.deleted|length > 0 %}
|
||||
|
||||
{% for exc in data.jscexceptions.deleted %}
|
||||
{{ EXCEPTIONS.DELETE(jscid, exc) }}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% if 'changed' in data.jscexceptions and data.jscexceptions.changed|length > 0 %}
|
||||
|
||||
{% for exc in data.jscexceptions.changed %}
|
||||
{{ EXCEPTIONS.UPDATE(jscid, exc) }}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{%- endmacro %}
|
||||
{% macro DELETE(jid, jscid) -%}
|
||||
-- Removing the existing schedule (id: {{ jscid|qtLiteral }}, jobid: {{ jid|qtLiteral }})
|
||||
DELETE FROM pgagent.pga_schedule WHERE jscid={{ jscid|qtLiteral }}::integer AND jscjobid={{ jid|qtLiteral }}::integer;
|
||||
{%- endmacro %}
|
||||
{% macro FETCH_CURRENT() -%}
|
||||
SELECT jscid FROM pgagent.pga_schedule WHERE xmin::text = (txid_current() % (2^32)::bigint)::text;
|
||||
{%- endmacro %}
|
||||
{% macro PROPERTIES(jid, jscid) -%}
|
||||
SELECT
|
||||
jscid, jscjobid, jscname, jscdesc, jscenabled, jscstart, jscend,
|
||||
jscminutes, jschours, jscweekdays, jscmonthdays, jscmonths,
|
||||
jexid, jexdate, jextime
|
||||
FROM
|
||||
pgagent.pga_schedule s
|
||||
LEFT JOIN (
|
||||
SELECT
|
||||
jexscid, array_agg(jexid) AS jexid, array_agg(to_char(jexdate, 'YYYY-MM-DD')) AS jexdate,
|
||||
array_agg(jextime) AS jextime
|
||||
FROM
|
||||
pgagent.pga_exception ex
|
||||
GROUP BY
|
||||
jexscid
|
||||
) e ON s.jscid = e.jexscid
|
||||
WHERE
|
||||
{% if jscid %}
|
||||
s.jscid = {{ jscid|qtLiteral }}::integer AND
|
||||
{% endif %}
|
||||
s.jscjobid = {{ jid|qtLiteral }}::integer
|
||||
ORDER BY jscname;
|
||||
{%- endmacro %}
|
|
@ -0,0 +1,28 @@
|
|||
.icon-pga_job {
|
||||
background-image: url('{{ url_for('NODE-pga_job.static', filename='img/pga_job.png') }}') !important;
|
||||
background-repeat: no-repeat;
|
||||
align-content: center;
|
||||
vertical-align: middle;
|
||||
height: 1.3em;
|
||||
}
|
||||
|
||||
.icon-pga_job-disabled {
|
||||
background-image: url('{{ url_for('NODE-pga_job.static', filename='img/pga_job-disabled.png') }}') !important;
|
||||
background-repeat: no-repeat;
|
||||
align-content: center;
|
||||
vertical-align: middle;
|
||||
height: 1.3em;
|
||||
}
|
||||
|
||||
.pg-el-container[el=sm] .pga-round-border {
|
||||
border: 1px solid darkgray;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.pg-el-container[el=sm] .pga-round-border {
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
div[role=tabpanel] > .pgadmin-control-group.form-group.pg-el-xs-12.jscexceptions {
|
||||
min-height: 400px;
|
||||
}
|
|
@ -0,0 +1,161 @@
|
|||
define(
|
||||
[
|
||||
'jquery', 'underscore', 'underscore.string', 'pgadmin',
|
||||
'pgadmin.browser', 'alertify', 'pgadmin.node.pga_jobstep',
|
||||
'pgadmin.node.pga_schedule'
|
||||
],
|
||||
function($, _, S, pgAdmin, pgBrowser, Alertify) {
|
||||
|
||||
if (!pgBrowser.Nodes['coll-pga_job']) {
|
||||
var pga_jobs = pgBrowser.Nodes['coll-pga_job'] =
|
||||
pgBrowser.Collection.extend({
|
||||
node: 'pga_job',
|
||||
label: '{{ _('pga_jobs') }}',
|
||||
type: 'coll-pga_job',
|
||||
columns: ['jobid', 'jobname', 'jobenabled', 'jlgstatus', 'jobnextrun', 'joblastrun', 'jobdesc'],
|
||||
hasStatistics: false
|
||||
});
|
||||
};
|
||||
|
||||
if (!pgBrowser.Nodes['pga_job']) {
|
||||
pgBrowser.Nodes['pga_job'] = pgBrowser.Node.extend({
|
||||
parent_type: 'server',
|
||||
type: 'pga_job',
|
||||
hasSQL: true,
|
||||
hasDepends: false,
|
||||
hasStatistics: true,
|
||||
hasCollectiveStatistics: true,
|
||||
width: '80%',
|
||||
height: '80%',
|
||||
canDrop: function(node) {
|
||||
return true;
|
||||
},
|
||||
label: '{{ _('pgAgent Job') }}',
|
||||
node_image: function() {
|
||||
return 'icon-pga_job';
|
||||
},
|
||||
Init: function() {
|
||||
/* Avoid mulitple registration of menus */
|
||||
if (this.initialized)
|
||||
return;
|
||||
|
||||
this.initialized = true;
|
||||
|
||||
pgBrowser.add_menus([{
|
||||
name: 'create_pga_job_on_coll', node: 'coll-pga_job', module: this,
|
||||
applies: ['object', 'context'], callback: 'show_obj_properties',
|
||||
category: 'create', priority: 4, label: '{{ _('pgAgent Job...') }}',
|
||||
icon: 'wcTabIcon icon-pga_job', data: {action: 'create'}
|
||||
},{
|
||||
name: 'create_pga_job', node: 'pga_job', module: this,
|
||||
applies: ['object', 'context'], callback: 'show_obj_properties',
|
||||
category: 'create', priority: 4, label: '{{ _('pgAgent Job...') }}',
|
||||
icon: 'wcTabIcon icon-pga_job', data: {action: 'create'}
|
||||
}]);
|
||||
},
|
||||
model: pgBrowser.Node.Model.extend({
|
||||
defaults: {
|
||||
jobname: '',
|
||||
jobid: undefined,
|
||||
jobenabled: true,
|
||||
jobhostagent: '',
|
||||
jobjclid: 1,
|
||||
jobcreated: undefined,
|
||||
jobchanged: undefined,
|
||||
jobnextrun: undefined,
|
||||
joblastrun: undefined,
|
||||
jlgstatus: undefined,
|
||||
jobrunningat: undefined,
|
||||
jobdesc: '',
|
||||
jsteps: [],
|
||||
jschedules: []
|
||||
},
|
||||
idAttribute: 'jobid',
|
||||
parse: function() {
|
||||
var d = pgBrowser.Node.Model.prototype.parse.apply(this, arguments);
|
||||
|
||||
if (d) {
|
||||
d.jobrunningat = d.jaghostagent || "{{ _('Not running currently.') }}";
|
||||
d.jlgstatus = d.jlgstatus || "{{ _('Unknown') }}";
|
||||
}
|
||||
return d;
|
||||
},
|
||||
schema: [{
|
||||
id: 'jobname', label: '{{ _('Name') }}', type: 'text',
|
||||
cellHeaderClasses: 'width_percent_30'
|
||||
},{
|
||||
id: 'jobid', label:'{{ _('ID') }}', mode: ['properties'],
|
||||
type: 'int'
|
||||
},{
|
||||
id: 'jobenabled', label:'{{ _('Enabled') }}', type: 'switch',
|
||||
cellHeaderClasses: 'width_percent_5'
|
||||
},{
|
||||
id: 'jobclass', label: '{{ _('Job Class') }}', type: 'text',
|
||||
mode: ['properties']
|
||||
},{
|
||||
id: 'jobjclid', label: '{{ _('Job Class') }}', type: 'integer',
|
||||
control: 'node-ajax-options', url: 'classes', url_with_id: false,
|
||||
cache_node: 'server', mode: ['create', 'edit'],
|
||||
select2: {allowClear: false},
|
||||
helpMessage: '{{ _('Please a class to categorize the job. This option will not affect the way the job runs.') }}'
|
||||
},{
|
||||
id: 'jobhostagent', label: '{{ _('Host Agent') }}', type: 'text',
|
||||
mode: ['edit', 'create'],
|
||||
helpMessage: '{{ _('Enter the hostname of a machine running pgAgent if you wish to ensure only that machine will run this job. Leave blank if any host may run the job.') }}'
|
||||
},{
|
||||
id: 'jobhostagent', label: '{{ _('Host Agent') }}', type: 'text',
|
||||
mode: ['properties']
|
||||
},{
|
||||
id: 'jobcreated', type: 'text', mode: ['properties'],
|
||||
label: '{{ _('Created') }}'
|
||||
},{
|
||||
id: 'jobchanged', type: 'text', mode: ['properties'],
|
||||
label: '{{ _('Changed') }}'
|
||||
},{
|
||||
id: 'jobnextrun', type: 'text', mode: ['properties'],
|
||||
label: '{{ _('Next run') }}', cellHeaderClasses: 'width_percent_20'
|
||||
},{
|
||||
id: 'joblastrun', type: 'text', mode: ['properties'],
|
||||
label: '{{ _('Last run') }}', cellHeaderClasses: 'width_percent_20'
|
||||
},{
|
||||
id: 'jlgstatus', type: 'text', label: '{{ _('Last result') }}',
|
||||
cellHeaderClasses: 'width_percent_5', mode: ['properties']
|
||||
},{
|
||||
id: 'jobrunningat', type: 'text', mode: ['properties'],
|
||||
label: '{{ _('Running at') }}'
|
||||
},{
|
||||
id: 'jobdesc', label:'{{ _('Comment') }}', type: 'multiline',
|
||||
cellHeaderClasses: 'width_percent_15'
|
||||
},{
|
||||
id: 'jsteps', label: '', group: '{{ _("Steps") }}',
|
||||
type: 'collection', mode: ['edit', 'create'],
|
||||
model: pgBrowser.Nodes['pga_jobstep'].model, canEdit: true,
|
||||
control: 'sub-node-collection', canAdd: true, canDelete: true,
|
||||
columns: [
|
||||
'jstname', 'jstenabled', 'jstkind', 'jstconntype', 'jstonerror'
|
||||
]
|
||||
},{
|
||||
id: 'jschedules', label: '', group: '{{ _("Schedules") }}',
|
||||
type: 'collection', mode: ['edit', 'create'],
|
||||
control: 'sub-node-collection', canAdd: true, canDelete: true,
|
||||
canEdit: true, model: pgBrowser.Nodes['pga_schedule'].model,
|
||||
columns: ['jscname', 'jscenabled', 'jscstart', 'jscend']
|
||||
}],
|
||||
validate: function(keys) {
|
||||
var name = this.get('jobname');
|
||||
if (_.isUndefined(name) || _.isNull(name) ||
|
||||
String(name).replace(/^\s+|\s+$/g, '') == '') {
|
||||
var msg = '{{ _('Name cannot be empty.') }}';
|
||||
this.errorModel.set('jobname', msg);
|
||||
return msg;
|
||||
} else {
|
||||
this.errorModel.unset('jobname');
|
||||
}
|
||||
return null;
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
return pgBrowser.Nodes['pga_job'];
|
||||
});
|
|
@ -0,0 +1,31 @@
|
|||
{% import 'macros/pga_jobstep.macros' as STEP %}
|
||||
{% import 'macros/pga_schedule.macros' as SCHEDULE %}
|
||||
DO $$
|
||||
DECLARE
|
||||
jid integer;{% if 'jschedules' in data and data.jschedules|length > 0 %}
|
||||
|
||||
scid integer;{% endif %}
|
||||
|
||||
BEGIN
|
||||
-- Creating a new job
|
||||
INSERT INTO pgagent.pga_job(
|
||||
jobjclid, jobname, jobdesc, jobhostagent, jobenabled
|
||||
) VALUES (
|
||||
{{ data.jobjclid|qtLiteral }}::integer, {{ data.jobname|qtLiteral }}::text, {{ data.jobdesc|qtLiteral }}::text, {{ data.jobhostagent|qtLiteral }}::text, {% if data.jobenabled %}true{% else %}false{% endif %}
|
||||
|
||||
) RETURNING jobid INTO jid;{% if 'jsteps' in data and data.jsteps|length > 0 %}
|
||||
|
||||
|
||||
-- Steps
|
||||
{% for step in data.jsteps %}{{ STEP.INSERT(has_connstr, None, step) }}{% endfor %}
|
||||
{% endif %}{% if 'jschedules' in data and data.jschedules|length > 0 %}
|
||||
|
||||
|
||||
-- Schedules
|
||||
{% for schedule in data.jschedules %}{{ SCHEDULE.INSERT(None, schedule) }}{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
END
|
||||
$$;{% if fetch_id %}
|
||||
|
||||
SELECT jobid FROM pgagent.pga_job WHERE xmin::text = (txid_current() % (2^32)::bigint)::text;{% endif %}
|
|
@ -0,0 +1 @@
|
|||
DELETE FROM pgagent.pga_job WHERE jobid = {{ jid|qtLiteral }}::integer;
|
|
@ -0,0 +1 @@
|
|||
SELECT jclid AS value, jclname AS label FROM pgagent.pga_jobclass
|
|
@ -0,0 +1,8 @@
|
|||
SELECT
|
||||
jobid, jobname, jobenabled
|
||||
FROM
|
||||
pgagent.pga_job
|
||||
{% if jid %}
|
||||
WHERE jobid = {{ jid|qtLiteral }}::integer
|
||||
{% endif %}
|
||||
ORDER BY jobname;
|
|
@ -0,0 +1,21 @@
|
|||
SELECT
|
||||
j.jobid AS jobid, j.jobname as jobname, j.jobenabled as jobenabled,
|
||||
j.jobdesc AS jobdesc, j.jobhostagent AS jobhostagent,
|
||||
j.jobcreated AS jobcreated, j.jobchanged AS jobchanged,
|
||||
ag.jagstation AS jagagent, sub.jlgstatus AS jlgstatus,
|
||||
j.jobagentid AS jobagentid, j.jobnextrun AS jobnextrun,
|
||||
j.joblastrun AS joblastrun, j.jobjclid AS jobjclid,
|
||||
jc.jclname AS jobclass
|
||||
FROM
|
||||
pgagent.pga_job j
|
||||
LEFT OUTER JOIN pgagent.pga_jobagent ag ON ag.jagpid=jobagentid
|
||||
LEFT OUTER JOIN (
|
||||
SELECT DISTINCT ON (jlgjobid) jlgstatus, jlgjobid
|
||||
FROM pgagent.pga_joblog
|
||||
ORDER BY jlgjobid, jlgid DESC
|
||||
) sub ON sub.jlgjobid = j.jobid
|
||||
LEFT JOIN pgagent.pga_jobclass jc ON (j.jobjclid = jc.jclid)
|
||||
{% if jid %}
|
||||
WHERE j.jobid = {{ jid|qtLiteral }}::integer
|
||||
{% endif %}
|
||||
ORDER BY j.jobname;
|
|
@ -0,0 +1,3 @@
|
|||
UPDATE pgagent.pga_job
|
||||
SET jobnextrun=now()::timestamptz
|
||||
WHERE jobid={{ jid|qtLiteral }}::integer
|
|
@ -0,0 +1,2 @@
|
|||
{% import 'macros/pga_schedule.macros' as SCHEDULE %}
|
||||
{{ SCHEDULE.PROPERTIES(jid, jscid) }}
|
|
@ -0,0 +1,11 @@
|
|||
SELECT
|
||||
jlgid AS {{ conn|qtIdent(_('Run')) }},
|
||||
jlgstatus AS {{ conn|qtIdent(_('Status')) }},
|
||||
jlgstart AS {{ conn|qtIdent(_('Start time')) }},
|
||||
jlgduration AS {{ conn|qtIdent(_('Duration')) }},
|
||||
(jlgstart + jlgduration) AS {{ conn|qtIdent(_('End time')) }}
|
||||
FROM
|
||||
pgagent.pga_joblog
|
||||
WHERE
|
||||
jlgjobid = {{ jid|qtLiteral }}::integer
|
||||
ORDER BY jlgid DESC;
|
|
@ -0,0 +1,2 @@
|
|||
{% import 'macros/pga_jobstep.macros' as STEP %}
|
||||
{{ STEP.PROPERTIES(has_connstr, jid, jstid) }}
|
|
@ -0,0 +1,20 @@
|
|||
{% import 'macros/pga_jobstep.macros' as STEP %}
|
||||
{% import 'macros/pga_schedule.macros' as SCHEDULE %}
|
||||
{% if 'jobjclid' in data or 'jobname' in data or 'jobdesc' in data or 'jobhostagent' in data or 'jobenabled' in data %}
|
||||
UPDATE pgagent.pga_job
|
||||
SET {% if 'jobjclid' in data %}jobjclid={{ data.jobjclid|qtLiteral }}::integer{% if 'jobname' in data or 'jobdesc' in data or 'jobhostagent' in data or 'jobenabled' in data %}, {% endif %}{% endif %}
|
||||
{% if 'jobname' in data %}jobname={{ data.jobname|qtLiteral }}::text{%if 'jobdesc' in data or 'jobhostagent' in data or 'jobenabled' in data %}, {% endif %}{% endif %}
|
||||
{% if 'jobdesc' in data %}jobdesc={{ data.jobdesc|qtLiteral }}::text{%if 'jobhostagent' in data or 'jobenabled' in data %}, {% endif %}{% endif %}
|
||||
{%if 'jobhostagent' in data %}jobhostagent={{ data.jobhostagent|qtLiteral }}::text{% if 'jobenabled' in data %}, {% endif %}{% endif %}
|
||||
{% if 'jobenabled' in data %}jobenabled={% if data.jobenabled %}true{% else %}false{% endif %}{% endif %}
|
||||
|
||||
WHERE jobid = {{ jid }};
|
||||
{% endif %}{% if 'jsteps' in data %}
|
||||
|
||||
{% if 'deleted' in data.jsteps %}{% for step in data.jsteps.deleted %}{{ STEP.DELETE(jid, step.jstid) }}{% endfor %}{% endif %}
|
||||
{% if 'changed' in data.jsteps %}{% for step in data.jsteps.changed %}{{ STEP.UPDATE(has_connstr, jid, step.jstid, step) }}{% endfor %}{% endif %}
|
||||
{% if 'added' in data.jsteps %}{% for step in data.jsteps.added %}{{ STEP.INSERT(has_connstr, jid, step) }}{% endfor %}{% endif %}{% endif %}{% if 'jschedules' in data %}
|
||||
|
||||
{% if 'deleted' in data.jschedules %}{% for schedule in data.jschedules.deleted %}{{ SCHEDULE.DELETE(jid, schedule.jscid) }}{% endfor %}{% endif %}
|
||||
{% if 'changed' in data.jschedules %}{% for schedule in data.jschedules.changed %}{{ SCHEDULE.UPDATE(has_connstr, jid, schedule.jscid, schedule) }}{% endfor %}{% endif %}
|
||||
{% if 'added' in data.jschedules %}{% for schedule in data.jschedules.added %}{{ SCHEDULE.INSERT(has_connstr, jid, schedule) }}{% endfor %}{% endif %}{% endif %}
|
|
@ -0,0 +1,2 @@
|
|||
{% import 'macros/pga_jobstep.macros' as STEP %}
|
||||
{{ STEP.INSERT(has_connstr, jid, data) }}
|
|
@ -0,0 +1,2 @@
|
|||
{% import 'macros/pga_jobstep.macros' as STEP %}
|
||||
{{ STEP.DELETE(jid, jstid) }}
|
|
@ -0,0 +1,10 @@
|
|||
SELECT
|
||||
jstid, jstjobid, jstname, jstenabled, jstkind = 's'::bpchar AS jstkind
|
||||
FROM
|
||||
pgagent.pga_jobstep
|
||||
WHERE
|
||||
{% if jstid %}
|
||||
jstid = {{ jstid|qtLiteral }}::integer AND
|
||||
{% endif %}
|
||||
jstjobid = {{ jid|qtLiteral }}::integer
|
||||
ORDER BY jstname;
|
|
@ -0,0 +1,2 @@
|
|||
{% import 'macros/pga_jobstep.macros' as STEP %}
|
||||
{{ STEP.PROPERTIES(has_connstr, jid, jstid) }}
|
|
@ -0,0 +1,13 @@
|
|||
SELECT
|
||||
jslid AS {{ conn|qtIdent(_('Run')) }},
|
||||
jslstatus AS {{ conn|qtIdent(_('Status')) }},
|
||||
jslresult AS {{ conn|qtIdent(_('Result')) }},
|
||||
jslstart AS {{ conn|qtIdent(_('Start time')) }},
|
||||
(jslstart + jslduration) AS {{ conn|qtIdent(_('End time')) }},
|
||||
jslduration AS {{ conn|qtIdent(_('Duration')) }},
|
||||
jsloutput AS {{ conn|qtIdent(_('Output')) }}
|
||||
FROM
|
||||
pgagent.pga_jobsteplog
|
||||
WHERE
|
||||
jsljstid = {{ jstid|qtLiteral }}::integer
|
||||
ORDER BY jslid DESC;
|
|
@ -0,0 +1,2 @@
|
|||
{% import 'macros/pga_jobstep.macros' as STEP %}
|
||||
{{ STEP.UPDATE(has_connstr, jid, jstid, data) }}
|
|
@ -0,0 +1,10 @@
|
|||
{% import 'macros/pga_schedule.macros' as SCHEDULE %}
|
||||
DO $$
|
||||
DECLARE
|
||||
jscid integer;
|
||||
BEGIN
|
||||
{{ SCHEDULE.INSERT(jid, data) }}
|
||||
END
|
||||
$$ LANGUAGE 'plpgsql';{% if fetch_id %}
|
||||
|
||||
{{ SCHEDULE.FETCH_CURRENT() }}{% endif %}
|
|
@ -0,0 +1,2 @@
|
|||
{% import 'macros/pga_schedule.macros' as SCHEDULE %}
|
||||
{{ SCHEDULE.DELETE(jid, jscid) }}
|
|
@ -0,0 +1,11 @@
|
|||
SELECT
|
||||
jscid, jscjobid, jscname, jscenabled
|
||||
FROM
|
||||
pgagent.pga_schedule
|
||||
WHERE
|
||||
{% if jscid %}
|
||||
jscid = {{ jscid|qtLiteral }}::integer
|
||||
{% else %}
|
||||
jscjobid = {{ jid|qtLiteral }}::integer
|
||||
{% endif %}
|
||||
ORDER BY jscname;
|
|
@ -0,0 +1,2 @@
|
|||
{% import 'macros/pga_schedule.macros' as SCHEDULE %}
|
||||
{{ SCHEDULE.PROPERTIES(jid, jstid) }}
|
|
@ -0,0 +1,2 @@
|
|||
{% import 'macros/pga_schedule.macros' as SCHEDULE %}
|
||||
{{ SCHEDULE.UPDATE(jid, jscid, data) }}
|